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 }
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:26
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
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
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
ordered_vector::end
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
Definition: ordered_vector.I:50
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.
ordered_vector::begin
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
Definition: ordered_vector.I:41
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