Panda3D
mouseWatcher.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file mouseWatcher.cxx
10  * @author drose
11  * @date 2002-03-12
12  */
13 
14 #include "mouseWatcher.h"
15 #include "config_tform.h"
16 #include "dataGraphTraverser.h"
17 #include "mouseWatcherParameter.h"
18 #include "mouseAndKeyboard.h"
19 #include "mouseData.h"
20 #include "buttonEventList.h"
21 #include "mouseButton.h"
22 #include "throw_event.h"
23 #include "eventParameter.h"
24 #include "dataNodeTransmit.h"
25 #include "transformState.h"
26 #include "displayRegion.h"
27 #include "stereoDisplayRegion.h"
28 #include "geomVertexWriter.h"
29 #include "geomLinestrips.h"
30 #include "geomPoints.h"
31 #include "dcast.h"
32 #include "indent.h"
33 #include "lightMutexHolder.h"
34 #include "nearly_zero.h"
35 
36 #include <algorithm>
37 
38 using std::string;
39 
40 TypeHandle MouseWatcher::_type_handle;
41 
42 /**
43  *
44  */
45 MouseWatcher::
46 MouseWatcher(const string &name) :
47  DataNode(name)
48 {
49  _pixel_xy_input = define_input("pixel_xy", EventStoreVec2::get_class_type());
50  _pixel_size_input = define_input("pixel_size", EventStoreVec2::get_class_type());
51  _xy_input = define_input("xy", EventStoreVec2::get_class_type());
52  _button_events_input = define_input("button_events", ButtonEventList::get_class_type());
53  _pointer_events_input = define_input("pointer_events", PointerEventList::get_class_type());
54 
55  _pixel_xy_output = define_output("pixel_xy", EventStoreVec2::get_class_type());
56  _pixel_size_output = define_output("pixel_size", EventStoreVec2::get_class_type());
57  _xy_output = define_output("xy", EventStoreVec2::get_class_type());
58  _button_events_output = define_output("button_events", ButtonEventList::get_class_type());
59 
60  _pixel_xy = new EventStoreVec2(LPoint2(0.0f, 0.0f));
61  _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f));
62  _pixel_size = new EventStoreVec2(LPoint2(0.0f, 0.0f));
63  _button_events = new ButtonEventList;
64 
65  _has_mouse = false;
66  _internal_suppress = 0;
67  _preferred_region = nullptr;
68  _preferred_button_down_region = nullptr;
69  _button_down = false;
70  _eh = nullptr;
71  _display_region = nullptr;
72  _button_down_display_region = nullptr;
73 
74  _frame.set(-1.0f, 1.0f, -1.0f, 1.0f);
75 
76  _inactivity_timeout = inactivity_timeout;
77  _has_inactivity_timeout = !IS_NEARLY_ZERO(_inactivity_timeout);
78 
79  _num_trail_recent = 0;
80  _trail_log_duration = 0.0;
81  _trail_log = new PointerEventList();
82 
83  _inactivity_timeout_event = "inactivity_timeout";
84  _last_activity = 0.0;
85  _inactivity_state = IS_active;
86 
87  // When this flag is true, the mouse pointer is allowed to be "entered" into
88  // multiple regions simultaneously; when false, it will only be "within"
89  // multiple regions, but "entered" into the topmost of those.
90  _enter_multiple = false;
91 
92  // When this flag is true, moving the pointer into a region is enough to
93  // click it. The click is simulated with mouse button one.
94  _implicit_click = false;
95 }
96 
97 /**
98  *
99  */
100 MouseWatcher::
101 ~MouseWatcher() {
102 }
103 
104 /**
105  * Removes the indicated region from the group. Returns true if it was
106  * successfully removed, or false if it wasn't there in the first place.
107  */
110  LightMutexHolder holder(_lock);
111 
112  remove_region_from(_current_regions, region);
113  if (region == _preferred_region) {
114  if (_preferred_region != nullptr) {
115  exit_region(_preferred_region, MouseWatcherParameter());
116  }
117  _preferred_region = nullptr;
118  }
119  if (region == _preferred_button_down_region) {
120  _preferred_button_down_region = nullptr;
121  }
122 
123  return MouseWatcherBase::do_remove_region(region);
124 }
125 
126 /**
127  * Returns the preferred region the mouse is over. In the case of overlapping
128  * regions, the region with the largest sort order is preferred; if two
129  * regions have the same sort order, then the smaller region is preferred.
130  */
132 get_over_region(const LPoint2 &pos) const {
133  LightMutexHolder holder(_lock);
134 
135  Regions regions;
136  get_over_regions(regions, pos);
137  return get_preferred_region(regions);
138 }
139 
140 /**
141  * Adds the indicated group of regions to the set of regions the MouseWatcher
142  * will monitor each frame.
143  *
144  * Since the MouseWatcher itself inherits from MouseWatcherBase, this
145  * operation is normally not necessary--you can simply add the Regions you
146  * care about one at a time. Adding a complete group is useful when you may
147  * want to explicitly remove the regions as a group later.
148  *
149  * Returns true if the group was successfully added, or false if it was
150  * already on the list.
151  */
154  LightMutexHolder holder(_lock);
155 
156  // See if the group is in the setvector already
157  PT(MouseWatcherGroup) pt = group;
158  Groups::const_iterator gi =
159  find(_groups.begin(), _groups.end(), pt);
160  if (gi != _groups.end()) {
161  // Already in the set, return false
162  return false;
163  }
164 
165 #ifndef NDEBUG
166  if (!_show_regions_render2d.is_empty()) {
167  group->show_regions(_show_regions_render2d, _show_regions_bin_name,
168  _show_regions_draw_order);
169  }
170 #endif // NDEBUG
171 
172  // Not in the set, add it and return true
173  _groups.push_back(pt);
174  return true;
175 }
176 
177 /**
178  * Removes the indicated group from the set of extra groups associated with
179  * the MouseWatcher. Returns true if successful, or false if the group was
180  * already removed or was never added via add_group().
181  */
184  LightMutexHolder holder(_lock);
185  LightMutexHolder holder2(group->_lock);
186 
187  group->do_sort_regions();
188 
189  Regions only_a, only_b, both;
190  intersect_regions(only_a, only_b, both,
191  _current_regions, group->_regions);
192  set_current_regions(only_a);
193 
194  if (has_region_in(both, _preferred_region)) {
195  if (_preferred_region != nullptr) {
196  exit_region(_preferred_region, MouseWatcherParameter());
197  }
198  _preferred_region = nullptr;
199  }
200  if (has_region_in(both, _preferred_button_down_region)) {
201  _preferred_button_down_region = nullptr;
202  }
203 
204 #ifndef NDEBUG
205  if (!_show_regions_render2d.is_empty()) {
206  group->do_hide_regions();
207  }
208 #endif // NDEBUG
209 
210  // See if the group is in the setvector
211  PT(MouseWatcherGroup) pt = group;
212  Groups::iterator gi =
213  find(_groups.begin(), _groups.end(), pt);
214  if (gi != _groups.end()) {
215  // Found it, now erase it
216  _groups.erase(gi);
217  return true;
218  }
219 
220  // Did not find the group to erase
221  return false;
222 }
223 
224 /**
225  * Atomically removes old_group from the MouseWatcher, and replaces it with
226  * new_group. Presumably old_group and new_group might have some regions in
227  * common; these are handled properly.
228  *
229  * If old_group is not already present, simply adds new_group and returns
230  * false. Otherwise, removes old_group and adds new_group, and then returns
231  * true.
232  */
234 replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group) {
235  if (old_group == new_group) {
236  // Trivial.
237  return true;
238  }
239 
240  LightMutexHolder holder(_lock);
241 
242  LightMutexHolder holder2(old_group->_lock);
243  LightMutexHolder holder3(new_group->_lock);
244 
245  old_group->do_sort_regions();
246  new_group->do_sort_regions();
247 
248 #ifndef NDEBUG
249  if (!_show_regions_render2d.is_empty()) {
250  old_group->do_hide_regions();
251  new_group->do_show_regions(_show_regions_render2d, _show_regions_bin_name,
252  _show_regions_draw_order);
253  }
254 #endif // NDEBUG
255 
256  // Figure out the list of regions that change
257  Regions remove, add, keep;
258  intersect_regions(remove, add, keep,
259  old_group->_regions, new_group->_regions);
260 
261  Regions new_current_regions;
262  bool any_new_current_regions = false;
263 
264  // Remove the old regions
265  if (!remove.empty()) {
266  Regions only_a, only_b, both;
267  intersect_regions(only_a, only_b, both,
268  _current_regions, remove);
269  new_current_regions.swap(only_a);
270  any_new_current_regions = true;
271 
272  if (has_region_in(both, _preferred_region)) {
273  if (_preferred_region != nullptr) {
274  exit_region(_preferred_region, MouseWatcherParameter());
275  }
276  _preferred_region = nullptr;
277  }
278  if (has_region_in(both, _preferred_button_down_region)) {
279  _preferred_button_down_region = nullptr;
280  }
281  }
282 
283  // Don't add the new regions--we have no reason to believe these should
284  // become current; some of them may not even be under the mouse.
285  /*
286  // And add the new regions
287  if (!add.empty()) {
288  Regions new_list;
289  if (any_new_current_regions) {
290  intersect_regions(new_list, new_list, new_list,
291  new_current_regions, add);
292  } else {
293  intersect_regions(new_list, new_list, new_list,
294  _current_regions, add);
295  }
296  new_current_regions.swap(new_list);
297  any_new_current_regions = true;
298  }
299  */
300 
301  if (any_new_current_regions) {
302  set_current_regions(new_current_regions);
303  }
304 
305  // Add the new group, if it's not already there.
306  PT(MouseWatcherGroup) pt = new_group;
307  Groups::iterator gi =
308  find(_groups.begin(), _groups.end(), pt);
309  if (gi == _groups.end()) {
310  _groups.push_back(new_group);
311  }
312 
313 #ifndef NDEBUG
314  if (!_show_regions_render2d.is_empty()) {
315  new_group->do_update_regions();
316  }
317 #endif // NDEBUG
318 
319  // Remove the old group, if it is already there.
320  pt = old_group;
321  gi = find(_groups.begin(), _groups.end(), pt);
322  if (gi != _groups.end()) {
323  // Found it, now erase it
324  _groups.erase(gi);
325  return true;
326  }
327 
328  // Did not find the group to erase
329  return false;
330 }
331 
332 /**
333  * Returns the number of separate groups added to the MouseWatcher via
334  * add_group().
335  */
336 int MouseWatcher::
337 get_num_groups() const {
338  LightMutexHolder holder(_lock);
339  return _groups.size();
340 }
341 
342 /**
343  * Returns the nth group added to the MouseWatcher via add_group().
344  */
346 get_group(int n) const {
347  LightMutexHolder holder(_lock);
348  nassertr(n >= 0 && n < (int)_groups.size(), nullptr);
349  return _groups[n];
350 }
351 
352 /**
353  * If the duration is nonzero, causes the MouseWatcher to log the mouse's
354  * trail. Events older than the specified duration are discarded. If the
355  * duration is zero, logging is disabled.
356  */
358 set_trail_log_duration(double duration) {
359  if (duration < 0.0) {
360  duration = 0.0;
361  }
362  _trail_log_duration = duration;
363  discard_excess_trail_log();
364 }
365 
366 /**
367  * Discards trail log events whose age exceed the desired log duration. Keeps
368  * one event that is beyond the specified age, because otherwise, it is not
369  * always possible to determine where the mouse was for the full logging
370  * duration. Also, keeps a minimum of two events in the queue. If the
371  * duration is zero, this method discards all trail events.
372  */
373 void MouseWatcher::
374 discard_excess_trail_log() {
375  if (_trail_log_duration == 0.0) {
376  _trail_log->clear();
377  } else {
378  if (_trail_log->get_num_events() > 2) {
379  double old = ClockObject::get_global_clock()->get_frame_time() - _trail_log_duration;
380  while ((_trail_log->get_num_events() > 2)&&
381  (_trail_log->get_time(0) <= old)&&
382  (_trail_log->get_time(1) <= old)) {
383  _trail_log->pop_front();
384  }
385  }
386  }
387 }
388 
389 /**
390  * Returns a GeomNode that represents the mouse trail. The intent is that you
391  * should reparent this GeomNode to Render2D, and then forget about it. The
392  * MouseWatcher will continually update the trail node. There is only one
393  * trail node, it does not create a new one each time you call get_trail_node.
394  *
395  * This is not a particularly beautiful way to render a mouse trail. It is
396  * intended more for debugging purposes than for finished applications. Even
397  * so, It is suggested that you might want to apply a line thickness and
398  * antialias mode to the line --- doing so makes it look a lot better.
399  */
400 PT(GeomNode) MouseWatcher::
401 get_trail_node() {
402  if (_trail_node == nullptr) {
403  _trail_node = new GeomNode("Mouse Trail Node");
404  update_trail_node();
405  }
406  return _trail_node;
407 }
408 
409 /**
410  * If you have previously fetched the trail node using get_trail_node, then
411  * the MouseWatcher is continually updating the trail node every frame. Using
412  * clear_trail_node causes the MouseWatcher to forget the trail node and stop
413  * updating it.
414  */
417  _trail_node = nullptr;
418 }
419 
420 /**
421  * Causes the trail node to represent the mouse trail.
422  */
423 void MouseWatcher::
424 update_trail_node() {
425  if (_trail_node == nullptr) {
426  return;
427  }
428  _trail_node->remove_all_geoms();
429 
430  if (_trail_log->get_num_events() < 2) {
431  return;
432  }
433 
434  PT(GeomVertexData) data = new GeomVertexData
435  ("mouseTrailSegs", GeomVertexFormat::get_v3(), Geom::UH_static);
436 
437  GeomVertexWriter vertex(data, InternalName::get_vertex());
438 
439  PT(GeomLinestrips) lines = new GeomLinestrips(Geom::UH_static);
440 
441  double xscale = 2.0 / _pixel_size->get_value().get_x();
442  double yscale = 2.0 / _pixel_size->get_value().get_y();
443 
444  for (int i=0; i<(int)_trail_log->get_num_events(); i++) {
445  double x = (_trail_log->get_xpos(i) * xscale) - 1.0;
446  double y = (_trail_log->get_ypos(i) * yscale) - 1.0;
447  vertex.add_data3(LVecBase3(x,0.0,-y));
448  lines->add_vertex(i);
449  }
450  lines->close_primitive();
451 
452  PT(Geom) l_geom = new Geom(data);
453  l_geom->add_primitive(lines);
454  _trail_node->add_geom(l_geom);
455 }
456 
457 /**
458  * Can be used in conjunction with the inactivity timeout to inform the
459  * MouseWatcher that the user has just performed some action which proves
460  * he/she is present. It may be necessary to call this for external events,
461  * such as joystick action, that the MouseWatcher might otherwise not know
462  * about. This will reset the current inactivity timer. When the inactivity
463  * timer reaches the length of time specified by set_inactivity_timeout(),
464  * with no keyboard or mouse activity and no calls to note_activity(), then
465  * any buttons held will be automatically released.
466  */
468 note_activity() {
469  _last_activity = ClockObject::get_global_clock()->get_frame_time();
470  switch (_inactivity_state) {
471  case IS_active:
472  break;
473 
474  case IS_inactive:
475  _inactivity_state = IS_inactive_to_active;
476  break;
477 
478  case IS_active_to_inactive:
479  _inactivity_state = IS_active;
480  break;
481 
482  case IS_inactive_to_active:
483  break;
484  }
485 }
486 
487 
488 /**
489  *
490  */
491 void MouseWatcher::
492 output(std::ostream &out) const {
493  LightMutexHolder holder(_lock);
494  DataNode::output(out);
495 
496  if (!_sorted) {
497  ((MouseWatcher *)this)->do_sort_regions();
498  }
499 
500  size_t count = _regions.size();
501  for (MouseWatcherGroup *group : _groups) {
502  count += group->get_num_regions();
503  }
504 
505  out << " (" << count << " regions)";
506 }
507 
508 /**
509  *
510  */
511 void MouseWatcher::
512 write(std::ostream &out, int indent_level) const {
513  indent(out, indent_level)
514  << "MouseWatcher " << get_name() << ":\n";
515  MouseWatcherBase::write(out, indent_level + 2);
516 
517  LightMutexHolder holder(_lock);
518  for (MouseWatcherGroup *group : _groups) {
519  indent(out, indent_level + 2)
520  << "Subgroup:\n";
521  group->write(out, indent_level + 4);
522  }
523 }
524 
525 /**
526  * Fills up the "regions" list with the set of regions that the indicated
527  * point is over, sorted in order by pointer. Assumes the lock is held.
528  */
529 void MouseWatcher::
530 get_over_regions(MouseWatcher::Regions &regions, const LPoint2 &pos) const {
531  nassertv(_lock.debug_is_locked());
532 
533  // Scale the mouse coordinates into the frame.
534  PN_stdfloat mx = (pos[0] + 1.0f) * 0.5f * (_frame[1] - _frame[0]) + _frame[0];
535  PN_stdfloat my = (pos[1] + 1.0f) * 0.5f * (_frame[3] - _frame[2]) + _frame[2];
536 
537  // pos[0] = 2.0f * (mx - _frame[0]) (_frame[1] - _frame[0]) - 1.0f; pos[1]
538  // = 2.0f * (my - _frame[2]) (_frame[3] - _frame[2]) - 1.0f;
539 
540  // Ensure the vector is empty before we begin.
541  regions.clear();
542 
543  // Make sure there are no duplicates in the regions vector.
544  if (!_sorted) {
545  ((MouseWatcher *)this)->do_sort_regions();
546  }
547 
548  for (MouseWatcherRegion *region : _regions) {
549  const LVecBase4 &frame = region->get_frame();
550 
551  if (region->get_active() &&
552  mx >= frame[0] && mx <= frame[1] &&
553  my >= frame[2] && my <= frame[3]) {
554 
555  regions.push_back(region);
556  }
557  }
558 
559  // Also check all of our sub-groups.
560  for (MouseWatcherGroup *group : _groups) {
561  group->sort_regions();
562 
563  for (MouseWatcherRegion *region : group->_regions) {
564  const LVecBase4 &frame = region->get_frame();
565 
566  if (region->get_active() &&
567  mx >= frame[0] && mx <= frame[1] &&
568  my >= frame[2] && my <= frame[3]) {
569 
570  regions.push_back(region);
571  }
572  }
573  }
574 
575  // Now sort the regions by pointer. By convention, the Regions vectors are
576  // always kept in order by pointer, so we can do easy linear comparison and
577  // intersection operations.
578  sort(regions.begin(), regions.end());
579 }
580 
581 /**
582  * Returns the innermost region of all the regions indicated in the given
583  * vector (usually, the regions the mouse is over). This is the "preferred"
584  * region that gets some special treatment. Assumes the lock is already held.
585  */
586 MouseWatcherRegion *MouseWatcher::
587 get_preferred_region(const MouseWatcher::Regions &regions) {
588  if (regions.empty()) {
589  return nullptr;
590  }
591 
592  Regions::const_iterator ri;
593  ri = regions.begin();
594  MouseWatcherRegion *preferred = *ri;
595  ++ri;
596  while (ri != regions.end()) {
597  MouseWatcherRegion *region = *ri;
598 
599  if (*region < *preferred) {
600  preferred = region;
601  }
602  ++ri;
603  }
604 
605  return preferred;
606 }
607 
608 /**
609  * Changes the "current" regions--the one we consider the mouse to be over--to
610  * the indicated list, and throws whatever events are appropriate because of
611  * that.
612  *
613  * The list passed in is destroyed. Assumes the lock is already held.
614  */
615 void MouseWatcher::
616 set_current_regions(MouseWatcher::Regions &regions) {
617  nassertv(_lock.debug_is_locked());
618 
619  // Set up a parameter for passing through any change events.
620  MouseWatcherParameter param;
621  param.set_modifier_buttons(_mods);
622  param.set_mouse(_mouse);
623 
624  // Now do a standard sorted comparison between the two vectors.
625  Regions::const_iterator new_ri = regions.begin();
626  Regions::const_iterator old_ri = _current_regions.begin();
627 
628  // Queue up all the new regions so we can send the within patterns all at
629  // once, after all of the without patterns have been thrown.
630  std::vector<MouseWatcherRegion *> new_regions;
631 
632  bool any_changes = false;
633  while (new_ri != regions.end() && old_ri != _current_regions.end()) {
634  if ((*new_ri) < (*old_ri)) {
635  // Here's a new region that we didn't have last frame.
636  MouseWatcherRegion *new_region = (*new_ri);
637  new_regions.push_back(new_region);
638  any_changes = true;
639  ++new_ri;
640 
641  } else if ((*old_ri) < (*new_ri)) {
642  // Here's a region we don't have any more.
643  MouseWatcherRegion *old_region = (*old_ri);
644  without_region(old_region, param);
645  any_changes = true;
646  ++old_ri;
647 
648  } else {
649  // Here's a region that hasn't changed.
650  ++new_ri;
651  ++old_ri;
652  }
653  }
654 
655  while (new_ri != regions.end()) {
656  // Here's a new region that we didn't have last frame.
657  MouseWatcherRegion *new_region = (*new_ri);
658  new_regions.push_back(new_region);
659  any_changes = true;
660  ++new_ri;
661  }
662 
663  while (old_ri != _current_regions.end()) {
664  // Here's a region we don't have any more.
665  MouseWatcherRegion *old_region = (*old_ri);
666  without_region(old_region, param);
667  any_changes = true;
668  ++old_ri;
669  }
670 
671  if (any_changes) {
672  // Now that we've compared the two vectors, simply swap them to set the
673  // new vector.
674  _current_regions.swap(regions);
675 
676  // And don't forget to throw all of the new regions' "within" events.
677  std::vector<MouseWatcherRegion *>::const_iterator ri;
678  for (ri = new_regions.begin(); ri != new_regions.end(); ++ri) {
679  MouseWatcherRegion *new_region = (*ri);
680  within_region(new_region, param);
681  }
682  }
683 
684  if (!_enter_multiple) {
685  // Determine which is the "preferred region", if any. This is the topmost
686  // region that the mouse cursor is over, and the one that we are
687  // considered "entered" into.
688  MouseWatcherRegion *new_preferred_region =
689  get_preferred_region(_current_regions);
690 
691  if (_button_down && new_preferred_region != _preferred_button_down_region) {
692  // If the button's being held down, we're only allowed to select the
693  // preferred button down region.
694  new_preferred_region = nullptr;
695  }
696 
697  if (new_preferred_region != _preferred_region) {
698  if (_preferred_region != nullptr) {
699  exit_region(_preferred_region, param);
700  }
701  _preferred_region = new_preferred_region;
702  if (_preferred_region != nullptr) {
703  enter_region(_preferred_region, param);
704  }
705  }
706  }
707 }
708 
709 /**
710  * Empties the set of current regions. Assumes the lock is already held.
711  */
712 void MouseWatcher::
713 clear_current_regions() {
714  nassertv(_lock.debug_is_locked());
715 
716  if (!_current_regions.empty()) {
717  // Set up a parameter for passing through any change events.
718  MouseWatcherParameter param;
719  param.set_modifier_buttons(_mods);
720  param.set_mouse(_mouse);
721 
722  Regions::const_iterator old_ri = _current_regions.begin();
723 
724  while (old_ri != _current_regions.end()) {
725  // Here's a region we don't have any more.
726  MouseWatcherRegion *old_region = (*old_ri);
727  old_region->exit_region(param);
728  throw_event_pattern(_leave_pattern, old_region, ButtonHandle::none());
729  if (_preferred_region == old_region) {
730  _preferred_region = nullptr;
731  }
732  ++old_ri;
733  }
734 
735  _current_regions.clear();
736 
737  if (_preferred_region != nullptr) {
738  _preferred_region->exit_region(param);
739  throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none());
740  _preferred_region = nullptr;
741  }
742  }
743 }
744 
745 /**
746  * The protected implementation of show_regions(). This assumes the lock is
747  * already held.
748  */
749 void MouseWatcher::
750 do_show_regions(const NodePath &render2d, const string &bin_name,
751  int draw_order) {
752 #ifndef NDEBUG
753  MouseWatcherBase::do_show_regions(render2d, bin_name, draw_order);
754  _show_regions_render2d = render2d;
755  _show_regions_bin_name = bin_name;
756  _show_regions_draw_order = draw_order;
757 
758  for (MouseWatcherGroup *group : _groups) {
759  group->show_regions(render2d, bin_name, draw_order);
760  }
761 #endif // NDEBUG
762 }
763 
764 /**
765  * The protected implementation of hide_regions(). This assumes the lock is
766  * already held.
767  */
768 void MouseWatcher::
769 do_hide_regions() {
770 #ifndef NDEBUG
771  MouseWatcherBase::do_hide_regions();
772  _show_regions_render2d = NodePath();
773  _show_regions_bin_name = string();
774  _show_regions_draw_order = 0;
775 
776  for (MouseWatcherGroup *group : _groups) {
777  group->hide_regions();
778  }
779 #endif // NDEBUG
780 }
781 
782 /**
783  * Computes the list of regions that are in both regions_a and regions_b, as
784  * well as the list of regions only in regions_a, and the list of regions only
785  * in regions_b. Any or all of the three output lists may be the same object,
786  * but they must be different objects from both of the input lists.
787  *
788  * It is assumed that both vectors are already sorted in pointer order. It is
789  * also assumed that any relevant locks are already held.
790  */
791 void MouseWatcher::
792 intersect_regions(MouseWatcher::Regions &only_a,
793  MouseWatcher::Regions &only_b,
794  MouseWatcher::Regions &both,
795  const MouseWatcher::Regions &regions_a,
796  const MouseWatcher::Regions &regions_b) {
797  // Now do a standard sorted intersection between the two vectors.
798  Regions::const_iterator a_ri = regions_a.begin();
799  Regions::const_iterator b_ri = regions_b.begin();
800 
801  while (a_ri != regions_a.end() && b_ri != regions_b.end()) {
802  if ((*a_ri) < (*b_ri)) {
803  // Here's a region in a, not in b.
804  only_a.push_back(*a_ri);
805  ++a_ri;
806 
807  } else if ((*b_ri) < (*a_ri)) {
808  // Here's a region in b, not in a.
809  only_b.push_back(*b_ri);
810  ++b_ri;
811 
812  } else {
813  // Here's a region in both vectors.
814  both.push_back(*a_ri);
815  ++a_ri;
816  ++b_ri;
817  }
818  }
819 }
820 
821 /**
822  * Removes the indicated region from the given vector. Assumes the vector is
823  * sorted in pointer order. Returns true if removed, false if it wasn't
824  * there. Assumes any relevent locks are already held.
825  */
826 bool MouseWatcher::
827 remove_region_from(MouseWatcher::Regions &regions,
828  MouseWatcherRegion *region) {
829  PT(MouseWatcherRegion) ptr = region;
830  Regions::iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
831  if (ri != regions.end() && (*ri) == ptr) {
832  // The region is in the vector. Remove it.
833  regions.erase(ri);
834  return true;
835  }
836 
837  return false;
838 }
839 
840 /**
841  * Returns true if the indicated region is a member of the given sorted list,
842  * false otherwise.
843  */
844 bool MouseWatcher::
845 has_region_in(const MouseWatcher::Regions &regions,
846  MouseWatcherRegion *region) {
847  PT(MouseWatcherRegion) ptr = region;
848  Regions::const_iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
849  return (ri != regions.end() && (*ri) == ptr);
850 }
851 
852 /**
853  * Throws an event associated with the indicated region, using the given
854  * pattern.
855  */
856 void MouseWatcher::
857 throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
858  const ButtonHandle &button) {
859  if (pattern.empty()) {
860  return;
861  }
862 #ifndef NDEBUG
863  if (region != nullptr) {
864  region->test_ref_count_integrity();
865  }
866 #endif
867 
868  string button_name;
869  if (button != ButtonHandle::none()) {
870  if (!_mods.has_button(button)) {
871  // We only prepend modifier names for buttons which are not themselves
872  // modifiers.
873  button_name = _mods.get_prefix();
874  }
875  button_name += button.get_name();
876  }
877 
878  string event;
879  for (size_t p = 0; p < pattern.size(); ++p) {
880  if (pattern[p] == '%') {
881  string cmd = pattern.substr(p + 1, 1);
882  p++;
883  if (cmd == "r") {
884  if (region != nullptr) {
885  event += region->get_name();
886  }
887 
888  } else if (cmd == "b") {
889  event += button.get_name();
890 
891  } else {
892  tform_cat.error()
893  << "Invalid symbol in event_pattern: %" << cmd << "\n";
894  }
895  } else {
896  event += pattern[p];
897  }
898  }
899 
900  if (!event.empty()) {
901  throw_event(event, EventParameter(region), EventParameter(button_name));
902  if (_eh != nullptr)
903  throw_event_directly(*_eh, event, EventParameter(region),
904  EventParameter(button_name));
905  }
906 }
907 
908 /**
909  * Records the indicated mouse or keyboard button as being moved from last
910  * position.
911  */
912 void MouseWatcher::
913 move() {
914  nassertv(_lock.debug_is_locked());
915 
916  MouseWatcherParameter param;
917  param.set_modifier_buttons(_mods);
918  param.set_mouse(_mouse);
919 
920  if (_preferred_button_down_region != nullptr) {
921  _preferred_button_down_region->move(param);
922  }
923 }
924 
925 /**
926  * Records the indicated mouse or keyboard button as being depressed.
927  */
928 void MouseWatcher::
929 press(ButtonHandle button, bool keyrepeat) {
930  nassertv(_lock.debug_is_locked());
931 
932  MouseWatcherParameter param;
933  param.set_button(button);
934  param.set_keyrepeat(keyrepeat);
935  param.set_modifier_buttons(_mods);
936  param.set_mouse(_mouse);
937 
938  if (MouseButton::is_mouse_button(button)) {
939  // Mouse buttons are inextricably linked to the mouse position.
940 
941  if (!_button_down) {
942  _preferred_button_down_region = _preferred_region;
943  }
944  _button_down = true;
945 
946  if (_preferred_button_down_region != nullptr) {
947  _preferred_button_down_region->press(param);
948  if (keyrepeat) {
949  throw_event_pattern(_button_repeat_pattern,
950  _preferred_button_down_region, button);
951  } else {
952  throw_event_pattern(_button_down_pattern,
953  _preferred_button_down_region, button);
954  }
955  }
956 
957  } else {
958  // It's a keyboard button; therefore, send the event to every region that
959  // wants keyboard buttons, regardless of the mouse position.
960  if (_preferred_region != nullptr) {
961  // Our current region, the one under the mouse, always get all the
962  // keyboard events, even if it doesn't set its keyboard flag.
963  _preferred_region->press(param);
964  consider_keyboard_suppress(_preferred_region);
965  }
966 
967  if ((_internal_suppress & MouseWatcherRegion::SF_other_button) == 0) {
968  // All the other regions only get the keyboard events if they set their
969  // global keyboard flag, *and* the current region does not suppress
970  // keyboard buttons.
971  param.set_outside(true);
972  global_keyboard_press(param);
973  }
974  }
975 }
976 
977 /**
978  * Records the indicated mouse or keyboard button as being released.
979  */
980 void MouseWatcher::
981 release(ButtonHandle button) {
982  nassertv(_lock.debug_is_locked());
983 
984  MouseWatcherParameter param;
985  param.set_button(button);
986  param.set_modifier_buttons(_mods);
987  param.set_mouse(_mouse);
988 
989  if (MouseButton::is_mouse_button(button)) {
990  // Button up. Send the up event associated with the region(s) we were
991  // over when the button went down.
992 
993  if (_preferred_button_down_region != nullptr) {
994  param.set_outside(_preferred_button_down_region != _preferred_region);
995  _preferred_button_down_region->release(param);
996  throw_event_pattern(_button_up_pattern,
997  _preferred_button_down_region, button);
998  }
999 
1000  // Do not stop capturing until the last mouse button has gone up. This is
1001  // needed to prevent stopping the capture until the capturing region has
1002  // finished processing all the releases.
1003  bool has_button = false;
1004  for (size_t i = 0; i < MouseButton::num_mouse_buttons; ++i) {
1005  if (MouseButton::_buttons[i] != button &&
1006  _current_buttons_down.get_bit(MouseButton::_buttons[i].get_index())) {
1007  has_button = true;
1008  }
1009  }
1010 
1011  if (!has_button) {
1012  // The last mouse button went up.
1013  _button_down = false;
1014  _preferred_button_down_region = nullptr;
1015  }
1016 
1017  } else {
1018  // It's a keyboard button; therefore, send the event to every region that
1019  // wants keyboard buttons, regardless of the mouse position.
1020  if (_preferred_region != nullptr) {
1021  _preferred_region->release(param);
1022  }
1023 
1024  param.set_outside(true);
1025  global_keyboard_release(param);
1026  }
1027 }
1028 
1029 /**
1030  * Records that the indicated keystroke has been generated.
1031  */
1032 void MouseWatcher::
1033 keystroke(int keycode) {
1034  nassertv(_lock.debug_is_locked());
1035 
1036  MouseWatcherParameter param;
1037  param.set_keycode(keycode);
1038  param.set_modifier_buttons(_mods);
1039  param.set_mouse(_mouse);
1040 
1041  // Make sure there are no duplicates in the regions vector.
1042  if (!_sorted) {
1043  ((MouseWatcher *)this)->do_sort_regions();
1044  }
1045 
1046  // Keystrokes go to all those regions that want keyboard events, regardless
1047  // of which is the "preferred" region (that is, without respect to the mouse
1048  // position). However, we do set the outside flag according to whether the
1049  // given region is the preferred region or not.
1050 
1051  for (MouseWatcherRegion *region : _regions) {
1052  if (region->get_keyboard()) {
1053  param.set_outside(region != _preferred_region);
1054  region->keystroke(param);
1055  consider_keyboard_suppress(region);
1056  }
1057  }
1058 
1059  // Also check all of our sub-groups.
1060  for (MouseWatcherGroup *group : _groups) {
1061  group->sort_regions();
1062 
1063  for (MouseWatcherRegion *region : group->_regions) {
1064  if (region->get_keyboard()) {
1065  param.set_outside(region != _preferred_region);
1066  region->keystroke(param);
1067  consider_keyboard_suppress(region);
1068  }
1069  }
1070  }
1071 }
1072 
1073 /**
1074  * Records that the indicated candidate string has been highlighted in the
1075  * IME.
1076  */
1077 void MouseWatcher::
1078 candidate(const std::wstring &candidate_string, size_t highlight_start,
1079  size_t highlight_end, size_t cursor_pos) {
1080  nassertv(_lock.debug_is_locked());
1081 
1082  MouseWatcherParameter param;
1083  param.set_candidate(candidate_string, highlight_start, highlight_end, cursor_pos);
1084  param.set_modifier_buttons(_mods);
1085  param.set_mouse(_mouse);
1086 
1087  // Make sure there are no duplicates in the regions vector.
1088  if (!_sorted) {
1089  ((MouseWatcher *)this)->do_sort_regions();
1090  }
1091 
1092  // Candidate strings go to all those regions that want keyboard events,
1093  // exactly like keystrokes, above.
1094 
1095  for (MouseWatcherRegion *region : _regions) {
1096  if (region->get_keyboard()) {
1097  param.set_outside(region != _preferred_region);
1098  region->candidate(param);
1099  }
1100  }
1101 
1102  // Also check all of our sub-groups.
1103  for (MouseWatcherGroup *group : _groups) {
1104  group->sort_regions();
1105 
1106  for (MouseWatcherRegion *region : group->_regions) {
1107  if (region->get_keyboard()) {
1108  param.set_outside(region != _preferred_region);
1109  region->candidate(param);
1110  }
1111  }
1112  }
1113 }
1114 
1115 /**
1116  * Calls press() on all regions that are interested in receiving global
1117  * keyboard events, except for the current region (which already received this
1118  * one).
1119  */
1120 void MouseWatcher::
1121 global_keyboard_press(const MouseWatcherParameter &param) {
1122  nassertv(_lock.debug_is_locked());
1123 
1124  // Make sure there are no duplicates in the regions vector.
1125  if (!_sorted) {
1126  ((MouseWatcher *)this)->do_sort_regions();
1127  }
1128 
1129  for (MouseWatcherRegion *region : _regions) {
1130  if (region != _preferred_region && region->get_keyboard()) {
1131  region->press(param);
1132  consider_keyboard_suppress(region);
1133  }
1134  }
1135 
1136  // Also check all of our sub-groups.
1137  for (MouseWatcherGroup *group : _groups) {
1138  group->sort_regions();
1139 
1140  for (MouseWatcherRegion *region : group->_regions) {
1141  if (region != _preferred_region && region->get_keyboard()) {
1142  region->press(param);
1143  consider_keyboard_suppress(region);
1144  }
1145  }
1146  }
1147 }
1148 /**
1149  * Calls release() on all regions that are interested in receiving global
1150  * keyboard events, except for the current region (which already received this
1151  * one).
1152  */
1153 void MouseWatcher::
1154 global_keyboard_release(const MouseWatcherParameter &param) {
1155  nassertv(_lock.debug_is_locked());
1156 
1157  // Make sure there are no duplicates in the regions vector.
1158  if (!_sorted) {
1159  ((MouseWatcher *)this)->do_sort_regions();
1160  }
1161 
1162  for (MouseWatcherRegion *region : _regions) {
1163  if (region != _preferred_region && region->get_keyboard()) {
1164  region->release(param);
1165  }
1166  }
1167 
1168  // Also check all of our sub-groups.
1169  for (MouseWatcherGroup *group : _groups) {
1170  group->sort_regions();
1171 
1172  for (MouseWatcherRegion *region : group->_regions) {
1173  if (region != _preferred_region && region->get_keyboard()) {
1174  region->release(param);
1175  }
1176  }
1177  }
1178 }
1179 
1180 /**
1181  * Called internally to indicate the mouse pointer is favoring the indicated
1182  * region.
1183  */
1184 void MouseWatcher::
1185 enter_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1186  nassertv(_lock.debug_is_locked());
1187 
1188  region->enter_region(param);
1189  throw_event_pattern(_enter_pattern, region, ButtonHandle::none());
1190  if (_implicit_click) {
1191  MouseWatcherParameter param1(param);
1192  param1.set_button(MouseButton::one());
1193  region->press(param1);
1194  }
1195 }
1196 
1197 /**
1198  * Called internally to indicate the mouse pointer is no longer favoring the
1199  * indicated region.
1200  */
1201 void MouseWatcher::
1202 exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1203  nassertv(_lock.debug_is_locked());
1204 
1205  if (_implicit_click) {
1206  MouseWatcherParameter param1(param);
1207  param1.set_button(MouseButton::one());
1208  region->release(param1);
1209  }
1210  region->exit_region(param);
1211  throw_event_pattern(_leave_pattern, region, ButtonHandle::none());
1212 }
1213 
1214 /**
1215  * Called from do_transmit_data() to indicate the mouse is not within the
1216  * window.
1217  */
1218 void MouseWatcher::
1219 set_no_mouse() {
1220  nassertv(_lock.debug_is_locked());
1221 
1222  if (_has_mouse) {
1223  // Hide the mouse pointer.
1224  if (!_geometry.is_null()) {
1225  _geometry->set_overall_hidden(true);
1226  }
1227  }
1228 
1229  _has_mouse = false;
1230  clear_current_regions();
1231 }
1232 
1233 /**
1234  * Called from do_transmit_data() to indicate the mouse is within the window,
1235  * and to specify its current position.
1236  */
1237 void MouseWatcher::
1238 set_mouse(const LVecBase2 &xy, const LVecBase2 &pixel_xy) {
1239  nassertv(_lock.debug_is_locked());
1240 
1241  if (!_geometry.is_null()) {
1242  // Transform the mouse pointer.
1243  _geometry->set_transform(TransformState::make_pos(LVecBase3(xy[0], 0, xy[1])));
1244  if (!_has_mouse) {
1245  // Show the mouse pointer.
1246  _geometry->set_overall_hidden(false);
1247  }
1248  }
1249 
1250  _has_mouse = true;
1251  _mouse = xy;
1252  _mouse_pixel = pixel_xy;
1253 
1254  Regions regions;
1255  get_over_regions(regions, _mouse);
1256  set_current_regions(regions);
1257 }
1258 
1259 /**
1260  * If we send any keyboard events to a region that has the SF_other_button
1261  * suppress flag set, that means we should not send the keyboard event along
1262  * the data graph.
1263  *
1264  * This method is called as each keyboard event is sent to a region; it should
1265  * update the internal _keyboard_suppress bitmask to indicate this.
1266  */
1267 void MouseWatcher::
1268 consider_keyboard_suppress(const MouseWatcherRegion *region) {
1269  if ((region->get_suppress_flags() & MouseWatcherRegion::SF_other_button) != 0) {
1270  _external_suppress |= MouseWatcherRegion::SF_other_button;
1271  }
1272 }
1273 
1274 /**
1275  * The virtual implementation of transmit_data(). This function receives an
1276  * array of input parameters and should generate an array of output
1277  * parameters. The input parameters may be accessed with the index numbers
1278  * returned by the define_input() calls that were made earlier (presumably in
1279  * the constructor); likewise, the output parameters should be set with the
1280  * index numbers returned by the define_output() calls.
1281  */
1282 void MouseWatcher::
1283 do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
1284  DataNodeTransmit &output) {
1285  Thread *current_thread = trav->get_current_thread();
1286  LightMutexHolder holder(_lock);
1287 
1288  bool activity = false;
1289 
1290  // Initially, we do not suppress any events to objects below us in the data
1291  // graph.
1292  _internal_suppress = 0;
1293  _external_suppress = 0;
1294 
1295  // We always pass the pixel_size data through.
1296  EventStoreVec2 *pixel_size;
1297  DCAST_INTO_V(pixel_size, input.get_data(_pixel_size_input).get_ptr());
1298  output.set_data(_pixel_size_output, pixel_size);
1299  _pixel_size = pixel_size;
1300 
1301  if (input.has_data(_xy_input)) {
1302  // The mouse is within the window. Get the current mouse position.
1303  const EventStoreVec2 *xy, *pixel_xy;
1304  DCAST_INTO_V(xy, input.get_data(_xy_input).get_ptr());
1305  DCAST_INTO_V(pixel_xy, input.get_data(_pixel_xy_input).get_ptr());
1306 
1307  LVecBase2 f = xy->get_value();
1308  LVecBase2 p = pixel_xy->get_value();
1309 
1310  // Asad: determine if mouse moved from last position
1311  const LVecBase2 &last_f = _xy->get_value();
1312  if (f != last_f) {
1313  activity = true;
1314  move();
1315  }
1316 
1317  if (_display_region != nullptr) {
1318  // If we've got a display region, constrain the mouse to it.
1319  if (constrain_display_region(_display_region, f, p, current_thread)) {
1320  set_mouse(f, p);
1321 
1322  } else {
1323  // The mouse is outside the display region, even though it's within
1324  // the window. This is considered not having a mouse.
1325  set_no_mouse();
1326 
1327  // This also means we should suppress mouse button events below us.
1328  _internal_suppress |= MouseWatcherRegion::SF_mouse_button;
1329  }
1330 
1331  } else {
1332  // No display region; respect the whole window.
1333  set_mouse(f, p);
1334  }
1335  }
1336 
1337  // Code for recording the mouse trail.
1338  _num_trail_recent = 0;
1339  if (input.has_data(_pointer_events_input) && (_trail_log_duration > 0.0)) {
1340  const PointerEventList *this_pointer_events;
1341  DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr());
1342  _num_trail_recent = this_pointer_events->get_num_events();
1343  for (size_t i = 0; i < _num_trail_recent; i++) {
1344  bool in_win = this_pointer_events->get_in_window(i);
1345  int xpos = this_pointer_events->get_xpos(i);
1346  int ypos = this_pointer_events->get_ypos(i);
1347  int sequence = this_pointer_events->get_sequence(i);
1348  double time = this_pointer_events->get_time(i);
1349  _trail_log->add_event(in_win, xpos, ypos, sequence, time);
1350  }
1351  }
1352  if (_trail_log->get_num_events() > 0) {
1353  discard_excess_trail_log();
1354  update_trail_node();
1355  }
1356  if (_num_trail_recent > _trail_log->get_num_events()) {
1357  _num_trail_recent = _trail_log->get_num_events();
1358  }
1359 
1360  // If the mouse is over a particular region, or still considered owned by a
1361  // region because of a recent button-down event, that region determines
1362  // whether we suppress events below us.
1363  if (_preferred_region != nullptr) {
1364  _internal_suppress |= _preferred_region->get_suppress_flags();
1365  }
1366 
1367  ButtonEventList new_button_events;
1368 
1369  // Look for new button events.
1370  if (input.has_data(_button_events_input)) {
1371  const ButtonEventList *this_button_events;
1372  DCAST_INTO_V(this_button_events, input.get_data(_button_events_input).get_ptr());
1373  int num_events = this_button_events->get_num_events();
1374  for (int i = 0; i < num_events; i++) {
1375  const ButtonEvent &be = this_button_events->get_event(i);
1376  be.update_mods(_mods);
1377 
1378  switch (be._type) {
1379  case ButtonEvent::T_down:
1380  if (!_current_buttons_down.get_bit(be._button.get_index())) {
1381  // The button was not already depressed; thus, this is not
1382  // keyrepeat.
1383  activity = true;
1384  _current_buttons_down.set_bit(be._button.get_index());
1385  press(be._button, false);
1386  new_button_events.add_event(be);
1387  break;
1388  }
1389  // The button was already depressed, so this is really just keyrepeat.
1390  // Fall through.
1391 
1392  case ButtonEvent::T_repeat:
1393  _current_buttons_down.set_bit(be._button.get_index());
1394  press(be._button, true);
1395  new_button_events.add_event(ButtonEvent(be._button, ButtonEvent::T_repeat,
1396  be._time));
1397  break;
1398 
1399  case ButtonEvent::T_up:
1400  activity = true;
1401  _current_buttons_down.clear_bit(be._button.get_index());
1402  release(be._button);
1403  new_button_events.add_event(be);
1404  break;
1405 
1406  case ButtonEvent::T_keystroke:
1407  // We don't consider "keystroke" an activity event, because it might
1408  // be just keyrepeat.
1409  keystroke(be._keycode);
1410  new_button_events.add_event(be);
1411  break;
1412 
1413  case ButtonEvent::T_candidate:
1414  activity = true;
1415  candidate(be._candidate_string, be._highlight_start, be._highlight_end, be._cursor_pos);
1416  new_button_events.add_event(be);
1417  break;
1418 
1419  case ButtonEvent::T_resume_down:
1420  // _current_buttons_down.set_bit(be._button.get_index()); Don't call
1421  // press(), since the button wasn't actually pressed just now.
1422  new_button_events.add_event(be);
1423  break;
1424 
1425  case ButtonEvent::T_move:
1426  // This is handled below.
1427  break;
1428 
1429  case ButtonEvent::T_raw_down:
1430  case ButtonEvent::T_raw_up:
1431  // These are passed through.
1432  new_button_events.add_event(be);
1433  break;
1434  }
1435  }
1436  }
1437 
1438  if (!input.has_data(_xy_input)) {
1439  // No mouse in the window. We check this down here, below the button
1440  // checking, in case the mouse left the window in the same frame it
1441  // released a button (particularly likely with a touchscreen input that's
1442  // emulating a mouse).
1443  set_no_mouse();
1444  }
1445 
1446  // Now check the inactivity timer.
1447  if (_has_inactivity_timeout) {
1448  if (activity) {
1449  note_activity();
1450 
1451  } else {
1453  double elapsed = now - _last_activity;
1454 
1455  // Toggle the inactivity state to inactive.
1456  if (elapsed > _inactivity_timeout) {
1457  switch (_inactivity_state) {
1458  case IS_active:
1459  _inactivity_state = IS_active_to_inactive;
1460  break;
1461 
1462  case IS_inactive:
1463  break;
1464 
1465  case IS_active_to_inactive:
1466  break;
1467 
1468  case IS_inactive_to_active:
1469  _inactivity_state = IS_inactive;
1470  break;
1471  }
1472  }
1473  }
1474  }
1475 
1476  switch (_inactivity_state) {
1477  case IS_active:
1478  case IS_inactive:
1479  break;
1480 
1481  case IS_active_to_inactive:
1482  // "Release" all of the currently-held buttons.
1483  if (tform_cat.is_debug()) {
1484  tform_cat.info()
1485  << "MouseWatcher detected " << _inactivity_timeout
1486  << " seconds of inactivity; releasing held buttons.\n";
1487  }
1488  {
1489  for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1490  if (_current_buttons_down.get_bit(i)) {
1491  release(ButtonHandle((int)i));
1492  new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_up));
1493  }
1494  }
1495  }
1496  _inactivity_state = IS_inactive;
1497  throw_event(_inactivity_timeout_event);
1498  break;
1499 
1500  case IS_inactive_to_active:
1501  // "Press" all of the buttons we "released" before.
1502  {
1503  for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1504  if (_current_buttons_down.get_bit(i)) {
1505  press(ButtonHandle((int)i), false);
1506  new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_down));
1507  }
1508  }
1509  }
1510  _inactivity_state = IS_active;
1511  break;
1512  }
1513 
1514  if (_has_mouse &&
1515  (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
1516  // Transmit the mouse position.
1517  _xy->set_value(_mouse);
1518  output.set_data(_xy_output, EventParameter(_xy));
1519  _pixel_xy->set_value(_mouse_pixel);
1520  output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
1521  }
1522 
1523  // Now transmit the buttons events down the graph.
1524  int suppress_buttons = ((_internal_suppress | _external_suppress) & MouseWatcherRegion::SF_any_button);
1525 
1526  _button_events->clear();
1527 
1528  int num_events = new_button_events.get_num_events();
1529  for (int i = 0; i < num_events; i++) {
1530  const ButtonEvent &be = new_button_events.get_event(i);
1531  bool suppress = true;
1532 
1533  if (be._type != ButtonEvent::T_keystroke &&
1534  MouseButton::is_mouse_button(be._button)) {
1535  suppress = ((suppress_buttons & MouseWatcherRegion::SF_mouse_button) != 0);
1536  } else {
1537  suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
1538  }
1539 
1540  if (!suppress || be._type == ButtonEvent::T_up) {
1541  // Don't suppress this button event; pass it through.
1542  _button_events->add_event(be);
1543  }
1544  }
1545 
1546  if (_button_events->get_num_events() != 0) {
1547  output.set_data(_button_events_output, EventParameter(_button_events));
1548  }
1549 }
1550 
1551 /**
1552  * Constrains the mouse coordinates to within the indicated DisplayRegion. If
1553  * the mouse pointer does indeed fall within the DisplayRegion, rescales f and
1554  * p correspondingly, and returns true. If the mouse pointer does not fall
1555  * within the DisplayRegion, leaves f and p unchanged, and returns false.
1556  */
1557 bool MouseWatcher::
1558 constrain_display_region(DisplayRegion *display_region,
1559  LVecBase2 &f, LVecBase2 &p,
1560  Thread *current_thread) {
1561  if (!_button_down) {
1562  _button_down_display_region = nullptr;
1563  }
1564  if (_button_down_display_region != nullptr) {
1565  // If the button went down over this DisplayRegion, we consider the button
1566  // within the same DisplayRegion until it is released (even if it wanders
1567  // outside the borders).
1568  display_region = _button_down_display_region;
1569 
1570  } else {
1571  // If it's a stereo DisplayRegion, we should actually call this method
1572  // twice, once for each eye, in case we have side-by-side stereo.
1573  if (display_region->is_stereo()) {
1574  StereoDisplayRegion *stereo_display_region;
1575  DCAST_INTO_R(stereo_display_region, display_region, false);
1576  return constrain_display_region(stereo_display_region->get_left_eye(), f, p, current_thread) ||
1577  constrain_display_region(stereo_display_region->get_right_eye(), f, p, current_thread);
1578  }
1579  }
1580 
1581  DisplayRegionPipelineReader dr_reader(display_region, current_thread);
1582  PN_stdfloat left, right, bottom, top;
1583  dr_reader.get_dimensions(left, right, bottom, top);
1584 
1585  // Need to translate this into DisplayRegion [0, 1] space
1586  PN_stdfloat x = (f[0] + 1.0f) / 2.0f;
1587  PN_stdfloat y = (f[1] + 1.0f) / 2.0f;
1588 
1589  if (_button_down_display_region == nullptr &&
1590  (x < left || x >= right || y < bottom || y >= top)) {
1591  // The mouse is outside the display region.
1592  return false;
1593  }
1594 
1595  // The mouse is within the display region; rescale it.
1596  if (_button_down) {
1597  _button_down_display_region = display_region;
1598  }
1599 
1600  // Scale in DR space
1601  PN_stdfloat xp = (x - left) / (right - left);
1602  // Translate back into [-1, 1] space
1603  PN_stdfloat xpp = (xp * 2.0f) - 1.0f;
1604 
1605  PN_stdfloat yp = (y - bottom) / (top - bottom);
1606  PN_stdfloat ypp = (yp * 2.0f) - 1.0f;
1607 
1608  int xo, yo, w, h;
1609  dr_reader.get_region_pixels_i(xo, yo, w, h);
1610 
1611  f.set(xpp, ypp);
1612  p.set(p[0] - xo, p[1] - yo);
1613  return true;
1614 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear_bit(int index)
Sets the nth bit off.
Definition: bitArray.I:133
size_t get_num_bits() const
Returns the current number of possibly different bits in this array.
Definition: bitArray.I:89
void set_bit(int index)
Sets the nth bit on.
Definition: bitArray.I:115
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
Definition: bitArray.I:99
Records a set of button events that happened recently.
const ButtonEvent & get_event(int n) const
Returns the nth event in the list.
void add_event(ButtonEvent event)
Adds a new event to the end of the list.
int get_num_events() const
Returns the number of events in the list.
Records a button event of some kind.
Definition: buttonEvent.h:46
bool update_mods(ModifierButtons &mods) const
Calls button_down() or button_up(), as appropriate, according to the ButtonEvent.
Definition: buttonEvent.I:140
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
get_name
Returns the name of the button.
Definition: buttonHandle.h:61
get_index
Returns the integer index associated with this ButtonHandle.
Definition: buttonHandle.h:60
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition: clockObject.h:91
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
This object supervises the traversal of the data graph and the moving of data from one DataNode to it...
Thread * get_current_thread() const
Returns the currently-executing thread object, as passed to the DataGraphTraverser constructor.
Encapsulates the data generated from (or sent into) any particular DataNode.
const EventParameter & get_data(int index) const
Extracts the data for the indicated index, if it has been stored, or the empty parameter if it has no...
void set_data(int index, const EventParameter &data)
Sets the data for the indicated parameter.
bool has_data(int index) const
Returns true if the indicated parameter has been stored, false otherwise.
The fundamental type of node for the data graph.
Definition: dataNode.h:52
Encapsulates the data from a DisplayRegion, pre-fetched for one stage of the pipeline.
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
void get_region_pixels_i(int &xo, int &yo, int &w, int &h) const
Similar to get_region_pixels(), but returns the upper left corner, and the pixel numbers are numbered...
An optional parameter associated with an event.
TypedWritableReferenceCount * get_ptr() const
Retrieves a pointer to the actual value stored in the parameter.
Defines a series of line strips.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
Similar to MutexHolder, but for a light mutex.
std::string get_prefix() const
Returns a string which can be used to prefix any button name or event name with the unique set of mod...
bool has_button(ButtonHandle button) const
Returns true if the indicated button is in the set of buttons being monitored, false otherwise.
static bool is_mouse_button(ButtonHandle button)
Returns true if the indicated ButtonHandle is a mouse button, false if it is some other kind of butto...
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
void show_regions(const NodePath &render2d, const std::string &bin_name, int draw_order)
Enables the visualization of all of the regions handled by this MouseWatcherBase.
This represents a collection of MouseWatcherRegions that may be managed as a group.
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
void set_keyrepeat(bool flag)
Sets the state of the "keyrepeat" flag.
void set_mouse(const LPoint2 &mouse)
Sets the mouse position that was current at the time the event was generated.
void set_keycode(int keycode)
Sets the keycode associated with this event, if any.
void set_modifier_buttons(const ModifierButtons &mods)
Sets the modifier buttons that were being held while this event was generated.
void set_candidate(const std::wstring &candidate_string, size_t highlight_start, size_t higlight_end, size_t cursor_pos)
Sets the candidate string associated with this event, if any.
void set_outside(bool flag)
Sets the state of the "outside" flag.
void set_button(const ButtonHandle &button)
Sets the mouse or keyboard button that generated this event, if any.
This is the class that defines a rectangular region on the screen for the MouseWatcher.
virtual void release(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button previously depressed wit...
virtual void keystroke(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a keystroke is generated by the user.
virtual void exit_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse exits the region.
virtual void press(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button is depressed while the m...
get_active
Returns whether the region is active or not.
get_suppress_flags
Returns the current suppress_flags.
virtual void enter_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse enters the region.
virtual void candidate(const MouseWatcherParameter &param)
This is a callback hook function, called whenever an IME candidate is highlighted by the user.
get_keyboard
Returns whether the region is interested in global keyboard events; see set_keyboard().
This TFormer maintains a list of rectangular regions on the screen that are considered special mouse ...
Definition: mouseWatcher.h:61
bool add_group(MouseWatcherGroup *group)
Adds the indicated group of regions to the set of regions the MouseWatcher will monitor each frame.
bool remove_group(MouseWatcherGroup *group)
Removes the indicated group from the set of extra groups associated with the MouseWatcher.
void set_trail_log_duration(double duration)
If the duration is nonzero, causes the MouseWatcher to log the mouse's trail.
get_group
Returns the nth group added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
bool replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group)
Atomically removes old_group from the MouseWatcher, and replaces it with new_group.
void clear_trail_node()
If you have previously fetched the trail node using get_trail_node, then the MouseWatcher is continua...
void note_activity()
Can be used in conjunction with the inactivity timeout to inform the MouseWatcher that the user has j...
bool remove_region(MouseWatcherRegion *region)
Removes the indicated region from the group.
MouseWatcherRegion * get_over_region() const
Returns the smallest region the mouse is currently over, or NULL if it is over no region.
Definition: mouseWatcher.I:128
get_num_groups
Returns the number of separate groups added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
A handy class object for storing simple values (like integers or strings) passed along with an Event ...
Definition: paramValue.h:103
get_value
Retrieves the value stored in the parameter.
Definition: paramValue.h:115
Records a set of pointer events that happened recently.
int get_xpos(size_t n) const
Get the x-coordinate of the nth event.
int get_sequence(size_t n) const
Get the sequence number of the nth event.
double get_time(size_t n) const
Get the timestamp of the nth event.
size_t get_num_events() const
Returns the number of events in the list.
int get_ypos(size_t n) const
Get the y-coordinate of the nth event.
bool get_in_window(size_t n) const
Get the in-window flag of the nth event.
bool test_ref_count_integrity() const
Does some easy checks to make sure that the reference count isn't completely bogus.
This is a special DisplayRegion wrapper that actually includes a pair of DisplayRegions internally: t...
get_right_eye
Returns a pointer to the right DisplayRegion managed by this stereo object.
get_left_eye
Returns a pointer to the left DisplayRegion managed by this stereo object.
A thread; that is, a lightweight process.
Definition: thread.h:46
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
void push_back(const value_type_0 &key)
Adds the new element to the end of the vector without regard for proper sorting.
void swap(ordered_vector< Key, Compare, Vector > &other)
Exchanges the contents of this vector and the other vector, in constant time (e.g....
bool empty() const
Returns true if the ordered vector is empty, false otherwise.
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
void clear()
Removes all elements from the ordered vector.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(GeomNode) MouseWatcher
Returns a GeomNode that represents the mouse trail.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.