Sfoglia il codice sorgente

add MouseWatcher::set_display_region()

David Rose 22 anni fa
parent
commit
a79af737d2

+ 87 - 4
panda/src/framework/pandaFramework.cxx

@@ -26,6 +26,8 @@
 #include "graphicsPipeSelection.h"
 #include "nodePathCollection.h"
 #include "textNode.h"
+#include "mouseAndKeyboard.h"
+#include "mouseRecorder.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaFramework::Constructor
@@ -157,6 +159,59 @@ get_default_pipe() {
   return _default_pipe;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaFramework::get_mouse
+//       Access: Public
+//  Description: Returns a NodePath to the MouseAndKeyboard associated
+//               with the indicated GraphicsWindow object.  If there's
+//               not yet a mouse associated with the window, creates
+//               one.
+//
+//               This allows multiple WindowFramework objects that
+//               represent different display regions of the same
+//               GraphicsWindow to share the same mouse.
+////////////////////////////////////////////////////////////////////
+NodePath PandaFramework::
+get_mouse(GraphicsWindow *window) {
+  Mouses::iterator mi = _mouses.find(window);
+  if (mi != _mouses.end()) {
+    return (*mi).second;
+  }
+
+  NodePath mouse;
+
+  NodePath data_root = get_data_root();
+  MouseAndKeyboard *mouse_node = new MouseAndKeyboard(window, 0, "mouse");
+  mouse = data_root.attach_new_node(mouse_node);
+
+  RecorderController *recorder = get_recorder();
+  if (recorder != (RecorderController *)NULL) {
+    // If we're in recording or playback mode, associate a recorder.
+    MouseRecorder *mouse_recorder = new MouseRecorder("mouse");
+    mouse = mouse.attach_new_node(mouse_recorder);
+    recorder->add_recorder("mouse", mouse_recorder);
+  }
+
+  _mouses[window] = mouse;
+
+  return mouse;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaFramework::remove_mouse
+//       Access: Public
+//  Description: Removes the mouse that may have been created by an
+//               earlier call to get_mouse().
+////////////////////////////////////////////////////////////////////
+void PandaFramework::
+remove_mouse(const GraphicsWindow *window) {
+  Mouses::iterator mi = _mouses.find(window);
+  if (mi != _mouses.end()) {
+    (*mi).second.remove_node();
+    _mouses.erase(mi);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaFramework::define_key
 //       Access: Public
@@ -276,9 +331,9 @@ open_window(const WindowProperties &props, GraphicsPipe *pipe,
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaFramework::find_window
 //       Access: Public
-//  Description: Returns the index of the WindowFramework object that
-//               references the indicated GraphicsWindow pointer, or
-//               -1 if none do.
+//  Description: Returns the index of the first WindowFramework object
+//               found that references the indicated GraphicsWindow
+//               pointer, or -1 if none do.
 ////////////////////////////////////////////////////////////////////
 int PandaFramework::
 find_window(const GraphicsWindow *win) const {
@@ -351,7 +406,13 @@ close_all_windows() {
     wf->close_window();
   }
 
+  Mouses::iterator mi;
+  for (mi = _mouses.begin(); mi != _mouses.end(); ++mi) {
+    (*mi).second.remove_node();
+  }
+
   _windows.clear();
+  _mouses.clear();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -758,9 +819,21 @@ event_esc(CPT_Event event, void *data) {
     WindowFramework *wf;
     DCAST_INTO_V(wf, param.get_ptr());
 
+    PT(GraphicsWindow) win = wf->get_graphics_window();
+
     PandaFramework *self = (PandaFramework *)data;
     self->close_window(wf);
 
+    // Also close any other WindowFrameworks on the same window.
+    int window_index = self->find_window(win);
+    while (window_index != -1) {
+      self->close_window(window_index);
+      window_index = self->find_window(win);
+    }
+
+    // Free up the mouse for that window.
+    self->remove_mouse(win);
+
     // If we closed the last window, shut down.
     if (self->_windows.empty()) {
       self->_exit_flag = true;
@@ -1221,10 +1294,20 @@ event_window_event(CPT_Event event, void *data) {
     if (window_index == -1) {
       framework_cat.debug()
         << "Ignoring message from unknown window.\n";
+
     } else {
       if (!win->get_properties().get_open()) {
+        int window_index = self->find_window(win);
+        while (window_index != -1) {
+          self->close_window(window_index);
+          window_index = self->find_window(win);
+        }
+        
+        // Free up the mouse for that window.
+        self->remove_mouse(win);
+
         // If the last window was closed, exit the application.
-        if (self->all_windows_closed() && !self->_exit_flag) {
+        if (self->_windows.empty() && !self->_exit_flag) {
           framework_cat.info()
             << "Last window was closed by user.\n";
           self->_exit_flag = true;

+ 7 - 0
panda/src/framework/pandaFramework.h

@@ -51,6 +51,8 @@ public:
   INLINE GraphicsEngine *get_graphics_engine();
   INLINE const NodePath &get_data_root() const;
   INLINE EventHandler &get_event_handler();
+  NodePath get_mouse(GraphicsWindow *window);
+  void remove_mouse(const GraphicsWindow *window);
 
   void define_key(const string &event_name, 
                   const string &description,
@@ -155,6 +157,9 @@ private:
   typedef pvector< PT(WindowFramework) > Windows;
   Windows _windows;
 
+  typedef pmap< const GraphicsWindow *, NodePath > Mouses;
+  Mouses _mouses;
+
   NodePath _models;
 
   // For counting frame rate.
@@ -187,6 +192,8 @@ private:
   double _screenshot_clear_time;
 
   PT(RecorderController) _recorder;
+
+  friend class WindowFramework;
 };
 
 #include "pandaFramework.I"

+ 148 - 61
panda/src/framework/windowFramework.cxx

@@ -18,8 +18,7 @@
 
 #include "windowFramework.h"
 #include "pandaFramework.h"
-#include "mouseAndKeyboard.h"
-#include "mouseRecorder.h"
+#include "displayRegion.h"
 #include "buttonThrower.h"
 #include "transform2sg.h"
 #include "dSearchPath.h"
@@ -77,6 +76,34 @@ WindowFramework(PandaFramework *panda_framework) :
   _background_type = BT_default;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: WindowFramework::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+WindowFramework::
+WindowFramework(const WindowFramework &copy, DisplayRegion *display_region) :
+  _panda_framework(copy._panda_framework),
+  _window(copy._window),
+  _display_region_3d(display_region)
+{
+  _alight = (AmbientLight *)NULL;
+  _dlight = (DirectionalLight *)NULL;
+  _got_keyboard = false;
+  _got_trackball = false;
+  _got_lights = false;
+  _wireframe_enabled = false;
+  _texture_enabled = true;
+  _two_sided_enabled = false;
+  _one_sided_reverse_enabled = false;
+  _lighting_enabled = false;
+  _background_type = BT_default;
+
+  set_background_type(copy._background_type);
+  // Set up a 3-d camera for the window by default.
+  make_camera();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: WindowFramework::Destructor
 //       Access: Public, Virtual
@@ -120,6 +147,21 @@ open_window(const WindowProperties &props, GraphicsEngine *engine,
   _window = engine->make_window(ptgsg, name, 0);
   if (_window != (GraphicsWindow *)NULL) {
     _window->request_properties(props);
+
+    // Get the first channel on the window.  This will be the only
+    // channel on non-SGI hardware.
+    PT(GraphicsChannel) channel = _window->get_channel(0);
+    
+    // Make a layer on the channel to hold our display region.
+    PT(GraphicsLayer) layer = channel->make_layer();
+    
+    // And create a display region that covers the entire window.
+    _display_region_3d = layer->make_display_region();
+
+    // Make sure the DisplayRegion does the clearing, not the window,
+    // so we can have multiple DisplayRegions of different colors.
+    _window->set_clear_color_active(false);
+    _window->set_clear_depth_active(false);
     set_background_type(_background_type);
 
     // Set up a 3-d camera for the window by default.
@@ -228,8 +270,11 @@ get_render_2d() {
     // Make a layer on the channel to hold our display region.
     PT(GraphicsLayer) layer = channel->make_layer(10);
     
-    // And create a display region that covers the entire window.
-    PT(DisplayRegion) dr = layer->make_display_region();
+    // And create a display region that matches the size of the 3-d
+    // display region.
+    float l, r, b, t;
+    _display_region_3d->get_dimensions(l, r, b, t);
+    _display_region_2d = layer->make_display_region(l, r, b, t);
     
     // Finally, we need a camera to associate with the display region.
     PT(Camera) camera = new Camera("camera2d");
@@ -247,7 +292,7 @@ get_render_2d() {
 
     camera->set_lens(lens);
     camera->set_scene(_render_2d);
-    dr->set_camera(camera_np);
+    _display_region_2d->set_camera(camera_np);
   }
 
   return _render_2d;
@@ -294,19 +339,16 @@ get_aspect_2d() {
 const NodePath &WindowFramework::
 get_mouse() {
   if (_mouse.is_empty()) {
-    nassertr(_window->get_num_input_devices() > 0, _mouse);
-    NodePath data_root = _panda_framework->get_data_root();
-    MouseAndKeyboard *mouse_node = 
-      new MouseAndKeyboard(_window, 0, "mouse");
-    _mouse = data_root.attach_new_node(mouse_node);
-
-    RecorderController *recorder = _panda_framework->get_recorder();
-    if (recorder != (RecorderController *)NULL) {
-      // If we're in recording or playback mode, associate a recorder.
-      MouseRecorder *mouse_recorder = new MouseRecorder("mouse");
-      _mouse = _mouse.attach_new_node(mouse_recorder);
-      recorder->add_recorder("mouse", mouse_recorder);
-    }
+    NodePath mouse = _panda_framework->get_mouse(_window);
+
+    // Create a MouseWatcher to filter the mouse input.  We do this
+    // mainly so we can constrain the mouse input to our particular
+    // display region, if we have one.  This means the node we return
+    // from get_mouse() is actually a MouseWatcher, but since it
+    // presents the same interface as a Mouse, no one should mind.
+    PT(MouseWatcher) mw = new MouseWatcher("watcher");
+    mw->set_display_region(_display_region_3d);
+    _mouse = mouse.attach_new_node(mw);
   }
   return _mouse;
 }
@@ -326,6 +368,8 @@ enable_keyboard() {
   if (_window->get_num_input_devices() > 0) {
     NodePath mouse = get_mouse();
 
+    // Create a button thrower to listen for our keyboard events and
+    // associate this WindowFramework pointer with each one.
     PT(ButtonThrower) bt = new ButtonThrower("kb-events");
     bt->add_parameter(EventParameter(this));
     ModifierButtons mods;
@@ -479,8 +523,6 @@ bool WindowFramework::
 load_models(const NodePath &parent, const pvector<Filename> &files) {
   bool all_ok = true;
 
-  NodePath render = get_render();
-
   pvector<Filename>::const_iterator fi;
   for (fi = files.begin(); fi != files.end(); ++fi) {
     const Filename &filename = (*fi);
@@ -610,6 +652,59 @@ loop_animations() {
   _anim_controls.loop_all(true);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: WindowFramework::split_window
+//       Access: Public
+//  Description: Divides the window into two display regions, each of
+//               which gets its own trackball and keyboard events.
+//               The new window pointer is returned.
+//
+//               There is not an interface for recombining divided
+//               windows.
+////////////////////////////////////////////////////////////////////
+WindowFramework *WindowFramework::
+split_window(SplitType split_type) {
+  DisplayRegion *new_region = NULL;
+
+  if (split_type == ST_default) {
+    // Choose either horizontal or vertical according to the largest
+    // dimension.
+    
+    if (_display_region_3d->get_pixel_width() > 
+        _display_region_3d->get_pixel_height()) {
+      split_type = ST_horizontal;
+    } else {
+      split_type = ST_vertical;
+    }
+  }
+  
+  float left, right, bottom, top;
+  _display_region_3d->get_dimensions(left, right, bottom, top);
+  new_region = _display_region_3d->get_layer()->make_display_region();
+
+  if (split_type == ST_vertical) {
+    _display_region_3d->set_dimensions(left, right, bottom, (top + bottom) / 2.0f);
+    if (_display_region_2d != (DisplayRegion *)NULL) {
+      _display_region_2d->set_dimensions(left, right, bottom, (top + bottom) / 2.0f);
+    }
+      
+    new_region->set_dimensions(left, right, (top + bottom) / 2.0f, top);
+    
+  } else {
+    _display_region_3d->set_dimensions(left, (left + right) / 2.0f, bottom, top);
+    if (_display_region_2d != (DisplayRegion *)NULL) {
+      _display_region_2d->set_dimensions(left, (left + right) / 2.0f, bottom, top);
+    }
+
+    new_region->set_dimensions((left + right) / 2.0f, right, bottom, top);
+  }
+
+  PT(WindowFramework) wf = new WindowFramework(*this, new_region);
+  _panda_framework->_windows.push_back(wf);
+
+  return wf;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: WindowFramework::set_wireframe
 //       Access: Public
@@ -756,38 +851,40 @@ void WindowFramework::
 set_background_type(WindowFramework::BackgroundType type) {
   _background_type = type;
 
-  if (_window != (GraphicsWindow *)NULL) {
-    switch (_background_type) {
-    case BT_other:
-      break;
-      
-    case BT_default:
-      _window->set_clear_color_active(true);
-      _window->set_clear_depth_active(true);
-      _window->set_clear_color(Colorf(win_background_r, win_background_g, 
-                                      win_background_b, 1.0f));
-      _window->set_clear_depth(1.0f);
-      break;
-      
-    case BT_gray:
-      _window->set_clear_color_active(true);
-      _window->set_clear_depth_active(true);
-      _window->set_clear_color(Colorf(0.3f, 0.3f, 0.3f, 1.0f));
-      _window->set_clear_depth(1.0f);
-      break;
-      
-    case BT_black:
-      _window->set_clear_color_active(true);
-      _window->set_clear_depth_active(true);
-      _window->set_clear_color(Colorf(0.0f, 0.0f, 0.0f, 1.0f));
-      _window->set_clear_depth(1.0f);
-      break;
+  if (_display_region_3d == (DisplayRegion *)NULL) {
+    return;
+  }
 
-    case BT_none:
-      _window->set_clear_color_active(false);
-      _window->set_clear_depth_active(false);
+  switch (_background_type) {
+  case BT_other:
+    break;
+    
+  case BT_default:
+    _display_region_3d->set_clear_color_active(true);
+    _display_region_3d->set_clear_depth_active(true);
+    _display_region_3d->set_clear_color(Colorf(win_background_r, win_background_g, 
+                                               win_background_b, 1.0f));
+    _display_region_3d->set_clear_depth(1.0f);
+    break;
+    
+  case BT_gray:
+    _display_region_3d->set_clear_color_active(true);
+    _display_region_3d->set_clear_depth_active(true);
+    _display_region_3d->set_clear_color(Colorf(0.3f, 0.3f, 0.3f, 1.0f));
+    _display_region_3d->set_clear_depth(1.0f);
+    break;
+    
+  case BT_black:
+    _display_region_3d->set_clear_color_active(true);
+    _display_region_3d->set_clear_depth_active(true);
+    _display_region_3d->set_clear_color(Colorf(0.0f, 0.0f, 0.0f, 1.0f));
+    _display_region_3d->set_clear_depth(1.0f);
       break;
-    }
+
+  case BT_none:
+    _display_region_3d->set_clear_color_active(false);
+    _display_region_3d->set_clear_depth_active(false);
+    break;
   }
 }
 
@@ -798,16 +895,6 @@ set_background_type(WindowFramework::BackgroundType type) {
 ////////////////////////////////////////////////////////////////////
 PT(Camera) WindowFramework::
 make_camera() {
-  // Get the first channel on the window.  This will be the only
-  // channel on non-SGI hardware.
-  PT(GraphicsChannel) channel = _window->get_channel(0);
-
-  // Make a layer on the channel to hold our display region.
-  PT(GraphicsLayer) layer = channel->make_layer();
-
-  // And create a display region that covers the entire window.
-  PT(DisplayRegion) dr = layer->make_display_region();
-
   // Finally, we need a camera to associate with the display region.
   PT(Camera) camera = new Camera("camera");
   NodePath camera_np = get_camera_group().attach_new_node(camera);
@@ -833,7 +920,7 @@ make_camera() {
 
   camera->set_lens(lens);
   camera->set_scene(get_render());
-  dr->set_camera(camera_np);
+  _display_region_3d->set_camera(camera_np);
 
   return camera;
 }

+ 13 - 0
panda/src/framework/windowFramework.h

@@ -37,6 +37,7 @@ class AmbientLight;
 class DirectionalLight;
 class GraphicsEngine;
 class GraphicsPipe;
+class DisplayRegion;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : WindowFramework
@@ -46,6 +47,8 @@ class GraphicsPipe;
 class EXPCL_FRAMEWORK WindowFramework : public TypedWritableReferenceCount {
 protected:
   WindowFramework(PandaFramework *panda_framework);
+  WindowFramework(const WindowFramework &copy, DisplayRegion *display_region);
+
 public:
   virtual ~WindowFramework();
 
@@ -58,6 +61,7 @@ protected:
 public:
   INLINE PandaFramework *get_panda_framework() const;
   INLINE GraphicsWindow *get_graphics_window() const;
+  INLINE DisplayRegion *get_display_region() const;
   const NodePath &get_camera_group();
 
   INLINE int get_num_cameras() const;
@@ -88,6 +92,13 @@ public:
     BT_none
   };
 
+  enum SplitType {
+    ST_default,
+    ST_horizontal,
+    ST_vertical,
+  };
+  WindowFramework *split_window(SplitType split_type = ST_default);
+
   void set_wireframe(bool enable);
   void set_texture(bool enable);
   void set_two_sided(bool enable);
@@ -112,6 +123,8 @@ private:
 private:
   PandaFramework *_panda_framework;
   PT(GraphicsWindow) _window;
+  PT(DisplayRegion) _display_region_2d;
+  PT(DisplayRegion) _display_region_3d;
 
   NodePath _camera_group;
   typedef pvector< PT(Camera) > Cameras;

+ 1 - 1
panda/src/putil/mouseButton.cxx

@@ -111,7 +111,7 @@ is_mouse_button(ButtonHandle button) {
     }
   }
 
-  return false;
+  return button == _wheel_up || button == _wheel_down;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 17 - 0
panda/src/testbed/pview.cxx

@@ -52,6 +52,22 @@ event_W(CPT_Event, void *) {
   }
 }
 
+void
+event_2(CPT_Event event, void *) {
+  // 2: split the window into two display regions.
+
+  EventParameter param = event->get_parameter(0);
+  WindowFramework *wf;
+  DCAST_INTO_V(wf, param.get_ptr());
+
+  WindowFramework *split = wf->split_window();
+  if (split != (WindowFramework *)NULL) {
+    split->enable_keyboard();
+    split->setup_trackball();
+    framework.get_models().instance_to(split->get_render());
+  }
+}
+
 void 
 usage() {
   cerr <<
@@ -162,6 +178,7 @@ main(int argc, char *argv[]) {
 
     framework.enable_default_keys();
     framework.define_key("shift-w", "open a new window", event_W, NULL);
+    framework.define_key("2", "split the window", event_2, NULL);
     framework.main_loop();
     framework.report_frame_rate(nout);
   }

+ 57 - 0
panda/src/tform/mouseWatcher.I

@@ -406,6 +406,63 @@ get_modifier_buttons() const {
   return _mods;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_display_region
+//       Access: Published
+//  Description: Constrains the MouseWatcher to watching the mouse
+//               within a particular indicated region of the screen.
+//               DataNodes parented under the MouseWatcher will
+//               observe the mouse and keyboard events only when the
+//               mouse is within the indicated region, and the
+//               observed range will be from -1 .. 1 across the
+//               region.
+//
+//               Do not delete the DisplayRegion while it is owned by
+//               the MouseWatcher.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+set_display_region(DisplayRegion *dr) {
+  _display_region = dr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::clear_display_region
+//       Access: Published
+//  Description: Removes the display region constraint from the
+//               MouseWatcher, and restores it to the default behavior
+//               of watching the whole window.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+clear_display_region() {
+  _display_region = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_display_region
+//       Access: Published
+//  Description: Returns the display region the MouseWatcher is
+//               constrained to by set_display_region(), or NULL if it
+//               is not constrained.
+////////////////////////////////////////////////////////////////////
+INLINE DisplayRegion *MouseWatcher::
+get_display_region() const {
+  return _display_region;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::has_display_region
+//       Access: Published
+//  Description: Returns true if the MouseWatcher has been constrained
+//               to a particular region of the screen via
+//               set_display_region(), or false otherwise.  If this
+//               returns true, get_display_region() may be used to
+//               return the particular region.
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcher::
+has_display_region() const {
+  return (_display_region != (DisplayRegion *)NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::within_region
 //       Access: Protected

+ 99 - 37
panda/src/tform/mouseWatcher.cxx

@@ -27,6 +27,7 @@
 #include "eventParameter.h"
 #include "dataNodeTransmit.h"
 #include "transformState.h"
+#include "displayRegion.h"
 #include "dcast.h"
 
 #include <algorithm>
@@ -59,7 +60,8 @@ MouseWatcher(const string &name) :
   _preferred_region = (MouseWatcherRegion *)NULL;
   _preferred_button_down_region = (MouseWatcherRegion *)NULL;
   _button_down = false;
-  _eh = (EventHandler*)0L;
+  _eh = (EventHandler *)NULL;
+  _display_region = (DisplayRegion *)NULL;
 
   // When this flag is true, the mouse pointer is allowed to be
   // "entered" into multiple regions simultaneously; when false, it
@@ -707,8 +709,8 @@ keystroke(int keycode) {
   // Keystrokes go to all those regions that want keyboard events,
   // regardless of which is the "preferred" region (that is, without
   // respect to the mouse position).  However, we do set the outside
-  // flag according to whether the given region is preferred region or
-  // not.
+  // flag according to whether the given region is the preferred
+  // region or not.
 
   Regions::const_iterator ri;
   for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
@@ -832,6 +834,52 @@ exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
   throw_event_pattern(_leave_pattern, region, ButtonHandle::none());
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_no_mouse
+//       Access: Protected
+//  Description: Called from do_transmit_data() to indicate the mouse
+//               is not within the window.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+set_no_mouse() {
+  if (_has_mouse) {
+    // Hide the mouse pointer.
+    if (!_geometry.is_null()) {
+      _geometry->set_draw_mask(DrawMask::all_off());
+    }
+  }
+  
+  _has_mouse = false;
+  clear_current_regions();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_mouse
+//       Access: Protected
+//  Description: Called from do_transmit_data() to indicate the mouse
+//               is within the window, and to specify its current
+//               position.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+set_mouse(const LVecBase2f &xy, const LVecBase2f &pixel_xy) {
+  if (!_geometry.is_null()) {
+    // Transform the mouse pointer.
+    _geometry->set_transform(TransformState::make_pos(LVecBase3f(xy[0], 0, xy[1])));
+    if (!_has_mouse) {
+      // Show the mouse pointer.
+      _geometry->set_draw_mask(DrawMask::all_on());
+    }
+  }
+  
+  _has_mouse = true;
+  _mouse = xy;
+  _mouse_pixel = pixel_xy;
+    
+  VRegions regions;
+  get_over_regions(regions, _mouse);
+  set_current_regions(regions);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::do_transmit_data
 //       Access: Protected, Virtual
@@ -847,43 +895,58 @@ exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param) {
 ////////////////////////////////////////////////////////////////////
 void MouseWatcher::
 do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
+  // Initially, we do not suppress any events to objects below us in
+  // the data graph.
+  _suppress_flags = 0;
+
   if (!input.has_data(_xy_input)) {
     // No mouse in the window.
-
-    if (_has_mouse) {
-      // Hide the mouse pointer.
-      if (!_geometry.is_null()) {
-        _geometry->set_draw_mask(DrawMask::all_off());
-      }
-    }
-
-    _has_mouse = false;
-    clear_current_regions();
+    set_no_mouse();
 
   } else {
     // The mouse is within the window.  Get the current mouse position.
-    const EventStoreVec2 *xy;
+    const EventStoreVec2 *xy, *pixel_xy;
     DCAST_INTO_V(xy, input.get_data(_xy_input).get_ptr());
-    const LVecBase2f &p = xy->get_value();
-    _mouse.set(p[0], p[1]);
-    
-    if (!_geometry.is_null()) {
-      // Transform the mouse pointer.
-      _geometry->set_transform(TransformState::make_pos(LVecBase3f(p[0], 0, p[1])));
-      if (!_has_mouse) {
-        // Show the mouse pointer.
-        _geometry->set_draw_mask(DrawMask::all_on());
-      }
-    }
+    DCAST_INTO_V(pixel_xy, input.get_data(_pixel_xy_input).get_ptr());
+
+    const LVecBase2f &f = xy->get_value();
+    const LVecBase2f &p = pixel_xy->get_value();
+
+    if (_display_region != (DisplayRegion *)NULL) {
+      // If we've got a display region, constrain the mouse to it.
+      int xo, yo, width, height;
+      _display_region->get_region_pixels_i(xo, yo, width, height);
 
-    _has_mouse = true;
+      if (p[0] < xo || p[0] >= xo + width || 
+          p[1] < yo || p[1] >= yo + height) {
+        // The mouse is outside the display region, even though it's
+        // within the window.  This is considered not having a mouse.
+        set_no_mouse();
 
-    VRegions regions;
-    get_over_regions(regions, _mouse);
-    set_current_regions(regions);
+        // This also means we should suppress button events below us.
+        _suppress_flags = MouseWatcherRegion::SF_any_button;
+
+      } else {
+        // The mouse is within the display region; rescale it.
+        float left, right, bottom, top;
+        _display_region->get_dimensions(left, right, bottom, top);
+
+        LVecBase2f new_f((f[0] - left) / (right - left), 
+                         (f[1] - bottom) / (top - bottom));
+        LVecBase2f new_p(p[0] - xo, p[1] - xo);
+
+        set_mouse(new_f, new_p);
+      }
+
+    } else {
+      // No display region; respect the whole window.
+      set_mouse(f, p);
+    }
   }
 
-  _suppress_flags = 0;
+  // If the mouse is over a particular region, or still considered
+  // owned by a region because of a recent button-down event, that
+  // region determines whether we suppress events below us.
   if (_preferred_region != (MouseWatcherRegion *)NULL) {
     _suppress_flags = _preferred_region->get_suppress_flags();
   }
@@ -920,15 +983,15 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
   if (_has_mouse &&
       (_suppress_flags & MouseWatcherRegion::SF_mouse_position) == 0) {
     // Transmit the mouse position.
-    output.set_data(_xy_output, input.get_data(_xy_input));
-    output.set_data(_pixel_xy_output, input.get_data(_pixel_xy_input));
+    _xy->set_value(_mouse);
+    output.set_data(_xy_output, EventParameter(_xy));
+    _pixel_xy->set_value(_mouse_pixel);
+    output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
   }
 
   int suppress_buttons = (_suppress_flags & MouseWatcherRegion::SF_any_button);
-  if (suppress_buttons == MouseWatcherRegion::SF_any_button) {
-    // Suppress all buttons.
 
-  } else if (suppress_buttons != 0) {
+  if (suppress_buttons != 0) {
     // Suppress some buttons.
     _button_events->clear();
 
@@ -947,7 +1010,7 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
           suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
         }
 
-        if (!suppress) {
+        if (!suppress || be._type == ButtonEvent::T_up) {
           // Don't suppress this button event; pass it through.
           _button_events->add_event(be);
         }
@@ -963,4 +1026,3 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
     output.set_data(_button_events_output, input.get_data(_button_events_input));
   }
 }
-

+ 11 - 1
panda/src/tform/mouseWatcher.h

@@ -34,6 +34,7 @@
 #include "pvector.h"
 
 class MouseWatcherParameter;
+class DisplayRegion;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : MouseWatcher
@@ -103,6 +104,11 @@ PUBLISHED:
   INLINE void set_modifier_buttons(const ModifierButtons &mods);
   INLINE ModifierButtons get_modifier_buttons() const;
 
+  INLINE void set_display_region(DisplayRegion *dr);
+  INLINE void clear_display_region();
+  INLINE DisplayRegion *get_display_region() const;
+  INLINE bool has_display_region() const;
+
 public:
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
@@ -141,6 +147,9 @@ protected:
   void enter_region(MouseWatcherRegion *region, const MouseWatcherParameter &param);
   void exit_region(MouseWatcherRegion *region, const MouseWatcherParameter &param);
 
+  void set_no_mouse();
+  void set_mouse(const LVecBase2f &xy, const LVecBase2f &pixel_xy);
+
   // This wants to be a set, but because you cannot export sets across
   // dlls in windows, we will make it a vector instead
   typedef pvector< PT(MouseWatcherGroup) > Groups;
@@ -149,6 +158,7 @@ protected:
   bool _has_mouse;
   int _suppress_flags;
   LPoint2f _mouse;
+  LPoint2f _mouse_pixel;
 
   VRegions _current_regions;
   PT(MouseWatcherRegion) _preferred_region;
@@ -168,8 +178,8 @@ protected:
   PT(PandaNode) _geometry;
 
   EventHandler *_eh;
-
   ModifierButtons _mods;
+  DisplayRegion *_display_region;
 
 protected:
   // Inherited from DataNode