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  */
108 bool MouseWatcher::
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  */
152 bool MouseWatcher::
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  */
182 bool MouseWatcher::
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  */
233 bool MouseWatcher::
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  */
357 void MouseWatcher::
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  */
415 void MouseWatcher::
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  */
467 void MouseWatcher::
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  ++old_ri;
730  }
731 
732  _current_regions.clear();
733 
734  if (_preferred_region != nullptr) {
735  _preferred_region->exit_region(param);
736  throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none());
737  _preferred_region = nullptr;
738  }
739  }
740 }
741 
742 #ifndef NDEBUG
743 /**
744  * The protected implementation of show_regions(). This assumes the lock is
745  * already held.
746  */
747 void MouseWatcher::
748 do_show_regions(const NodePath &render2d, const string &bin_name,
749  int draw_order) {
750  MouseWatcherBase::do_show_regions(render2d, bin_name, draw_order);
751  _show_regions_render2d = render2d;
752  _show_regions_bin_name = bin_name;
753  _show_regions_draw_order = draw_order;
754 
755  for (MouseWatcherGroup *group : _groups) {
756  group->show_regions(render2d, bin_name, draw_order);
757  }
758 }
759 #endif // NDEBUG
760 
761 #ifndef NDEBUG
762 /**
763  * The protected implementation of hide_regions(). This assumes the lock is
764  * already held.
765  */
766 void MouseWatcher::
767 do_hide_regions() {
768  MouseWatcherBase::do_hide_regions();
769  _show_regions_render2d = NodePath();
770  _show_regions_bin_name = string();
771  _show_regions_draw_order = 0;
772 
773  for (MouseWatcherGroup *group : _groups) {
774  group->hide_regions();
775  }
776 }
777 #endif // NDEBUG
778 
779 /**
780  * Computes the list of regions that are in both regions_a and regions_b, as
781  * well as the list of regions only in regions_a, and the list of regions only
782  * in regions_b. Any or all of the three output lists may be the same object,
783  * but they must be different objects from both of the input lists.
784  *
785  * It is assumed that both vectors are already sorted in pointer order. It is
786  * also assumed that any relevant locks are already held.
787  */
788 void MouseWatcher::
789 intersect_regions(MouseWatcher::Regions &only_a,
790  MouseWatcher::Regions &only_b,
791  MouseWatcher::Regions &both,
792  const MouseWatcher::Regions &regions_a,
793  const MouseWatcher::Regions &regions_b) {
794  // Now do a standard sorted intersection between the two vectors.
795  Regions::const_iterator a_ri = regions_a.begin();
796  Regions::const_iterator b_ri = regions_b.begin();
797 
798  while (a_ri != regions_a.end() && b_ri != regions_b.end()) {
799  if ((*a_ri) < (*b_ri)) {
800  // Here's a region in a, not in b.
801  only_a.push_back(*a_ri);
802  ++a_ri;
803 
804  } else if ((*b_ri) < (*a_ri)) {
805  // Here's a region in b, not in a.
806  only_b.push_back(*b_ri);
807  ++b_ri;
808 
809  } else {
810  // Here's a region in both vectors.
811  both.push_back(*a_ri);
812  ++a_ri;
813  ++b_ri;
814  }
815  }
816 }
817 
818 /**
819  * Removes the indicated region from the given vector. Assumes the vector is
820  * sorted in pointer order. Returns true if removed, false if it wasn't
821  * there. Assumes any relevent locks are already held.
822  */
823 bool MouseWatcher::
824 remove_region_from(MouseWatcher::Regions &regions,
825  MouseWatcherRegion *region) {
826  PT(MouseWatcherRegion) ptr = region;
827  Regions::iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
828  if (ri != regions.end() && (*ri) == ptr) {
829  // The region is in the vector. Remove it.
830  regions.erase(ri);
831  return true;
832  }
833 
834  return false;
835 }
836 
837 /**
838  * Returns true if the indicated region is a member of the given sorted list,
839  * false otherwise.
840  */
841 bool MouseWatcher::
842 has_region_in(const MouseWatcher::Regions &regions,
843  MouseWatcherRegion *region) {
844  PT(MouseWatcherRegion) ptr = region;
845  Regions::const_iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
846  return (ri != regions.end() && (*ri) == ptr);
847 }
848 
849 /**
850  * Throws an event associated with the indicated region, using the given
851  * pattern.
852  */
853 void MouseWatcher::
854 throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
855  const ButtonHandle &button) {
856  if (pattern.empty()) {
857  return;
858  }
859 #ifndef NDEBUG
860  if (region != nullptr) {
861  region->test_ref_count_integrity();
862  }
863 #endif
864 
865  string button_name;
866  if (button != ButtonHandle::none()) {
867  if (!_mods.has_button(button)) {
868  // We only prepend modifier names for buttons which are not themselves
869  // modifiers.
870  button_name = _mods.get_prefix();
871  }
872  button_name += button.get_name();
873  }
874 
875  string event;
876  for (size_t p = 0; p < pattern.size(); ++p) {
877  if (pattern[p] == '%') {
878  string cmd = pattern.substr(p + 1, 1);
879  p++;
880  if (cmd == "r") {
881  if (region != nullptr) {
882  event += region->get_name();
883  }
884 
885  } else if (cmd == "b") {
886  event += button.get_name();
887 
888  } else {
889  tform_cat.error()
890  << "Invalid symbol in event_pattern: %" << cmd << "\n";
891  }
892  } else {
893  event += pattern[p];
894  }
895  }
896 
897  if (!event.empty()) {
898  throw_event(event, EventParameter(region), EventParameter(button_name));
899  if (_eh != nullptr)
900  throw_event_directly(*_eh, event, EventParameter(region),
901  EventParameter(button_name));
902  }
903 }
904 
905 /**
906  * Records the indicated mouse or keyboard button as being moved from last
907  * position.
908  */
909 void MouseWatcher::
910 move() {
911  nassertv(_lock.debug_is_locked());
912 
913  MouseWatcherParameter param;
914  param.set_modifier_buttons(_mods);
915  param.set_mouse(_mouse);
916 
917  if (_preferred_button_down_region != nullptr) {
918  _preferred_button_down_region->move(param);
919  }
920 }
921 
922 /**
923  * Records the indicated mouse or keyboard button as being depressed.
924  */
925 void MouseWatcher::
926 press(ButtonHandle button, bool keyrepeat) {
927  nassertv(_lock.debug_is_locked());
928 
929  MouseWatcherParameter param;
930  param.set_button(button);
931  param.set_keyrepeat(keyrepeat);
932  param.set_modifier_buttons(_mods);
933  param.set_mouse(_mouse);
934 
935  if (MouseButton::is_mouse_button(button)) {
936  // Mouse buttons are inextricably linked to the mouse position.
937 
938  if (!_button_down) {
939  _preferred_button_down_region = _preferred_region;
940  }
941  _button_down = true;
942 
943  if (_preferred_button_down_region != nullptr) {
944  _preferred_button_down_region->press(param);
945  if (keyrepeat) {
946  throw_event_pattern(_button_repeat_pattern,
947  _preferred_button_down_region, button);
948  } else {
949  throw_event_pattern(_button_down_pattern,
950  _preferred_button_down_region, button);
951  }
952  }
953 
954  } else {
955  // It's a keyboard button; therefore, send the event to every region that
956  // wants keyboard buttons, regardless of the mouse position.
957  if (_preferred_region != nullptr) {
958  // Our current region, the one under the mouse, always get all the
959  // keyboard events, even if it doesn't set its keyboard flag.
960  _preferred_region->press(param);
961  consider_keyboard_suppress(_preferred_region);
962  }
963 
964  if ((_internal_suppress & MouseWatcherRegion::SF_other_button) == 0) {
965  // All the other regions only get the keyboard events if they set their
966  // global keyboard flag, *and* the current region does not suppress
967  // keyboard buttons.
968  param.set_outside(true);
969  global_keyboard_press(param);
970  }
971  }
972 }
973 
974 /**
975  * Records the indicated mouse or keyboard button as being released.
976  */
977 void MouseWatcher::
978 release(ButtonHandle button) {
979  nassertv(_lock.debug_is_locked());
980 
981  MouseWatcherParameter param;
982  param.set_button(button);
983  param.set_modifier_buttons(_mods);
984  param.set_mouse(_mouse);
985 
986  if (MouseButton::is_mouse_button(button)) {
987  // Button up. Send the up event associated with the region(s) we were
988  // over when the button went down.
989 
990  // There is some danger of losing button-up events here. If more than one
991  // button goes down together, we won't detect both of the button-up events
992  // properly.
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  _button_down = false;
1001  _preferred_button_down_region = nullptr;
1002 
1003  } else {
1004  // It's a keyboard button; therefore, send the event to every region that
1005  // wants keyboard buttons, regardless of the mouse position.
1006  if (_preferred_region != nullptr) {
1007  _preferred_region->release(param);
1008  }
1009 
1010  param.set_outside(true);
1011  global_keyboard_release(param);
1012  }
1013 }
1014 
1015 /**
1016  * Records that the indicated keystroke has been generated.
1017  */
1018 void MouseWatcher::
1019 keystroke(int keycode) {
1020  nassertv(_lock.debug_is_locked());
1021 
1022  MouseWatcherParameter param;
1023  param.set_keycode(keycode);
1024  param.set_modifier_buttons(_mods);
1025  param.set_mouse(_mouse);
1026 
1027  // Make sure there are no duplicates in the regions vector.
1028  if (!_sorted) {
1029  ((MouseWatcher *)this)->do_sort_regions();
1030  }
1031 
1032  // Keystrokes go to all those regions that want keyboard events, regardless
1033  // of which is the "preferred" region (that is, without respect to the mouse
1034  // position). However, we do set the outside flag according to whether the
1035  // given region is the preferred region or not.
1036 
1037  for (MouseWatcherRegion *region : _regions) {
1038  if (region->get_keyboard()) {
1039  param.set_outside(region != _preferred_region);
1040  region->keystroke(param);
1041  consider_keyboard_suppress(region);
1042  }
1043  }
1044 
1045  // Also check all of our sub-groups.
1046  for (MouseWatcherGroup *group : _groups) {
1047  group->sort_regions();
1048 
1049  for (MouseWatcherRegion *region : group->_regions) {
1050  if (region->get_keyboard()) {
1051  param.set_outside(region != _preferred_region);
1052  region->keystroke(param);
1053  consider_keyboard_suppress(region);
1054  }
1055  }
1056  }
1057 }
1058 
1059 /**
1060  * Records that the indicated candidate string has been highlighted in the
1061  * IME.
1062  */
1063 void MouseWatcher::
1064 candidate(const std::wstring &candidate_string, size_t highlight_start,
1065  size_t highlight_end, size_t cursor_pos) {
1066  nassertv(_lock.debug_is_locked());
1067 
1068  MouseWatcherParameter param;
1069  param.set_candidate(candidate_string, highlight_start, highlight_end, cursor_pos);
1070  param.set_modifier_buttons(_mods);
1071  param.set_mouse(_mouse);
1072 
1073  // Make sure there are no duplicates in the regions vector.
1074  if (!_sorted) {
1075  ((MouseWatcher *)this)->do_sort_regions();
1076  }
1077 
1078  // Candidate strings go to all those regions that want keyboard events,
1079  // exactly like keystrokes, above.
1080 
1081  for (MouseWatcherRegion *region : _regions) {
1082  if (region->get_keyboard()) {
1083  param.set_outside(region != _preferred_region);
1084  region->candidate(param);
1085  }
1086  }
1087 
1088  // Also check all of our sub-groups.
1089  for (MouseWatcherGroup *group : _groups) {
1090  group->sort_regions();
1091 
1092  for (MouseWatcherRegion *region : group->_regions) {
1093  if (region->get_keyboard()) {
1094  param.set_outside(region != _preferred_region);
1095  region->candidate(param);
1096  }
1097  }
1098  }
1099 }
1100 
1101 /**
1102  * Calls press() on all regions that are interested in receiving global
1103  * keyboard events, except for the current region (which already received this
1104  * one).
1105  */
1106 void MouseWatcher::
1107 global_keyboard_press(const MouseWatcherParameter &param) {
1108  nassertv(_lock.debug_is_locked());
1109 
1110  // Make sure there are no duplicates in the regions vector.
1111  if (!_sorted) {
1112  ((MouseWatcher *)this)->do_sort_regions();
1113  }
1114 
1115  for (MouseWatcherRegion *region : _regions) {
1116  if (region != _preferred_region && region->get_keyboard()) {
1117  region->press(param);
1118  consider_keyboard_suppress(region);
1119  }
1120  }
1121 
1122  // Also check all of our sub-groups.
1123  for (MouseWatcherGroup *group : _groups) {
1124  group->sort_regions();
1125 
1126  for (MouseWatcherRegion *region : group->_regions) {
1127  if (region != _preferred_region && region->get_keyboard()) {
1128  region->press(param);
1129  consider_keyboard_suppress(region);
1130  }
1131  }
1132  }
1133 }
1134 /**
1135  * Calls release() on all regions that are interested in receiving global
1136  * keyboard events, except for the current region (which already received this
1137  * one).
1138  */
1139 void MouseWatcher::
1140 global_keyboard_release(const MouseWatcherParameter &param) {
1141  nassertv(_lock.debug_is_locked());
1142 
1143  // Make sure there are no duplicates in the regions vector.
1144  if (!_sorted) {
1145  ((MouseWatcher *)this)->do_sort_regions();
1146  }
1147 
1148  for (MouseWatcherRegion *region : _regions) {
1149  if (region != _preferred_region && region->get_keyboard()) {
1150  region->release(param);
1151  }
1152  }
1153 
1154  // Also check all of our sub-groups.
1155  for (MouseWatcherGroup *group : _groups) {
1156  group->sort_regions();
1157 
1158  for (MouseWatcherRegion *region : group->_regions) {
1159  if (region != _preferred_region && region->get_keyboard()) {
1160  region->release(param);
1161  }
1162  }
1163  }
1164 }
1165 
1166 /**
1167  * Called internally to indicate the mouse pointer is favoring the indicated
1168  * region.
1169  */
1170 void MouseWatcher::
1171 enter_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1172  nassertv(_lock.debug_is_locked());
1173 
1174  region->enter_region(param);
1175  throw_event_pattern(_enter_pattern, region, ButtonHandle::none());
1176  if (_implicit_click) {
1177  MouseWatcherParameter param1(param);
1178  param1.set_button(MouseButton::one());
1179  region->press(param1);
1180  }
1181 }
1182 
1183 /**
1184  * Called internally to indicate the mouse pointer is no longer favoring the
1185  * indicated region.
1186  */
1187 void MouseWatcher::
1188 exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1189  nassertv(_lock.debug_is_locked());
1190 
1191  if (_implicit_click) {
1192  MouseWatcherParameter param1(param);
1193  param1.set_button(MouseButton::one());
1194  region->release(param1);
1195  }
1196  region->exit_region(param);
1197  throw_event_pattern(_leave_pattern, region, ButtonHandle::none());
1198 }
1199 
1200 /**
1201  * Called from do_transmit_data() to indicate the mouse is not within the
1202  * window.
1203  */
1204 void MouseWatcher::
1205 set_no_mouse() {
1206  nassertv(_lock.debug_is_locked());
1207 
1208  if (_has_mouse) {
1209  // Hide the mouse pointer.
1210  if (!_geometry.is_null()) {
1211  _geometry->set_overall_hidden(true);
1212  }
1213  }
1214 
1215  _has_mouse = false;
1216  clear_current_regions();
1217 }
1218 
1219 /**
1220  * Called from do_transmit_data() to indicate the mouse is within the window,
1221  * and to specify its current position.
1222  */
1223 void MouseWatcher::
1224 set_mouse(const LVecBase2 &xy, const LVecBase2 &pixel_xy) {
1225  nassertv(_lock.debug_is_locked());
1226 
1227  if (!_geometry.is_null()) {
1228  // Transform the mouse pointer.
1229  _geometry->set_transform(TransformState::make_pos(LVecBase3(xy[0], 0, xy[1])));
1230  if (!_has_mouse) {
1231  // Show the mouse pointer.
1232  _geometry->set_overall_hidden(false);
1233  }
1234  }
1235 
1236  _has_mouse = true;
1237  _mouse = xy;
1238  _mouse_pixel = pixel_xy;
1239 
1240  Regions regions;
1241  get_over_regions(regions, _mouse);
1242  set_current_regions(regions);
1243 }
1244 
1245 /**
1246  * If we send any keyboard events to a region that has the SF_other_button
1247  * suppress flag set, that means we should not send the keyboard event along
1248  * the data graph.
1249  *
1250  * This method is called as each keyboard event is sent to a region; it should
1251  * update the internal _keyboard_suppress bitmask to indicate this.
1252  */
1253 void MouseWatcher::
1254 consider_keyboard_suppress(const MouseWatcherRegion *region) {
1255  if ((region->get_suppress_flags() & MouseWatcherRegion::SF_other_button) != 0) {
1256  _external_suppress |= MouseWatcherRegion::SF_other_button;
1257  }
1258 }
1259 
1260 /**
1261  * The virtual implementation of transmit_data(). This function receives an
1262  * array of input parameters and should generate an array of output
1263  * parameters. The input parameters may be accessed with the index numbers
1264  * returned by the define_input() calls that were made earlier (presumably in
1265  * the constructor); likewise, the output parameters should be set with the
1266  * index numbers returned by the define_output() calls.
1267  */
1268 void MouseWatcher::
1269 do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
1270  DataNodeTransmit &output) {
1271  Thread *current_thread = trav->get_current_thread();
1272  LightMutexHolder holder(_lock);
1273 
1274  bool activity = false;
1275 
1276  // Initially, we do not suppress any events to objects below us in the data
1277  // graph.
1278  _internal_suppress = 0;
1279  _external_suppress = 0;
1280 
1281  // We always pass the pixel_size data through.
1282  EventStoreVec2 *pixel_size;
1283  DCAST_INTO_V(pixel_size, input.get_data(_pixel_size_input).get_ptr());
1284  output.set_data(_pixel_size_output, pixel_size);
1285  _pixel_size = pixel_size;
1286 
1287  if (input.has_data(_xy_input)) {
1288  // The mouse is within the window. Get the current mouse position.
1289  const EventStoreVec2 *xy, *pixel_xy;
1290  DCAST_INTO_V(xy, input.get_data(_xy_input).get_ptr());
1291  DCAST_INTO_V(pixel_xy, input.get_data(_pixel_xy_input).get_ptr());
1292 
1293  LVecBase2 f = xy->get_value();
1294  LVecBase2 p = pixel_xy->get_value();
1295 
1296  // Asad: determine if mouse moved from last position
1297  const LVecBase2 &last_f = _xy->get_value();
1298  if (f != last_f) {
1299  activity = true;
1300  move();
1301  }
1302 
1303  if (_display_region != nullptr) {
1304  // If we've got a display region, constrain the mouse to it.
1305  if (constrain_display_region(_display_region, f, p, current_thread)) {
1306  set_mouse(f, p);
1307 
1308  } else {
1309  // The mouse is outside the display region, even though it's within
1310  // the window. This is considered not having a mouse.
1311  set_no_mouse();
1312 
1313  // This also means we should suppress mouse button events below us.
1314  _internal_suppress |= MouseWatcherRegion::SF_mouse_button;
1315  }
1316 
1317  } else {
1318  // No display region; respect the whole window.
1319  set_mouse(f, p);
1320  }
1321  }
1322 
1323  // Code for recording the mouse trail.
1324  _num_trail_recent = 0;
1325  if (input.has_data(_pointer_events_input) && (_trail_log_duration > 0.0)) {
1326  const PointerEventList *this_pointer_events;
1327  DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr());
1328  _num_trail_recent = this_pointer_events->get_num_events();
1329  for (size_t i = 0; i < _num_trail_recent; i++) {
1330  bool in_win = this_pointer_events->get_in_window(i);
1331  int xpos = this_pointer_events->get_xpos(i);
1332  int ypos = this_pointer_events->get_ypos(i);
1333  int sequence = this_pointer_events->get_sequence(i);
1334  double time = this_pointer_events->get_time(i);
1335  _trail_log->add_event(in_win, xpos, ypos, sequence, time);
1336  }
1337  }
1338  if (_trail_log->get_num_events() > 0) {
1339  discard_excess_trail_log();
1340  update_trail_node();
1341  }
1342  if (_num_trail_recent > _trail_log->get_num_events()) {
1343  _num_trail_recent = _trail_log->get_num_events();
1344  }
1345 
1346  // If the mouse is over a particular region, or still considered owned by a
1347  // region because of a recent button-down event, that region determines
1348  // whether we suppress events below us.
1349  if (_preferred_region != nullptr) {
1350  _internal_suppress |= _preferred_region->get_suppress_flags();
1351  }
1352 
1353  ButtonEventList new_button_events;
1354 
1355  // Look for new button events.
1356  if (input.has_data(_button_events_input)) {
1357  const ButtonEventList *this_button_events;
1358  DCAST_INTO_V(this_button_events, input.get_data(_button_events_input).get_ptr());
1359  int num_events = this_button_events->get_num_events();
1360  for (int i = 0; i < num_events; i++) {
1361  const ButtonEvent &be = this_button_events->get_event(i);
1362  be.update_mods(_mods);
1363 
1364  switch (be._type) {
1365  case ButtonEvent::T_down:
1366  if (!_current_buttons_down.get_bit(be._button.get_index())) {
1367  // The button was not already depressed; thus, this is not
1368  // keyrepeat.
1369  activity = true;
1370  _current_buttons_down.set_bit(be._button.get_index());
1371  press(be._button, false);
1372  new_button_events.add_event(be);
1373  break;
1374  }
1375  // The button was already depressed, so this is really just keyrepeat.
1376  // Fall through.
1377 
1378  case ButtonEvent::T_repeat:
1379  _current_buttons_down.set_bit(be._button.get_index());
1380  press(be._button, true);
1381  new_button_events.add_event(ButtonEvent(be._button, ButtonEvent::T_repeat,
1382  be._time));
1383  break;
1384 
1385  case ButtonEvent::T_up:
1386  activity = true;
1387  _current_buttons_down.clear_bit(be._button.get_index());
1388  release(be._button);
1389  new_button_events.add_event(be);
1390  break;
1391 
1392  case ButtonEvent::T_keystroke:
1393  // We don't consider "keystroke" an activity event, because it might
1394  // be just keyrepeat.
1395  keystroke(be._keycode);
1396  new_button_events.add_event(be);
1397  break;
1398 
1399  case ButtonEvent::T_candidate:
1400  activity = true;
1401  candidate(be._candidate_string, be._highlight_start, be._highlight_end, be._cursor_pos);
1402  new_button_events.add_event(be);
1403  break;
1404 
1405  case ButtonEvent::T_resume_down:
1406  // _current_buttons_down.set_bit(be._button.get_index()); Don't call
1407  // press(), since the button wasn't actually pressed just now.
1408  new_button_events.add_event(be);
1409  break;
1410 
1411  case ButtonEvent::T_move:
1412  // This is handled below.
1413  break;
1414 
1415  case ButtonEvent::T_raw_down:
1416  case ButtonEvent::T_raw_up:
1417  // These are passed through.
1418  new_button_events.add_event(be);
1419  break;
1420  }
1421  }
1422  }
1423 
1424  if (!input.has_data(_xy_input)) {
1425  // No mouse in the window. We check this down here, below the button
1426  // checking, in case the mouse left the window in the same frame it
1427  // released a button (particularly likely with a touchscreen input that's
1428  // emulating a mouse).
1429  set_no_mouse();
1430  }
1431 
1432  // Now check the inactivity timer.
1433  if (_has_inactivity_timeout) {
1434  if (activity) {
1435  note_activity();
1436 
1437  } else {
1439  double elapsed = now - _last_activity;
1440 
1441  // Toggle the inactivity state to inactive.
1442  if (elapsed > _inactivity_timeout) {
1443  switch (_inactivity_state) {
1444  case IS_active:
1445  _inactivity_state = IS_active_to_inactive;
1446  break;
1447 
1448  case IS_inactive:
1449  break;
1450 
1451  case IS_active_to_inactive:
1452  break;
1453 
1454  case IS_inactive_to_active:
1455  _inactivity_state = IS_inactive;
1456  break;
1457  }
1458  }
1459  }
1460  }
1461 
1462  switch (_inactivity_state) {
1463  case IS_active:
1464  case IS_inactive:
1465  break;
1466 
1467  case IS_active_to_inactive:
1468  // "Release" all of the currently-held buttons.
1469  if (tform_cat.is_debug()) {
1470  tform_cat.info()
1471  << "MouseWatcher detected " << _inactivity_timeout
1472  << " seconds of inactivity; releasing held buttons.\n";
1473  }
1474  {
1475  for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1476  if (_current_buttons_down.get_bit(i)) {
1477  release(ButtonHandle((int)i));
1478  new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_up));
1479  }
1480  }
1481  }
1482  _inactivity_state = IS_inactive;
1483  throw_event(_inactivity_timeout_event);
1484  break;
1485 
1486  case IS_inactive_to_active:
1487  // "Press" all of the buttons we "released" before.
1488  {
1489  for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1490  if (_current_buttons_down.get_bit(i)) {
1491  press(ButtonHandle((int)i), false);
1492  new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_down));
1493  }
1494  }
1495  }
1496  _inactivity_state = IS_active;
1497  break;
1498  }
1499 
1500  if (_has_mouse &&
1501  (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
1502  // Transmit the mouse position.
1503  _xy->set_value(_mouse);
1504  output.set_data(_xy_output, EventParameter(_xy));
1505  _pixel_xy->set_value(_mouse_pixel);
1506  output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
1507  }
1508 
1509  // Now transmit the buttons events down the graph.
1510  int suppress_buttons = ((_internal_suppress | _external_suppress) & MouseWatcherRegion::SF_any_button);
1511 
1512  _button_events->clear();
1513 
1514  int num_events = new_button_events.get_num_events();
1515  for (int i = 0; i < num_events; i++) {
1516  const ButtonEvent &be = new_button_events.get_event(i);
1517  bool suppress = true;
1518 
1519  if (be._type != ButtonEvent::T_keystroke &&
1520  MouseButton::is_mouse_button(be._button)) {
1521  suppress = ((suppress_buttons & MouseWatcherRegion::SF_mouse_button) != 0);
1522  } else {
1523  suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
1524  }
1525 
1526  if (!suppress || be._type == ButtonEvent::T_up) {
1527  // Don't suppress this button event; pass it through.
1528  _button_events->add_event(be);
1529  }
1530  }
1531 
1532  if (_button_events->get_num_events() != 0) {
1533  output.set_data(_button_events_output, EventParameter(_button_events));
1534  }
1535 }
1536 
1537 /**
1538  * Constrains the mouse coordinates to within the indicated DisplayRegion. If
1539  * the mouse pointer does indeed fall within the DisplayRegion, rescales f and
1540  * p correspondingly, and returns true. If the mouse pointer does not fall
1541  * within the DisplayRegion, leaves f and p unchanged, and returns false.
1542  */
1543 bool MouseWatcher::
1544 constrain_display_region(DisplayRegion *display_region,
1545  LVecBase2 &f, LVecBase2 &p,
1546  Thread *current_thread) {
1547  if (!_button_down) {
1548  _button_down_display_region = nullptr;
1549  }
1550  if (_button_down_display_region != nullptr) {
1551  // If the button went down over this DisplayRegion, we consider the button
1552  // within the same DisplayRegion until it is released (even if it wanders
1553  // outside the borders).
1554  display_region = _button_down_display_region;
1555 
1556  } else {
1557  // If it's a stereo DisplayRegion, we should actually call this method
1558  // twice, once for each eye, in case we have side-by-side stereo.
1559  if (display_region->is_stereo()) {
1560  StereoDisplayRegion *stereo_display_region;
1561  DCAST_INTO_R(stereo_display_region, display_region, false);
1562  return constrain_display_region(stereo_display_region->get_left_eye(), f, p, current_thread) ||
1563  constrain_display_region(stereo_display_region->get_right_eye(), f, p, current_thread);
1564  }
1565  }
1566 
1567  DisplayRegionPipelineReader dr_reader(display_region, current_thread);
1568  PN_stdfloat left, right, bottom, top;
1569  dr_reader.get_dimensions(left, right, bottom, top);
1570 
1571  // Need to translate this into DisplayRegion [0, 1] space
1572  PN_stdfloat x = (f[0] + 1.0f) / 2.0f;
1573  PN_stdfloat y = (f[1] + 1.0f) / 2.0f;
1574 
1575  if (_button_down_display_region == nullptr &&
1576  (x < left || x >= right || y < bottom || y >= top)) {
1577  // The mouse is outside the display region.
1578  return false;
1579  }
1580 
1581  // The mouse is within the display region; rescale it.
1582  if (_button_down) {
1583  _button_down_display_region = display_region;
1584  }
1585 
1586  // Scale in DR space
1587  PN_stdfloat xp = (x - left) / (right - left);
1588  // Translate back into [-1, 1] space
1589  PN_stdfloat xpp = (xp * 2.0f) - 1.0f;
1590 
1591  PN_stdfloat yp = (y - bottom) / (top - bottom);
1592  PN_stdfloat ypp = (yp * 2.0f) - 1.0f;
1593 
1594  int xo, yo, w, h;
1595  dr_reader.get_region_pixels_i(xo, yo, w, h);
1596 
1597  f.set(xpp, ypp);
1598  p.set(p[0] - xo, p[1] - yo);
1599  return true;
1600 }
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
size_t get_num_events() const
Returns the number of events in the list.
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
This TFormer maintains a list of rectangular regions on the screen that are considered special mouse ...
Definition: mouseWatcher.h:61
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_event(ButtonEvent event)
Adds a new event to the end of the list.
This represents a collection of MouseWatcherRegions that may be managed as a group.
void set_modifier_buttons(const ModifierButtons &mods)
Sets the modifier buttons that were being held while this event was generated.
int get_ypos(size_t n) const
Get the y-coordinate of the nth event.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_index
Returns the integer index associated with this ButtonHandle.
Definition: buttonHandle.h:60
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An optional parameter associated with an event.
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
Definition: bitArray.I:99
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void exit_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse exits the region.
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...
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
get_keyboard
Returns whether the region is interested in global keyboard events; see set_keyboard().
void clear()
Removes all elements from the ordered vector.
virtual void release(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button previously depressed wit...
bool update_mods(ModifierButtons &mods) const
Calls button_down() or button_up(), as appropriate, according to the ButtonEvent.
Definition: buttonEvent.I:140
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...
get_value
Retrieves the value stored in the parameter.
Definition: paramValue.h:115
bool remove_region(MouseWatcherRegion *region)
Removes the indicated region from the group.
int get_num_events() const
Returns the number of events in the list.
void set_trail_log_duration(double duration)
If the duration is nonzero, causes the MouseWatcher to log the mouse's trail.
void set_button(const ButtonHandle &button)
Sets the mouse or keyboard button that generated this event, if any.
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
int get_xpos(size_t n) const
Get the x-coordinate of the nth event.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_button(ButtonHandle button) const
Returns true if the indicated button is in the set of buttons being monitored, false otherwise.
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
virtual void enter_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse enters the region.
bool test_ref_count_integrity() const
Does some easy checks to make sure that the reference count isn't completely bogus.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Records a set of pointer events that happened recently.
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_sequence(size_t n) const
Get the sequence number of the nth event.
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.
Records a button event of some kind.
Definition: buttonEvent.h:46
double get_time(size_t n) const
Get the timestamp of the nth event.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_data(int index) const
Returns true if the indicated parameter has been stored, false otherwise.
A handy class object for storing simple values (like integers or strings) passed along with an Event ...
Definition: paramValue.h:103
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Records a set of button events that happened recently.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool empty() const
Returns true if the ordered vector is empty, false otherwise.
void swap(ordered_vector< Key, Compare, Vector > &other)
Exchanges the contents of this vector and the other vector, in constant time (e.g....
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
void clear_bit(int index)
Sets the nth bit off.
Definition: bitArray.I:133
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void press(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button is depressed while the m...
virtual void candidate(const MouseWatcherParameter &param)
This is a callback hook function, called whenever an IME candidate is highlighted by the user.
void set_data(int index, const EventParameter &data)
Sets the data for the indicated parameter.
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
void set_mouse(const LPoint2 &mouse)
Sets the mouse position that was current at the time the event was generated.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
Similar to MutexHolder, but for a light mutex.
This is the class that defines a rectangular region on the screen for the MouseWatcher.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
A container for geometry primitives.
Definition: geom.h:54
get_group
Returns the nth group added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
get_suppress_flags
Returns the current suppress_flags.
Thread * get_current_thread() const
Returns the currently-executing thread object, as passed to the DataGraphTraverser constructor.
get_right_eye
Returns a pointer to the right DisplayRegion managed by this stereo object.
void set_keycode(int keycode)
Sets the keycode associated with this event, if any.
TypedWritableReferenceCount * get_ptr() const
Retrieves a pointer to the actual value stored in the parameter.
This is a special DisplayRegion wrapper that actually includes a pair of DisplayRegions internally: t...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void push_back(const value_type_0 &key)
Adds the new element to the end of the vector without regard for proper sorting.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_bit(int index)
Sets the nth bit on.
Definition: bitArray.I:115
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...
bool add_group(MouseWatcherGroup *group)
Adds the indicated group of regions to the set of regions the MouseWatcher will monitor each frame.
get_active
Returns whether the region is active or not.
get_name
Returns the name of the button.
Definition: buttonHandle.h:61
virtual void keystroke(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a keystroke is generated by the user.
PT(GeomNode) MouseWatcher
Returns a GeomNode that represents the mouse trail.
void note_activity()
Can be used in conjunction with the inactivity timeout to inform the MouseWatcher that the user has j...
A thread; that is, a lightweight process.
Definition: thread.h:46
Defines a series of line strips.
get_left_eye
Returns a pointer to the left DisplayRegion managed by this stereo object.
void clear_trail_node()
If you have previously fetched the trail node using get_trail_node, then the MouseWatcher is continua...
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
bool remove_group(MouseWatcherGroup *group)
Removes the indicated group from the set of extra groups associated with the MouseWatcher.
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...
size_t get_num_bits() const
Returns the current number of possibly different bits in this array.
Definition: bitArray.I:89
const ButtonEvent & get_event(int n) const
Returns the nth event in the list.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Encapsulates the data generated from (or sent into) any particular DataNode.
This object supervises the traversal of the data graph and the moving of data from one DataNode to it...
bool replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group)
Atomically removes old_group from the MouseWatcher, and replaces it with new_group.
bool get_in_window(size_t n) const
Get the in-window flag of the nth event.