From 7c00e5b6ce826629441817ba838fc8ae36fe8d25 Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 24 Oct 2001 20:39:42 +0000 Subject: [PATCH] add within/without messages --- panda/src/pgui/pgItem.I | 59 ++- panda/src/pgui/pgItem.cxx | 43 ++- panda/src/pgui/pgItem.h | 6 + panda/src/pgui/pgMouseWatcherRegion.cxx | 41 ++- panda/src/pgui/pgMouseWatcherRegion.h | 2 + panda/src/tform/mouseWatcher.I | 85 ++++- panda/src/tform/mouseWatcher.cxx | 453 ++++++++++++++++++------ panda/src/tform/mouseWatcher.h | 29 +- panda/src/tform/mouseWatcherRegion.cxx | 35 +- panda/src/tform/mouseWatcherRegion.h | 2 + 10 files changed, 628 insertions(+), 127 deletions(-) diff --git a/panda/src/pgui/pgItem.I b/panda/src/pgui/pgItem.I index 0495bb0ea2..3ec0f764fc 100644 --- a/panda/src/pgui/pgItem.I +++ b/panda/src/pgui/pgItem.I @@ -237,6 +237,30 @@ get_exit_prefix() { 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 // Access: Published, Static @@ -297,7 +321,8 @@ get_release_prefix() { // Function: PGItem::get_enter_event // Access: Published // 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:: get_enter_event() const { @@ -308,13 +333,43 @@ get_enter_event() const { // Function: PGItem::get_exit_event // Access: Published // 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:: get_exit_event() const { 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 // Access: Published diff --git a/panda/src/pgui/pgItem.cxx b/panda/src/pgui/pgItem.cxx index 30ebfa72a2..d434754742 100644 --- a/panda/src/pgui/pgItem.cxx +++ b/panda/src/pgui/pgItem.cxx @@ -226,7 +226,10 @@ draw_item(PGTop *top, GraphicsStateGuardian *gsg, // Function: PGItem::enter // Access: Public, Virtual // 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:: enter(const MouseWatcherParameter ¶m) { @@ -240,7 +243,10 @@ enter(const MouseWatcherParameter ¶m) { // Function: PGItem::exit // Access: Public, Virtual // 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:: exit(const MouseWatcherParameter ¶m) { @@ -250,6 +256,39 @@ exit(const MouseWatcherParameter ¶m) { 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 ¶m) { + 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 ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_without_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + //////////////////////////////////////////////////////////////////// // Function: PGItem::focus_in // Access: Public, Virtual diff --git a/panda/src/pgui/pgItem.h b/panda/src/pgui/pgItem.h index 0c0d289d8e..d8d1e5490f 100644 --- a/panda/src/pgui/pgItem.h +++ b/panda/src/pgui/pgItem.h @@ -75,6 +75,8 @@ public: virtual void enter(const MouseWatcherParameter ¶m); virtual void exit(const MouseWatcherParameter ¶m); + virtual void within(const MouseWatcherParameter ¶m); + virtual void without(const MouseWatcherParameter ¶m); virtual void focus_in(); virtual void focus_out(); virtual void press(const MouseWatcherParameter ¶m, bool background); @@ -119,6 +121,8 @@ PUBLISHED: INLINE static string get_enter_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_out_prefix(); INLINE static string get_press_prefix(); @@ -126,6 +130,8 @@ PUBLISHED: INLINE string get_enter_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_out_event() const; INLINE string get_press_event(const ButtonHandle &button) const; diff --git a/panda/src/pgui/pgMouseWatcherRegion.cxx b/panda/src/pgui/pgMouseWatcherRegion.cxx index cf693b4147..4bb3cdd95e 100644 --- a/panda/src/pgui/pgMouseWatcherRegion.cxx +++ b/panda/src/pgui/pgMouseWatcherRegion.cxx @@ -52,7 +52,10 @@ PGMouseWatcherRegion:: // Function: PGMouseWatcherRegion::enter // Access: Public, Virtual // 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:: enter(const MouseWatcherParameter ¶m) { @@ -65,7 +68,10 @@ enter(const MouseWatcherParameter ¶m) { // Function: PGMouseWatcherRegion::exit // Access: Public, Virtual // 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:: exit(const MouseWatcherParameter ¶m) { @@ -74,6 +80,37 @@ exit(const MouseWatcherParameter ¶m) { } } +//////////////////////////////////////////////////////////////////// +// 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 ¶m) { + 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 ¶m) { + if (_item != (PGItem *)NULL) { + _item->without(param); + } +} + //////////////////////////////////////////////////////////////////// // Function: PGMouseWatcherRegion::press // Access: Public, Virtual diff --git a/panda/src/pgui/pgMouseWatcherRegion.h b/panda/src/pgui/pgMouseWatcherRegion.h index eee2ca170b..ea30c05d35 100644 --- a/panda/src/pgui/pgMouseWatcherRegion.h +++ b/panda/src/pgui/pgMouseWatcherRegion.h @@ -39,6 +39,8 @@ public: virtual void enter(const MouseWatcherParameter ¶m); virtual void exit(const MouseWatcherParameter ¶m); + virtual void within(const MouseWatcherParameter ¶m); + virtual void without(const MouseWatcherParameter ¶m); virtual void press(const MouseWatcherParameter ¶m); virtual void release(const MouseWatcherParameter ¶m); diff --git a/panda/src/tform/mouseWatcher.I b/panda/src/tform/mouseWatcher.I index 18d679def3..6eacfe8366 100644 --- a/panda/src/tform/mouseWatcher.I +++ b/panda/src/tform/mouseWatcher.I @@ -124,7 +124,7 @@ is_over_region(const LPoint2f &pos) const { //////////////////////////////////////////////////////////////////// INLINE MouseWatcherRegion *MouseWatcher:: get_over_region() const { - return _current_region; + return _preferred_region; } //////////////////////////////////////////////////////////////////// @@ -197,8 +197,11 @@ get_button_up_pattern() const { // Function: MouseWatcher::set_enter_pattern // Access: Published // 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:: set_enter_pattern(const string &pattern) { @@ -209,8 +212,10 @@ set_enter_pattern(const string &pattern) { // Function: MouseWatcher::get_enter_pattern // Access: Published // 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:: get_enter_pattern() const { @@ -222,7 +227,10 @@ get_enter_pattern() const { // Access: Published // Description: Sets the pattern string that indicates how the event // 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:: set_leave_pattern(const string &pattern) { @@ -233,14 +241,75 @@ set_leave_pattern(const string &pattern) { // Function: MouseWatcher::get_leave_pattern // Access: Published // 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:: get_leave_pattern() const { 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 // Access: Published diff --git a/panda/src/tform/mouseWatcher.cxx b/panda/src/tform/mouseWatcher.cxx index ef0c4710a8..82e3275138 100644 --- a/panda/src/tform/mouseWatcher.cxx +++ b/panda/src/tform/mouseWatcher.cxx @@ -29,6 +29,8 @@ #include "pruneTransition.h" #include "transformTransition.h" +#include + TypeHandle MouseWatcher::_type_handle; TypeHandle MouseWatcher::_xyz_type; @@ -44,8 +46,8 @@ MouseWatcher:: MouseWatcher(const string &name) : DataNode(name) { _has_mouse = false; _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; _eh = (EventHandler*)0L; } @@ -68,12 +70,14 @@ MouseWatcher:: //////////////////////////////////////////////////////////////////// bool MouseWatcher:: 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); } @@ -88,47 +92,9 @@ remove_region(MouseWatcherRegion *region) { //////////////////////////////////////////////////////////////////// MouseWatcherRegion *MouseWatcher:: 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:: output(ostream &out) const { 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,46 +177,319 @@ add_group(MouseWatcherGroup *group) { //////////////////////////////////////////////////////////////////// bool MouseWatcher:: 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; } //////////////////////////////////////////////////////////////////// -// Function: MouseWatcher::set_current_region +// Function: MouseWatcher::get_over_regions // Access: Private -// Description: Changes the "current" region--the one we consider the -// mouse to be over--to the indicated one, and throws -// whatever events are appropriate because of that. +// Description: Fills up the "regions" list with the set of regions +// that the indicated point is over, sorted in order by +// pointer. //////////////////////////////////////////////////////////////////// void MouseWatcher:: -set_current_region(MouseWatcherRegion *region) { -#ifndef NDEBUG - if (region != (MouseWatcherRegion *)NULL) { - region->test_ref_count_integrity(); +get_over_regions(MouseWatcher::VRegions ®ions, 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); + } } -#endif - if (region != _current_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 ®ions) { + 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 +// 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. +// +// The list passed in is destroyed. +//////////////////////////////////////////////////////////////////// +void MouseWatcher:: +set_current_regions(MouseWatcher::VRegions ®ions) { + // 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; + } + } + + 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; param.set_modifier_buttons(_mods); param.set_mouse(_mouse); - - if (_current_region != (MouseWatcherRegion *)NULL) { - _current_region->exit(param); - throw_event_pattern(_leave_pattern, _current_region, ButtonHandle::none()); + + 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_region = region; - if (_current_region != (MouseWatcherRegion *)NULL) { - _current_region->enter(param); - throw_event_pattern(_enter_pattern, _current_region, ButtonHandle::none()); + + _current_regions.clear(); + + if (_preferred_region != (MouseWatcherRegion *)NULL) { + _preferred_region->exit(param); + throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none()); + _preferred_region = (MouseWatcherRegion *)NULL; } } } +//////////////////////////////////////////////////////////////////// +// 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 ®ions_a, + const MouseWatcher::VRegions ®ions_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 ®ions, + 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 ®ions, + 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); +} + //////////////////////////////////////////////////////////////////// // Function: MouseWatcher::throw_event_for // Access: Private @@ -318,24 +565,25 @@ press(ButtonHandle button) { // Mouse buttons are inextricably linked to the mouse position. if (!_button_down) { - _button_down_region = _current_region; + _preferred_button_down_region = _preferred_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); + + 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 { // 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) { + if (_preferred_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); + _preferred_region->press(param); } if ((_suppress_flags & MouseWatcherRegion::SF_other_button) == 0) { @@ -362,26 +610,28 @@ release(ButtonHandle button) { param.set_mouse(_mouse); 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. // 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); + 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; + _preferred_button_down_region = (MouseWatcherRegion *)NULL; } 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); + if (_preferred_region != (MouseWatcherRegion *)NULL) { + _preferred_region->release(param); } param.set_outside(true); @@ -402,7 +652,7 @@ global_keyboard_press(const MouseWatcherParameter ¶m) { for (ri = _regions.begin(); ri != _regions.end(); ++ri) { MouseWatcherRegion *region = (*ri); - if (region != _current_region && region->get_keyboard()) { + if (region != _preferred_region && region->get_keyboard()) { region->press(param); } } @@ -414,7 +664,7 @@ global_keyboard_press(const MouseWatcherParameter ¶m) { for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) { MouseWatcherRegion *region = (*ri); - if (region != _current_region && region->get_keyboard()) { + if (region != _preferred_region && region->get_keyboard()) { region->press(param); } } @@ -434,7 +684,7 @@ global_keyboard_release(const MouseWatcherParameter ¶m) { for (ri = _regions.begin(); ri != _regions.end(); ++ri) { MouseWatcherRegion *region = (*ri); - if (region != _current_region && region->get_keyboard()) { + if (region != _preferred_region && region->get_keyboard()) { region->release(param); } } @@ -446,7 +696,7 @@ global_keyboard_release(const MouseWatcherParameter ¶m) { for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) { MouseWatcherRegion *region = (*ri); - if (region != _current_region && region->get_keyboard()) { + if (region != _preferred_region && region->get_keyboard()) { region->release(param); } } @@ -473,7 +723,7 @@ transmit_data(AllTransitionsWrapper &data) { _has_mouse = false; // If the mouse is outside the window, do nothing; let all the // events continue down the pipe unmolested. - set_current_region(NULL); + clear_current_regions(); return; } @@ -492,26 +742,13 @@ transmit_data(AllTransitionsWrapper &data) { _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; - 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. diff --git a/panda/src/tform/mouseWatcher.h b/panda/src/tform/mouseWatcher.h index f7acac62cd..75da89de63 100644 --- a/panda/src/tform/mouseWatcher.h +++ b/panda/src/tform/mouseWatcher.h @@ -89,6 +89,12 @@ PUBLISHED: INLINE void set_leave_pattern(const string &pattern); 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 bool has_geometry() const; INLINE NodeRelation *get_geometry() const; @@ -108,7 +114,21 @@ public: bool remove_group(MouseWatcherGroup *group); private: - void set_current_region(MouseWatcherRegion *region); + typedef pvector< PT(MouseWatcherRegion) > VRegions; + void get_over_regions(VRegions ®ions, const LPoint2f &pos) const; + static MouseWatcherRegion *get_preferred_region(const VRegions ®ions); + + void set_current_regions(VRegions ®ions); + void clear_current_regions(); + static void intersect_regions(MouseWatcher::VRegions &result, + const MouseWatcher::VRegions ®ions_a, + const MouseWatcher::VRegions ®ions_b); + static void remove_region_from(MouseWatcher::VRegions ®ions, + MouseWatcherRegion *region); + static void remove_regions_from(MouseWatcher::VRegions ®ions, + MouseWatcherGroup *group); + + void throw_event_pattern(const string &pattern, const MouseWatcherRegion *region, const ButtonHandle &button); @@ -125,14 +145,17 @@ private: int _suppress_flags; 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; string _button_down_pattern; string _button_up_pattern; string _enter_pattern; string _leave_pattern; + string _within_pattern; + string _without_pattern; PT_NodeRelation _geometry; diff --git a/panda/src/tform/mouseWatcherRegion.cxx b/panda/src/tform/mouseWatcherRegion.cxx index 693e51e4f3..093cfd89d9 100644 --- a/panda/src/tform/mouseWatcherRegion.cxx +++ b/panda/src/tform/mouseWatcherRegion.cxx @@ -47,7 +47,10 @@ write(ostream &out, int indent_level) const { // Function: MouseWatcherRegion::enter // Access: Public, Virtual // 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:: enter(const MouseWatcherParameter &) { @@ -57,12 +60,40 @@ enter(const MouseWatcherParameter &) { // Function: MouseWatcherRegion::exit // Access: Public, Virtual // 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:: 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 // Access: Public, Virtual diff --git a/panda/src/tform/mouseWatcherRegion.h b/panda/src/tform/mouseWatcherRegion.h index 5e16c30397..84dc8bde1f 100644 --- a/panda/src/tform/mouseWatcherRegion.h +++ b/panda/src/tform/mouseWatcherRegion.h @@ -72,6 +72,8 @@ public: virtual void enter(const MouseWatcherParameter ¶m); virtual void exit(const MouseWatcherParameter ¶m); + virtual void within(const MouseWatcherParameter ¶m); + virtual void without(const MouseWatcherParameter ¶m); virtual void press(const MouseWatcherParameter ¶m); virtual void release(const MouseWatcherParameter ¶m);