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 }
Geom
A container for geometry primitives.
Definition: geom.h:54
ButtonEventList::add_event
void add_event(ButtonEvent event)
Adds a new event to the end of the list.
Definition: buttonEventList.I:42
MouseButton::one
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
StereoDisplayRegion::get_right_eye
get_right_eye
Returns a pointer to the right DisplayRegion managed by this stereo object.
Definition: stereoDisplayRegion.h:67
LightMutexHolder
Similar to MutexHolder, but for a light mutex.
Definition: lightMutexHolder.h:25
indent
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
MouseWatcherParameter::set_mouse
void set_mouse(const LPoint2 &mouse)
Sets the mouse position that was current at the time the event was generated.
Definition: mouseWatcherParameter.I:115
ButtonEvent
Records a button event of some kind.
Definition: buttonEvent.h:46
MouseWatcherParameter::set_outside
void set_outside(bool flag)
Sets the state of the "outside" flag.
Definition: mouseWatcherParameter.I:126
geomVertexWriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
throw_event.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MouseWatcherRegion::get_suppress_flags
get_suppress_flags
Returns the current suppress_flags.
Definition: mouseWatcherRegion.h:71
ButtonHandle::get_name
get_name
Returns the name of the button.
Definition: buttonHandle.h:61
MouseWatcherRegion::release
virtual void release(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button previously depressed wit...
Definition: mouseWatcherRegion.cxx:90
MouseWatcher
This TFormer maintains a list of rectangular regions on the screen that are considered special mouse ...
Definition: mouseWatcher.h:61
MouseWatcher::get_group
get_group
Returns the nth group added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
eventParameter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexData
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
Definition: geomVertexData.h:68
MouseWatcherParameter
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
Definition: mouseWatcherParameter.h:28
ButtonEvent::update_mods
bool update_mods(ModifierButtons &mods) const
Calls button_down() or button_up(), as appropriate, according to the ButtonEvent.
Definition: buttonEvent.I:140
DataGraphTraverser
This object supervises the traversal of the data graph and the moving of data from one DataNode to it...
Definition: dataGraphTraverser.h:32
MouseWatcherGroup
This represents a collection of MouseWatcherRegions that may be managed as a group.
Definition: mouseWatcherGroup.h:27
PointerEventList
Records a set of pointer events that happened recently.
Definition: pointerEventList.h:33
MouseWatcherParameter::set_modifier_buttons
void set_modifier_buttons(const ModifierButtons &mods)
Sets the modifier buttons that were being held while this event was generated.
Definition: mouseWatcherParameter.I:106
dcast.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MouseWatcherBase::show_regions
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.
Definition: mouseWatcherBase.cxx:195
mouseButton.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DisplayRegion::get_region_pixels_i
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...
Definition: displayRegion.I:451
ordered_vector::empty
bool empty() const
Returns true if the ordered vector is empty, false otherwise.
Definition: ordered_vector.I:240
ordered_vector::begin
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
Definition: ordered_vector.I:41
ModifierButtons::has_button
bool has_button(ButtonHandle button) const
Returns true if the indicated button is in the set of buttons being monitored, false otherwise.
Definition: modifierButtons.cxx:206
stereoDisplayRegion.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ButtonHandle
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
MouseWatcher::set_trail_log_duration
void set_trail_log_duration(double duration)
If the duration is nonzero, causes the MouseWatcher to log the mouse's trail.
Definition: mouseWatcher.cxx:358
ClockObject::get_global_clock
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
GeomVertexWriter
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
Definition: geomVertexWriter.h:55
MouseWatcherRegion::exit_region
virtual void exit_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse exits the region.
Definition: mouseWatcherRegion.cxx:56
DataNodeTransmit::has_data
bool has_data(int index) const
Returns true if the indicated parameter has been stored, false otherwise.
Definition: dataNodeTransmit.I:64
DisplayRegion
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
ButtonEventList::get_num_events
int get_num_events() const
Returns the number of events in the list.
Definition: buttonEventList.I:50
MouseWatcherParameter::set_button
void set_button(const ButtonHandle &button)
Sets the mouse or keyboard button that generated this event, if any.
Definition: mouseWatcherParameter.I:59
GeomLinestrips
Defines a series of line strips.
Definition: geomLinestrips.h:23
mouseData.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MouseWatcher::clear_trail_node
void clear_trail_node()
If you have previously fetched the trail node using get_trail_node, then the MouseWatcher is continua...
Definition: mouseWatcher.cxx:416
ButtonEventList::get_event
const ButtonEvent & get_event(int n) const
Returns the nth event in the list.
Definition: buttonEventList.I:60
ordered_vector::end
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
Definition: ordered_vector.I:50
EventParameter::get_ptr
TypedWritableReferenceCount * get_ptr() const
Retrieves a pointer to the actual value stored in the parameter.
Definition: eventParameter.I:219
dataGraphTraverser.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ordered_vector::swap
void swap(ordered_vector< Key, Compare, Vector > &other)
Exchanges the contents of this vector and the other vector, in constant time (e.g....
Definition: ordered_vector.I:561
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
MouseWatcherRegion::get_keyboard
get_keyboard
Returns whether the region is interested in global keyboard events; see set_keyboard().
Definition: mouseWatcherRegion.h:70
GeomNode
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
PointerEventList::get_ypos
int get_ypos(size_t n) const
Get the y-coordinate of the nth event.
Definition: pointerEventList.I:84
DataGraphTraverser::get_current_thread
Thread * get_current_thread() const
Returns the currently-executing thread object, as passed to the DataGraphTraverser constructor.
Definition: dataGraphTraverser.I:19
PT
PT(GeomNode) MouseWatcher
Returns a GeomNode that represents the mouse trail.
Definition: mouseWatcher.cxx:400
MouseWatcher::remove_group
bool remove_group(MouseWatcherGroup *group)
Removes the indicated group from the set of extra groups associated with the MouseWatcher.
Definition: mouseWatcher.cxx:183
displayRegion.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ParamValue
A handy class object for storing simple values (like integers or strings) passed along with an Event ...
Definition: paramValue.h:103
StereoDisplayRegion
This is a special DisplayRegion wrapper that actually includes a pair of DisplayRegions internally: t...
Definition: stereoDisplayRegion.h:32
MouseWatcher::get_num_groups
get_num_groups
Returns the number of separate groups added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
transformState.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MouseWatcherRegion::press
virtual void press(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button is depressed while the m...
Definition: mouseWatcherRegion.cxx:82
lightMutexHolder.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
nearly_zero.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ordered_vector::push_back
void push_back(const value_type_0 &key)
Adds the new element to the end of the vector without regard for proper sorting.
Definition: ordered_vector.I:614
ButtonEventList
Records a set of button events that happened recently.
Definition: buttonEventList.h:33
buttonEventList.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ReferenceCount::test_ref_count_integrity
bool test_ref_count_integrity() const
Does some easy checks to make sure that the reference count isn't completely bogus.
Definition: referenceCount.I:197
MouseWatcherRegion::candidate
virtual void candidate(const MouseWatcherParameter &param)
This is a callback hook function, called whenever an IME candidate is highlighted by the user.
Definition: mouseWatcherRegion.cxx:106
MouseWatcher::replace_group
bool replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group)
Atomically removes old_group from the MouseWatcher, and replaces it with new_group.
Definition: mouseWatcher.cxx:234
GeomVertexFormat::get_v3
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
Definition: geomVertexFormat.I:251
MouseButton::is_mouse_button
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...
Definition: mouseButton.cxx:120
MouseWatcher::note_activity
void note_activity()
Can be used in conjunction with the inactivity timeout to inform the MouseWatcher that the user has j...
Definition: mouseWatcher.cxx:468
PointerEventList::get_xpos
int get_xpos(size_t n) const
Get the x-coordinate of the nth event.
Definition: pointerEventList.I:75
DataNode
The fundamental type of node for the data graph.
Definition: dataNode.h:52
MouseWatcherRegion::enter_region
virtual void enter_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse enters the region.
Definition: mouseWatcherRegion.cxx:46
PointerEventList::get_in_window
bool get_in_window(size_t n) const
Get the in-window flag of the nth event.
Definition: pointerEventList.I:66
DisplayRegion::is_stereo
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
PointerEventList::get_num_events
size_t get_num_events() const
Returns the number of events in the list.
Definition: pointerEventList.I:42
DataNodeTransmit::set_data
void set_data(int index, const EventParameter &data)
Sets the data for the indicated parameter.
Definition: dataNodeTransmit.I:75
mouseWatcher.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
EventParameter
An optional parameter associated with an event.
Definition: eventParameter.h:35
DataNodeTransmit
Encapsulates the data generated from (or sent into) any particular DataNode.
Definition: dataNodeTransmit.h:32
MouseWatcher::add_group
bool add_group(MouseWatcherGroup *group)
Adds the indicated group of regions to the set of regions the MouseWatcher will monitor each frame.
Definition: mouseWatcher.cxx:153
BitArray::clear_bit
void clear_bit(int index)
Sets the nth bit off.
Definition: bitArray.I:133
BitArray::get_bit
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
Definition: bitArray.I:99
ClockObject::get_frame_time
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
MouseWatcherParameter::set_keyrepeat
void set_keyrepeat(bool flag)
Sets the state of the "keyrepeat" flag.
Definition: mouseWatcherParameter.I:70
MouseWatcherRegion
This is the class that defines a rectangular region on the screen for the MouseWatcher.
Definition: mouseWatcherRegion.h:31
geomLinestrips.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BitArray::set_bit
void set_bit(int index)
Sets the nth bit on.
Definition: bitArray.I:115
PointerEventList::get_sequence
int get_sequence(size_t n) const
Get the sequence number of the nth event.
Definition: pointerEventList.I:138
ModifierButtons::get_prefix
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...
Definition: modifierButtons.cxx:306
geomPoints.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MouseWatcherRegion::keystroke
virtual void keystroke(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a keystroke is generated by the user.
Definition: mouseWatcherRegion.cxx:98
MouseWatcher::remove_region
bool remove_region(MouseWatcherRegion *region)
Removes the indicated region from the group.
Definition: mouseWatcher.cxx:109
ButtonHandle::get_index
get_index
Returns the integer index associated with this ButtonHandle.
Definition: buttonHandle.h:60
MouseWatcherRegion::get_active
get_active
Returns whether the region is active or not.
Definition: mouseWatcherRegion.h:69
indent.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ParamValue::get_value
get_value
Retrieves the value stored in the parameter.
Definition: paramValue.h:115
StereoDisplayRegion::get_left_eye
get_left_eye
Returns a pointer to the left DisplayRegion managed by this stereo object.
Definition: stereoDisplayRegion.h:66
MouseWatcher::get_over_region
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
DataNodeTransmit::get_data
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...
Definition: dataNodeTransmit.I:52
MouseWatcherParameter::set_candidate
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.
Definition: mouseWatcherParameter.I:91
dataNodeTransmit.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ov_set< PT(MouseWatcherRegion) >
MouseWatcherParameter::set_keycode
void set_keycode(int keycode)
Sets the keycode associated with this event, if any.
Definition: mouseWatcherParameter.I:82
config_tform.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Thread
A thread; that is, a lightweight process.
Definition: thread.h:46
BitArray::get_num_bits
size_t get_num_bits() const
Returns the current number of possibly different bits in this array.
Definition: bitArray.I:89
DisplayRegionPipelineReader
Encapsulates the data from a DisplayRegion, pre-fetched for one stage of the pipeline.
Definition: displayRegion.h:311
mouseWatcherParameter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PointerEventList::get_time
double get_time(size_t n) const
Get the timestamp of the nth event.
Definition: pointerEventList.I:147
ordered_vector::clear
void clear()
Removes all elements from the ordered vector.
Definition: ordered_vector.I:412
mouseAndKeyboard.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath::is_empty
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188