Add rotate cursor

This commit is contained in:
Rebecca Breu 2021-04-05 14:45:18 +02:00
parent 1ff957bca4
commit 8204d3141f
9 changed files with 200 additions and 10 deletions

View file

@ -22,6 +22,7 @@ import sys
from PyQt6 import QtCore, QtGui, QtWidgets
from beeref.assets import BeeAssets
from beeref.config import CommandlineArgs
from beeref.view import BeeGraphicsView
@ -33,10 +34,7 @@ class BeeRefMainWindow(QtWidgets.QWidget):
def __init__(self, app, filename=None):
super().__init__()
self.setWindowTitle('BeeRef')
root = os.path.dirname(__file__)
logo = os.path.join(root, 'assets', 'logo.png')
logger.debug(f'Loading icon {logo}')
self.setWindowIcon(QtGui.QIcon(logo))
self.setWindowIcon(BeeAssets().logo)
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(QtCore.QMargins(0, 0, 0, 0))
self.setLayout(layout)

48
beeref/assets.py Normal file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
# This file is part of BeeRef.
#
# BeeRef is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BeeRef is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
import logging
import os.path
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
logger = logging.getLogger('BeeRef')
class BeeAssets:
_instance = None
PATH = os.path.join( os.path.dirname(__file__), 'assets')
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
cls._instance.on_new()
return cls._instance
def on_new(self):
PATH = os.path.join(os.path.dirname(__file__), 'assets')
logger.debug(f'Assets path: {self.PATH}')
self.logo = QtGui.QIcon(os.path.join(self.PATH, 'logo.png'))
self.cursor_rotate = self.cursor_from_image('cursor_rotate', (6, 10))
def cursor_from_image(self, filename, hotspot):
img = QtGui.QImage(os.path.join(self.PATH, filename))
return QtGui.QCursor(
QtGui.QPixmap.fromImage(img), hotspot[0], hotspot[1])

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="43.466164mm"
height="43.466164mm"
viewBox="0 0 43.466164 43.466164"
version="1.1"
id="svg8"
sodipodi:docname="cursor_rotate_2.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/rbreu/code/python/beeref/git/beeref/assets/cursor_rotate_2.png"
inkscape:export-xdpi="19.870001"
inkscape:export-ydpi="19.870001">
<defs
id="defs2">
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter1107"
x="-0.026028319"
width="1.0520566"
y="-0.022264951"
height="1.0445299">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.3499727"
id="feGaussianBlur1109" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#8affc7"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="4.1593846"
inkscape:cx="79.730822"
inkscape:cy="62.010665"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1311"
inkscape:window-x="0"
inkscape:window-y="55"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-45.073624,-71.54261)">
<path
style="display:inline;opacity:0.27999998;fill:#060000;fill-opacity:1;stroke:#141212;stroke-width:2.26152635;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1107)"
d="m 67.706833,81.380683 0.01655,-6.509726 -14.216038,11.446056 14.128554,9.936714 0.0371,-6.139976 c 4.096961,0.008 7.48908,3.132026 7.4158,6.983515 -0.06361,3.343294 -3.168469,6.638494 -6.97212,6.633644 -3.803652,-0.005 -7.30065,-4.457607 -6.273868,-7.248175 l -8.267827,-5.990456 c -5.40715,13.579601 6.14067,22.168771 14.232955,22.102821 8.19885,-0.0668 16.472123,-6.26946 16.642924,-15.237257 C 84.626131,88.1555 77.590725,80.975864 67.706831,81.380683 Z"
id="path895-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccszccssc"
inkscape:label="shadow"
transform="matrix(1.0214918,0,0,1.0214918,-2.1351019,-1.5473716)" />
<path
style="display:inline;fill:#060000;fill-opacity:1;stroke:#f6f0f0;stroke-width:2.3101306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 66.035745,79.802895 0.01691,-6.649632 -14.521566,11.692052 14.432202,10.150272 0.0379,-6.271935 c 4.185012,0.0082 7.650034,3.199339 7.575179,7.133603 -0.06498,3.415148 -3.236565,6.781175 -7.121963,6.776215 -3.885399,-0.005 -7.457554,-4.553411 -6.408705,-7.403953 l -8.445517,-6.119202 c -5.523359,13.871445 6.272644,22.645215 14.538846,22.577855 8.375058,-0.0682 16.826137,-6.40421 17.00061,-15.564738 C 83.318677,86.723315 76.132066,79.389376 66.03575,79.802895 Z"
id="path895"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccszccssc"
inkscape:label="arrow" />
<g
id="g1090"
inkscape:label="guides"
style="display:none">
<ellipse
ry="14.478881"
rx="14.928679"
cy="95.189369"
cx="67.291611"
id="path1071"
style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#ff0000;stroke-width:0.19997676;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
r="7.8203492"
cy="95.24012"
cx="67.361153"
id="path1071-1"
style="display:inline;opacity:1;fill:none;fill-opacity:1;stroke:#ff0000;stroke-width:0.13681354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -35,7 +35,7 @@ parser.add_argument(
help='draw debug shapes for bounding rects and interactable areas')
class CommandlineArgs():
class CommandlineArgs:
"""Wrapper around argument parsing.
Checking for unknown arugments is configurable so that it can be
@ -52,10 +52,11 @@ class CommandlineArgs():
return cls._instance
def __init__(self, with_check=False):
if with_check:
self._args = parser.parse_args()
else:
self._args = parser.parse_known_args()[0]
if not hasattr(self, '_args'):
if with_check:
self._args = parser.parse_args()
else:
self._args = parser.parse_known_args()[0]
def __getattribute__(self, name):
if name == '_args':

View file

@ -21,6 +21,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QGraphicsItem
from beeref.assets import BeeAssets
from beeref import commands
from beeref.config import CommandlineArgs
from beeref import utils
@ -204,7 +205,7 @@ class SelectableMixin(BaseItemMixin):
self.setCursor(Qt.CursorShape.SizeBDiagCursor)
return
if self.get_rotate_bounds(corner).contains(event.pos()):
self.setCursor(Qt.CursorShape.ForbiddenCursor)
self.setCursor(BeeAssets().cursor_rotate)
return
self.setCursor(Qt.CursorShape.ArrowCursor)

14
tests/test_assets.py Normal file
View file

@ -0,0 +1,14 @@
from PyQt6 import QtGui
from beeref.assets import BeeAssets
from .base import BeeTestCase
class GetRectFromPointsTestCase(BeeTestCase):
def test_singleton(self):
assert BeeAssets() is BeeAssets()
assert BeeAssets().logo is BeeAssets().logo
def test_has_logo(self):
assert isinstance(BeeAssets().logo, QtGui.QIcon)

8
tests/test_config.py Normal file
View file

@ -0,0 +1,8 @@
from unittest.mock import patch
from beeref.config import CommandlineArgs
def test_singleton():
assert CommandlineArgs() is CommandlineArgs()
assert CommandlineArgs()._args is CommandlineArgs()._args

View file

@ -3,6 +3,7 @@ from unittest.mock import patch, MagicMock, PropertyMock
from PyQt6 import QtCore, QtGui
from PyQt6.QtCore import Qt
from beeref.assets import BeeAssets
from beeref.items import BeePixmapItem
from beeref.scene import BeeGraphicsScene
from beeref.selection import MultiSelectItem, RubberbandItem
@ -406,6 +407,14 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
self.item.setCursor.assert_called_once_with(
Qt.CursorShape.SizeBDiagCursor)
def test_hover_move_event_rotate(self):
self.item.setSelected(True)
self.item.SELECT_RESIZE_SIZE = 10
self.item.SELECT_ROTATE_SIZE = 10
self.event.pos = MagicMock(return_value=QtCore.QPointF(110, 90))
self.item.hoverMoveEvent(self.event)
self.item.setCursor.assert_called_once_with(BeeAssets().cursor_rotate)
def test_hover_move_event_not_in_handles(self):
self.item.setSelected(True)
self.event.pos = MagicMock(return_value=QtCore.QPointF(50, 50))