Refactor drag-to-move out of MoveTool and into BoxHandle.

This is a bit icky - PendingImportNode emits a signal that MoveTool handles by moving the "current import", instead of by moving the PendingImportNode's import.

TODO: Give CloneTool the same treatment.
This commit is contained in:
David Vierra 2015-10-01 21:29:25 -10:00
parent cc569bce1c
commit 5deb2f2723
4 changed files with 83 additions and 76 deletions

View File

@ -319,7 +319,7 @@ class GenerateTool(EditorTool):
self.currentGenerator.boundsChanged(bounds)
self.updatePreview()
def boundsDidChangeDone(self, bounds, newSelection):
def boundsDidChangeDone(self, bounds, oldBounds):
# box finished resize
if not self.currentGenerator:
return

View File

@ -10,6 +10,7 @@ from PySide.QtCore import Qt
from mcedit2.editorsession import PendingImport
from mcedit2.editortools import EditorTool
from mcedit2.command import SimpleRevisionCommand
from mcedit2.handles.boxhandle import BoxHandle
from mcedit2.rendering.scenegraph.matrix import TranslateNode
from mcedit2.rendering.scenegraph.scenenode import Node
from mcedit2.rendering.selection import SelectionBoxNode, SelectionFaceNode, boxFaceUnderCursor
@ -82,9 +83,14 @@ class MoveFinishCommand(SimpleRevisionCommand):
self.moveTool.removePendingImport(self.pendingImport)
class PendingImportNode(Node):
class PendingImportNode(Node, QtCore.QObject):
__node_id_counter = 0
def __init__(self, pendingImport, textureAtlas):
super(PendingImportNode, self).__init__()
PendingImportNode.__node_id_counter += 1
self.id = PendingImportNode.__node_id_counter
self.pendingImport = pendingImport
dim = pendingImport.sourceDim
@ -100,14 +106,14 @@ class PendingImportNode(Node):
self.positionTranslateNode.addChild(self.worldSceneTranslateNode)
box = BoundingBox(self.pos, pendingImport.bounds.size)
self.outlineNode = SelectionBoxNode()
self.outlineNode.filled = False
self.outlineNode.selectionBox = box
self.addChild(self.outlineNode)
self.faceHoverNode = SelectionFaceNode()
self.faceHoverNode.selectionBox = box
self.addChild(self.faceHoverNode)
self.handleNode = BoxHandle()
self.handleNode.bounds = box
self.handleNode.resizable = False
self.handleNode.boundsChanged.connect(self.handleBoundsChanged)
self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone)
self.addChild(self.handleNode)
self.pos = pendingImport.pos
@ -115,25 +121,27 @@ class PendingImportNode(Node):
list(pendingImport.selection.chunkPositions()))
self.loader.timer.start()
def handleBoundsChanged(self, bounds):
self.pos = bounds.origin
# newPos, oldPos
importMoved = QtCore.Signal(object, object)
def handleBoundsChangedDone(self, bounds, oldBounds):
self.importMoved.emit(bounds.origin, oldBounds.origin)
@property
def pos(self):
return self.positionTranslateNode.translateOffset
@pos.setter
def pos(self, value):
if value == self.positionTranslateNode.translateOffset:
return
self.positionTranslateNode.translateOffset = value
bounds = BoundingBox(value, self.pendingImport.bounds.size)
self.outlineNode.selectionBox = bounds
self.faceHoverNode.selectionBox = bounds
def hoverFace(self, face):
if face is not None:
self.faceHoverNode.color = 0.3, 1, 1
self.faceHoverNode.visible = True
self.faceHoverNode.face = face
else:
self.faceHoverNode.visible = False
self.handleNode.bounds = bounds
class RotationWidget(QtGui.QWidget):
def __init__(self):
@ -186,8 +194,6 @@ class RotationWidget(QtGui.QWidget):
self.emitRotationChanged(self.zRotSlider.isSliderDown())
class MoveTool(EditorTool):
iconName = "move"
name = "Move"
@ -260,6 +266,8 @@ class MoveTool(EditorTool):
self.importsListModel.appendRow(item)
self.importsListWidget.setCurrentIndex(self.importsListModel.index(self.importsListModel.rowCount()-1, 0))
node = self.pendingImportNodes[pendingImport] = PendingImportNode(pendingImport, self.editorSession.textureAtlas)
node.importMoved.connect(self.importDidMove)
self.overlayNode.addChild(node)
self.currentImport = pendingImport
@ -272,7 +280,7 @@ class MoveTool(EditorTool):
if node:
self.overlayNode.removeChild(node)
def doMoveOffsetCommand(self, oldPoint, newPoint):
def importDidMove(self, newPoint, oldPoint):
if newPoint != oldPoint:
command = MoveOffsetCommand(self, oldPoint, newPoint)
self.editorSession.pushCommand(command)
@ -297,67 +305,34 @@ class MoveTool(EditorTool):
def currentImport(self, value):
self._currentImport = value
self.pointInput.setEnabled(value is not None)
for node in self.pendingImportNodes.itervalues():
node.outlineNode.wireColor = (.2, 1., .2, .5) if node.pendingImport is value else (1, 1, 1, .3)
# Set current import to different color?
# for node in self.pendingImportNodes.itervalues():
# node.outlineNode.wireColor = (.2, 1., .2, .5) if node.pendingImport is value else (1, 1, 1, .3)
@property
def currentImportNode(self):
return self.pendingImportNodes.get(self.currentImport)
@property
def schematicBox(self):
box = self.currentImport.selection
return BoundingBox(self.movePosition, box.size)
# --- Mouse events ---
def dragMovePoint(self, ray):
"""
Return a point representing the intersection between the mouse ray
and an imaginary plane coplanar to the dragged face
:type ray: Ray
:rtype: Vector
"""
dim = self.dragStartFace.dimension
return ray.intersectPlane(dim, self.dragStartPoint[dim])
def mouseMove(self, event):
# Hilite face cursor is over
if self.currentImport is None:
return
node = self.currentImportNode
if node:
point, face = boxFaceUnderCursor(self.schematicBox, event.ray)
node.hoverFace(face)
if self.currentImport is not None:
self.currentImportNode.handleNode.mouseMove(event)
# Highlight face of box to move along, or else axis pointers to grab and drag?
pass
def mouseDrag(self, event):
# Move box using face or axis pointers
if self.currentImport is None:
return
if self.dragStartFace is None:
return
delta = self.dragMovePoint(event.ray) - self.dragStartPoint
self.movePosition = self.dragStartMovePosition + map(int, delta)
self.mouseMove(event)
def mousePress(self, event):
if self.currentImport is not None:
point, face = boxFaceUnderCursor(self.schematicBox, event.ray)
self.dragStartFace = face
self.dragStartPoint = point
self.dragStartMovePosition = self.movePosition
self.currentImportNode.handleNode.mousePress(event)
def mouseRelease(self, event):
# Don't paste cut selection in yet. Wait for tool switch or "Confirm" button press. Begin new revision
# for paste operation, paste stored world, store revision after paste (should be previously stored revision
# +2), commit MoveCommand to undo history.
if self.currentImport is not None:
self.doMoveOffsetCommand(self.dragStartMovePosition, self.movePosition)
self.currentImportNode.handleNode.mouseRelease(event)
def toolActive(self):
self.editorSession.selectionTool.hideSelectionWalls = True

