mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add dragging of edges in crop mode
This commit is contained in:
parent
4cd7ae1cc5
commit
df071a99fc
2 changed files with 145 additions and 59 deletions
|
|
@ -209,6 +209,44 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
self.crop_handle_bottomright,
|
||||
self.crop_handle_topright)
|
||||
|
||||
def crop_edge_top(self):
|
||||
topleft = self.crop_temp.topLeft()
|
||||
return QtCore.QRectF(
|
||||
topleft.x() + self.crop_handle_size,
|
||||
topleft.y(),
|
||||
self.crop_temp.width() - 2 * self.crop_handle_size,
|
||||
self.crop_handle_size)
|
||||
|
||||
def crop_edge_left(self):
|
||||
topleft = self.crop_temp.topLeft()
|
||||
return QtCore.QRectF(
|
||||
topleft.x(),
|
||||
topleft.y() + self.crop_handle_size,
|
||||
self.crop_handle_size,
|
||||
self.crop_temp.height() - 2 * self.crop_handle_size)
|
||||
|
||||
def crop_edge_bottom(self):
|
||||
bottomleft = self.crop_temp.bottomLeft()
|
||||
return QtCore.QRectF(
|
||||
bottomleft.x() + self.crop_handle_size,
|
||||
bottomleft.y() - self.crop_handle_size,
|
||||
self.crop_temp.width() - 2 * self.crop_handle_size,
|
||||
self.crop_handle_size)
|
||||
|
||||
def crop_edge_right(self):
|
||||
topright = self.crop_temp.topRight()
|
||||
return QtCore.QRectF(
|
||||
topright.x() - self.crop_handle_size,
|
||||
topright.y() + self.crop_handle_size,
|
||||
self.crop_handle_size,
|
||||
self.crop_temp.height() - 2 * self.crop_handle_size)
|
||||
|
||||
def crop_edges(self):
|
||||
return (self.crop_edge_top,
|
||||
self.crop_edge_left,
|
||||
self.crop_edge_bottom,
|
||||
self.crop_edge_right)
|
||||
|
||||
def get_crop_handle_cursor(self, handle):
|
||||
"""Gets the crop cursor for the given handle."""
|
||||
|
||||
|
|
@ -216,6 +254,19 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
self.crop_handle_topleft, self.crop_handle_bottomright)
|
||||
return self.get_diag_cursor(is_topleft_or_bottomright)
|
||||
|
||||
def get_crop_edge_cursor(self, edge):
|
||||
"""Gets the crop edge cursor for the given edge."""
|
||||
|
||||
top_or_bottom = edge in (
|
||||
self.crop_edge_top, self.crop_edge_bottom)
|
||||
sideways = (45 < self.rotation() < 135
|
||||
or 225 < self.rotation() < 315)
|
||||
|
||||
if top_or_bottom is sideways:
|
||||
return Qt.CursorShape.SizeHorCursor
|
||||
else:
|
||||
return Qt.CursorShape.SizeVerCursor
|
||||
|
||||
def draw_crop_rect(self, painter, rect):
|
||||
"""Paint a dotted rectangle for the cropping UI."""
|
||||
pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
|
||||
|
|
@ -291,6 +342,10 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
if handle().contains(event.pos()):
|
||||
self.setCursor(self.get_crop_handle_cursor(handle))
|
||||
return
|
||||
for edge in self.crop_edges():
|
||||
if edge().contains(event.pos()):
|
||||
self.setCursor(self.get_crop_edge_cursor(edge))
|
||||
return
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
|
|
@ -304,6 +359,12 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
self.crop_mode_event_start = event.pos()
|
||||
self.crop_mode_move = handle
|
||||
return
|
||||
for edge in self.crop_edges():
|
||||
# Click into an edge handle?
|
||||
if edge().contains(event.pos()):
|
||||
self.crop_mode_event_start = event.pos()
|
||||
self.crop_mode_move = edge
|
||||
return
|
||||
# Click not in handle, end cropping mode:
|
||||
self.exit_crop_mode(
|
||||
confirm=self.crop_temp.contains(event.pos()))
|
||||
|
|
@ -333,6 +394,22 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
new = self.ensure_point_within_pixmap_bounds(
|
||||
self.crop_temp.topRight() + diff)
|
||||
self.crop_temp.setTopRight(new)
|
||||
if self.crop_mode_move == self.crop_edge_top:
|
||||
new = self.ensure_point_within_pixmap_bounds(
|
||||
self.crop_temp.topLeft() + diff)
|
||||
self.crop_temp.setTop(new.y())
|
||||
if self.crop_mode_move == self.crop_edge_left:
|
||||
new = self.ensure_point_within_pixmap_bounds(
|
||||
self.crop_temp.topLeft() + diff)
|
||||
self.crop_temp.setLeft(new.x())
|
||||
if self.crop_mode_move == self.crop_edge_bottom:
|
||||
new = self.ensure_point_within_pixmap_bounds(
|
||||
self.crop_temp.bottomLeft() + diff)
|
||||
self.crop_temp.setBottom(new.y())
|
||||
if self.crop_mode_move == self.crop_edge_right:
|
||||
new = self.ensure_point_within_pixmap_bounds(
|
||||
self.crop_temp.topRight() + diff)
|
||||
self.crop_temp.setRight(new.x())
|
||||
self.update()
|
||||
self.crop_mode_event_start = event.pos()
|
||||
event.accept()
|
||||
|
|
|
|||
|
|
@ -237,6 +237,29 @@ def test_crop_handle_topright(qapp, item):
|
|||
assert item.crop_handle_topright() == QtCore.QRectF(385, 200, 15, 15)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('edge,rotation,expected',
|
||||
[('crop_edge_top', 0, 'SizeVerCursor'),
|
||||
('crop_edge_top', 90, 'SizeHorCursor'),
|
||||
('crop_edge_top', 180, 'SizeVerCursor'),
|
||||
('crop_edge_top', 270, 'SizeHorCursor'),
|
||||
('crop_edge_bottom', 0, 'SizeVerCursor'),
|
||||
('crop_edge_bottom', 90, 'SizeHorCursor'),
|
||||
('crop_edge_bottom', 180, 'SizeVerCursor'),
|
||||
('crop_edge_bottom', 270, 'SizeHorCursor'),
|
||||
('crop_edge_left', 0, 'SizeHorCursor'),
|
||||
('crop_edge_left', 90, 'SizeVerCursor'),
|
||||
('crop_edge_left', 180, 'SizeHorCursor'),
|
||||
('crop_edge_left', 270, 'SizeVerCursor'),
|
||||
('crop_edge_right', 0, 'SizeHorCursor'),
|
||||
('crop_edge_right', 90, 'SizeVerCursor'),
|
||||
('crop_edge_right', 180, 'SizeHorCursor'),
|
||||
('crop_edge_right', 270, 'SizeVerCursor')])
|
||||
def test_get_crop_edge_cursor(edge, rotation, expected, qapp, item):
|
||||
item.setRotation(rotation)
|
||||
cursor = item.get_crop_edge_cursor(getattr(item, edge))
|
||||
assert cursor == getattr(Qt.CursorShape, expected)
|
||||
|
||||
|
||||
def test_paint(qapp, item):
|
||||
item.pixmap = MagicMock()
|
||||
item.paint_selectable = MagicMock()
|
||||
|
|
@ -390,6 +413,18 @@ def test_hover_move_event_crop_mode_inside_handle(hover_mock, qapp, item):
|
|||
hover_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.hoverMoveEvent')
|
||||
def test_hover_move_event_crop_mode_inside_edge(hover_mock, qapp, item):
|
||||
item.crop_mode = True
|
||||
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(5, 40)
|
||||
|
||||
item.hoverMoveEvent(event)
|
||||
item.cursor() == Qt.CursorShape.SizeHorCursor
|
||||
hover_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.hoverMoveEvent')
|
||||
def test_hover_move_event_crop_mode_outside_handle(hover_mock, qapp, item):
|
||||
item.crop_mode = True
|
||||
|
|
@ -434,6 +469,23 @@ def test_mouse_press_event_crop_mode_inside_handle(mouse_mock, qapp, item):
|
|||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mousePressEvent')
|
||||
def test_mouse_press_event_crop_mode_inside_edge(mouse_mock, qapp, item):
|
||||
item.crop_mode = True
|
||||
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
|
||||
item.crop_mode_move = None
|
||||
item.exit_crop_mode = MagicMock()
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(5, 40)
|
||||
|
||||
item.mousePressEvent(event)
|
||||
assert item.crop_mode_move == item.crop_edge_left
|
||||
assert item.crop_mode_event_start == QtCore.QPointF(5, 40)
|
||||
item.exit_crop_mode.assert_not_called()
|
||||
mouse_mock.assert_not_called()
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mousePressEvent')
|
||||
def test_mouse_press_event_crop_mode_outside_handle_inside_crop(
|
||||
mouse_mock, qapp, item):
|
||||
|
|
@ -482,74 +534,31 @@ def test_ensure_point_within_pixmap_bounds_inside(point, expected, qapp, item):
|
|||
assert result == QtCore.QPointF(*expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'start,pos,handle,expected',
|
||||
[[(10, 10), (5, 5), 'crop_handle_topleft', (5, 15, 35, 45)],
|
||||
[(10, 60), (5, 55), 'crop_handle_bottomleft', (5, 20, 35, 35)],
|
||||
[(40, 60), (35, 55), 'crop_handle_bottomright', (10, 20, 25, 35)],
|
||||
[(40, 10), (35, 5), 'crop_handle_topright', (10, 15, 25, 45)],
|
||||
[(25, 10), (20, 5), 'crop_edge_top', (10, 15, 30, 45)],
|
||||
[(10, 40), (5, 35), 'crop_edge_left', (5, 20, 35, 40)],
|
||||
[(35, 25), (30, 20), 'crop_edge_bottom', (10, 20, 30, 35)],
|
||||
[(40, 40), (35, 35), 'crop_edge_right', (10, 20, 25, 40)]])
|
||||
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
|
||||
def test_mouse_move_when_crop_mode_bottomleft(mouse_mock, qapp, item):
|
||||
def test_mouse_move_when_crop_mode_inside_handle(
|
||||
mouse_mock, start, pos, handle, expected, qapp, item):
|
||||
pixmap = MagicMock()
|
||||
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
|
||||
item.crop_mode = True
|
||||
item.pixmap = MagicMock(return_value=pixmap)
|
||||
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
|
||||
item.crop_mode_event_start = QtCore.QPointF(15, 75)
|
||||
item.crop_mode_move = item.crop_handle_bottomleft
|
||||
item.crop_mode_event_start = QtCore.QPointF(*start)
|
||||
item.crop_mode_move = getattr(item, handle)
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(10, 70)
|
||||
event.pos.return_value = QtCore.QPointF(*pos)
|
||||
|
||||
item.mouseMoveEvent(event)
|
||||
assert item.crop_temp == QtCore.QRectF(5, 20, 35, 35)
|
||||
event.accept.assert_called_once()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
|
||||
def test_mouse_move_when_crop_mode_bottomright(mouse_mock, qapp, item):
|
||||
pixmap = MagicMock()
|
||||
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
|
||||
item.crop_mode = True
|
||||
item.pixmap = MagicMock(return_value=pixmap)
|
||||
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
|
||||
item.crop_mode_event_start = QtCore.QPointF(95, 75)
|
||||
item.crop_mode_move = item.crop_handle_bottomright
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(90, 70)
|
||||
|
||||
item.mouseMoveEvent(event)
|
||||
assert item.crop_temp == QtCore.QRectF(10, 20, 25, 35)
|
||||
event.accept.assert_called_once()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
|
||||
def test_mouse_move_when_crop_mode_topleft(mouse_mock, qapp, item):
|
||||
pixmap = MagicMock()
|
||||
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
|
||||
item.crop_mode = True
|
||||
item.pixmap = MagicMock(return_value=pixmap)
|
||||
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
|
||||
item.crop_mode_event_start = QtCore.QPointF(15, 25)
|
||||
item.crop_mode_move = item.crop_handle_topleft
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(10, 20)
|
||||
|
||||
item.mouseMoveEvent(event)
|
||||
assert item.crop_temp == QtCore.QRectF(5, 15, 35, 45)
|
||||
event.accept.assert_called_once()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
|
||||
def test_mouse_move_when_crop_mode_topright(mouse_mock, qapp, item):
|
||||
pixmap = MagicMock()
|
||||
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
|
||||
item.crop_mode = True
|
||||
item.pixmap = MagicMock(return_value=pixmap)
|
||||
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
|
||||
item.crop_mode_event_start = QtCore.QPointF(35, 55)
|
||||
item.crop_mode_move = item.crop_handle_topright
|
||||
event = MagicMock()
|
||||
event.pos.return_value = QtCore.QPointF(30, 50)
|
||||
|
||||
item.mouseMoveEvent(event)
|
||||
assert item.crop_temp == QtCore.QRectF(10, 15, 25, 45)
|
||||
assert item.crop_temp == QtCore.QRectF(*expected)
|
||||
event.accept.assert_called_once()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue