diff --git a/src/mcedit2/dialogs/error_dialog.py b/src/mcedit2/dialogs/error_dialog.py index 47bd903..5aafaf9 100644 --- a/src/mcedit2/dialogs/error_dialog.py +++ b/src/mcedit2/dialogs/error_dialog.py @@ -22,6 +22,9 @@ _errorShown = False def showErrorDialog(text, tb, fatal): global _errorShown _errorShown = True + grabber = QtGui.QWidget.mouseGrabber() + grabber.releaseMouse() + dialog = ErrorDialog(text, tb, fatal) dialog.exec_() _errorShown = False diff --git a/src/mcedit2/rendering/compass.py b/src/mcedit2/rendering/compass.py index 4431906..073b963 100644 --- a/src/mcedit2/rendering/compass.py +++ b/src/mcedit2/rendering/compass.py @@ -51,9 +51,11 @@ class CompassRenderNode(rendernode.RenderNode): with gl.glEnable(GL.GL_BLEND, GL.GL_TEXTURE_2D): GL.glDrawArrays(GL.GL_QUADS, 0, 4) + class CompassNode(scenenode.Node): _yawPitch = (0., 0.) RenderNodeClass = CompassRenderNode + def __init__(self, small=False): super(CompassNode, self).__init__() self.small = small diff --git a/src/mcedit2/worldview/camera.py b/src/mcedit2/worldview/camera.py index 96530e4..ab18297 100644 --- a/src/mcedit2/worldview/camera.py +++ b/src/mcedit2/worldview/camera.py @@ -30,6 +30,8 @@ log = logging.getLogger(__name__) settings = Settings().getNamespace("worldview/camera") ViewDistanceSetting = settings.getOption("view_distance", int, 12) PerspectiveSetting = settings.getOption("perspective", bool, True) +StickyMouselookSetting = settings.getOption("sticky_mouselook", bool, True) + class CameraWorldViewFrame(QtGui.QWidget): def __init__(self, dimension, textureAtlas, geometryCache, shareGLWidget, *args, **kwargs): @@ -37,7 +39,17 @@ class CameraWorldViewFrame(QtGui.QWidget): self.worldView = view = CameraWorldView(dimension, textureAtlas, geometryCache, shareGLWidget) - self.viewControls = ViewControls(view) + auxControlWidget = QtGui.QWidget() + + StickyMouselookSetting.connectAndCall(view.setStickyMouselook) + + stickyCheckbox = QtGui.QCheckBox(self.tr("Sticky Mouselook")) + stickyCheckbox.setChecked(StickyMouselookSetting.value()) + stickyCheckbox.toggled.connect(StickyMouselookSetting.setValue) + + auxControlWidget.setLayout(Column(stickyCheckbox)) + + self.viewControls = ViewControls(view, auxControlWidget) ViewDistanceSetting.connectAndCall(view.setViewDistance) @@ -256,14 +268,20 @@ class CameraWorldView(WorldView): self.fov = 70.0 # needed by updateMatrices called from WorldView.__init__ self._yawPitch = -45., 25. self.viewDistance = 32 + + self.stickyMouselook = False self.workplaneNode = WorkplaneNode() self.workplaneNode.visible = False WorldView.__init__(self, *a, **kw) self.compassNode.yawPitch = self._yawPitch + + stickyPanAction = CameraStickyPanMouseAction() + panAction = CameraPanMouseAction(stickyPanAction) + self.viewActions = [CameraMoveMouseAction(), - CameraPanMouseAction()] + panAction, stickyPanAction] self.cameraControls = CameraKeyControls(self) self.viewActions.extend(self.cameraControls.viewActions) @@ -277,6 +295,8 @@ class CameraWorldView(WorldView): self.workplaneEnabled = False self.viewportMoved.connect(self.updateWorkplane) + self.forceMouseCenter = False + def updateWorkplane(self): distance = 40 pos = self.centerPoint + self.cameraVector * distance @@ -299,6 +319,10 @@ class CameraWorldView(WorldView): return scenegraph def augmentEvent(self, x, y, event): + if self.forceMouseCenter: + x = self.width() / 2 + y = self.height() / 2 + super(CameraWorldView, self).augmentEvent(x, y, event) if not self.workplaneEnabled: return @@ -320,6 +344,9 @@ class CameraWorldView(WorldView): self._chunkIter = None self.discardChunksOutsideViewDistance() self.update() + + def setStickyMouselook(self, val): + self.stickyMouselook = val def centerOnPoint(self, pos, distance=20): awayVector = self.cameraVector * -distance @@ -448,18 +475,121 @@ class CameraWorldView(WorldView): return iter([]) return super(CameraWorldView, self).recieveChunk(chunk) + +class CameraStickyPanMouseAction(ViewAction): + button = Qt.NoButton + modifiers = Qt.NoModifier + labelText = "Sticky Pan (Don't Edit)" + hidden = True + settingsKey = None + + _sticking = False + mouseDragStart = None + + def toggleSticky(self, event): + view = event.view + if not self._sticking: + self._sticking = True + cursor = view.cursor() + self._oldPos = cursor.pos() + self._oldShape = cursor.shape() + cursor.setShape(Qt.BlankCursor) + view.setCursor(cursor) + view.grabMouse() + view.forceMouseCenter = True + + self.mouseDragStart = event.x(), event.y() + + else: + self._sticking = False + cursor = view.cursor() + cursor.setPos(self._oldPos) + cursor.setShape(self._oldShape) + view.setCursor(cursor) + view.releaseMouse() + view.forceMouseCenter = False + + self.mouseDragStart = None + + sensitivity = .15 + + _last_dx = _last_dy = 0 + + def mouseMoveEvent(self, event): + if not self._sticking: + return + + x = event.x() + y = event.y() + if self.mouseDragStart: + oldx, oldy = self.mouseDragStart + yaw, pitch = event.view.yawPitch + dx = (oldx - x) + dy = (oldy - y) + + yaw -= dx * self.sensitivity + pitch -= dy * self.sensitivity + + event.view.yawPitch = yaw, pitch + + self.mouseDragStart = (x, y) + + # If the (invisible) cursor goes out of the widget's bounds, + # move it back to the center so it doesn't stop at the screen's edge + margin = 10 + + w = event.view.width() + h = event.view.height() + if (x < margin + or y < margin + or x >= w - margin + or y >= h - margin): + + # Disable camera movement while the cursor is repositioned + # since QCursor.setPos emits a mouseMoved event + + self._sticking = False + + self.mouseDragStart = (w / 2, h / 2) + + pos = QtCore.QPoint(*self.mouseDragStart) + pos = event.view.mapToGlobal(pos) + QtGui.QCursor.setPos(pos) + + def resumeSticking(): + self._sticking = True + + QtCore.QTimer.singleShot(0, resumeSticking) + + class CameraPanMouseAction(ViewAction): button = Qt.RightButton - mouseDragStart = None modifiers = Qt.NoModifier labelText = "Turn Camera" settingsKey = "worldview/camera/holdToTurn" + _stickyAction = None + mouseDragStart = None + + _stickyThreshold = 0.25 + + def __init__(self, stickyAction): + super(CameraPanMouseAction, self).__init__() + self._stickyAction = stickyAction + def buttonPressEvent(self, event): + self.downTime = time.time() + x = event.x() y = event.y() self.mouseDragStart = x, y + def buttonReleaseEvent(self, event): + if event.view.stickyMouselook and time.time() - self.downTime < self._stickyThreshold: + self._stickyAction.toggleSticky(event) + + self.mouseDragStart = None + sensitivity = .15 def mouseMoveEvent(self, event): @@ -477,10 +607,6 @@ class CameraPanMouseAction(ViewAction): self.mouseDragStart = (x, y) - def buttonReleaseEvent(self, event): - self.mouseDragStart = None - - class CameraMoveMouseAction(ViewAction): button = Qt.MiddleButton mouseDragStart = None @@ -512,6 +638,5 @@ class CameraMoveMouseAction(ViewAction): self.mouseDragStart = (x, y) - def buttonReleaseEvent(self, event): self.mouseDragStart = None diff --git a/src/mcedit2/worldview/viewcontrols.py b/src/mcedit2/worldview/viewcontrols.py index d4afb2c..7a2fbfc 100644 --- a/src/mcedit2/worldview/viewcontrols.py +++ b/src/mcedit2/worldview/viewcontrols.py @@ -149,7 +149,7 @@ class ButtonModifierInput(QtGui.QPushButton): class ViewControls(QtGui.QFrame): - def __init__(self, worldView, *args, **kwargs): + def __init__(self, worldView, auxWidget=None, *args, **kwargs): """ Popup window for quickly reviewing and assigning movement controls for a world view. @@ -178,7 +178,11 @@ class ViewControls(QtGui.QFrame): self.controlsButton.setDefaultAction(action) self.unlockButton = QtGui.QPushButton(self.tr("Edit Controls"), clicked=self.toggleUnlockControls) - self.setLayout(Column(layout, self.unlockButton)) + col = [layout, self.unlockButton] + if auxWidget is not None: + col.append(auxWidget) + + self.setLayout(Column(*col)) self.unlocked = False def toggleUnlockControls(self):