Explorar el Código

support multi-line PGEntries

David Rose hace 24 años
padre
commit
5285800a36
Se han modificado 5 ficheros con 193 adiciones y 18 borrados
  1. 31 0
      panda/src/pgui/pgEntry.I
  2. 127 16
      panda/src/pgui/pgEntry.cxx
  3. 15 2
      panda/src/pgui/pgEntry.h
  4. 18 0
      panda/src/pgui/pgItem.I
  5. 2 0
      panda/src/pgui/pgItem.h

+ 31 - 0
panda/src/pgui/pgEntry.I

@@ -105,10 +105,16 @@ get_max_chars() const {
 //               number of characters; also set_max_chars().
 //
 //               If this is 0, there is no limit.
+//
+//               If _num_lines is more than 1, rather than being a
+//               fixed width on the whole entry, this becomes instead
+//               the wordwrap width (and the width limit on the entry
+//               is essentially _max_width * _num_lines).
 ////////////////////////////////////////////////////////////////////
 INLINE void PGEntry:: 
 set_max_width(float max_width) {
   _max_width = max_width;
+  _text_geom_stale = true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -123,6 +129,31 @@ get_max_width() const {
   return _max_width;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_num_lines
+//       Access: Published
+//  Description: Sets the number of lines of text the PGEntry will
+//               use.  This only has meaning if _max_width is not 0;
+//               _max_width indicates the wordwrap width of each line.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_num_lines(int num_lines) {
+  nassertv(num_lines >= 1);
+  _num_lines = num_lines;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_num_lines
+//       Access: Published
+//  Description: Returns the number of lines of text the PGEntry will
+//               use, if _max_width is not 0.  See set_num_lines().
+////////////////////////////////////////////////////////////////////
+INLINE int PGEntry:: 
+get_num_lines() const {
+  return _num_lines;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGEntry::set_blink_rate
 //       Access: Published

+ 127 - 16
panda/src/pgui/pgEntry.cxx

@@ -46,6 +46,7 @@ PGEntry(const string &name) : PGItem(name)
   _cursor_stale = true;
   _max_chars = 0;
   _max_width = 0.0;
+  _num_lines = 1;
   _last_text_def = (TextNode *)NULL;
   _text_geom_stale = true;
   _blink_start = 0.0;
@@ -220,14 +221,64 @@ press(const MouseWatcherParameter &param) {
                 _text.substr(0, _cursor_position) + key +
                 _text.substr(_cursor_position);
               
-              // Check the width.
-              bool too_wide = false;
-              if (get_max_width() > 0.0) {
+              // Check the length.
+              bool too_long = false;
+              if (_max_width > 0.0) {
                 TextNode *text_node = get_text_def(S_focus);
-                too_wide = (text_node->calc_width(new_text) > get_max_width());
+                if (_num_lines <= 1) {
+                  // If we have only one line, we can check the length
+                  // by simply measuring the width of the text.
+                  too_long = (text_node->calc_width(new_text) > _max_width);
+
+                } else {
+                  // If we have multiple lines, we have to check the
+                  // length by wordwrapping it and counting up the
+                  // number of lines.
+                  string ww_text = text_node->wordwrap_to(new_text, _max_width, true);
+                  int num_lines = 1;
+                  size_t last_line_start = 0;
+                  for (size_t p = 0;
+                       p < ww_text.length() && !too_long;
+                       ++p) {
+                    if (ww_text[p] == '\n') {
+                      last_line_start = p + 1;
+                      num_lines++;
+                      too_long = (num_lines > _num_lines);
+                    }
+                  }
+
+                  if (!too_long) {
+                    // We must also ensure that the last line is not too
+                    // long (it might be, because of additional
+                    // whitespace on the end).
+                    string last_line = ww_text.substr(last_line_start);
+                    float last_line_width = text_node->calc_width(last_line);
+                    if (num_lines == _num_lines) {
+                      // Mainly we only care about this if we're on
+                      // the very last line.
+                      too_long = (last_line_width > _max_width);
+
+                    } else {
+                      // If we're not on the very last line, the width
+                      // is still important, just so we don't allow an
+                      // infinite number of spaces to accumulate.
+                      // However, we must allow at least *one* space
+                      // on the end of a line.
+                      if (_text.length() >= 1 && 
+                          _text[_text.length() - 1] == ' ') {
+                        if (last_line_width > _max_width) {
+                          // In this case, however, it's not exactly
+                          // an overflow; we just want to reject the
+                          // space.
+                          return;
+                        }
+                      }
+                    }
+                  }
+                }
               }
               
-              if (too_wide) {
+              if (too_long) {
                 overflow(param);
                 
               } else {
@@ -280,20 +331,22 @@ overflow(const MouseWatcherParameter &param) {
 //       Access: Published
 //  Description: Sets up the entry for normal use.  The width is the
 //               maximum width of characters that will be typed, and
-//               determines the size of the entry, based on the
-//               TextNode in effect.
+//               num_lines is the integer number of lines of text of
+//               the entry.  Both of these together determine the size
+//               of the entry, based on the TextNode in effect.
 ////////////////////////////////////////////////////////////////////
 void PGEntry::
-setup(float width) {
+setup(float width, int num_lines) {
   set_text(string());
   _cursor_position = 0;
   set_max_chars(0);
   set_max_width(width);
+  set_num_lines(num_lines);
 
   TextNode *text_node = get_text_def(S_focus);
-  float height = text_node->get_line_height();
+  float line_height = text_node->get_line_height();
 
-  LVecBase4f frame(0.0, width, -0.3 * height, height);
+  LVecBase4f frame(0.0, width, -0.3 * line_height - (line_height * (num_lines - 1)), line_height);
   switch (text_node->get_align()) {
   case TM_ALIGN_LEFT:
     // The default case.
@@ -329,8 +382,8 @@ setup(float width) {
   clear_cursor_def();
   LineSegs ls;
   ls.set_color(0.0, 0.0, 0.0, 1.0);
-  ls.move_to(0.0, 0.0, -0.15 * height);
-  ls.draw_to(0.0, 0.0, 0.85 * height);
+  ls.move_to(0.0, 0.0, -0.15 * line_height);
+  ls.draw_to(0.0, 0.0, 0.85 * line_height);
   new RenderRelation(get_cursor_def(), ls.create());
 
   // An underscore cursor would work too.
@@ -437,7 +490,53 @@ update_text() {
   if (_text_geom_stale || node != _last_text_def) {
     // We need to regenerate.
     _last_text_def = node;
-    _last_text_def->set_text(_text);
+
+    if (_max_width > 0.0 && _num_lines > 1) {
+      // Fold the text into multiple lines.
+      string ww_text = _last_text_def->wordwrap_to(_text, _max_width, true);
+
+      // And chop the lines up into pieces.
+      _ww_lines.clear();
+      size_t p = 0;
+      size_t q = ww_text.find('\n');
+      while (q != string::npos) {
+        _ww_lines.push_back(WWLine());
+        WWLine &line = _ww_lines.back();
+        line._str = ww_text.substr(p, q - p);
+
+        // Get the left edge of the text at this line.
+        line._left = 0.0;
+        if (_last_text_def->get_align() != TM_ALIGN_LEFT) {
+          _last_text_def->set_text(line._str);
+          line._left = _last_text_def->get_left();
+        }
+
+        p = q + 1;
+        q = ww_text.find('\n', p);
+      }
+      _ww_lines.push_back(WWLine());
+      WWLine &line = _ww_lines.back();
+      line._str = ww_text.substr(p);
+      
+      // Get the left edge of the text at this line.
+      line._left = 0.0;
+      if (_last_text_def->get_align() != TM_ALIGN_LEFT) {
+        _last_text_def->set_text(line._str);
+        line._left = _last_text_def->get_left();
+      }
+
+      _last_text_def->set_text(ww_text);
+
+    } else {
+      // Only one line.
+      _ww_lines.clear();
+      _ww_lines.push_back(WWLine());
+      WWLine &line = _ww_lines.back();
+      line._str = _text;
+
+      _last_text_def->set_text(_text);
+      line._left = _last_text_def->get_left();
+    }
 
     if (_current_text_arc != (NodeRelation *)NULL) {
       remove_arc(_current_text_arc);
@@ -445,7 +544,6 @@ update_text() {
     PT_Node text = _last_text_def->generate();
     _current_text_arc = new RenderRelation(_text_render_root, text);
     _text_geom_stale = false;
-    _text_left = _last_text_def->get_left();
     _cursor_stale = true;
   }
 }
@@ -465,10 +563,23 @@ update_cursor() {
 
     _cursor_position = min(_cursor_position, (int)_text.length());
 
+    // Determine the row and column of the cursor.
+    int row = 0;
+    int column = _cursor_position;
+    while (row + 1 < (int)_ww_lines.size() &&
+           column > (int)_ww_lines[row]._str.length()) {
+      column -= _ww_lines[row]._str.length();
+      row++;
+    }
+
+    nassertv(row >= 0 && row < (int)_ww_lines.size());
+    nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
+
     float width = 
-      _last_text_def->calc_width(_text.substr(0, _cursor_position));
+      _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column));
+    float line_height = _last_text_def->get_line_height();
 
-    LVecBase3f trans(_text_left + width, 0.0, 0.0);
+    LVecBase3f trans(_ww_lines[row]._left + width, 0.0, -line_height * row);
     LMatrix4f pos = LMatrix4f::translate_mat(trans);
     _cursor_def->set_transition(new TransformTransition(pos));
 

+ 15 - 2
panda/src/pgui/pgEntry.h

@@ -60,7 +60,7 @@ PUBLISHED:
     S_inactive
   };
 
-  void setup(float width);
+  void setup(float width, int num_lines);
 
   INLINE void set_text(const string &text);
   INLINE const string &get_text() const;
@@ -72,6 +72,8 @@ PUBLISHED:
   INLINE int get_max_chars() const;
   INLINE void set_max_width(float max_width);
   INLINE float get_max_width() const;
+  INLINE void set_num_lines(int num_lines);
+  INLINE int get_num_lines() const;
 
   INLINE void set_blink_rate(float blink_rate);
   INLINE float get_blink_rate() const;
@@ -104,6 +106,7 @@ private:
 
   int _max_chars;
   float _max_width;
+  int _num_lines;
 
   typedef pvector< PT(TextNode) > TextDefs;
   TextDefs _text_defs;
@@ -116,9 +119,19 @@ private:
   // is generated.
   NodeRelation *_current_text_arc;
   TextNode *_last_text_def;
-  float _text_left;
   bool _text_geom_stale;
 
+  // This is a list of each row of text in the entry, after it has
+  // been wordwrapped by update_text().  It's used by update_cursor()
+  // to compute the correct cursor position.
+  class WWLine {
+  public:
+    string _str;
+    float _left;
+  };
+  typedef pvector<WWLine> WWLines;
+  WWLines _ww_lines;
+
   // This is the arc above the node that represents the cursor
   // geometry.  It is also attached to the above node, and is
   // transformed around and/or hidden according to the cursor's

+ 18 - 0
panda/src/pgui/pgItem.I

@@ -160,6 +160,24 @@ get_id() const {
   return _region->get_name();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::set_id
+//       Access: Published
+//  Description: Set the unique ID assigned to this PGItem.  It is the
+//               user's responsibility to ensure that this ID is
+//               unique.
+//
+//               Normally, this should not need to be called, as the
+//               PGItem will assign itself an ID when it is created,
+//               but this function allows the user to decide to
+//               redefine the ID to be something possibly more
+//               meaningful.
+////////////////////////////////////////////////////////////////////
+INLINE void PGItem::
+set_id(const string &id) {
+  _region->set_name(id);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_enter_prefix
 //       Access: Published, Static

+ 2 - 0
panda/src/pgui/pgItem.h

@@ -102,6 +102,8 @@ PUBLISHED:
   void set_frame_style(int state, const PGFrameStyle &style);
 
   INLINE const string &get_id() const;
+  INLINE void set_id(const string &id);
+
   INLINE static string get_enter_prefix();
   INLINE static string get_exit_prefix();
   INLINE static string get_press_prefix();