View File

@ -255,11 +255,11 @@ class SelectionTool(EditorTool):
if box is not None:
self.selectionNode.selection = self.createShapedSelection(box)
def boxHandleResizedDone(self, box, newSelection):
def boxHandleResizedDone(self, box, oldBox):
if box is not None:
selection = self.createShapedSelection(box)
command = SelectCommand(self.editorSession, selection)
if not newSelection:
if oldBox is not None:
command.setText(self.tr("Resize Selection"))
self.editorSession.undoStack.push(command)
self.updateNodes()

View File

@ -27,6 +27,8 @@ class BoxHandle(scenenode.Node, QtCore.QObject):
dragStartPoint = None
dragStartFace = None
dragStartMovePosition = None
oldBounds = None
hiliteFace = True
@ -42,14 +44,17 @@ class BoxHandle(scenenode.Node, QtCore.QObject):
mouse button is released.
The handle initially has no box; the first mouse action will define
the initial box with height=0. Subsequent mouse actions will move or resize
the box.
the initial box with height=0. If a subsequent mouse action does not intersect
the box, a new initial box will be created; otherwise, the existing box will be
moved or resized.
Mouse events must be forwarded to the handle using mousePress, mouseDrag, and
mouseRelease.
A modifier can be set (default Shift) to move the box instead of resizing it;
or the `resizable` attribute can be set to False to always move the box.
or the `resizable` attribute can be set to False to always move the box. When
`resizable` is False, new initial boxes cannot be created.
:return:
:rtype:
"""
@ -61,7 +66,9 @@ class BoxHandle(scenenode.Node, QtCore.QObject):
self.addChild(self.faceDragNode)
boundsChanged = QtCore.Signal(BoundingBox)
boundsChangedDone = QtCore.Signal(BoundingBox, bool)
# newBox, oldBox
boundsChangedDone = QtCore.Signal(BoundingBox, BoundingBox)
@property
def bounds(self):
@ -185,10 +192,11 @@ class BoxHandle(scenenode.Node, QtCore.QObject):
def endResize(self, event):
self.bounds = self.boxFromDragResize(self.oldBounds, event.ray)
oldBounds = self.oldBounds
self.oldBounds = None
self.dragResizeFace = None
self.faceDragNode.visible = False
self.boundsChangedDone.emit(self.bounds, False)
self.boundsChangedDone.emit(self.bounds, oldBounds)
# --- Create ---
@ -205,18 +213,42 @@ class BoxHandle(scenenode.Node, QtCore.QObject):
newBox = self.boxFromDragSelect(event.ray)
self.dragStartPoint = None
self.bounds = newBox
self.boundsChangedDone.emit(newBox, True)
self.boundsChangedDone.emit(newBox, None)
# --- Move helpers ---
def dragMovePoint(self, ray):
"""
Return a point representing the intersection between the mouse ray
and an imaginary plane coplanar to the dragged face
:type ray: Ray
:rtype: Vector
"""
dim = self.dragStartFace.dimension
return ray.intersectPlane(dim, self.dragStartPoint[dim])
# --- Move ---
def beginMove(self, event):
pass
point, face = boxFaceUnderCursor(self.bounds, event.ray)
self.dragStartFace = face
self.dragStartPoint = point
self.dragStartMovePosition = self.bounds.origin
self.oldBounds = self.bounds
def continueMove(self, event):
pass
if self.dragStartFace is None:
return
delta = self.dragMovePoint(event.ray) - self.dragStartPoint
movePosition = self.dragStartMovePosition + map(int, delta)
self.bounds = BoundingBox(movePosition, self.bounds.size)
def endMove(self, event):
pass
self.boundsChangedDone.emit(self.bounds, self.oldBounds)
self.dragStartFace = None
# --- Update ---