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"
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
38using std::string;
39
40TypeHandle MouseWatcher::_type_handle;
41
42/**
43 *
44 */
45MouseWatcher::
46MouseWatcher(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 */
100MouseWatcher::
101~MouseWatcher() {
102}
103
104/**
105 * Removes the indicated region from the group. Returns true if it was
106 * successfully removed, or false if it wasn't there in the first place.
107 */
110 LightMutexHolder holder(_lock);
111
112 remove_region_from(_current_regions, region);
113 if (region == _preferred_region) {
114 if (_preferred_region != nullptr) {
115 exit_region(_preferred_region, MouseWatcherParameter());
116 }
117 _preferred_region = nullptr;
118 }
119 if (region == _preferred_button_down_region) {
120 _preferred_button_down_region = nullptr;
121 }
122
123 return MouseWatcherBase::do_remove_region(region);
124}
125
126/**
127 * Returns the preferred region the mouse is over. In the case of overlapping
128 * regions, the region with the largest sort order is preferred; if two
129 * regions have the same sort order, then the smaller region is preferred.
130 */
132get_over_region(const LPoint2 &pos) const {
133 LightMutexHolder holder(_lock);
134
135 Regions regions;
136 get_over_regions(regions, pos);
137 return get_preferred_region(regions);
138}
139
140/**
141 * Adds the indicated group of regions to the set of regions the MouseWatcher
142 * will monitor each frame.
143 *
144 * Since the MouseWatcher itself inherits from MouseWatcherBase, this
145 * operation is normally not necessary--you can simply add the Regions you
146 * care about one at a time. Adding a complete group is useful when you may
147 * want to explicitly remove the regions as a group later.
148 *
149 * Returns true if the group was successfully added, or false if it was
150 * already on the list.
151 */
154 LightMutexHolder holder(_lock);
155
156 // See if the group is in the setvector already
157 PT(MouseWatcherGroup) pt = group;
158 Groups::const_iterator gi =
159 find(_groups.begin(), _groups.end(), pt);
160 if (gi != _groups.end()) {
161 // Already in the set, return false
162 return false;
163 }
164
165#ifndef NDEBUG
166 if (!_show_regions_render2d.is_empty()) {
167 group->show_regions(_show_regions_render2d, _show_regions_bin_name,
168 _show_regions_draw_order);
169 }
170#endif // NDEBUG
171
172 // Not in the set, add it and return true
173 _groups.push_back(pt);
174 return true;
175}
176
177/**
178 * Removes the indicated group from the set of extra groups associated with
179 * the MouseWatcher. Returns true if successful, or false if the group was
180 * already removed or was never added via add_group().
181 */
184 LightMutexHolder holder(_lock);
185 LightMutexHolder holder2(group->_lock);
186
187 group->do_sort_regions();
188
189 Regions only_a, only_b, both;
190 intersect_regions(only_a, only_b, both,
191 _current_regions, group->_regions);
192 set_current_regions(only_a);
193
194 if (has_region_in(both, _preferred_region)) {
195 if (_preferred_region != nullptr) {
196 exit_region(_preferred_region, MouseWatcherParameter());
197 }
198 _preferred_region = nullptr;
199 }
200 if (has_region_in(both, _preferred_button_down_region)) {
201 _preferred_button_down_region = nullptr;
202 }
203
204#ifndef NDEBUG
205 if (!_show_regions_render2d.is_empty()) {
206 group->do_hide_regions();
207 }
208#endif // NDEBUG
209
210 // See if the group is in the setvector
211 PT(MouseWatcherGroup) pt = group;
212 Groups::iterator gi =
213 find(_groups.begin(), _groups.end(), pt);
214 if (gi != _groups.end()) {
215 // Found it, now erase it
216 _groups.erase(gi);
217 return true;
218 }
219
220 // Did not find the group to erase
221 return false;
222}
223
224/**
225 * Atomically removes old_group from the MouseWatcher, and replaces it with
226 * new_group. Presumably old_group and new_group might have some regions in
227 * common; these are handled properly.
228 *
229 * If old_group is not already present, simply adds new_group and returns
230 * false. Otherwise, removes old_group and adds new_group, and then returns
231 * true.
232 */
234replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group) {
235 if (old_group == new_group) {
236 // Trivial.
237 return true;
238 }
239
240 LightMutexHolder holder(_lock);
241
242 LightMutexHolder holder2(old_group->_lock);
243 LightMutexHolder holder3(new_group->_lock);
244
245 old_group->do_sort_regions();
246 new_group->do_sort_regions();
247
248#ifndef NDEBUG
249 if (!_show_regions_render2d.is_empty()) {
250 old_group->do_hide_regions();
251 new_group->do_show_regions(_show_regions_render2d, _show_regions_bin_name,
252 _show_regions_draw_order);
253 }
254#endif // NDEBUG
255
256 // Figure out the list of regions that change
257 Regions remove, add, keep;
258 intersect_regions(remove, add, keep,
259 old_group->_regions, new_group->_regions);
260
261 Regions new_current_regions;
262 bool any_new_current_regions = false;
263
264 // Remove the old regions
265 if (!remove.empty()) {
266 Regions only_a, only_b, both;
267 intersect_regions(only_a, only_b, both,
268 _current_regions, remove);
269 new_current_regions.swap(only_a);
270 any_new_current_regions = true;
271
272 if (has_region_in(both, _preferred_region)) {
273 if (_preferred_region != nullptr) {
274 exit_region(_preferred_region, MouseWatcherParameter());
275 }
276 _preferred_region = nullptr;
277 }
278 if (has_region_in(both, _preferred_button_down_region)) {
279 _preferred_button_down_region = nullptr;
280 }
281 }
282
283 // Don't add the new regions--we have no reason to believe these should
284 // become current; some of them may not even be under the mouse.
285 /*
286 // And add the new regions
287 if (!add.empty()) {
288 Regions new_list;
289 if (any_new_current_regions) {
290 intersect_regions(new_list, new_list, new_list,
291 new_current_regions, add);
292 } else {
293 intersect_regions(new_list, new_list, new_list,
294 _current_regions, add);
295 }
296 new_current_regions.swap(new_list);
297 any_new_current_regions = true;
298 }
299 */
300
301 if (any_new_current_regions) {
302 set_current_regions(new_current_regions);
303 }
304
305 // Add the new group, if it's not already there.
306 PT(MouseWatcherGroup) pt = new_group;
307 Groups::iterator gi =
308 find(_groups.begin(), _groups.end(), pt);
309 if (gi == _groups.end()) {
310 _groups.push_back(new_group);
311 }
312
313#ifndef NDEBUG
314 if (!_show_regions_render2d.is_empty()) {
315 new_group->do_update_regions();
316 }
317#endif // NDEBUG
318
319 // Remove the old group, if it is already there.
320 pt = old_group;
321 gi = find(_groups.begin(), _groups.end(), pt);
322 if (gi != _groups.end()) {
323 // Found it, now erase it
324 _groups.erase(gi);
325 return true;
326 }
327
328 // Did not find the group to erase
329 return false;
330}
331
332/**
333 * Returns the number of separate groups added to the MouseWatcher via
334 * add_group().
335 */
337get_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 */
346get_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 */
358set_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 */
373void MouseWatcher::
374discard_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 */
400PT(GeomNode) MouseWatcher::
401get_trail_node() {
402 if (_trail_node == nullptr) {
403 _trail_node = new GeomNode("Mouse Trail Node");
404 update_trail_node();
405 }
406 return _trail_node;
407}
408
409/**
410 * If you have previously fetched the trail node using get_trail_node, then
411 * the MouseWatcher is continually updating the trail node every frame. Using
412 * clear_trail_node causes the MouseWatcher to forget the trail node and stop
413 * updating it.
414 */
417 _trail_node = nullptr;
418}
419
420/**
421 * Causes the trail node to represent the mouse trail.
422 */
423void MouseWatcher::
424update_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 */
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 */
491void MouseWatcher::
492output(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 */
511void MouseWatcher::
512write(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 */
529void MouseWatcher::
530get_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 */
586MouseWatcherRegion *MouseWatcher::
587get_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 */
615void MouseWatcher::
616set_current_regions(MouseWatcher::Regions &regions) {
617 nassertv(_lock.debug_is_locked());
618
619 // Set up a parameter for passing through any change events.
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 */
712void MouseWatcher::
713clear_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.
719 param.set_modifier_buttons(_mods);
720 param.set_mouse(_mouse);
721
722 Regions::const_iterator old_ri = _current_regions.begin();
723
724 while (old_ri != _current_regions.end()) {
725 // Here's a region we don't have any more.
726 MouseWatcherRegion *old_region = (*old_ri);
727 old_region->exit_region(param);
728 throw_event_pattern(_leave_pattern, old_region, ButtonHandle::none());
729 if (_preferred_region == old_region) {
730 _preferred_region = nullptr;
731 }
732 ++old_ri;
733 }
734
735 _current_regions.clear();
736
737 if (_preferred_region != nullptr) {
738 _preferred_region->exit_region(param);
739 throw_event_pattern(_leave_pattern, _preferred_region, ButtonHandle::none());
740 _preferred_region = nullptr;
741 }
742 }
743}
744
745/**
746 * The protected implementation of show_regions(). This assumes the lock is
747 * already held.
748 */
749void MouseWatcher::
750do_show_regions(const NodePath &render2d, const string &bin_name,
751 int draw_order) {
752#ifndef NDEBUG
753 MouseWatcherBase::do_show_regions(render2d, bin_name, draw_order);
754 _show_regions_render2d = render2d;
755 _show_regions_bin_name = bin_name;
756 _show_regions_draw_order = draw_order;
757
758 for (MouseWatcherGroup *group : _groups) {
759 group->show_regions(render2d, bin_name, draw_order);
760 }
761#endif // NDEBUG
762}
763
764/**
765 * The protected implementation of hide_regions(). This assumes the lock is
766 * already held.
767 */
768void MouseWatcher::
769do_hide_regions() {
770#ifndef NDEBUG
771 MouseWatcherBase::do_hide_regions();
772 _show_regions_render2d = NodePath();
773 _show_regions_bin_name = string();
774 _show_regions_draw_order = 0;
775
776 for (MouseWatcherGroup *group : _groups) {
777 group->hide_regions();
778 }
779#endif // NDEBUG
780}
781
782/**
783 * Computes the list of regions that are in both regions_a and regions_b, as
784 * well as the list of regions only in regions_a, and the list of regions only
785 * in regions_b. Any or all of the three output lists may be the same object,
786 * but they must be different objects from both of the input lists.
787 *
788 * It is assumed that both vectors are already sorted in pointer order. It is
789 * also assumed that any relevant locks are already held.
790 */
791void MouseWatcher::
792intersect_regions(MouseWatcher::Regions &only_a,
793 MouseWatcher::Regions &only_b,
795 const MouseWatcher::Regions &regions_a,
796 const MouseWatcher::Regions &regions_b) {
797 // Now do a standard sorted intersection between the two vectors.
798 Regions::const_iterator a_ri = regions_a.begin();
799 Regions::const_iterator b_ri = regions_b.begin();
800
801 while (a_ri != regions_a.end() && b_ri != regions_b.end()) {
802 if ((*a_ri) < (*b_ri)) {
803 // Here's a region in a, not in b.
804 only_a.push_back(*a_ri);
805 ++a_ri;
806
807 } else if ((*b_ri) < (*a_ri)) {
808 // Here's a region in b, not in a.
809 only_b.push_back(*b_ri);
810 ++b_ri;
811
812 } else {
813 // Here's a region in both vectors.
814 both.push_back(*a_ri);
815 ++a_ri;
816 ++b_ri;
817 }
818 }
819}
820
821/**
822 * Removes the indicated region from the given vector. Assumes the vector is
823 * sorted in pointer order. Returns true if removed, false if it wasn't
824 * there. Assumes any relevent locks are already held.
825 */
826bool MouseWatcher::
827remove_region_from(MouseWatcher::Regions &regions,
828 MouseWatcherRegion *region) {
829 PT(MouseWatcherRegion) ptr = region;
830 Regions::iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
831 if (ri != regions.end() && (*ri) == ptr) {
832 // The region is in the vector. Remove it.
833 regions.erase(ri);
834 return true;
835 }
836
837 return false;
838}
839
840/**
841 * Returns true if the indicated region is a member of the given sorted list,
842 * false otherwise.
843 */
844bool MouseWatcher::
845has_region_in(const MouseWatcher::Regions &regions,
846 MouseWatcherRegion *region) {
847 PT(MouseWatcherRegion) ptr = region;
848 Regions::const_iterator ri = lower_bound(regions.begin(), regions.end(), ptr);
849 return (ri != regions.end() && (*ri) == ptr);
850}
851
852/**
853 * Throws an event associated with the indicated region, using the given
854 * pattern.
855 */
856void MouseWatcher::
857throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
858 const ButtonHandle &button) {
859 if (pattern.empty()) {
860 return;
861 }
862#ifndef NDEBUG
863 if (region != nullptr) {
864 region->test_ref_count_integrity();
865 }
866#endif
867
868 string button_name;
869 if (button != ButtonHandle::none()) {
870 if (!_mods.has_button(button)) {
871 // We only prepend modifier names for buttons which are not themselves
872 // modifiers.
873 button_name = _mods.get_prefix();
874 }
875 button_name += button.get_name();
876 }
877
878 string event;
879 for (size_t p = 0; p < pattern.size(); ++p) {
880 if (pattern[p] == '%') {
881 string cmd = pattern.substr(p + 1, 1);
882 p++;
883 if (cmd == "r") {
884 if (region != nullptr) {
885 event += region->get_name();
886 }
887
888 } else if (cmd == "b") {
889 event += button.get_name();
890
891 } else {
892 tform_cat.error()
893 << "Invalid symbol in event_pattern: %" << cmd << "\n";
894 }
895 } else {
896 event += pattern[p];
897 }
898 }
899
900 if (!event.empty()) {
901 throw_event(event, EventParameter(region), EventParameter(button_name));
902 if (_eh != nullptr)
903 throw_event_directly(*_eh, event, EventParameter(region),
904 EventParameter(button_name));
905 }
906}
907
908/**
909 * Records the indicated mouse or keyboard button as being moved from last
910 * position.
911 */
912void MouseWatcher::
913move() {
914 nassertv(_lock.debug_is_locked());
915
917 param.set_modifier_buttons(_mods);
918 param.set_mouse(_mouse);
919
920 if (_preferred_button_down_region != nullptr) {
921 _preferred_button_down_region->move(param);
922 }
923}
924
925/**
926 * Records the indicated mouse or keyboard button as being depressed.
927 */
928void MouseWatcher::
929press(ButtonHandle button, bool keyrepeat) {
930 nassertv(_lock.debug_is_locked());
931
933 param.set_button(button);
934 param.set_keyrepeat(keyrepeat);
935 param.set_modifier_buttons(_mods);
936 param.set_mouse(_mouse);
937
938 if (MouseButton::is_mouse_button(button)) {
939 // Mouse buttons are inextricably linked to the mouse position.
940
941 if (!_button_down) {
942 _preferred_button_down_region = _preferred_region;
943 }
944 _button_down = true;
945
946 if (_preferred_button_down_region != nullptr) {
947 _preferred_button_down_region->press(param);
948 if (keyrepeat) {
949 throw_event_pattern(_button_repeat_pattern,
950 _preferred_button_down_region, button);
951 } else {
952 throw_event_pattern(_button_down_pattern,
953 _preferred_button_down_region, button);
954 }
955 }
956
957 } else {
958 // It's a keyboard button; therefore, send the event to every region that
959 // wants keyboard buttons, regardless of the mouse position.
960 if (_preferred_region != nullptr) {
961 // Our current region, the one under the mouse, always get all the
962 // keyboard events, even if it doesn't set its keyboard flag.
963 _preferred_region->press(param);
964 consider_keyboard_suppress(_preferred_region);
965 }
966
967 if ((_internal_suppress & MouseWatcherRegion::SF_other_button) == 0) {
968 // All the other regions only get the keyboard events if they set their
969 // global keyboard flag, *and* the current region does not suppress
970 // keyboard buttons.
971 param.set_outside(true);
972 global_keyboard_press(param);
973 }
974 }
975}
976
977/**
978 * Records the indicated mouse or keyboard button as being released.
979 */
980void MouseWatcher::
981release(ButtonHandle button) {
982 nassertv(_lock.debug_is_locked());
983
985 param.set_button(button);
986 param.set_modifier_buttons(_mods);
987 param.set_mouse(_mouse);
988
989 if (MouseButton::is_mouse_button(button)) {
990 // Button up. Send the up event associated with the region(s) we were
991 // over when the button went down.
992
993 if (_preferred_button_down_region != nullptr) {
994 param.set_outside(_preferred_button_down_region != _preferred_region);
995 _preferred_button_down_region->release(param);
996 throw_event_pattern(_button_up_pattern,
997 _preferred_button_down_region, button);
998 }
999
1000 // Do not stop capturing until the last mouse button has gone up. This is
1001 // needed to prevent stopping the capture until the capturing region has
1002 // finished processing all the releases.
1003 bool has_button = false;
1004 for (size_t i = 0; i < MouseButton::num_mouse_buttons; ++i) {
1005 if (MouseButton::_buttons[i] != button &&
1006 _current_buttons_down.get_bit(MouseButton::_buttons[i].get_index())) {
1007 has_button = true;
1008 }
1009 }
1010
1011 if (!has_button) {
1012 // The last mouse button went up.
1013 _button_down = false;
1014 _preferred_button_down_region = nullptr;
1015 }
1016
1017 } else {
1018 // It's a keyboard button; therefore, send the event to every region that
1019 // wants keyboard buttons, regardless of the mouse position.
1020 if (_preferred_region != nullptr) {
1021 _preferred_region->release(param);
1022 }
1023
1024 param.set_outside(true);
1025 global_keyboard_release(param);
1026 }
1027}
1028
1029/**
1030 * Records that the indicated keystroke has been generated.
1031 */
1032void MouseWatcher::
1033keystroke(int keycode) {
1034 nassertv(_lock.debug_is_locked());
1035
1037 param.set_keycode(keycode);
1038 param.set_modifier_buttons(_mods);
1039 param.set_mouse(_mouse);
1040
1041 // Make sure there are no duplicates in the regions vector.
1042 if (!_sorted) {
1043 ((MouseWatcher *)this)->do_sort_regions();
1044 }
1045
1046 // Keystrokes go to all those regions that want keyboard events, regardless
1047 // of which is the "preferred" region (that is, without respect to the mouse
1048 // position). However, we do set the outside flag according to whether the
1049 // given region is the preferred region or not.
1050
1051 for (MouseWatcherRegion *region : _regions) {
1052 if (region->get_keyboard()) {
1053 param.set_outside(region != _preferred_region);
1054 region->keystroke(param);
1055 consider_keyboard_suppress(region);
1056 }
1057 }
1058
1059 // Also check all of our sub-groups.
1060 for (MouseWatcherGroup *group : _groups) {
1061 group->sort_regions();
1062
1063 for (MouseWatcherRegion *region : group->_regions) {
1064 if (region->get_keyboard()) {
1065 param.set_outside(region != _preferred_region);
1066 region->keystroke(param);
1067 consider_keyboard_suppress(region);
1068 }
1069 }
1070 }
1071}
1072
1073/**
1074 * Records that the indicated candidate string has been highlighted in the
1075 * IME.
1076 */
1077void MouseWatcher::
1078candidate(const std::wstring &candidate_string, size_t highlight_start,
1079 size_t highlight_end, size_t cursor_pos) {
1080 nassertv(_lock.debug_is_locked());
1081
1083 param.set_candidate(candidate_string, highlight_start, highlight_end, cursor_pos);
1084 param.set_modifier_buttons(_mods);
1085 param.set_mouse(_mouse);
1086
1087 // Make sure there are no duplicates in the regions vector.
1088 if (!_sorted) {
1089 ((MouseWatcher *)this)->do_sort_regions();
1090 }
1091
1092 // Candidate strings go to all those regions that want keyboard events,
1093 // exactly like keystrokes, above.
1094
1095 for (MouseWatcherRegion *region : _regions) {
1096 if (region->get_keyboard()) {
1097 param.set_outside(region != _preferred_region);
1098 region->candidate(param);
1099 }
1100 }
1101
1102 // Also check all of our sub-groups.
1103 for (MouseWatcherGroup *group : _groups) {
1104 group->sort_regions();
1105
1106 for (MouseWatcherRegion *region : group->_regions) {
1107 if (region->get_keyboard()) {
1108 param.set_outside(region != _preferred_region);
1109 region->candidate(param);
1110 }
1111 }
1112 }
1113}
1114
1115/**
1116 * Calls press() on all regions that are interested in receiving global
1117 * keyboard events, except for the current region (which already received this
1118 * one).
1119 */
1120void MouseWatcher::
1121global_keyboard_press(const MouseWatcherParameter &param) {
1122 nassertv(_lock.debug_is_locked());
1123
1124 // Make sure there are no duplicates in the regions vector.
1125 if (!_sorted) {
1126 ((MouseWatcher *)this)->do_sort_regions();
1127 }
1128
1129 for (MouseWatcherRegion *region : _regions) {
1130 if (region != _preferred_region && region->get_keyboard()) {
1131 region->press(param);
1132 consider_keyboard_suppress(region);
1133 }
1134 }
1135
1136 // Also check all of our sub-groups.
1137 for (MouseWatcherGroup *group : _groups) {
1138 group->sort_regions();
1139
1140 for (MouseWatcherRegion *region : group->_regions) {
1141 if (region != _preferred_region && region->get_keyboard()) {
1142 region->press(param);
1143 consider_keyboard_suppress(region);
1144 }
1145 }
1146 }
1147}
1148/**
1149 * Calls release() on all regions that are interested in receiving global
1150 * keyboard events, except for the current region (which already received this
1151 * one).
1152 */
1153void MouseWatcher::
1154global_keyboard_release(const MouseWatcherParameter &param) {
1155 nassertv(_lock.debug_is_locked());
1156
1157 // Make sure there are no duplicates in the regions vector.
1158 if (!_sorted) {
1159 ((MouseWatcher *)this)->do_sort_regions();
1160 }
1161
1162 for (MouseWatcherRegion *region : _regions) {
1163 if (region != _preferred_region && region->get_keyboard()) {
1164 region->release(param);
1165 }
1166 }
1167
1168 // Also check all of our sub-groups.
1169 for (MouseWatcherGroup *group : _groups) {
1170 group->sort_regions();
1171
1172 for (MouseWatcherRegion *region : group->_regions) {
1173 if (region != _preferred_region && region->get_keyboard()) {
1174 region->release(param);
1175 }
1176 }
1177 }
1178}
1179
1180/**
1181 * Called internally to indicate the mouse pointer is favoring the indicated
1182 * region.
1183 */
1184void MouseWatcher::
1185enter_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1186 nassertv(_lock.debug_is_locked());
1187
1188 region->enter_region(param);
1189 throw_event_pattern(_enter_pattern, region, ButtonHandle::none());
1190 if (_implicit_click) {
1191 MouseWatcherParameter param1(param);
1192 param1.set_button(MouseButton::one());
1193 region->press(param1);
1194 }
1195}
1196
1197/**
1198 * Called internally to indicate the mouse pointer is no longer favoring the
1199 * indicated region.
1200 */
1201void MouseWatcher::
1202exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
1203 nassertv(_lock.debug_is_locked());
1204
1205 if (_implicit_click) {
1206 MouseWatcherParameter param1(param);
1207 param1.set_button(MouseButton::one());
1208 region->release(param1);
1209 }
1210 region->exit_region(param);
1211 throw_event_pattern(_leave_pattern, region, ButtonHandle::none());
1212}
1213
1214/**
1215 * Called from do_transmit_data() to indicate the mouse is not within the
1216 * window.
1217 */
1218void MouseWatcher::
1219set_no_mouse() {
1220 nassertv(_lock.debug_is_locked());
1221
1222 if (_has_mouse) {
1223 // Hide the mouse pointer.
1224 if (!_geometry.is_null()) {
1225 _geometry->set_overall_hidden(true);
1226 }
1227 }
1228
1229 _has_mouse = false;
1230 clear_current_regions();
1231}
1232
1233/**
1234 * Called from do_transmit_data() to indicate the mouse is within the window,
1235 * and to specify its current position.
1236 */
1237void MouseWatcher::
1238set_mouse(const LVecBase2 &xy, const LVecBase2 &pixel_xy) {
1239 nassertv(_lock.debug_is_locked());
1240
1241 if (!_geometry.is_null()) {
1242 // Transform the mouse pointer.
1243 _geometry->set_transform(TransformState::make_pos(LVecBase3(xy[0], 0, xy[1])));
1244 if (!_has_mouse) {
1245 // Show the mouse pointer.
1246 _geometry->set_overall_hidden(false);
1247 }
1248 }
1249
1250 _has_mouse = true;
1251 _mouse = xy;
1252 _mouse_pixel = pixel_xy;
1253
1254 Regions regions;
1255 get_over_regions(regions, _mouse);
1256 set_current_regions(regions);
1257}
1258
1259/**
1260 * If we send any keyboard events to a region that has the SF_other_button
1261 * suppress flag set, that means we should not send the keyboard event along
1262 * the data graph.
1263 *
1264 * This method is called as each keyboard event is sent to a region; it should
1265 * update the internal _keyboard_suppress bitmask to indicate this.
1266 */
1267void MouseWatcher::
1268consider_keyboard_suppress(const MouseWatcherRegion *region) {
1269 if ((region->get_suppress_flags() & MouseWatcherRegion::SF_other_button) != 0) {
1270 _external_suppress |= MouseWatcherRegion::SF_other_button;
1271 }
1272}
1273
1274/**
1275 * The virtual implementation of transmit_data(). This function receives an
1276 * array of input parameters and should generate an array of output
1277 * parameters. The input parameters may be accessed with the index numbers
1278 * returned by the define_input() calls that were made earlier (presumably in
1279 * the constructor); likewise, the output parameters should be set with the
1280 * index numbers returned by the define_output() calls.
1281 */
1282void MouseWatcher::
1283do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
1284 DataNodeTransmit &output) {
1285 Thread *current_thread = trav->get_current_thread();
1286 LightMutexHolder holder(_lock);
1287
1288 bool activity = false;
1289
1290 // Initially, we do not suppress any events to objects below us in the data
1291 // graph.
1292 _internal_suppress = 0;
1293 _external_suppress = 0;
1294
1295 // We always pass the pixel_size data through.
1296 EventStoreVec2 *pixel_size;
1297 DCAST_INTO_V(pixel_size, input.get_data(_pixel_size_input).get_ptr());
1298 output.set_data(_pixel_size_output, pixel_size);
1299 _pixel_size = pixel_size;
1300
1301 if (input.has_data(_xy_input)) {
1302 // The mouse is within the window. Get the current mouse position.
1303 const EventStoreVec2 *xy, *pixel_xy;
1304 DCAST_INTO_V(xy, input.get_data(_xy_input).get_ptr());
1305 DCAST_INTO_V(pixel_xy, input.get_data(_pixel_xy_input).get_ptr());
1306
1307 LVecBase2 f = xy->get_value();
1308 LVecBase2 p = pixel_xy->get_value();
1309
1310 // Asad: determine if mouse moved from last position
1311 const LVecBase2 &last_f = _xy->get_value();
1312 if (f != last_f) {
1313 activity = true;
1314 move();
1315 }
1316
1317 if (_display_region != nullptr) {
1318 // If we've got a display region, constrain the mouse to it.
1319 if (constrain_display_region(_display_region, f, p, current_thread)) {
1320 set_mouse(f, p);
1321
1322 } else {
1323 // The mouse is outside the display region, even though it's within
1324 // the window. This is considered not having a mouse.
1325 set_no_mouse();
1326
1327 // This also means we should suppress mouse button events below us.
1328 _internal_suppress |= MouseWatcherRegion::SF_mouse_button;
1329 }
1330
1331 } else {
1332 // No display region; respect the whole window.
1333 set_mouse(f, p);
1334 }
1335 }
1336
1337 // Code for recording the mouse trail.
1338 _num_trail_recent = 0;
1339 if (input.has_data(_pointer_events_input) && (_trail_log_duration > 0.0)) {
1340 const PointerEventList *this_pointer_events;
1341 DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr());
1342 _num_trail_recent = this_pointer_events->get_num_events();
1343 for (size_t i = 0; i < _num_trail_recent; i++) {
1344 bool in_win = this_pointer_events->get_in_window(i);
1345 int xpos = this_pointer_events->get_xpos(i);
1346 int ypos = this_pointer_events->get_ypos(i);
1347 int sequence = this_pointer_events->get_sequence(i);
1348 double time = this_pointer_events->get_time(i);
1349 _trail_log->add_event(in_win, xpos, ypos, sequence, time);
1350 }
1351 }
1352 if (_trail_log->get_num_events() > 0) {
1353 discard_excess_trail_log();
1354 update_trail_node();
1355 }
1356 if (_num_trail_recent > _trail_log->get_num_events()) {
1357 _num_trail_recent = _trail_log->get_num_events();
1358 }
1359
1360 // If the mouse is over a particular region, or still considered owned by a
1361 // region because of a recent button-down event, that region determines
1362 // whether we suppress events below us.
1363 if (_preferred_region != nullptr) {
1364 _internal_suppress |= _preferred_region->get_suppress_flags();
1365 }
1366
1367 ButtonEventList new_button_events;
1368
1369 // Look for new button events.
1370 if (input.has_data(_button_events_input)) {
1371 const ButtonEventList *this_button_events;
1372 DCAST_INTO_V(this_button_events, input.get_data(_button_events_input).get_ptr());
1373 int num_events = this_button_events->get_num_events();
1374 for (int i = 0; i < num_events; i++) {
1375 const ButtonEvent &be = this_button_events->get_event(i);
1376 be.update_mods(_mods);
1377
1378 switch (be._type) {
1379 case ButtonEvent::T_down:
1380 if (!_current_buttons_down.get_bit(be._button.get_index())) {
1381 // The button was not already depressed; thus, this is not
1382 // keyrepeat.
1383 activity = true;
1384 _current_buttons_down.set_bit(be._button.get_index());
1385 press(be._button, false);
1386 new_button_events.add_event(be);
1387 break;
1388 }
1389 // The button was already depressed, so this is really just keyrepeat.
1390 // Fall through.
1391
1392 case ButtonEvent::T_repeat:
1393 _current_buttons_down.set_bit(be._button.get_index());
1394 press(be._button, true);
1395 new_button_events.add_event(ButtonEvent(be._button, ButtonEvent::T_repeat,
1396 be._time));
1397 break;
1398
1399 case ButtonEvent::T_up:
1400 activity = true;
1401 _current_buttons_down.clear_bit(be._button.get_index());
1402 release(be._button);
1403 new_button_events.add_event(be);
1404 break;
1405
1406 case ButtonEvent::T_keystroke:
1407 // We don't consider "keystroke" an activity event, because it might
1408 // be just keyrepeat.
1409 keystroke(be._keycode);
1410 new_button_events.add_event(be);
1411 break;
1412
1413 case ButtonEvent::T_candidate:
1414 activity = true;
1415 candidate(be._candidate_string, be._highlight_start, be._highlight_end, be._cursor_pos);
1416 new_button_events.add_event(be);
1417 break;
1418
1419 case ButtonEvent::T_resume_down:
1420 // _current_buttons_down.set_bit(be._button.get_index()); Don't call
1421 // press(), since the button wasn't actually pressed just now.
1422 new_button_events.add_event(be);
1423 break;
1424
1425 case ButtonEvent::T_move:
1426 // This is handled below.
1427 break;
1428
1429 case ButtonEvent::T_raw_down:
1430 case ButtonEvent::T_raw_up:
1431 // These are passed through.
1432 new_button_events.add_event(be);
1433 break;
1434 }
1435 }
1436 }
1437
1438 if (!input.has_data(_xy_input)) {
1439 // No mouse in the window. We check this down here, below the button
1440 // checking, in case the mouse left the window in the same frame it
1441 // released a button (particularly likely with a touchscreen input that's
1442 // emulating a mouse).
1443 set_no_mouse();
1444 }
1445
1446 // Now check the inactivity timer.
1447 if (_has_inactivity_timeout) {
1448 if (activity) {
1449 note_activity();
1450
1451 } else {
1453 double elapsed = now - _last_activity;
1454
1455 // Toggle the inactivity state to inactive.
1456 if (elapsed > _inactivity_timeout) {
1457 switch (_inactivity_state) {
1458 case IS_active:
1459 _inactivity_state = IS_active_to_inactive;
1460 break;
1461
1462 case IS_inactive:
1463 break;
1464
1465 case IS_active_to_inactive:
1466 break;
1467
1468 case IS_inactive_to_active:
1469 _inactivity_state = IS_inactive;
1470 break;
1471 }
1472 }
1473 }
1474 }
1475
1476 switch (_inactivity_state) {
1477 case IS_active:
1478 case IS_inactive:
1479 break;
1480
1481 case IS_active_to_inactive:
1482 // "Release" all of the currently-held buttons.
1483 if (tform_cat.is_debug()) {
1484 tform_cat.info()
1485 << "MouseWatcher detected " << _inactivity_timeout
1486 << " seconds of inactivity; releasing held buttons.\n";
1487 }
1488 {
1489 for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1490 if (_current_buttons_down.get_bit(i)) {
1491 release(ButtonHandle((int)i));
1492 new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_up));
1493 }
1494 }
1495 }
1496 _inactivity_state = IS_inactive;
1497 throw_event(_inactivity_timeout_event);
1498 break;
1499
1500 case IS_inactive_to_active:
1501 // "Press" all of the buttons we "released" before.
1502 {
1503 for (size_t i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
1504 if (_current_buttons_down.get_bit(i)) {
1505 press(ButtonHandle((int)i), false);
1506 new_button_events.add_event(ButtonEvent(ButtonHandle((int)i), ButtonEvent::T_down));
1507 }
1508 }
1509 }
1510 _inactivity_state = IS_active;
1511 break;
1512 }
1513
1514 if (_has_mouse &&
1515 (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
1516 // Transmit the mouse position.
1517 _xy->set_value(_mouse);
1518 output.set_data(_xy_output, EventParameter(_xy));
1519 _pixel_xy->set_value(_mouse_pixel);
1520 output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
1521 }
1522
1523 // Now transmit the buttons events down the graph.
1524 int suppress_buttons = ((_internal_suppress | _external_suppress) & MouseWatcherRegion::SF_any_button);
1525
1526 _button_events->clear();
1527
1528 int num_events = new_button_events.get_num_events();
1529 for (int i = 0; i < num_events; i++) {
1530 const ButtonEvent &be = new_button_events.get_event(i);
1531 bool suppress = true;
1532
1533 if (be._type != ButtonEvent::T_keystroke &&
1534 MouseButton::is_mouse_button(be._button)) {
1535 suppress = ((suppress_buttons & MouseWatcherRegion::SF_mouse_button) != 0);
1536 } else {
1537 suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
1538 }
1539
1540 if (!suppress || be._type == ButtonEvent::T_up) {
1541 // Don't suppress this button event; pass it through.
1542 _button_events->add_event(be);
1543 }
1544 }
1545
1546 if (_button_events->get_num_events() != 0) {
1547 output.set_data(_button_events_output, EventParameter(_button_events));
1548 }
1549}
1550
1551/**
1552 * Constrains the mouse coordinates to within the indicated DisplayRegion. If
1553 * the mouse pointer does indeed fall within the DisplayRegion, rescales f and
1554 * p correspondingly, and returns true. If the mouse pointer does not fall
1555 * within the DisplayRegion, leaves f and p unchanged, and returns false.
1556 */
1557bool MouseWatcher::
1558constrain_display_region(DisplayRegion *display_region,
1559 LVecBase2 &f, LVecBase2 &p,
1560 Thread *current_thread) {
1561 if (!_button_down) {
1562 _button_down_display_region = nullptr;
1563 }
1564 if (_button_down_display_region != nullptr) {
1565 // If the button went down over this DisplayRegion, we consider the button
1566 // within the same DisplayRegion until it is released (even if it wanders
1567 // outside the borders).
1568 display_region = _button_down_display_region;
1569
1570 } else {
1571 // If it's a stereo DisplayRegion, we should actually call this method
1572 // twice, once for each eye, in case we have side-by-side stereo.
1573 if (display_region->is_stereo()) {
1574 StereoDisplayRegion *stereo_display_region;
1575 DCAST_INTO_R(stereo_display_region, display_region, false);
1576 return constrain_display_region(stereo_display_region->get_left_eye(), f, p, current_thread) ||
1577 constrain_display_region(stereo_display_region->get_right_eye(), f, p, current_thread);
1578 }
1579 }
1580
1581 DisplayRegionPipelineReader dr_reader(display_region, current_thread);
1582 PN_stdfloat left, right, bottom, top;
1583 dr_reader.get_dimensions(left, right, bottom, top);
1584
1585 // Need to translate this into DisplayRegion [0, 1] space
1586 PN_stdfloat x = (f[0] + 1.0f) / 2.0f;
1587 PN_stdfloat y = (f[1] + 1.0f) / 2.0f;
1588
1589 if (_button_down_display_region == nullptr &&
1590 (x < left || x >= right || y < bottom || y >= top)) {
1591 // The mouse is outside the display region.
1592 return false;
1593 }
1594
1595 // The mouse is within the display region; rescale it.
1596 if (_button_down) {
1597 _button_down_display_region = display_region;
1598 }
1599
1600 // Scale in DR space
1601 PN_stdfloat xp = (x - left) / (right - left);
1602 // Translate back into [-1, 1] space
1603 PN_stdfloat xpp = (xp * 2.0f) - 1.0f;
1604
1605 PN_stdfloat yp = (y - bottom) / (top - bottom);
1606 PN_stdfloat ypp = (yp * 2.0f) - 1.0f;
1607
1608 int xo, yo, w, h;
1609 dr_reader.get_region_pixels_i(xo, yo, w, h);
1610
1611 f.set(xpp, ypp);
1612 p.set(p[0] - xo, p[1] - yo);
1613 return true;
1614}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear_bit(int index)
Sets the nth bit off.
Definition: bitArray.I:133
size_t get_num_bits() const
Returns the current number of possibly different bits in this array.
Definition: bitArray.I:89
void set_bit(int index)
Sets the nth bit on.
Definition: bitArray.I:115
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
Definition: bitArray.I:99
Records a set of button events that happened recently.
get_num_events
Returns the number of events in the list.
void add_event(ButtonEvent event)
Adds a new event to the end of the list.
get_event
Returns the nth event in the list.
Records a button event of some kind.
Definition: buttonEvent.h:49
bool update_mods(ModifierButtons &mods) const
Calls button_down() or button_up(), as appropriate, according to the ButtonEvent.
Definition: buttonEvent.I:140
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
get_name
Returns the name of the button.
Definition: buttonHandle.h:61
get_index
Returns the integer index associated with this ButtonHandle.
Definition: buttonHandle.h:60
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition: clockObject.h:91
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
This object supervises the traversal of the data graph and the moving of data from one DataNode to it...
Thread * get_current_thread() const
Returns the currently-executing thread object, as passed to the DataGraphTraverser constructor.
Encapsulates the data generated from (or sent into) any particular DataNode.
const EventParameter & get_data(int index) const
Extracts the data for the indicated index, if it has been stored, or the empty parameter if it has no...
void set_data(int index, const EventParameter &data)
Sets the data for the indicated parameter.
bool has_data(int index) const
Returns true if the indicated parameter has been stored, false otherwise.
The fundamental type of node for the data graph.
Definition: dataNode.h:52
Encapsulates the data from a DisplayRegion, pre-fetched for one stage of the pipeline.
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
void get_region_pixels_i(int &xo, int &yo, int &w, int &h) const
Similar to get_region_pixels(), but returns the upper left corner, and the pixel numbers are numbered...
An optional parameter associated with an event.
TypedWritableReferenceCount * get_ptr() const
Retrieves a pointer to the actual value stored in the parameter.
Defines a series of line strips.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static const GeomVertexFormat * get_v3()
Returns a standard vertex format with just a 3-component vertex position.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
Similar to MutexHolder, but for a light mutex.
std::string get_prefix() const
Returns a string which can be used to prefix any button name or event name with the unique set of mod...
bool has_button(ButtonHandle button) const
Returns true if the indicated button is in the set of buttons being monitored, false otherwise.
static bool is_mouse_button(ButtonHandle button)
Returns true if the indicated ButtonHandle is a mouse button, false if it is some other kind of butto...
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
void show_regions(const NodePath &render2d, const std::string &bin_name, int draw_order)
Enables the visualization of all of the regions handled by this MouseWatcherBase.
This represents a collection of MouseWatcherRegions that may be managed as a group.
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
void set_keyrepeat(bool flag)
Sets the state of the "keyrepeat" flag.
void set_mouse(const LPoint2 &mouse)
Sets the mouse position that was current at the time the event was generated.
void set_keycode(int keycode)
Sets the keycode associated with this event, if any.
void set_modifier_buttons(const ModifierButtons &mods)
Sets the modifier buttons that were being held while this event was generated.
void set_candidate(const std::wstring &candidate_string, size_t highlight_start, size_t higlight_end, size_t cursor_pos)
Sets the candidate string associated with this event, if any.
void set_outside(bool flag)
Sets the state of the "outside" flag.
void set_button(const ButtonHandle &button)
Sets the mouse or keyboard button that generated this event, if any.
This is the class that defines a rectangular region on the screen for the MouseWatcher.
virtual void release(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button previously depressed wit...
virtual void keystroke(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a keystroke is generated by the user.
virtual void exit_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse exits the region.
virtual void press(const MouseWatcherParameter &param)
This is a callback hook function, called whenever a mouse or keyboard button is depressed while the m...
get_active
Returns whether the region is active or not.
get_suppress_flags
Returns the current suppress_flags.
virtual void enter_region(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the mouse enters the region.
virtual void candidate(const MouseWatcherParameter &param)
This is a callback hook function, called whenever an IME candidate is highlighted by the user.
get_keyboard
Returns whether the region is interested in global keyboard events; see set_keyboard().
This TFormer maintains a list of rectangular regions on the screen that are considered special mouse ...
Definition: mouseWatcher.h:61
bool add_group(MouseWatcherGroup *group)
Adds the indicated group of regions to the set of regions the MouseWatcher will monitor each frame.
bool remove_group(MouseWatcherGroup *group)
Removes the indicated group from the set of extra groups associated with the MouseWatcher.
void set_trail_log_duration(double duration)
If the duration is nonzero, causes the MouseWatcher to log the mouse's trail.
get_group
Returns the nth group added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
bool replace_group(MouseWatcherGroup *old_group, MouseWatcherGroup *new_group)
Atomically removes old_group from the MouseWatcher, and replaces it with new_group.
void clear_trail_node()
If you have previously fetched the trail node using get_trail_node, then the MouseWatcher is continua...
void note_activity()
Can be used in conjunction with the inactivity timeout to inform the MouseWatcher that the user has j...
bool remove_region(MouseWatcherRegion *region)
Removes the indicated region from the group.
MouseWatcherRegion * get_over_region() const
Returns the smallest region the mouse is currently over, or NULL if it is over no region.
Definition: mouseWatcher.I:128
get_num_groups
Returns the number of separate groups added to the MouseWatcher via add_group().
Definition: mouseWatcher.h:130
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
A handy class object for storing simple values (like integers or strings) passed along with an Event ...
Definition: paramValue.h:103
get_value
Retrieves the value stored in the parameter.
Definition: paramValue.h:115
Records a set of pointer events that happened recently.
int get_xpos(size_t n) const
Get the x-coordinate of the nth event.
int get_sequence(size_t n) const
Get the sequence number of the nth event.
double get_time(size_t n) const
Get the timestamp of the nth event.
size_t get_num_events() const
Returns the number of events in the list.
int get_ypos(size_t n) const
Get the y-coordinate of the nth event.
bool get_in_window(size_t n) const
Get the in-window flag of the nth event.
bool test_ref_count_integrity() const
Does some easy checks to make sure that the reference count isn't completely bogus.
This is a special DisplayRegion wrapper that actually includes a pair of DisplayRegions internally: t...
get_right_eye
Returns a pointer to the right DisplayRegion managed by this stereo object.
get_left_eye
Returns a pointer to the left DisplayRegion managed by this stereo object.
A thread; that is, a lightweight process.
Definition: thread.h:46
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
void push_back(const value_type_0 &key)
Adds the new element to the end of the vector without regard for proper sorting.
void swap(ordered_vector< Key, Compare, Vector > &other)
Exchanges the contents of this vector and the other vector, in constant time (e.g....
bool empty() const
Returns true if the ordered vector is empty, false otherwise.
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
void clear()
Removes all elements from the ordered vector.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: dcindent.cxx:22
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(GeomNode) MouseWatcher
Returns a GeomNode that represents the mouse trail.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.