Browse Source

add within/without messages

David Rose 24 years ago
parent
commit
7c00e5b6ce

+ 57 - 2
panda/src/pgui/pgItem.I

@@ -237,6 +237,30 @@ get_exit_prefix() {
   return "exit-";
   return "exit-";
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_within_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the within
+//               event for all PGItems.  The within event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_within_prefix() {
+  return "within-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_without_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the without
+//               event for all PGItems.  The without event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_without_prefix() {
+  return "without-";
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_focus_in_prefix
 //     Function: PGItem::get_focus_in_prefix
 //       Access: Published, Static
 //       Access: Published, Static
@@ -297,7 +321,8 @@ get_release_prefix() {
 //     Function: PGItem::get_enter_event
 //     Function: PGItem::get_enter_event
 //       Access: Published
 //       Access: Published
 //  Description: Returns the event name that will be thrown when the
 //  Description: Returns the event name that will be thrown when the
-//               item is active and the mouse enters its frame.
+//               item is active and the mouse enters its frame, but
+//               not any nested frames.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE string PGItem::
 INLINE string PGItem::
 get_enter_event() const {
 get_enter_event() const {
@@ -308,13 +333,43 @@ get_enter_event() const {
 //     Function: PGItem::get_exit_event
 //     Function: PGItem::get_exit_event
 //       Access: Published
 //       Access: Published
 //  Description: Returns the event name that will be thrown when the
 //  Description: Returns the event name that will be thrown when the
-//               item is active and the mouse exits its frame.
+//               item is active and the mouse exits its frame, or
+//               enters a nested frame.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE string PGItem::
 INLINE string PGItem::
 get_exit_event() const {
 get_exit_event() const {
   return get_exit_prefix() + get_id();
   return get_exit_prefix() + get_id();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_within_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse moves within the
+//               boundaries of the frame.  This is different from the
+//               enter_event in that the mouse is considered within
+//               the frame even if it is also within a nested frame.
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_within_event() const {
+  return get_within_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_without_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse moves completely outside
+//               the boundaries of the frame.  This is different from
+//               the exit_event in that the mouse is considered
+//               within the frame even if it is also within a nested
+//               frame.
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_without_event() const {
+  return get_without_prefix() + get_id();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_focus_in_event
 //     Function: PGItem::get_focus_in_event
 //       Access: Published
 //       Access: Published

+ 41 - 2
panda/src/pgui/pgItem.cxx

@@ -226,7 +226,10 @@ draw_item(PGTop *top, GraphicsStateGuardian *gsg,
 //     Function: PGItem::enter
 //     Function: PGItem::enter
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse enters the region.
+//               mouse enters the region.  The mouse is only
+//               considered to be "entered" in one region at a time;
+//               in the case of nested regions, it exits the outer
+//               region before entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PGItem::
 void PGItem::
 enter(const MouseWatcherParameter &param) {
 enter(const MouseWatcherParameter &param) {
@@ -240,7 +243,10 @@ enter(const MouseWatcherParameter &param) {
 //     Function: PGItem::exit
 //     Function: PGItem::exit
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse exits the region.
+//               mouse exits the region.  The mouse is only considered
+//               to be "entered" in one region at a time; in the case
+//               of nested regions, it exits the outer region before
+//               entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PGItem::
 void PGItem::
 exit(const MouseWatcherParameter &param) {
 exit(const MouseWatcherParameter &param) {
@@ -250,6 +256,39 @@ exit(const MouseWatcherParameter &param) {
   throw_event(event, EventParameter(ep));
   throw_event(event, EventParameter(ep));
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::within
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves within the boundaries of the region, even
+//               if it is also within the boundaries of a nested
+//               region.  This is different from "enter", which is
+//               only called whenever the mouse is within only that
+//               region.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+within(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_within_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::without
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves completely outside the boundaries of the
+//               region.  See within().
+////////////////////////////////////////////////////////////////////
+void PGItem::
+without(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_without_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::focus_in
 //     Function: PGItem::focus_in
 //       Access: Public, Virtual
 //       Access: Public, Virtual

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

@@ -75,6 +75,8 @@ public:
 
 
   virtual void enter(const MouseWatcherParameter &param);
   virtual void enter(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
+  virtual void within(const MouseWatcherParameter &param);
+  virtual void without(const MouseWatcherParameter &param);
   virtual void focus_in();
   virtual void focus_in();
   virtual void focus_out();
   virtual void focus_out();
   virtual void press(const MouseWatcherParameter &param, bool background);
   virtual void press(const MouseWatcherParameter &param, bool background);
@@ -119,6 +121,8 @@ PUBLISHED:
 
 
   INLINE static string get_enter_prefix();
   INLINE static string get_enter_prefix();
   INLINE static string get_exit_prefix();
   INLINE static string get_exit_prefix();
+  INLINE static string get_within_prefix();
+  INLINE static string get_without_prefix();
   INLINE static string get_focus_in_prefix();
   INLINE static string get_focus_in_prefix();
   INLINE static string get_focus_out_prefix();
   INLINE static string get_focus_out_prefix();
   INLINE static string get_press_prefix();
   INLINE static string get_press_prefix();
@@ -126,6 +130,8 @@ PUBLISHED:
 
 
   INLINE string get_enter_event() const;
   INLINE string get_enter_event() const;
   INLINE string get_exit_event() const;
   INLINE string get_exit_event() const;
+  INLINE string get_within_event() const;
+  INLINE string get_without_event() const;
   INLINE string get_focus_in_event() const;
   INLINE string get_focus_in_event() const;
   INLINE string get_focus_out_event() const;
   INLINE string get_focus_out_event() const;
   INLINE string get_press_event(const ButtonHandle &button) const;
   INLINE string get_press_event(const ButtonHandle &button) const;

+ 39 - 2
panda/src/pgui/pgMouseWatcherRegion.cxx

@@ -52,7 +52,10 @@ PGMouseWatcherRegion::
 //     Function: PGMouseWatcherRegion::enter
 //     Function: PGMouseWatcherRegion::enter
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse enters the region.
+//               mouse enters the region.  The mouse is only
+//               considered to be "entered" in one region at a time;
+//               in the case of nested regions, it exits the outer
+//               region before entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PGMouseWatcherRegion::
 void PGMouseWatcherRegion::
 enter(const MouseWatcherParameter &param) {
 enter(const MouseWatcherParameter &param) {
@@ -65,7 +68,10 @@ enter(const MouseWatcherParameter &param) {
 //     Function: PGMouseWatcherRegion::exit
 //     Function: PGMouseWatcherRegion::exit
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse exits the region.
+//               mouse exits the region.  The mouse is only considered
+//               to be "entered" in one region at a time; in the case
+//               of nested regions, it exits the outer region before
+//               entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PGMouseWatcherRegion::
 void PGMouseWatcherRegion::
 exit(const MouseWatcherParameter &param) {
 exit(const MouseWatcherParameter &param) {
@@ -74,6 +80,37 @@ exit(const MouseWatcherParameter &param) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGMouseWatcherRegion::within
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves within the boundaries of the region, even
+//               if it is also within the boundaries of a nested
+//               region.  This is different from "enter", which is
+//               only called whenever the mouse is within only that
+//               region.
+////////////////////////////////////////////////////////////////////
+void PGMouseWatcherRegion::
+within(const MouseWatcherParameter &param) {
+  if (_item != (PGItem *)NULL) {
+    _item->within(param);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGMouseWatcherRegion::without
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves completely outside the boundaries of the
+//               region.  See within().
+////////////////////////////////////////////////////////////////////
+void PGMouseWatcherRegion::
+without(const MouseWatcherParameter &param) {
+  if (_item != (PGItem *)NULL) {
+    _item->without(param);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGMouseWatcherRegion::press
 //     Function: PGMouseWatcherRegion::press
 //       Access: Public, Virtual
 //       Access: Public, Virtual

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

@@ -39,6 +39,8 @@ public:
 
 
   virtual void enter(const MouseWatcherParameter &param);
   virtual void enter(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
+  virtual void within(const MouseWatcherParameter &param);
+  virtual void without(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
 
 

+ 77 - 8
panda/src/tform/mouseWatcher.I

@@ -124,7 +124,7 @@ is_over_region(const LPoint2f &pos) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE MouseWatcherRegion *MouseWatcher::
 INLINE MouseWatcherRegion *MouseWatcher::
 get_over_region() const {
 get_over_region() const {
-  return _current_region;
+  return _preferred_region;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -197,8 +197,11 @@ get_button_up_pattern() const {
 //     Function: MouseWatcher::set_enter_pattern
 //     Function: MouseWatcher::set_enter_pattern
 //       Access: Published
 //       Access: Published
 //  Description: Sets the pattern string that indicates how the event
 //  Description: Sets the pattern string that indicates how the event
-//               names are generated when the mouse wanders over a
-//               region.  See set_button_down_pattern().
+//               names are generated when the mouse enters a region.
+//               This is different from within_pattern, in that a
+//               mouse is only "entered" in the topmost region at a
+//               given time, while it might be "within" multiple
+//               nested regions.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void MouseWatcher::
 INLINE void MouseWatcher::
 set_enter_pattern(const string &pattern) {
 set_enter_pattern(const string &pattern) {
@@ -209,8 +212,10 @@ set_enter_pattern(const string &pattern) {
 //     Function: MouseWatcher::get_enter_pattern
 //     Function: MouseWatcher::get_enter_pattern
 //       Access: Published
 //       Access: Published
 //  Description: Returns the string that indicates how event names are
 //  Description: Returns the string that indicates how event names are
-//               generated when the mouse wanders over a region.  See
-//               set_button_down_pattern().
+//               generated when the mouse enters a region.  This is
+//               different from within_pattern, in that a mouse is
+//               only "entered" in the topmost region at a given time,
+//               while it might be "within" multiple nested regions.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const string &MouseWatcher::
 INLINE const string &MouseWatcher::
 get_enter_pattern() const {
 get_enter_pattern() const {
@@ -222,7 +227,10 @@ get_enter_pattern() const {
 //       Access: Published
 //       Access: Published
 //  Description: Sets the pattern string that indicates how the event
 //  Description: Sets the pattern string that indicates how the event
 //               names are generated when the mouse leaves a region.
 //               names are generated when the mouse leaves a region.
-//               See set_button_down_pattern().
+//               This is different from without_pattern, in that a
+//               mouse is only "entered" in the topmost region at a
+//               given time, while it might be "within" multiple
+//               nested regions.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void MouseWatcher::
 INLINE void MouseWatcher::
 set_leave_pattern(const string &pattern) {
 set_leave_pattern(const string &pattern) {
@@ -233,14 +241,75 @@ set_leave_pattern(const string &pattern) {
 //     Function: MouseWatcher::get_leave_pattern
 //     Function: MouseWatcher::get_leave_pattern
 //       Access: Published
 //       Access: Published
 //  Description: Returns the string that indicates how event names are
 //  Description: Returns the string that indicates how event names are
-//               generated when the mouse leaves a region.  See
-//               set_button_down_pattern().
+//               generated when the mouse leaves a region.  This is
+//               different from without_pattern, in that a mouse is
+//               only "entered" in the topmost region at a given time,
+//               while it might be "within" multiple nested regions.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const string &MouseWatcher::
 INLINE const string &MouseWatcher::
 get_leave_pattern() const {
 get_leave_pattern() const {
   return _leave_pattern;
   return _leave_pattern;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_within_pattern
+//       Access: Published
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated when the mouse wanders over a
+//               region.  This is different from enter_pattern, in
+//               that a mouse is only "entered" in the topmost region
+//               at a given time, while it might be "within" multiple
+//               nested regions.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+set_within_pattern(const string &pattern) {
+  _within_pattern = pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_within_pattern
+//       Access: Published
+//  Description: Returns the string that indicates how event names are
+//               generated when the mouse wanders over a region.  This
+//               is different from enter_pattern, in that a mouse is
+//               only "entered" in the topmost region at a given time,
+//               while it might be "within" multiple nested regions.
+////////////////////////////////////////////////////////////////////
+INLINE const string &MouseWatcher::
+get_within_pattern() const {
+  return _within_pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_without_pattern
+//       Access: Published
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated when the mouse wanders out of a
+//               region.  This is different from leave_pattern, in
+//               that a mouse is only "entered" in the topmost region
+//               at a given time, while it might be "within" multiple
+//               nested regions.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+set_without_pattern(const string &pattern) {
+  _without_pattern = pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_without_pattern
+//       Access: Published
+//  Description: Returns the string that indicates how event names are
+//               generated when the mouse wanders out of a region.
+//               This is different from leave_pattern, in that a mouse
+//               is only "entered" in the topmost region at a given
+//               time, while it might be "within" multiple nested
+//               regions.
+////////////////////////////////////////////////////////////////////
+INLINE const string &MouseWatcher::
+get_without_pattern() const {
+  return _without_pattern;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::set_geometry
 //     Function: MouseWatcher::set_geometry
 //       Access: Published
 //       Access: Published

+ 343 - 106
panda/src/tform/mouseWatcher.cxx

@@ -29,6 +29,8 @@
 #include "pruneTransition.h"
 #include "pruneTransition.h"
 #include "transformTransition.h"
 #include "transformTransition.h"
 
 
+#include <algorithm>
+
 TypeHandle MouseWatcher::_type_handle;
 TypeHandle MouseWatcher::_type_handle;
 
 
 TypeHandle MouseWatcher::_xyz_type;
 TypeHandle MouseWatcher::_xyz_type;
@@ -44,8 +46,8 @@ MouseWatcher::
 MouseWatcher(const string &name) : DataNode(name) {
 MouseWatcher(const string &name) : DataNode(name) {
   _has_mouse = false;
   _has_mouse = false;
   _suppress_flags = 0;
   _suppress_flags = 0;
-  _current_region = (MouseWatcherRegion *)NULL;
-  _button_down_region = (MouseWatcherRegion *)NULL;
+  _preferred_region = (MouseWatcherRegion *)NULL;
+  _preferred_button_down_region = (MouseWatcherRegion *)NULL;
   _button_down = false;
   _button_down = false;
   _eh = (EventHandler*)0L;
   _eh = (EventHandler*)0L;
 }
 }
@@ -68,12 +70,14 @@ MouseWatcher::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool MouseWatcher::
 bool MouseWatcher::
 remove_region(MouseWatcherRegion *region) {
 remove_region(MouseWatcherRegion *region) {
-  if (region == _current_region) {
-    _current_region = (MouseWatcherRegion *)NULL;
+  remove_region_from(_current_regions, region);
+  if (region == _preferred_region) {
+    _preferred_region = (MouseWatcherRegion *)NULL;
   }
   }
-  if (region == _button_down_region) {
-    _button_down_region = (MouseWatcherRegion *)NULL;
+  if (region == _preferred_button_down_region) {
+    _preferred_button_down_region = (MouseWatcherRegion *)NULL;
   }
   }
+
   return MouseWatcherGroup::remove_region(region);
   return MouseWatcherGroup::remove_region(region);
 }
 }
 
 
@@ -88,47 +92,9 @@ remove_region(MouseWatcherRegion *region) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 MouseWatcherRegion *MouseWatcher::
 MouseWatcherRegion *MouseWatcher::
 get_over_region(const LPoint2f &pos) const {
 get_over_region(const LPoint2f &pos) const {
-  MouseWatcherRegion *over_region = (MouseWatcherRegion *)NULL;
-
-  Regions::const_iterator ri;
-  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
-    MouseWatcherRegion *region = (*ri);
-    const LVecBase4f &frame = region->get_frame();
-
-    if (region->get_active() &&
-        pos[0] >= frame[0] && pos[0] <= frame[1] &&
-        pos[1] >= frame[2] && pos[1] <= frame[3]) {
-
-      // We're over this region.  Is it preferred to the other one?
-      if (over_region == (MouseWatcherRegion *)NULL ||
-          *region < *over_region) {
-        over_region = region;
-      }
-    }
-  }
-
-  // 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);
-      const LVecBase4f &frame = region->get_frame();
-      
-      if (region->get_active() &&
-          pos[0] >= frame[0] && pos[0] <= frame[1] &&
-          pos[1] >= frame[2] && pos[1] <= frame[3]) {
-        
-        // We're over this region.  Is it preferred to the other one?
-        if (over_region == (MouseWatcherRegion *)NULL ||
-            *region < *over_region) {
-          over_region = region;
-        }
-      }
-    }
-  }
-
-  return over_region;
+  VRegions regions;
+  get_over_regions(regions, pos);
+  return get_preferred_region(regions);
 }
 }
 
 
 
 
@@ -140,7 +106,15 @@ get_over_region(const LPoint2f &pos) const {
 void MouseWatcher::
 void MouseWatcher::
 output(ostream &out) const {
 output(ostream &out) const {
   DataNode::output(out);
   DataNode::output(out);
-  out << " (" << _regions.size() << " regions)";
+
+  int count = _regions.size();
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    MouseWatcherGroup *group = (*gi);
+    count += group->_regions.size();
+  }
+
+  out << " (" << count << " regions)";
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -203,44 +177,317 @@ add_group(MouseWatcherGroup *group) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool MouseWatcher::
 bool MouseWatcher::
 remove_group(MouseWatcherGroup *group) {
 remove_group(MouseWatcherGroup *group) {
-  if (group->has_region(_current_region)) {
-    _current_region = (MouseWatcherRegion *)NULL;
+  remove_regions_from(_current_regions, group);
+  if (group->has_region(_preferred_region)) {
+    _preferred_region = (MouseWatcherRegion *)NULL;
   }
   }
-  if (group->has_region(_button_down_region)) {
-    _button_down_region = (MouseWatcherRegion *)NULL;
+  if (group->has_region(_preferred_button_down_region)) {
+    _preferred_button_down_region = (MouseWatcherRegion *)NULL;
   }
   }
+
   return _groups.erase(group) != 0;
   return _groups.erase(group) != 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: MouseWatcher::set_current_region
+//     Function: MouseWatcher::get_over_regions
+//       Access: Private
+//  Description: Fills up the "regions" list with the set of regions
+//               that the indicated point is over, sorted in order by
+//               pointer.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+get_over_regions(MouseWatcher::VRegions &regions, const LPoint2f &pos) const {
+  // Ensure the vector is empty before we begin.
+  regions.clear();
+
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    MouseWatcherRegion *region = (*ri);
+    const LVecBase4f &frame = region->get_frame();
+
+    if (region->get_active() &&
+        pos[0] >= frame[0] && pos[0] <= frame[1] &&
+        pos[1] >= frame[2] && pos[1] <= frame[3]) {
+
+      regions.push_back(region);
+    }
+  }
+
+  // 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);
+      const LVecBase4f &frame = region->get_frame();
+      
+      if (region->get_active() &&
+          pos[0] >= frame[0] && pos[0] <= frame[1] &&
+          pos[1] >= frame[2] && pos[1] <= frame[3]) {
+        
+        regions.push_back(region);
+      }
+    }
+  }
+
+  // Now sort the regions by pointer.  By convention, the Regions
+  // vectors are always kept in order by pointer, so we can do easy
+  // linear comparison and intersection operations.
+  sort(regions.begin(), regions.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_preferred_region
+//       Access: Private, Static
+//  Description: Returns the innermost region of all the regions
+//               indicated in the given vector (usually, the regions
+//               the mouse is over).  This is the "preferred" region
+//               that gets some special treatment.
+////////////////////////////////////////////////////////////////////
+MouseWatcherRegion *MouseWatcher::
+get_preferred_region(const MouseWatcher::VRegions &regions) {
+  if (regions.empty()) {
+    return (MouseWatcherRegion *)NULL;
+  }
+
+  VRegions::const_iterator ri;
+  ri = regions.begin();
+  MouseWatcherRegion *preferred = *ri;
+  ++ri;
+  while (ri != regions.end()) {
+    MouseWatcherRegion *region = *ri;
+
+    if (*region < *preferred) {
+      preferred = region;
+    }
+    ++ri;
+  }
+
+  return preferred;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_current_regions
 //       Access: Private
 //       Access: Private
-//  Description: Changes the "current" region--the one we consider the
-//               mouse to be over--to the indicated one, and throws
+//  Description: Changes the "current" regions--the one we consider the
+//               mouse to be over--to the indicated list, and throws
 //               whatever events are appropriate because of that.
 //               whatever events are appropriate because of that.
+//
+//               The list passed in is destroyed.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MouseWatcher::
 void MouseWatcher::
-set_current_region(MouseWatcherRegion *region) {
-#ifndef NDEBUG
-  if (region != (MouseWatcherRegion *)NULL) {
-    region->test_ref_count_integrity();
+set_current_regions(MouseWatcher::VRegions &regions) {
+  // Set up a parameter for passing through any change events.
+  MouseWatcherParameter param;
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(_mouse);
+
+  // Now do a standard sorted comparison between the two vectors.
+  VRegions::const_iterator new_ri = regions.begin();
+  VRegions::const_iterator old_ri = _current_regions.begin();
+
+  bool any_changes = false;
+  while (new_ri != regions.end() && old_ri != _current_regions.end()) {
+    if ((*new_ri) < (*old_ri)) {
+      // Here's a new region that we didn't have last frame.
+      MouseWatcherRegion *new_region = (*new_ri);
+      new_region->within(param);
+      throw_event_pattern(_within_pattern, new_region, ButtonHandle::none());
+      any_changes = true;
+      ++new_ri;
+
+    } else if ((*old_ri) < (*new_ri)) {
+      // Here's a region we don't have any more.
+      MouseWatcherRegion *old_region = (*old_ri);
+      old_region->without(param);
+      throw_event_pattern(_without_pattern, old_region, ButtonHandle::none());
+      any_changes = true;
+      ++old_ri;
+
+    } else {
+      // Here's a region that hasn't changed.
+      ++new_ri;
+      ++old_ri;
+    }
   }
   }
-#endif
-  if (region != _current_region) {
+
+  while (new_ri != regions.end()) {
+    // Here's a new region that we didn't have last frame.
+    MouseWatcherRegion *new_region = (*new_ri);
+    new_region->within(param);
+    throw_event_pattern(_within_pattern, new_region, ButtonHandle::none());
+    any_changes = true;
+    ++new_ri;
+  }
+
+  while (old_ri != _current_regions.end()) {
+    // Here's a region we don't have any more.
+    MouseWatcherRegion *old_region = (*old_ri);
+    old_region->without(param);
+    throw_event_pattern(_without_pattern, old_region, ButtonHandle::none());
+    any_changes = true;
+    ++old_ri;
+  }
+
+  if (any_changes) {
+    // Now that we've compared the two vectors, simply swap them to set
+    // the new vector.
+    _current_regions.swap(regions);
+
+    // Determine which is the "preferred region", if any.  This is the
+    // topmost region that the mouse cursor is over, and the one that
+    // we are considred "entered" into.
+    MouseWatcherRegion *new_preferred_region = 
+      get_preferred_region(_current_regions);
+
+    if (_button_down && new_preferred_region != _preferred_button_down_region) {
+      // If the button's being held down, we're only allowed to select
+      // the preferred button down region.
+      new_preferred_region = (MouseWatcherRegion *)NULL;
+    }
+
+    if (new_preferred_region != _preferred_region) {
+      if (_preferred_region != (MouseWatcherRegion *)NULL) {
+        _preferred_region->exit(param);
+        throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none());
+      }
+      _preferred_region = new_preferred_region;
+      if (_preferred_region != (MouseWatcherRegion *)NULL) {
+        _preferred_region->enter(param);
+        throw_event_pattern(_enter_pattern, _preferred_region, ButtonHandle::none());
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::clear_current_regions
+//       Access: Private
+//  Description: Empties the set of current regions.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+clear_current_regions() {
+  if (!_current_regions.empty()) {
+    // Set up a parameter for passing through any change events.
     MouseWatcherParameter param;
     MouseWatcherParameter param;
     param.set_modifier_buttons(_mods);
     param.set_modifier_buttons(_mods);
     param.set_mouse(_mouse);
     param.set_mouse(_mouse);
+    
+    VRegions::const_iterator old_ri = _current_regions.begin();
+    
+    while (old_ri != _current_regions.end()) {
+      // Here's a region we don't have any more.
+      MouseWatcherRegion *old_region = (*old_ri);
+      old_region->exit(param);
+      throw_event_pattern(_leave_pattern, old_region, ButtonHandle::none());
+      ++old_ri;
+    }
+    
+    _current_regions.clear();
 
 
-    if (_current_region != (MouseWatcherRegion *)NULL) {
-      _current_region->exit(param);
-      throw_event_pattern(_leave_pattern, _current_region, ButtonHandle::none());
+    if (_preferred_region != (MouseWatcherRegion *)NULL) {
+      _preferred_region->exit(param);
+      throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none());
+      _preferred_region = (MouseWatcherRegion *)NULL;
     }
     }
-    _current_region = region;
-    if (_current_region != (MouseWatcherRegion *)NULL) {
-      _current_region->enter(param);
-      throw_event_pattern(_enter_pattern, _current_region, ButtonHandle::none());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::intersect_regions
+//       Access: Private, Static
+//  Description: Sets result to be the intersection of the list of
+//               regions in regions_a and regions_b.  It is assumed
+//               that both vectors are already sorted in pointer
+//               order.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+intersect_regions(MouseWatcher::VRegions &result,
+                  const MouseWatcher::VRegions &regions_a,
+                  const MouseWatcher::VRegions &regions_b) {
+  // Get a temporary vector for storing the result in.  We don't use
+  // result directly, because it might be the same vector as one of a
+  // or b.
+  VRegions temp;
+
+  // Now do a standard sorted intersection between the two vectors.
+  VRegions::const_iterator a_ri = regions_a.begin();
+  VRegions::const_iterator b_ri = regions_b.begin();
+
+  while (a_ri != regions_a.end() && b_ri != regions_b.end()) {
+    if ((*a_ri) < (*b_ri)) {
+      // Here's a region in a, not in b.
+      ++a_ri;
+
+    } else if ((*b_ri) < (*a_ri)) {
+      // Here's a region in b, not in a.
+      ++b_ri;
+
+    } else {
+      // Here's a region in both vectors.
+      temp.push_back(*a_ri);
+      ++a_ri;
+      ++b_ri;
+    }
+  }
+
+  // Now store the result!
+  result.swap(temp);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::remove_region_from
+//       Access: Private, Static
+//  Description: Removes the indicated region from the given vector.
+//               Assumes the vector is sorted in pointer order.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+remove_region_from(MouseWatcher::VRegions &regions,
+                   MouseWatcherRegion *region) {
+  VRegions::iterator ri = 
+    lower_bound(regions.begin(), regions.end(), region);
+  if (ri != regions.end() && (*ri) == region) {
+    // The region is in the vector.  Remove it.
+    regions.erase(ri);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::remove_regions_from
+//       Access: Private, Static
+//  Description: Removes all the regions in the indicated group from
+//               the given vector.  Assumes the vector is sorted in
+//               pointer order.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+remove_regions_from(MouseWatcher::VRegions &regions,
+                    MouseWatcherGroup *group) {
+  // Since the group stores a set of regions, which are also sorted in
+  // pointer order, we can just do an intersection operation here.
+  VRegions temp;
+
+  VRegions::const_iterator a_ri = regions.begin();
+  MouseWatcherGroup::Regions::const_iterator b_ri = group->_regions.begin();
+
+  while (a_ri != regions.end() && b_ri != group->_regions.end()) {
+    if ((*a_ri) < (*b_ri)) {
+      // Here's a region in the group, not in regions.
+      ++a_ri;
+
+    } else if ((*b_ri) < (*a_ri)) {
+      // Here's a region in regions, not in the group.
+      temp.push_back(*b_ri);
+      ++b_ri;
+
+    } else {
+      // Here's a region in the group and in regions.
+      ++a_ri;
+      ++b_ri;
     }
     }
   }
   }
+
+  // Now store the result!
+  regions.swap(temp);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -318,24 +565,25 @@ press(ButtonHandle button) {
     // Mouse buttons are inextricably linked to the mouse position.
     // Mouse buttons are inextricably linked to the mouse position.
     
     
     if (!_button_down) {
     if (!_button_down) {
-      _button_down_region = _current_region;
+      _preferred_button_down_region = _preferred_region;
     }
     }
     _button_down = true;
     _button_down = true;
-    if (_button_down_region != (MouseWatcherRegion *)NULL) {
-      _button_down_region->press(param);
-      throw_event_pattern(_button_down_pattern, _button_down_region,
-                          button);
+
+    if (_preferred_button_down_region != (MouseWatcherRegion *)NULL) {
+      _preferred_button_down_region->press(param);
+      throw_event_pattern(_button_down_pattern,
+                          _preferred_button_down_region, button);
     }
     }
     
     
   } else {
   } else {
     // It's a keyboard button; therefore, send the event to every
     // It's a keyboard button; therefore, send the event to every
     // region that wants keyboard buttons, regardless of the mouse
     // region that wants keyboard buttons, regardless of the mouse
     // position.
     // position.
-    if (_current_region != (MouseWatcherRegion *)NULL) {
+    if (_preferred_region != (MouseWatcherRegion *)NULL) {
       // Our current region, the one under the mouse, always get
       // Our current region, the one under the mouse, always get
       // all the keyboard events, even if it doesn't set its
       // all the keyboard events, even if it doesn't set its
       // keyboard flag.
       // keyboard flag.
-      _current_region->press(param);
+      _preferred_region->press(param);
     }
     }
 
 
     if ((_suppress_flags & MouseWatcherRegion::SF_other_button) == 0) {
     if ((_suppress_flags & MouseWatcherRegion::SF_other_button) == 0) {
@@ -362,26 +610,28 @@ release(ButtonHandle button) {
   param.set_mouse(_mouse);
   param.set_mouse(_mouse);
 
 
   if (MouseButton::is_mouse_button(button)) {
   if (MouseButton::is_mouse_button(button)) {
-    // Button up.  Send the up event associated with the region we
+    // Button up.  Send the up event associated with the region(s) we
     // were over when the button went down.
     // were over when the button went down.
     
     
     // There is some danger of losing button-up events here.  If
     // There is some danger of losing button-up events here.  If
     // more than one button goes down together, we won't detect
     // more than one button goes down together, we won't detect
     // both of the button-up events properly.
     // 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);
+    if (_preferred_button_down_region != (MouseWatcherRegion *)NULL) {
+      param.set_outside(_preferred_button_down_region != _preferred_region);
+      _preferred_button_down_region->release(param);
+      throw_event_pattern(_button_up_pattern,
+                          _preferred_button_down_region, button);
     }
     }
+
     _button_down = false;
     _button_down = false;
+    _preferred_button_down_region = (MouseWatcherRegion *)NULL;
     
     
   } else {
   } else {
     // It's a keyboard button; therefore, send the event to every
     // It's a keyboard button; therefore, send the event to every
     // region that wants keyboard buttons, regardless of the mouse
     // region that wants keyboard buttons, regardless of the mouse
     // position.
     // position.
-    if (_current_region != (MouseWatcherRegion *)NULL) {
-      _current_region->release(param);
+    if (_preferred_region != (MouseWatcherRegion *)NULL) {
+      _preferred_region->release(param);
     }
     }
     
     
     param.set_outside(true);
     param.set_outside(true);
@@ -402,7 +652,7 @@ global_keyboard_press(const MouseWatcherParameter &param) {
   for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
   for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
     MouseWatcherRegion *region = (*ri);
     MouseWatcherRegion *region = (*ri);
 
 
-    if (region != _current_region && region->get_keyboard()) {
+    if (region != _preferred_region && region->get_keyboard()) {
       region->press(param);
       region->press(param);
     }
     }
   }
   }
@@ -414,7 +664,7 @@ global_keyboard_press(const MouseWatcherParameter &param) {
     for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
     for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
       MouseWatcherRegion *region = (*ri);
       MouseWatcherRegion *region = (*ri);
 
 
-      if (region != _current_region && region->get_keyboard()) {
+      if (region != _preferred_region && region->get_keyboard()) {
         region->press(param);
         region->press(param);
       }
       }
     }
     }
@@ -434,7 +684,7 @@ global_keyboard_release(const MouseWatcherParameter &param) {
   for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
   for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
     MouseWatcherRegion *region = (*ri);
     MouseWatcherRegion *region = (*ri);
 
 
-    if (region != _current_region && region->get_keyboard()) {
+    if (region != _preferred_region && region->get_keyboard()) {
       region->release(param);
       region->release(param);
     }
     }
   }
   }
@@ -446,7 +696,7 @@ global_keyboard_release(const MouseWatcherParameter &param) {
     for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
     for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
       MouseWatcherRegion *region = (*ri);
       MouseWatcherRegion *region = (*ri);
 
 
-      if (region != _current_region && region->get_keyboard()) {
+      if (region != _preferred_region && region->get_keyboard()) {
         region->release(param);
         region->release(param);
       }
       }
     }
     }
@@ -473,7 +723,7 @@ transmit_data(AllTransitionsWrapper &data) {
     _has_mouse = false;
     _has_mouse = false;
     // If the mouse is outside the window, do nothing; let all the
     // If the mouse is outside the window, do nothing; let all the
     // events continue down the pipe unmolested.
     // events continue down the pipe unmolested.
-    set_current_region(NULL);
+    clear_current_regions();
     return;
     return;
   }
   }
 
 
@@ -492,26 +742,13 @@ transmit_data(AllTransitionsWrapper &data) {
 
 
   _has_mouse = true;
   _has_mouse = true;
 
 
-  if (!_button_down) {
-    // If the button is not currently being held down, we are free to
-    // set the mouse into whichever region we like.
-    set_current_region(get_over_region(_mouse));
-
-  } else {
-    // If the button *is* currently being held down, we can only move
-    // the mouse into a region if the region is the same region we
-    // started from.
-    MouseWatcherRegion *region = get_over_region(_mouse);
-    if (region == _button_down_region) {
-      set_current_region(region);
-    } else {
-      set_current_region((MouseWatcherRegion *)NULL);
-    }
-  }
+  VRegions regions;
+  get_over_regions(regions, _mouse);
+  set_current_regions(regions);
 
 
   _suppress_flags = 0;
   _suppress_flags = 0;
-  if (_current_region != (MouseWatcherRegion *)NULL) {
-    _suppress_flags = _current_region->get_suppress_flags();
+  if (_preferred_region != (MouseWatcherRegion *)NULL) {
+    _suppress_flags = _preferred_region->get_suppress_flags();
   }
   }
 
 
   // Look for button events.
   // Look for button events.

+ 26 - 3
panda/src/tform/mouseWatcher.h

@@ -89,6 +89,12 @@ PUBLISHED:
   INLINE void set_leave_pattern(const string &pattern);
   INLINE void set_leave_pattern(const string &pattern);
   INLINE const string &get_leave_pattern() const;
   INLINE const string &get_leave_pattern() const;
 
 
+  INLINE void set_within_pattern(const string &pattern);
+  INLINE const string &get_within_pattern() const;
+
+  INLINE void set_without_pattern(const string &pattern);
+  INLINE const string &get_without_pattern() const;
+
   INLINE void set_geometry(NodeRelation *arc);
   INLINE void set_geometry(NodeRelation *arc);
   INLINE bool has_geometry() const;
   INLINE bool has_geometry() const;
   INLINE NodeRelation *get_geometry() const;
   INLINE NodeRelation *get_geometry() const;
@@ -108,7 +114,21 @@ public:
   bool remove_group(MouseWatcherGroup *group);
   bool remove_group(MouseWatcherGroup *group);
 
 
 private:
 private:
-  void set_current_region(MouseWatcherRegion *region);
+  typedef pvector< PT(MouseWatcherRegion) > VRegions;
+  void get_over_regions(VRegions &regions, const LPoint2f &pos) const;
+  static MouseWatcherRegion *get_preferred_region(const VRegions &regions);
+
+  void set_current_regions(VRegions &regions);
+  void clear_current_regions();
+  static void intersect_regions(MouseWatcher::VRegions &result,
+                                const MouseWatcher::VRegions &regions_a,
+                                const MouseWatcher::VRegions &regions_b);
+  static void remove_region_from(MouseWatcher::VRegions &regions,
+                                 MouseWatcherRegion *region);
+  static void remove_regions_from(MouseWatcher::VRegions &regions,
+                                  MouseWatcherGroup *group);
+
+    
   void throw_event_pattern(const string &pattern,
   void throw_event_pattern(const string &pattern,
                            const MouseWatcherRegion *region,
                            const MouseWatcherRegion *region,
                            const ButtonHandle &button);
                            const ButtonHandle &button);
@@ -125,14 +145,17 @@ private:
   int _suppress_flags;
   int _suppress_flags;
   LPoint2f _mouse;
   LPoint2f _mouse;
 
 
-  PT(MouseWatcherRegion) _current_region;
-  PT(MouseWatcherRegion) _button_down_region;
+  VRegions _current_regions;
+  PT(MouseWatcherRegion) _preferred_region;
+  PT(MouseWatcherRegion) _preferred_button_down_region;
   bool _button_down;
   bool _button_down;
 
 
   string _button_down_pattern;
   string _button_down_pattern;
   string _button_up_pattern;
   string _button_up_pattern;
   string _enter_pattern;
   string _enter_pattern;
   string _leave_pattern;
   string _leave_pattern;
+  string _within_pattern;
+  string _without_pattern;
 
 
   PT_NodeRelation _geometry;
   PT_NodeRelation _geometry;
 
 

+ 33 - 2
panda/src/tform/mouseWatcherRegion.cxx

@@ -47,7 +47,10 @@ write(ostream &out, int indent_level) const {
 //     Function: MouseWatcherRegion::enter
 //     Function: MouseWatcherRegion::enter
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse enters the region.
+//               mouse enters the region.  The mouse is only
+//               considered to be "entered" in one region at a time;
+//               in the case of nested regions, it exits the outer
+//               region before entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MouseWatcherRegion::
 void MouseWatcherRegion::
 enter(const MouseWatcherParameter &) {
 enter(const MouseWatcherParameter &) {
@@ -57,12 +60,40 @@ enter(const MouseWatcherParameter &) {
 //     Function: MouseWatcherRegion::exit
 //     Function: MouseWatcherRegion::exit
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: This is a callback hook function, called whenever the
 //  Description: This is a callback hook function, called whenever the
-//               mouse exits the region.
+//               mouse exits the region.  The mouse is only considered
+//               to be "entered" in one region at a time; in the case
+//               of nested regions, it exits the outer region before
+//               entering the inner one.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MouseWatcherRegion::
 void MouseWatcherRegion::
 exit(const MouseWatcherParameter &) {
 exit(const MouseWatcherParameter &) {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherRegion::within
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves within the boundaries of the region, even
+//               if it is also within the boundaries of a nested
+//               region.  This is different from "enter", which is
+//               only called whenever the mouse is within only that
+//               region.
+////////////////////////////////////////////////////////////////////
+void MouseWatcherRegion::
+within(const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherRegion::without
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves completely outside the boundaries of the
+//               region.  See within().
+////////////////////////////////////////////////////////////////////
+void MouseWatcherRegion::
+without(const MouseWatcherParameter &) {
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherRegion::press
 //     Function: MouseWatcherRegion::press
 //       Access: Public, Virtual
 //       Access: Public, Virtual

+ 2 - 0
panda/src/tform/mouseWatcherRegion.h

@@ -72,6 +72,8 @@ public:
 
 
   virtual void enter(const MouseWatcherParameter &param);
   virtual void enter(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
   virtual void exit(const MouseWatcherParameter &param);
+  virtual void within(const MouseWatcherParameter &param);
+  virtual void without(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);