Browse Source

added keyboard focus, etc. to pgui for PGEntry

David Rose 24 years ago
parent
commit
98851bc0b1

+ 4 - 1
panda/src/pgui/Sources.pp

@@ -4,7 +4,7 @@
 #begin lib_target
   #define TARGET pgui
   #define LOCAL_LIBS \
-    text tform graph linmath event putil gobj \
+    grutil text tform graph linmath event putil gobj \
     mathutil sgraph sgraphutil
 
 //  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
@@ -12,6 +12,7 @@
   #define SOURCES  \
     config_pgui.h \
     pgButton.I pgButton.h \
+    pgEntry.I pgEntry.h \
     pgMouseWatcherGroup.I pgMouseWatcherGroup.h \
     pgMouseWatcherParameter.I pgMouseWatcherParameter.h \
     pgFrameStyle.I pgFrameStyle.h \
@@ -23,6 +24,7 @@
   #define SOURCES $[SOURCES] \
     config_pgui.cxx \
     pgButton.cxx \
+    pgEntry.cxx \
     pgMouseWatcherGroup.cxx \
     pgMouseWatcherParameter.cxx \
     pgFrameStyle.cxx \
@@ -32,6 +34,7 @@
 
   #define INSTALL_HEADERS \
     pgButton.I pgButton.h \
+    pgEntry.I pgEntry.h \
     pgMouseWatcherGroup.I pgMouseWatcherGroup.h \
     pgMouseWatcherParameter.I pgMouseWatcherParameter.h \
     pgFrameStyle.I pgFrameStyle.h \

+ 4 - 0
panda/src/pgui/config_pgui.cxx

@@ -18,7 +18,9 @@
 
 #include "config_pgui.h"
 #include "pgButton.h"
+#include "pgEntry.h"
 #include "pgMouseWatcherParameter.h"
+#include "pgMouseWatcherGroup.h"
 #include "pgItem.h"
 #include "pgMouseWatcherRegion.h"
 #include "pgTop.h"
