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