@@ -30,7 +32,9 @@ NotifyCategoryDef(pgui, "");
 
 ConfigureFn(config_pgui) {
   PGButton::init_type();
+  PGEntry::init_type();
   PGMouseWatcherParameter::init_type();
+  PGMouseWatcherGroup::init_type();
   PGItem::init_type();
   PGMouseWatcherRegion::init_type();
   PGTop::init_type();

+ 0 - 15
panda/src/pgui/pgButton.I

@@ -51,21 +51,6 @@ setup(const ArcChain &ready, const ArcChain &depressed,
   setup(ready, depressed, rollover, ready);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PGButton::set_active
-//       Access: Public
-//  Description: Toggles the active/inactive state of the button.  In
-//               the case of a PGButton, this also changes its visual
-//               appearance.
-////////////////////////////////////////////////////////////////////
-INLINE void PGButton:: 
-set_active(bool active) {
-  if (active != get_active()) {
-    set_state(active ? S_ready : S_inactive);
-    PGItem::set_active(active);
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PGButton::get_click_prefix
 //       Access: Published, Static

+ 15 - 0
panda/src/pgui/pgButton.cxx

@@ -240,6 +240,21 @@ setup(const ArcChain &ready, const ArcChain &depressed,
   instance_to_state_def(S_inactive, inactive);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGButton::set_active
+//       Access: Published, Virtual
+//  Description: Toggles the active/inactive state of the button.  In
+//               the case of a PGButton, this also changes its visual
+//               appearance.
+////////////////////////////////////////////////////////////////////
+void PGButton:: 
+set_active(bool active) {
+  if (active != get_active()) {
+    PGItem::set_active(active);
+    set_state(active ? S_ready : S_inactive);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGButton::add_click_button
 //       Access: Published

+ 1 - 1
panda/src/pgui/pgButton.h

@@ -66,7 +66,7 @@ PUBLISHED:
   void setup(const ArcChain &ready, const ArcChain &depressed,
              const ArcChain &rollover, const ArcChain &inactive);
 
-  INLINE void set_active(bool active);
+  virtual void set_active(bool active);
 
   bool add_click_button(const ButtonHandle &button);
   bool remove_click_button(const ButtonHandle &button);

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

@@ -0,0 +1,220 @@
+// Filename: pgEntry.I
+// Created by:  drose (10Jul01)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_text
+//       Access: Published
+//  Description: Changes the text currently displayed within the
+//               entry.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_text(const string &text) {
+  _text = text;
+  _text_geom_stale = true;
+  _cursor_stale = true;
+  _blink_start = ClockObject::get_global_clock()->get_frame_time();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_text
+//       Access: Published
+//  Description: Returns the text currently displayed within the
+//               entry.
+////////////////////////////////////////////////////////////////////
+INLINE const string &PGEntry:: 
+get_text() const {
+  return _text;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_cursor_position
+//       Access: Published
+//  Description: Sets the current position of the cursor.  This is the
+//               position within the text at which the next letter
+//               typed by the user will be inserted; normally it is
+//               the same as the length of the text.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_cursor_position(int position) {
+  if (_cursor_position != position) {
+    _cursor_position = position;
+    _cursor_stale = true;
+    _blink_start = ClockObject::get_global_clock()->get_frame_time();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_cursor_position
+//       Access: Published
+//  Description: Returns the current position of the cursor.
+////////////////////////////////////////////////////////////////////
+INLINE int PGEntry:: 
+get_cursor_position() const {
+  return _cursor_position;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_max_chars
+//       Access: Published
+//  Description: Sets the maximum number of characters that may be
+//               typed into the entry.  This is a limit on the number
+//               of characters, as opposed to the width of the entry;
+//               see also set_max_width().
+//
+//               If this is 0, there is no limit.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_max_chars(int max_chars) {
+  _max_chars = max_chars;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_max_chars
+//       Access: Published
+//  Description: Returns the current maximum number of characters that
+//               may be typed into the entry, or 0 if there is no
+//               limit.  See set_max_chars().
+////////////////////////////////////////////////////////////////////
+INLINE int PGEntry:: 
+get_max_chars() const {
+  return _max_chars;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_max_width
+//       Access: Published
+//  Description: Sets the maximum width of all characters that may be
+//               typed into the entry.  This is a limit on the width
+//               of the formatted text, not a fixed limit on the
+//               number of characters; also set_max_chars().
+//
+//               If this is 0, there is no limit.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_max_width(float max_width) {
+  _max_width = max_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_max_width
+//       Access: Published
+//  Description: Returns the current maximum width of the characters
+//               that may be typed into the entry, or 0 if there is no
+//               limit.  See set_max_width().
+////////////////////////////////////////////////////////////////////
+INLINE float PGEntry:: 
+get_max_width() const {
+  return _max_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_blink_rate
+//       Access: Published
+//  Description: Sets the number of times per second the cursor will
+//               blink while the entry has keyboard focus.
+//
+//               If this is 0, the cursor does not blink, but is held
+//               steady.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+set_blink_rate(float blink_rate) {
+  _blink_rate = blink_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_blink_rate
+//       Access: Published
+//  Description: Returns the number of times per second the cursor
+//               will blink, or 0 if the cursor is not to blink.
+////////////////////////////////////////////////////////////////////
+INLINE float PGEntry:: 
+get_blink_rate() const {
+  return _blink_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_cursor_def
+//       Access: Published
+//  Description: Returns the Node that will be rendered to represent
+//               the cursor.  You can attach suitable cursor geometry
+//               to this node.
+////////////////////////////////////////////////////////////////////
+INLINE Node *PGEntry:: 
+get_cursor_def() {
+  return _cursor_def->get_child();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::clear_cursor_def
+//       Access: Published
+//  Description: Removes all the children from the cursor_def Node, in
+//               preparation for adding a new definition.
+////////////////////////////////////////////////////////////////////
+INLINE void PGEntry:: 
+clear_cursor_def() {
+  remove_all_children(get_cursor_def());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_accept_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the accept
+//               event for all PGEntries.  The accept event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGEntry::
+get_accept_prefix() {
+  return "accept-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_accept_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               entry is accepted normally.
+////////////////////////////////////////////////////////////////////
+INLINE string PGEntry::
+get_accept_event(const ButtonHandle &button) const {
+  return "accept-" + button.get_name() + "-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_overflow_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the overflow
+//               event for all PGEntries.  The overflow event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGEntry::
+get_overflow_prefix() {
+  return "overflow-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_overflow_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when too
+//               much text is attempted to be entered into the
+//               PGEntry, exceeding either the limit set via
+//               set_max_chars() or via set_max_width().
+////////////////////////////////////////////////////////////////////
+INLINE string PGEntry::
+get_overflow_event(const ButtonHandle &button) const {
+  return "overflow-" + button.get_name() + "-" + get_id();
+}

+ 527 - 0
panda/src/pgui/pgEntry.cxx

@@ -0,0 +1,527 @@
+// Filename: pgEntry.cxx
+// Created by:  drose (10Jul01)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgEntry.h"
+#include "pgMouseWatcherParameter.h"
+
+#include "throw_event.h"
+#include "renderRelation.h"
+#include "mouseWatcherParameter.h"
+#include "directRenderTraverser.h"
+#include "allTransitionsWrapper.h"
+#include "transformTransition.h"
+#include "pruneTransition.h"
+#include "keyboardButton.h"
+#include "mouseButton.h"
+#include "lineSegs.h"
+
+#include "math.h"
+
+TypeHandle PGEntry::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGEntry::
+PGEntry(const string &name) : PGItem(name)
+{
+  _cursor_position = 0;
+  _cursor_stale = true;
+  _max_chars = 0;
+  _max_width = 0.0;
+  _last_text_def = (TextNode *)NULL;
+  _text_geom_stale = true;
+  _blink_start = 0.0;
+  _blink_rate = 1.0;
+
+  _text_render_root = new NamedNode("text_root");
+  _current_text_arc = (NodeRelation *)NULL;
+  Node *cursor = new NamedNode("cursor");
+  _cursor_def = new RenderRelation(_text_render_root, cursor);
+  _cursor_visible = true;
+
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGEntry::
+~PGEntry() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGEntry::
+PGEntry(const PGEntry &copy) :
+  PGItem(copy),
+  _text(copy._text),
+  _cursor_position(copy._cursor_position),
+  _max_chars(copy._max_chars),
+  _max_width(copy._max_width),
+  _text_defs(copy._text_defs),
+  _blink_start(copy._blink_start),
+  _blink_rate(copy._blink_rate)
+{
+  _cursor_stale = true;
+  _last_text_def = (TextNode *)NULL;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+operator = (const PGEntry &copy) {
+  _text = copy._text;
+  _cursor_position = copy._cursor_position;
+  _max_chars = copy._max_chars;
+  _max_width = copy._max_width;
+  _text_defs = copy._text_defs;
+  _blink_start = copy._blink_start;
+  _blink_rate = copy._blink_rate;
+
+  _cursor_stale = true;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+Node *PGEntry::
+make_copy() const {
+  return new PGEntry(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::draw_item
+//       Access: Public, Virtual
+//  Description: Called by the PGTop's traversal to draw this
+//               particular item.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+draw_item(PGTop *top, GraphicsStateGuardian *gsg, 
+          const AllAttributesWrapper &attrib) {
+  PGItem::draw_item(top, gsg, attrib);
+  update_text();
+  update_cursor();
+
+  nassertv(_text_render_root != (Node *)NULL);
+
+  // We'll use a normal DirectRenderTraverser to do the rendering
+  // of the text.
+  DirectRenderTraverser drt(gsg, RenderRelation::get_class_type());
+  drt.set_view_frustum_cull(false);
+  drt.traverse(_text_render_root, attrib, AllTransitionsWrapper());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::press
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard entry is depressed while the mouse
+//               is within the region.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+press(const MouseWatcherParameter &param) {
+  if (get_active()) {
+    if (param.has_button()) {
+      ButtonHandle button = param.get_button();
+      
+      if (button == MouseButton::one() ||
+          button == MouseButton::two() ||
+          button == MouseButton::three()) {
+        // Mouse button; set focus.
+        set_focus(true);
+        
+      } else if (get_focus()) {
+        // Keyboard button.
+        _cursor_position = min(_cursor_position, (int)_text.length());
+        _blink_start = ClockObject::get_global_clock()->get_frame_time();
+        if (button == KeyboardButton::enter()) {
+          // Enter.  Accept the entry.
+          accept(param);
+          
+        } else if (button == KeyboardButton::backspace()) {
+          // Backspace.  Remove the character to the left of the cursor.
+          if (_cursor_position > 0) {
+            _text.erase(_text.begin() + _cursor_position - 1);
+            _cursor_position--;
+            _cursor_stale = true;
+            _text_geom_stale = true;
+          }
+          
+        } else if (button == KeyboardButton::del()) {
+          // Delete.  Remove the character to the right of the cursor.
+          if (_cursor_position < (int)_text.length()) {
+            _text.erase(_text.begin() + _cursor_position);
+            _text_geom_stale = true;
+          }
+          
+        } else if (button == KeyboardButton::left()) {
+          // Left arrow.  Move the cursor position to the left.
+          _cursor_position = max(_cursor_position - 1, 0);
+          _cursor_stale = true;
+          
+        } else if (button == KeyboardButton::right()) {
+          // Right arrow.  Move the cursor position to the right.
+          _cursor_position = min(_cursor_position + 1, (int)_text.length());
+          _cursor_stale = true;
+          
+        } else if (button == KeyboardButton::home()) {
+          // Home.  Move the cursor position to the beginning.
+          _cursor_position = 0;
+          _cursor_stale = true;
+          
+        } else if (button == KeyboardButton::end()) {
+          // End.  Move the cursor position to the end.
+          _cursor_position = _text.length();
+          _cursor_stale = true;
+          
+        } else if (button.has_ascii_equivalent()) {
+          char key = button.get_ascii_equivalent();
+          if (isprint(key)) {
+            // A normal visible character.  Add a new character to the
+            // text entry, if there's room.
+            
+            if (get_max_chars() > 0 && (int)_text.length() >= get_max_chars()) {
+              overflow(param);
+            } else {
+              string new_text = 
+                _text.substr(0, _cursor_position) + key +
+                _text.substr(_cursor_position);
+              
+              // Check the width.
+              bool too_wide = false;
+              if (get_max_width() > 0.0) {
+                TextNode *text_node = get_text_def(S_focus);
+                too_wide = (text_node->calc_width(new_text) > get_max_width());
+              }
+              
+              if (too_wide) {
+                overflow(param);
+                
+              } else {
+                _text = new_text;
+                
+                _cursor_position++;
+                _cursor_stale = true;
+                _text_geom_stale = true;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  PGItem::press(param);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::accept
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               entry is accepted by the user pressing Enter normally.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+accept(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  throw_event(get_accept_event(param.get_button()), 
+              EventParameter(ep));
+  set_focus(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::overflow
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               entry is overflowed because the user attempts to type
+//               too many characters, exceeding either set_max_chars()
+//               or set_max_width().
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+overflow(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  throw_event(get_overflow_event(param.get_button()), 
+              EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::setup
+//       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.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+setup(float width) {
+  set_text(string());
+  _cursor_position = 0;
+  set_max_chars(0);
+  set_max_width(width);
+
+  TextNode *text_node = get_text_def(S_focus);
+  float height = text_node->get_line_height();
+
+  LVecBase4f frame(0.0, width, -0.3 * height, height);
+  switch (text_node->get_align()) {
+  case TM_ALIGN_LEFT:
+    // The default case.
+    break;
+
+  case TM_ALIGN_CENTER:
+    frame[0] = -width / 2.0;
+    frame[1] = width / 2.0;
+    break;
+
+  case TM_ALIGN_RIGHT:
+    frame[0] = -width;
+    frame[1] = 0.0;
+    break;
+  }
+
+  set_frame(frame[0] - 0.15, frame[1] + 0.15, frame[2], frame[3]);
+
+  PGFrameStyle style;
+  style.set_width(0.1, 0.1);
+  style.set_type(PGFrameStyle::T_bevel_in);
+  style.set_color(0.8, 0.8, 0.8, 1.0);
+
+  set_frame_style(S_no_focus, style);
+
+  style.set_color(0.9, 0.9, 0.9, 1.0);
+  set_frame_style(S_focus, style);
+
+  style.set_color(0.6, 0.6, 0.6, 1.0);
+  set_frame_style(S_inactive, style);
+
+  // Set up a default cursor: a vertical bar.
+  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);
+  new RenderRelation(get_cursor_def(), ls.create());
+
+  // An underscore cursor would work too.
+  //  text_node->set_text("_");
+  //  new RenderRelation(get_cursor_def(), text_node->generate());
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_text_def
+//       Access: Published
+//  Description: Changes the TextNode that will be used to render the
+//               text within the entry when the entry is in the
+//               indicated state.  The default if nothing is specified
+//               is the same TextNode returned by
+//               PGItem::get_text_node().
+//
+//               It is the responsibility of the user to ensure that
+//               this TextNode has been frozen by a call to freeze().
+//               Passing in an unfrozen TextNode will result in
+//               needless work.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+set_text_def(int state, TextNode *node) {
+  nassertv(state >= 0 && state < 1000);  // Sanity check.
+  if (node == (TextNode *)NULL && state >= (int)_text_defs.size()) {
+    // If we're setting it to NULL, we don't need to slot a new one.
+    return;
+  }
+  slot_text_def(state);
+
+  _text_defs[state] = node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::get_text_def
+//       Access: Published
+//  Description: Returns the TextNode that will be used to render the
+//               text within the entry when the entry is in the
+//               indicated state.  See set_text_def().
+////////////////////////////////////////////////////////////////////
+TextNode *PGEntry:: 
+get_text_def(int state) const {
+  if (state < 0 || state >= (int)_text_defs.size()) {
+    // If we don't have a definition, use the global one.
+    return get_text_node();
+  }
+  if (_text_defs[state] == (TextNode *)NULL) {
+    return get_text_node();
+  }
+  return _text_defs[state];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_active
+//       Access: Published, Virtual
+//  Description: Toggles the active/inactive state of the entry.  In
+//               the case of a PGEntry, this also changes its visual
+//               appearance.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+set_active(bool active) {
+  PGItem::set_active(active);
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::set_focus
+//       Access: Published, Virtual
+//  Description: Toggles the focus state of the entry.  In the case of
+//               a PGEntry, this also changes its visual appearance.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+set_focus(bool focus) {
+  PGItem::set_focus(focus);
+  _blink_start = ClockObject::get_global_clock()->get_frame_time();
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::slot_text_def
+//       Access: Private
+//  Description: Ensures there is a slot in the array for the given
+//               text definition.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+slot_text_def(int state) {
+  while (state >= (int)_text_defs.size()) {
+    _text_defs.push_back((TextNode *)NULL);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::update_text
+//       Access: Private
+//  Description: Causes the PGEntry to recompute its text, if
+//               necessary.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+update_text() {
+  TextNode *node = get_text_def(get_state());
+  nassertv(node != (TextNode *)NULL);
+
+  if (_text_geom_stale || node != _last_text_def) {
+    // We need to regenerate.
+    _last_text_def = node;
+    _last_text_def->set_text(_text);
+
+    if (_current_text_arc != (NodeRelation *)NULL) {
+      remove_arc(_current_text_arc);
+    }
+    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;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::update_cursor
+//       Access: Private
+//  Description: Moves the cursor to its correct position.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+update_cursor() {
+  TextNode *node = get_text_def(get_state());
+  nassertv(node != (TextNode *)NULL);
+
+  if (_cursor_stale || node != _last_text_def) {
+    update_text();
+
+    _cursor_position = min(_cursor_position, (int)_text.length());
+
+    float width = 
+      _last_text_def->calc_width(_text.substr(0, _cursor_position));
+
+    LVecBase3f trans(_text_left + width, 0.0, 0.0);
+    LMatrix4f pos = LMatrix4f::translate_mat(trans);
+    _cursor_def->set_transition(new TransformTransition(pos));
+
+    _cursor_stale = false;
+  }
+
+  // Should the cursor be visible?
+  if (!get_focus()) {
+    show_hide_cursor(false);
+  } else {
+    double elapsed_time = 
+      ClockObject::get_global_clock()->get_frame_time() - _blink_start;
+    int cycle = (int)floor(elapsed_time * _blink_rate * 2.0);
+    bool visible = ((cycle & 1) == 0);
+    show_hide_cursor(visible);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::show_hide_cursor
+//       Access: Private
+//  Description: Makes the cursor visible or invisible, e.g. during a
+//               blink cycle.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+show_hide_cursor(bool visible) {
+  if (visible != _cursor_visible) {
+    if (visible) {
+      // Reveal the cursor.
+      _cursor_def->clear_transition(PruneTransition::get_class_type());
+    } else {
+      // Hide the cursor.
+      _cursor_def->set_transition(new PruneTransition());
+    }
+    _cursor_visible = visible;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::update_state
+//       Access: Private
+//  Description: Determines what the correct state for the PGEntry
+//               should be.
+////////////////////////////////////////////////////////////////////
+void PGEntry:: 
+update_state() {
+  if (get_active()) {
+    if (get_focus()) {
+      set_state(S_focus);
+    } else {
+      set_state(S_no_focus);
+    }
+  } else {
+    set_state(S_inactive);
+  }
+}

+ 151 - 0
panda/src/pgui/pgEntry.h

@@ -0,0 +1,151 @@
+// Filename: pgEntry.h
+// Created by:  drose (10Jul01)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGENTRY_H
+#define PGENTRY_H
+
+#include "pandabase.h"
+
+#include "pgItem.h"
+
+#include "textNode.h"
+#include "pointerTo.h"
+#include "pvector.h"
+#include "clockObject.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGEntry
+// Description : This is a particular kind of PGItem that handles
+//               simple one-line text entries, of the sort where the
+//               user can type any string.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGEntry : public PGItem {
+PUBLISHED:
+  PGEntry(const string &name = "");
+  virtual ~PGEntry();
+
+public:
+  PGEntry(const PGEntry &copy);
+  void operator = (const PGEntry &copy);
+
+  virtual Node *make_copy() const;
+
+  virtual void draw_item(PGTop *top, GraphicsStateGuardian *gsg, 
+                         const AllAttributesWrapper &attrib);
+
+  virtual void press(const MouseWatcherParameter &param);
+
+  virtual void accept(const MouseWatcherParameter &param);
+  virtual void overflow(const MouseWatcherParameter &param);
+
+PUBLISHED:
+  enum State {
+    S_focus = 0,
+    S_no_focus,
+    S_inactive
+  };
+
+  void setup(float width);
+
+  INLINE void set_text(const string &text);
+  INLINE const string &get_text() const;
+
+  INLINE void set_cursor_position(int position);
+  INLINE int get_cursor_position() const;
+
+  INLINE void set_max_chars(int max_chars);
+  INLINE int get_max_chars() const;
+  INLINE void set_max_width(float max_width);
+  INLINE float get_max_width() const;
+
+  INLINE void set_blink_rate(float blink_rate);
+  INLINE float get_blink_rate() const;
+
+  INLINE Node *get_cursor_def();
+  INLINE void clear_cursor_def();
+
+  void set_text_def(int state, TextNode *node);
+  TextNode *get_text_def(int state) const;
+
+  virtual void set_active(bool active);
+  virtual void set_focus(bool focus);
+
+  INLINE static string get_accept_prefix();
+  INLINE string get_accept_event(const ButtonHandle &button) const;
+  INLINE static string get_overflow_prefix();
+  INLINE string get_overflow_event(const ButtonHandle &button) const;
+
+private:
+  void slot_text_def(int state);
+  void update_text();
+  void update_cursor();
+  void show_hide_cursor(bool visible);
+  void update_state();
+
+  string _text;
+  int _cursor_position;
+  bool _cursor_stale;
+  bool _cursor_visible;
+
+  int _max_chars;
+  float _max_width;
+
+  typedef pvector< PT(TextNode) > TextDefs;
+  TextDefs _text_defs;
+
+  // This node is the root of the subgraph that renders both the text
+  // and the cursor.
+  PT_Node _text_render_root;
+
+  // This is the arc that is attached to the above node when the text
+  // is generated.
+  NodeRelation *_current_text_arc;
+  TextNode *_last_text_def;
+  float _text_left;
+  bool _text_geom_stale;
+
+  // 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
+  // position and blink state.
+  NodeRelation *_cursor_def;
+
+  double _blink_start;
+  double _blink_rate;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PGItem::init_type();
+    register_type(_type_handle, "PGEntry",
+                  PGItem::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "pgEntry.I"
+
+#endif

+ 24 - 14
panda/src/pgui/pgItem.I

@@ -126,28 +126,25 @@ get_state() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGItem::set_active
+//     Function: PGItem::get_active
 //       Access: Published
-//  Description: Sets whether the PGItem is active for mouse watching.
-//               This is not necessarily related to the
-//               active/inactive appearance of the item, which is
-//               controlled by set_state(), but it does affect whether
-//               it responds to mouse events.
+//  Description: Returns whether the PGItem is currently active for
+//               mouse events.  See set_active().
 ////////////////////////////////////////////////////////////////////
-INLINE void PGItem::
-set_active(bool active) {
-  _active = active;
+INLINE bool PGItem::
+get_active() const {
+  return (_flags & F_active) != 0;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGItem::get_active
+//     Function: PGItem::get_focus
 //       Access: Published
-//  Description: Returns whether the PGItem is currently active for
-//               mouse events.  See set_active().
+//  Description: Returns whether the PGItem currently has focus for
+//               keyboard events.  See set_focus().
 ////////////////////////////////////////////////////////////////////
 INLINE bool PGItem::
-get_active() const {
-  return _active;
+get_focus() const {
+  return (_flags & F_focus) != 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -272,3 +269,16 @@ INLINE void PGItem::
 set_text_node(TextNode *node) {
   _text_node = node;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_focus_item
+//       Access: Published, Static
+//  Description: Returns the one PGItem in the world that currently
+//               has keyboard focus, if any, or NULL if no item has
+//               keyboard focus.  Use PGItem::set_focus() to activate
+//               or deactivate keyboard focus on a particular item.
+////////////////////////////////////////////////////////////////////
+INLINE PGItem *PGItem::
+get_focus_item() {
+  return _focus_item;
+}

+ 125 - 18
panda/src/pgui/pgItem.cxx

@@ -16,6 +16,7 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#include "pgTop.h"
 #include "pgItem.h"
 #include "pgMouseWatcherParameter.h"
 
@@ -25,9 +26,12 @@
 #include "arcChain.h"
 #include "transformTransition.h"
 #include "sceneGraphReducer.h"
+#include "directRenderTraverser.h"
+#include "allTransitionsWrapper.h"
 
 TypeHandle PGItem::_type_handle;
 PT(TextNode) PGItem::_text_node;
+PGItem *PGItem::_focus_item = (PGItem *)NULL;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::Constructor
@@ -41,7 +45,7 @@ PGItem(const string &name) : NamedNode(name)
   _frame.set(0, 0, 0, 0);
   _region = new PGMouseWatcherRegion(this);
   _state = 0;
-  _active = true;
+  _flags = F_active;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -53,6 +57,10 @@ PGItem::
 ~PGItem() {
   nassertv(_region->_item == this);
   _region->_item = (PGItem *)NULL;
+
+  if (_focus_item == this) {
+    _focus_item = (PGItem *)NULL;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -66,7 +74,7 @@ PGItem(const PGItem &copy) :
   _has_frame(copy._has_frame),
   _frame(copy._frame),
   _state(copy._state),
-  _active(copy._active)
+  _flags(copy._flags)
 {
   _region = new PGMouseWatcherRegion(this);
 }
@@ -82,7 +90,7 @@ operator = (const PGItem &copy) {
   _has_frame = copy._has_frame;
   _frame = copy._frame;
   _state = copy._state;
-  _active = copy._active;
+  _flags = copy._flags;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -157,7 +165,7 @@ xform(const LMatrix4f &mat) {
 //               as determined by the traversal from PGTop.
 ////////////////////////////////////////////////////////////////////
 void PGItem::
-activate_region(const LMatrix4f &transform, int sort) {
+activate_region(PGTop *, const LMatrix4f &transform, int sort) {
   LPoint3f ll(_frame[0], 0.0, _frame[2]);
   LPoint3f ur(_frame[1], 0.0, _frame[3]);
   ll = ll * transform;
@@ -167,6 +175,28 @@ activate_region(const LMatrix4f &transform, int sort) {
   _region->set_active(true);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::draw_item
+//       Access: Public, Virtual
+//  Description: Called by the PGTop's traversal to draw this
+//               particular item.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+draw_item(PGTop *top, GraphicsStateGuardian *gsg, 
+          const AllAttributesWrapper &attrib) {
+  if (has_state_def(get_state())) {
+    // This item has a current state definition that we should use
+    // to render the item.
+    Node *def = get_state_def(get_state());
+    
+    // We'll use a normal DirectRenderTraverser to do the rendering
+    // of the subgraph.
+    DirectRenderTraverser drt(gsg, RenderRelation::get_class_type());
+    drt.set_view_frustum_cull(false);
+    drt.traverse(def, attrib, AllTransitionsWrapper());
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::enter
 //       Access: Public, Virtual
@@ -221,6 +251,70 @@ release(const MouseWatcherParameter &param) {
               EventParameter(ep));
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::set_active
+//       Access: Published, Virtual
+//  Description: Sets whether the PGItem is active for mouse watching.
+//               This is not necessarily related to the
+//               active/inactive appearance of the item, which is
+//               controlled by set_state(), but it does affect whether
+//               it responds to mouse events.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+set_active(bool active) {
+  if (active) {
+    _flags |= F_active;
+  } else {
+    _flags &= ~F_active;
+    // Deactivating the item automatically defocuses it too.
+    if (get_focus()) {
+      set_focus(false);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::set_focus
+//       Access: Published, Virtual
+//  Description: Sets whether the PGItem currently has keyboard focus.
+//               This simply means that the item may respond to
+//               keyboard events as well as to mouse events; precisely
+//               what this means is up to the individual item.  
+//
+//               Only one PGItem in the world is allowed to have focus
+//               at any given time.  Setting the focus on any other
+//               item automatically disables the focus from the
+//               previous item.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+set_focus(bool focus) {
+  if (focus) {
+    if (!get_active()) {
+      // Cannot set focus on an inactive item.
+      return;
+    }
+
+    // Set the keyboard focus to this item.
+    if (_focus_item != this) {
+      if (_focus_item != (PGItem *)NULL) {
+        // Clear the focus from whatever item currently has it.
+        _focus_item->set_focus(false);
+      }
+      _focus_item = this;
+    }
+    _flags |= F_focus;
+
+  } else {
+    if (_focus_item == this) {
+      // Remove this item from the focus.
+      _focus_item = (PGItem *)NULL;
+    }
+
+    _flags &= ~F_focus;
+  }
+  _region->set_keyboard(focus);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_num_state_defs
 //       Access: Published
@@ -265,18 +359,7 @@ clear_state_def(int state) {
     return;
   }
 
-  Node *def = _state_defs[state]._node;
-  if (def != (Node *)NULL) {
-    // Remove all the children from this node.
-    int num_children = def->get_num_children(RenderRelation::get_class_type());
-    while (num_children > 0) {
-      nassertv(num_children == def->get_num_children(RenderRelation::get_class_type()));
-      NodeRelation *arc = 
-        def->get_child(RenderRelation::get_class_type(), num_children - 1);
-      remove_arc(arc);
-      num_children--;
-    }
-  }
+  remove_all_children(_state_defs[state]._node);
 
   _state_defs[state]._frame_arc = (NodeRelation *)NULL;
   _state_defs[state]._frame_stale = true;
@@ -292,7 +375,7 @@ clear_state_def(int state) {
 ////////////////////////////////////////////////////////////////////
 Node *PGItem::
 get_state_def(int state) {
-  nassertr(state < 1000, (Node *)NULL);  // Sanity check.
+  nassertr(state >= 0 && state < 1000, (Node *)NULL);  // Sanity check.
   slot_state_def(state);
 
   if (_state_defs[state]._node == (Node *)NULL) {
@@ -375,11 +458,35 @@ get_text_node() {
     _text_node = new TextNode("pguiText");
     _text_node->freeze();
     _text_node->set_text_color(0.0, 0.0, 0.0, 1.0);
-    _text_node->set_align(TM_ALIGN_CENTER);
+
+    // The default TextNode is aligned to the left, for the
+    // convenience of PGEntry.
+    _text_node->set_align(TM_ALIGN_LEFT);
   }
   return _text_node;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::remove_all_children
+//       Access: Protected, Static
+//  Description: An internal utility function to remove all the
+//               children of the indicated Node.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+remove_all_children(Node *node) {
+  if (node != (Node *)NULL) {
+    int num_children = 
+      node->get_num_children(RenderRelation::get_class_type());
+    while (num_children > 0) {
+      nassertv(num_children == node->get_num_children(RenderRelation::get_class_type()));
+      NodeRelation *arc = 
+        node->get_child(RenderRelation::get_class_type(), num_children - 1);
+      remove_arc(arc);
+      num_children--;
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::slot_state_def
 //       Access: Private

+ 24 - 4
panda/src/pgui/pgItem.h

@@ -32,8 +32,12 @@
 #include "pt_NodeRelation.h"
 #include "textNode.h"
 
-class ArcChain;
+class PGTop;
+class GraphicsStateGuardian;
+class AllAttributesWrapper;
+class AllTransitionsWrapper;
 class MouseWatcherParameter;
+class ArcChain;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PGItem
@@ -61,9 +65,12 @@ public:
   virtual Node *make_copy() const;
   virtual void xform(const LMatrix4f &mat);
 
-  void activate_region(const LMatrix4f &transform, int sort);
+  void activate_region(PGTop *top, const LMatrix4f &transform, int sort);
   INLINE PGMouseWatcherRegion *get_region() const;
 
+  virtual void draw_item(PGTop *top, GraphicsStateGuardian *gsg, 
+                         const AllAttributesWrapper &attrib);
+
   virtual void enter(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
@@ -79,9 +86,12 @@ PUBLISHED:
   INLINE void set_state(int state);
   INLINE int get_state() const;
 
-  INLINE void set_active(bool active);
+  virtual void set_active(bool active);
   INLINE bool get_active() const;
 
+  virtual void set_focus(bool focus);
+  INLINE bool get_focus() const;
+
   int get_num_state_defs() const;
   void clear_state_def(int state);
   bool has_state_def(int state) const;
@@ -105,6 +115,11 @@ PUBLISHED:
   static TextNode *get_text_node();
   INLINE static void set_text_node(TextNode *node);
 
+  INLINE static PGItem *get_focus_item();
+
+protected:
+  static void remove_all_children(Node *node);
+
 private:
   void slot_state_def(int state);
   void update_frame(int state);
@@ -113,7 +128,11 @@ private:
   bool _has_frame;
   LVecBase4f _frame;
   int _state;
-  bool _active;
+  enum Flags {
+    F_active  = 0x01,
+    F_focus   = 0x02,
+  };
+  int _flags;
 
   PT(PGMouseWatcherRegion) _region;
 
@@ -128,6 +147,7 @@ private:
   StateDefs _state_defs;
 
   static PT(TextNode) _text_node;
+  static PGItem *_focus_item;
 
 public:
   static TypeHandle get_class_type() {

+ 10 - 17
panda/src/pgui/pgTop.cxx

@@ -31,7 +31,6 @@
 #include "switchNode.h"
 #include "transformTransition.h"
 #include "nodeTransitionWrapper.h"
-#include "directRenderTraverser.h"
 #include "omniBoundingVolume.h"
 #include "pruneTransition.h"
 
@@ -196,27 +195,21 @@ r_traverse(Node *node, const ArcChain &chain) {
       mat = tt->get_matrix();
       
       // Now apply this transform to the item's frame.
-      pgi->activate_region(mat, _sort_index);
+      pgi->activate_region(this, mat, _sort_index);
       _sort_index++;
 
       add_region(pgi->get_region());
     }
 
-    if (pgi->has_state_def(pgi->get_state())) {
-      // This item has a current state definition that we should use
-      // to render the item.
-      Node *def = pgi->get_state_def(pgi->get_state());
-
-      // Get the net transitions to the PGItem.
-      AllTransitionsWrapper complete_trans;
-      wrt(pgi, chain.begin(), chain.end(), this,
-          complete_trans, RenderRelation::get_class_type());
-
-      // We'll use a normal DirectRenderTraverser to do the rendering
-      // of the subgraph.
-      DirectRenderTraverser drt(_gsg, RenderRelation::get_class_type());
-      drt.traverse(def, _attrib, complete_trans);
-    }
+    // And draw the item, however it wishes to be drawn.
+    
+    // Get the net transitions to the PGItem.
+    AllTransitionsWrapper complete_trans;
+    wrt(pgi, chain.begin(), chain.end(), this,
+        complete_trans, RenderRelation::get_class_type());
+    AllAttributesWrapper render_state;
+    render_state.apply_from(_attrib, complete_trans);
+    pgi->draw_item(this, _gsg, render_state);
 
   } else if (node->is_of_type(GeomNode::get_class_type())) {
     _gsg->_geom_nodes_pcollector.add_level(1);

+ 1 - 1
panda/src/pgui/pgTop.h

@@ -81,7 +81,7 @@ private:
   RenderTraverser *_trav;
   AllAttributesWrapper _attrib;
   int _sort_index;
-
+  
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 17 - 0
panda/src/putil/mouseButton.cxx

@@ -75,6 +75,23 @@ three() {
   return _buttons[2];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseButton::is_mouse_button
+//       Access: Public, Static
+//  Description: Returns true if the indicated ButtonHandle is a mouse
+//               button, false if it is some other kind of button.
+////////////////////////////////////////////////////////////////////
+bool MouseButton::
+is_mouse_button(ButtonHandle button) {
+  for (int i = 0; i < num_mouse_buttons; i++) {
+    if (button == _buttons[i]) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseButton::init_mouse_buttons
 //       Access: Public, Static

+ 2 - 0
panda/src/putil/mouseButton.h

@@ -36,6 +36,8 @@ PUBLISHED:
   static ButtonHandle two();
   static ButtonHandle three();
 
+  static bool is_mouse_button(ButtonHandle button);
+
 public:
   static void init_mouse_buttons();
 };

+ 12 - 0
panda/src/sgraphutil/directRenderTraverser.I

@@ -16,3 +16,15 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: DirectRenderTraverser::set_view_frustum_cull
+//       Access: Public
+//  Description: Sets the flag that indicates whether view-frustum
+//               culling should be performed during this traversal.
+//               This is normally true.
+////////////////////////////////////////////////////////////////////
+INLINE void DirectRenderTraverser::
+set_view_frustum_cull(bool flag) {
+  _view_frustum_cull = flag;
+}

+ 24 - 16
panda/src/sgraphutil/directRenderTraverser.cxx

@@ -19,6 +19,7 @@
 #include "directRenderTraverser.h"
 #include "config_sgraphutil.h"
 #include "frustumCullTraverser.h"
+#include "dftraverser.h"
 
 #include <wrt.h>
 #include <geomNode.h>
@@ -57,6 +58,7 @@ DirectRenderTraverser(GraphicsStateGuardian *gsg, TypeHandle graph_type,
                       const ArcChain &arc_chain) :
   RenderTraverser(gsg, graph_type, arc_chain)
 {
+  _view_frustum_cull = true;
 }
 
 
@@ -99,25 +101,31 @@ traverse(Node *root,
       }
     }
 
-  // Determine the relative transform matrix from the camera to our
-  // starting node.  This is important for proper view-frustum
-  // culling.
-  LMatrix4f rel_from_camera;
-  NodeTransitionWrapper ntw(TransformTransition::get_class_type());
-  const DisplayRegion *dr = _gsg->get_current_display_region();
-  ProjectionNode *camera = dr->get_cull_frustum();
-  wrt(camera, root, begin(), end(), ntw, get_graph_type());
-  const TransformTransition *tt;
-  if (get_transition_into(tt, ntw)) {
-    rel_from_camera = tt->get_matrix();
+  if (_view_frustum_cull) {
+    // Determine the relative transform matrix from the camera to our
+    // starting node.  This is important for proper view-frustum
+    // culling.
+    LMatrix4f rel_from_camera;
+    NodeTransitionWrapper ntw(TransformTransition::get_class_type());
+    const DisplayRegion *dr = _gsg->get_current_display_region();
+    ProjectionNode *camera = dr->get_cull_frustum();
+    wrt(camera, root, begin(), end(), ntw, get_graph_type());
+    const TransformTransition *tt;
+    if (get_transition_into(tt, ntw)) {
+      rel_from_camera = tt->get_matrix();
+    } else {
+      // No relative transform.
+      rel_from_camera = LMatrix4f::ident_mat();
+    }
+    fc_traverse(_arc_chain, root, rel_from_camera, *this,
+                render_state, level_state, _gsg, _graph_type);
+
   } else {
-    // No relative transform.
-    rel_from_camera = LMatrix4f::ident_mat();
+    // No view-frustum culling is requested; just do a normal
+    // depth-first traversal of the complete tree.
+    df_traverse(root, *this, render_state, level_state, _graph_type);
   }
 
-  fc_traverse(_arc_chain, root, rel_from_camera, *this,
-              render_state, level_state, _gsg, _graph_type);
-
   if (level_state._decal_mode &&
       root->is_of_type(GeomNode::get_class_type())) {
 #ifndef NDEBUG

+ 4 - 0
panda/src/sgraphutil/directRenderTraverser.h

@@ -57,6 +57,8 @@ public:
                         const AllAttributesWrapper &initial_state,
                         const AllTransitionsWrapper &net_trans);
 
+  INLINE void set_view_frustum_cull(bool flag);
+
 public:
   // These methods, from parent class TraverserVisitor, define the
   // behavior of the DirectRenderTraverser as it traverses the graph.
@@ -73,6 +75,8 @@ public:
                     const DirectRenderLevelState &level_state);
 
 private:
+  bool _view_frustum_cull;
+
   // Statistics
   static PStatCollector _draw_pcollector;
 

+ 70 - 36
panda/src/text/textNode.I

@@ -86,6 +86,7 @@ thaw() {
   return _freeze_level;
 }
 
+/*
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_font
 //       Access: Published
@@ -94,12 +95,12 @@ thaw() {
 INLINE void TextNode::
 set_font(Node *font_node) {
   if (font_node == (Node *)NULL) {
-    _font = (TextFont *)NULL;
+    set_font((TextFont *)NULL);
   } else {
-    _font = new TextFont(font_node);
+    set_font(new TextFont(font_node));
   }
-  rebuild(true);
 }
+*/
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TextNode::set_font
@@ -108,8 +109,10 @@ set_font(Node *font_node) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_font(TextFont *font) {
-  _font = font;
-  rebuild(true);
+  if (_font != font) {
+    _font = font;
+    rebuild(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -140,8 +143,10 @@ get_line_height() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_slant(float slant) {
-  _slant = slant;
-  rebuild(true);
+  if (_slant != slant) {
+    _slant = slant;
+    rebuild(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -161,8 +166,10 @@ get_slant() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_align(int align_type) {
-  _align = align_type;
-  rebuild(true);
+  if (_align != align_type) {
+    _align = align_type;
+    rebuild(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -183,9 +190,11 @@ get_align() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_wordwrap(float wordwrap) {
-  _flags |= F_has_wordwrap;
-  _wordwrap_width = wordwrap;
-  rebuild(true);
+  if (!has_wordwrap() || _wordwrap_width != wordwrap) {
+    _flags |= F_has_wordwrap;
+    _wordwrap_width = wordwrap;
+    rebuild(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -196,8 +205,10 @@ set_wordwrap(float wordwrap) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 clear_wordwrap() {
-  _flags &= ~F_has_wordwrap;
-  rebuild(true);
+  if (has_wordwrap()) {
+    _flags &= ~F_has_wordwrap;
+    rebuild(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -237,9 +248,11 @@ set_text_color(float r, float g, float b, float a) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_text_color(const Colorf &text_color) {
-  _text_color = text_color;
-  _flags |= F_has_text_color;
-  rebuild(false);
+  if (!has_text_color() || _text_color != text_color) {
+    _text_color = text_color;
+    _flags |= F_has_text_color;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -250,7 +263,10 @@ set_text_color(const Colorf &text_color) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 clear_text_color() {
-  _flags &= ~F_has_text_color;
+  if (has_text_color()) {
+    _flags &= ~F_has_text_color;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -290,8 +306,10 @@ set_frame_color(float r, float g, float b, float a) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_frame_color(const Colorf &frame_color) {
-  _frame_color = frame_color;
-  rebuild(false);
+  if (_frame_color != frame_color) {
+    _frame_color = frame_color;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -311,10 +329,12 @@ get_frame_color() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_card_border(float size, float uv_portion) {
-  _flags |= F_has_card_border;
-  _card_border_size = size;
-  _card_border_uv_portion = uv_portion;
-  rebuild(false);
+  if (!has_card_border() || _card_border_size != size || _card_border_uv_portion != uv_portion) {
+    _flags |= F_has_card_border;
+    _card_border_size = size;
+    _card_border_uv_portion = uv_portion;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -324,8 +344,10 @@ set_card_border(float size, float uv_portion) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 clear_card_border() {
-  _flags &= ~F_has_card_border;
-  rebuild(false);
+  if (has_card_border()) {
+    _flags &= ~F_has_card_border;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -375,8 +397,10 @@ set_card_color(float r, float g, float b, float a) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_card_color(const Colorf &card_color) {
-  _card_color = card_color;
-  rebuild(false);
+  if (_card_color != card_color) {
+    _card_color = card_color;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -396,9 +420,15 @@ get_card_color() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_card_texture(Texture *card_texture) {
-  _flags |= F_has_card_texture;
-  _card_texture = card_texture;
-  rebuild(false);
+  if (card_texture == (Texture *)NULL) {
+    clear_card_texture();
+  } else {
+    if (!has_card_texture() || _card_texture != card_texture) {
+      _flags |= F_has_card_texture;
+      _card_texture = card_texture;
+      rebuild(false);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -408,9 +438,11 @@ set_card_texture(Texture *card_texture) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 clear_card_texture() {
-  _flags &= ~F_has_card_texture;
-  _card_texture = NULL;
-  rebuild(false);
+  if (has_card_texture()) {
+    _flags &= ~F_has_card_texture;
+    _card_texture = NULL;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -450,8 +482,10 @@ set_shadow_color(float r, float g, float b, float a) {
 ////////////////////////////////////////////////////////////////////
 INLINE void TextNode::
 set_shadow_color(const Colorf &shadow_color) {
-  _shadow_color = shadow_color;
-  rebuild(false);
+  if (_shadow_color != shadow_color) {
+    _shadow_color = shadow_color;
+    rebuild(false);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/text/textNode.h

@@ -58,7 +58,7 @@ PUBLISHED:
   INLINE int get_freeze_level() const;
   INLINE int thaw();
 
-  INLINE void set_font(Node *font_node);
+  //  INLINE void set_font(Node *font_node);
   INLINE void set_font(TextFont *font);
   INLINE TextFont *get_font() const;
 

+ 1 - 1
panda/src/tform/mouseWatcher.I

@@ -319,7 +319,7 @@ set_modifier_buttons(const ModifierButtons &mods) {
 //  Description: Returns the set of buttons that are being monitored
 //               as modifier buttons, as well as their current state.
 ////////////////////////////////////////////////////////////////////
-INLINE const ModifierButtons &MouseWatcher::
+INLINE ModifierButtons MouseWatcher::
 get_modifier_buttons() const {
   return _mods;
 }

+ 156 - 33
panda/src/tform/mouseWatcher.cxx

@@ -24,7 +24,6 @@
 #include "mouseData.h"
 #include "buttonEventDataTransition.h"
 #include "buttonEventDataAttribute.h"
-#include "keyboardButton.h"
 #include "mouseButton.h"
 #include "throw_event.h"
 #include "eventParameter.h"
@@ -302,6 +301,159 @@ throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::press
+//       Access: Private
+//  Description: Records the indicated mouse or keyboard button as
+//               being depressed.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+press(ButtonHandle button) {
+  MouseWatcherParameter param;
+  param.set_button(button);
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(_mouse);
+
+  if (MouseButton::is_mouse_button(button)) {
+    // Mouse buttons are inextricably linked to the mouse position.
+    
+    if (!_button_down) {
+      _button_down_region = _current_region;
+    }
+    _button_down = true;
+    if (_button_down_region != (MouseWatcherRegion *)NULL) {
+      _button_down_region->press(param);
+      throw_event_pattern(_button_down_pattern, _button_down_region,
+                          button);
+    }
+    
+  } else {
+    // It's a keyboard button; therefore, send the event to every
+    // region that wants keyboard buttons, regardless of the mouse
+    // position.
+    if (_current_region != (MouseWatcherRegion *)NULL) {
+      // Our current region, the one under the mouse, always get
+      // all the keyboard events, even if it doesn't set its
+      // keyboard flag.
+      _current_region->press(param);
+    }
+    
+    // All the other regions only get the keyboard events if
+    // they set their global keyboard flag.
+    param.set_outside(true);
+    global_keyboard_press(param);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::release
+//       Access: Private
+//  Description: Records the indicated mouse or keyboard button as
+//               being released.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+release(ButtonHandle button) {
+  MouseWatcherParameter param;
+  param.set_button(button);
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(_mouse);
+
+  if (MouseButton::is_mouse_button(button)) {
+    // Button up.  Send the up event associated with the region we
+    // were over when the button went down.
+    
+    // There is some danger of losing button-up events here.  If
+    // more than one button goes down together, we won't detect
+    // both of the button-up events properly.
+    if (_button_down_region != (MouseWatcherRegion *)NULL) {
+      param.set_outside(_current_region != _button_down_region);
+      _button_down_region->release(param);
+      throw_event_pattern(_button_up_pattern, _button_down_region,
+                          button);
+    }
+    _button_down = false;
+    
+  } else {
+    // It's a keyboard button; therefore, send the event to every
+    // region that wants keyboard buttons, regardless of the mouse
+    // position.
+    if (_current_region != (MouseWatcherRegion *)NULL) {
+      _current_region->release(param);
+    }
+    
+    param.set_outside(true);
+    global_keyboard_release(param);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::global_keyboard_press
+//       Access: Private
+//  Description: Calls press() on all regions that are interested in
+//               receiving global keyboard events, except for the
+//               current region (which already received this one).
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+global_keyboard_press(const MouseWatcherParameter &param) {
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    MouseWatcherRegion *region = (*ri);
+
+    if (region != _current_region &&
+        region->get_active() && region->get_keyboard()) {
+      region->press(param);
+    }
+  }
+
+  // Also check all of our sub-groups.
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    MouseWatcherGroup *group = (*gi);
+    for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
+      MouseWatcherRegion *region = (*ri);
+
+      if (region != _current_region &&
+          region->get_active() && region->get_keyboard()) {
+        region->press(param);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::global_keyboard_release
+//       Access: Private
+//  Description: Calls release() on all regions that are interested in
+//               receiving global keyboard events, except for the
+//               current region (which already received this one).
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+global_keyboard_release(const MouseWatcherParameter &param) {
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    MouseWatcherRegion *region = (*ri);
+
+    if (region != _current_region &&
+        region->get_active() && region->get_keyboard()) {
+      region->release(param);
+    }
+  }
+
+  // Also check all of our sub-groups.
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    MouseWatcherGroup *group = (*gi);
+    for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
+      MouseWatcherRegion *region = (*ri);
+
+      if (region != _current_region &&
+          region->get_active() && region->get_keyboard()) {
+        region->release(param);
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::transmit_data
 //       Access: Public
@@ -366,39 +518,10 @@ transmit_data(NodeAttributes &data) {
       const ButtonEvent &be = (*bi);
       _mods.add_event(be);
 
-      MouseWatcherParameter param;
-      param.set_button(be._button);
-      param.set_modifier_buttons(_mods);
-      param.set_mouse(_mouse);
-
-      if (!be._down) {
-        // Button up.  Send the up event associated with the region we
-        // were over when the button went down.
-
-        // There is some danger of losing button-up events here.  If
-        // more than one button goes down together, we won't detect
-        // both of the button-up events properly.
-        if (_button_down_region != (MouseWatcherRegion *)NULL) {
-          param.set_outside(_current_region != _button_down_region);
-
-          _button_down_region->release(param);
-          throw_event_pattern(_button_up_pattern, _button_down_region,
-                              be._button);
-        }
-        _button_down = false;
-
+      if (be._down) {
+        press(be._button);
       } else {
-        // Button down.
-
-        if (!_button_down) {
-          _button_down_region = _current_region;
-        }
-        _button_down = true;
-        if (_button_down_region != (MouseWatcherRegion *)NULL) {
-          _button_down_region->press(param);
-          throw_event_pattern(_button_down_pattern, _button_down_region,
-                              be._button);
-        }
+        release(be._button);
       }
     }
   }

+ 8 - 1
panda/src/tform/mouseWatcher.h

@@ -37,6 +37,8 @@
 
 #include "pset.h"
 
+class MouseWatcherParameter;
+
 ////////////////////////////////////////////////////////////////////
 //       Class : MouseWatcher
 // Description : This TFormer maintains a list of rectangular regions
@@ -96,7 +98,7 @@ PUBLISHED:
   INLINE EventHandler *get_extra_handler(void) const;
 
   INLINE void set_modifier_buttons(const ModifierButtons &mods);
-  INLINE const ModifierButtons &get_modifier_buttons() const;
+  INLINE ModifierButtons get_modifier_buttons() const;
 
 public:
   virtual void output(ostream &out) const;
@@ -111,6 +113,11 @@ private:
                            const MouseWatcherRegion *region,
                            const ButtonHandle &button);
 
+  void press(ButtonHandle button);
+  void release(ButtonHandle button);
+  void global_keyboard_press(const MouseWatcherParameter &param);
+  void global_keyboard_release(const MouseWatcherParameter &param);
+
   typedef pset< PT(MouseWatcherGroup) > Groups;
   Groups _groups;
 

+ 44 - 8
panda/src/tform/mouseWatcherRegion.I

@@ -29,8 +29,7 @@ MouseWatcherRegion(const string &name, float left, float right,
   _frame(left, right, bottom, top)
 {
   _sort = 0;
-  _active = true;
-  _suppress_below = true;
+  _flags = F_active | F_suppress_below;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -44,8 +43,7 @@ MouseWatcherRegion(const string &name, const LVecBase4f &frame) :
   _frame(frame)
 {
   _sort = 0;
-  _active = true;
-  _suppress_below = false;
+  _flags = F_active | F_suppress_below;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -124,7 +122,11 @@ get_sort() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void MouseWatcherRegion::
 set_active(bool active) {
-  _active = active;
+  if (active) {
+    _flags |= F_active;
+  } else {
+    _flags &= ~F_active;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -135,7 +137,37 @@ set_active(bool active) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool MouseWatcherRegion::
 get_active() const {
-  return _active;
+  return ((_flags & F_active) != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherRegion::set_keyboard
+//       Access: Published
+//  Description: Sets whether the region is interested in global
+//               keyboard events.  If this is true, then any keyboard
+//               button events will be passed to press() and release()
+//               regardless of the position of the mouse onscreen;
+//               otherwise, these events will only be passed if the
+//               mouse is over the region.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcherRegion::
+set_keyboard(bool keyboard) {
+  if (keyboard) {
+    _flags |= F_keyboard;
+  } else {
+    _flags &= ~F_keyboard;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherRegion::get_keyboard
+//       Access: Published
+//  Description: Returns whether the region is interested in global
+//               keyboard events; see set_keyboard().
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcherRegion::
+get_keyboard() const {
+  return ((_flags & F_keyboard) != 0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -149,7 +181,11 @@ get_active() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void MouseWatcherRegion::
 set_suppress_below(bool suppress_below) {
-  _suppress_below = suppress_below;
+  if (suppress_below) {
+    _flags |= F_suppress_below;
+  } else {
+    _flags &= ~F_suppress_below;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -160,7 +196,7 @@ set_suppress_below(bool suppress_below) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool MouseWatcherRegion::
 get_suppress_below() const {
-  return _suppress_below;
+  return ((_flags & F_suppress_below) != 0);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 9 - 3
panda/src/tform/mouseWatcherRegion.h

@@ -51,6 +51,9 @@ PUBLISHED:
   INLINE void set_active(bool active);
   INLINE bool get_active() const;
 
+  INLINE void set_keyboard(bool keyboard);
+  INLINE bool get_keyboard() const;
+
   INLINE void set_suppress_below(bool suppress_below);
   INLINE bool get_suppress_below() const;
 
@@ -70,9 +73,12 @@ private:
   float _area;
   int _sort;
 
-  bool _active;
-  bool _suppress_below;
-
+  enum Flags {
+    F_active         = 0x001,
+    F_keyboard       = 0x002,
+    F_suppress_below = 0x004,
+  };
+  int _flags;
   ModifierButtons _mods;
 
 public: