Skip to content

Commit

Permalink
Merge pull request #542 from gaphor/feedforward
Browse files Browse the repository at this point in the history
Feedforward
  • Loading branch information
amolenaar authored Dec 16, 2022
2 parents 215006c + adb908f commit c996f75
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 100 deletions.
7 changes: 1 addition & 6 deletions gaphas/aspect/handlemove.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# flake8: noqa F401
from gaphas.connector import ConnectionSinkType
from gaphas.handlemove import (
ElementHandleMove,
HandleMove,
ItemHandleMove,
item_at_point,
)
from gaphas.handlemove import HandleMove, ItemHandleMove, item_at_point
34 changes: 34 additions & 0 deletions gaphas/cursor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from functools import singledispatch
from typing import Optional

from gi.repository import Gtk

from gaphas.connector import Handle
from gaphas.item import Element, Item, Line
from gaphas.types import Pos

DEFAULT_CURSOR = "left_ptr" if Gtk.get_major_version() == 3 else "default"


@singledispatch
def cursor(item: Optional[Item], handle: Optional[Handle], pos: Pos) -> str:
return DEFAULT_CURSOR


ELEMENT_CURSORS = ("nw-resize", "ne-resize", "se-resize", "sw-resize")


@cursor.register
def element_hover(item: Element, handle: Optional[Handle], pos: Pos) -> str:
if handle:
index = item.handles().index(handle)
return ELEMENT_CURSORS[index] if index < 4 else DEFAULT_CURSOR
return DEFAULT_CURSOR


LINE_CURSOR = "fleur" if Gtk.get_major_version() == 3 else "move"


@cursor.register
def line_hover(item: Line, handle: Optional[Handle], pos: Pos) -> str:
return LINE_CURSOR if handle else DEFAULT_CURSOR
4 changes: 2 additions & 2 deletions gaphas/guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from gaphas.canvas import all_children
from gaphas.connector import Handle
from gaphas.handlemove import ElementHandleMove, HandleMove, ItemHandleMove
from gaphas.handlemove import HandleMove, ItemHandleMove
from gaphas.item import Element, Item, Line
from gaphas.move import ItemMove, Move
from gaphas.types import Pos
Expand Down Expand Up @@ -255,7 +255,7 @@ def stop_move(self, pos: Pos) -> None:


@HandleMove.register(Element)
class GuidedElementHandleMove(GuidedItemHandleMoveMixin, ElementHandleMove):
class GuidedElementHandleMove(GuidedItemHandleMoveMixin, ItemHandleMove):
pass


Expand Down
39 changes: 1 addition & 38 deletions gaphas/handlemove.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
from operator import itemgetter
from typing import Iterable, Optional, Sequence, Tuple

from gi.repository import Gdk, Gtk

from gaphas.connector import ConnectionSink, ConnectionSinkType, Connector
from gaphas.handle import Handle
from gaphas.item import Element, Item
from gaphas.item import Item
from gaphas.types import Pos
from gaphas.view import GtkView

Expand Down Expand Up @@ -112,41 +110,6 @@ def connect(self, pos: Pos) -> None:
HandleMove = singledispatch(ItemHandleMove)


@HandleMove.register(Element)
class ElementHandleMove(ItemHandleMove):
CURSORS = ("nw-resize", "ne-resize", "se-resize", "sw-resize")

def __init__(self, item: Item, handle: Handle, view: GtkView):
super().__init__(item, handle, view)
self.cursor: Optional[Gdk.Cursor] = None

def start_move(self, pos: Pos) -> None:
super().start_move(pos)
self.set_cursor()

def stop_move(self, pos: Pos) -> None:
self.reset_cursor()
super().stop_move(pos)

def set_cursor(self) -> None:
index = self.item.handles().index(self.handle)
if index < 4:
display = self.view.get_display()
if Gtk.get_major_version() == 3:
cursor = Gdk.Cursor.new_from_name(display, self.CURSORS[index])
self.cursor = self.view.get_window().get_cursor()
self.view.get_window().set_cursor(cursor)
else:
cursor = Gdk.Cursor.new_from_name(self.CURSORS[index])
self.cursor = self.view.get_cursor()
self.view.set_cursor(cursor)

def reset_cursor(self) -> None:
self.view.get_window().set_cursor(
self.cursor
) if Gtk.get_major_version() == 3 else self.view.set_cursor(self.cursor)


def item_distance(
view: GtkView,
pos: Pos,
Expand Down
64 changes: 43 additions & 21 deletions gaphas/painter/handlepainter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from typing import TYPE_CHECKING, Collection

from cairo import ANTIALIAS_NONE
from cairo import Context as CairoContext

from gaphas.item import Item
Expand All @@ -11,6 +10,13 @@
from gaphas.view import GtkView


# Colors from the GNOME Palette
RED_4 = (0.753, 0.110, 0.157)
ORANGE_4 = (0.902, 0.380, 0)
GREEN_4 = (0.180, 0.7608, 0.494)
BLUE_4 = (0.110, 0.443, 0.847)


class HandlePainter:
"""Draw handles of items that are marked as selected in the view."""

Expand All @@ -33,39 +39,27 @@ def _draw_handles(
assert model
cairo.save()
if not opacity:
opacity = 0.7 if item is view.selection.focused_item else 0.4

cairo.set_antialias(ANTIALIAS_NONE)
cairo.set_line_width(1)
opacity = 0.9 if item is view.selection.focused_item else 0.6

get_connection = model.connections.get_connection
for h in item.handles():
if not h.visible:
continue
# connected and not being moved, see HandleTool.on_button_press
if get_connection(h):
r, g, b = 1.0, 0.0, 0.0
color = RED_4
elif h.glued:
r, g, b = 1, 0.6, 0
color = ORANGE_4
elif h.movable:
r, g, b = 0, 1, 0
color = GREEN_4
else:
r, g, b = 0, 0, 1
color = BLUE_4

vx, vy = cairo.user_to_device(*item.matrix_i2c.transform_point(*h.pos))
cairo.set_source_rgba(*color, opacity)

draw_handle(cairo, vx, vy)

cairo.save()
cairo.identity_matrix()
cairo.translate(vx, vy)
cairo.rectangle(-4, -4, 8, 8)
cairo.set_source_rgba(r, g, b, opacity)
cairo.fill_preserve()
cairo.set_source_rgba(r / 4.0, g / 4.0, b / 4.0, opacity * 1.3)
cairo.stroke()
if h.connectable:
cairo.rectangle(-2, -2, 4, 4)
cairo.fill()
cairo.restore()
cairo.restore()

def paint(self, items: Collection[Item], cairo: CairoContext) -> None:
Expand All @@ -80,3 +74,31 @@ def paint(self, items: Collection[Item], cairo: CairoContext) -> None:
hovered = selection.hovered_item
if hovered and hovered not in selection.selected_items:
self._draw_handles(hovered, cairo, opacity=0.25)


def draw_handle(
cairo: CairoContext, vx: float, vy: float, size: float = 12.0, corner: float = 2.0
) -> None:
"""Draw a handle with rounded corners."""
radius = size / 2.0
lower_right = size - corner

pi_05 = 0.5 * 3.142
pi = 3.142
pi_15 = 1.5 * 3.142

cairo.save()
cairo.identity_matrix()
cairo.translate(vx - radius, vy - radius)

cairo.move_to(0.0, corner)
cairo.arc(corner, corner, corner, pi, pi_15)
cairo.line_to(lower_right, 0.0)
cairo.arc(lower_right, corner, corner, pi_15, 0)
cairo.line_to(size, lower_right)
cairo.arc(lower_right, lower_right, corner, 0, pi_05)
cairo.line_to(corner, size)
cairo.arc(corner, lower_right, corner, pi_05, pi)
cairo.close_path()
cairo.fill()
cairo.restore()
35 changes: 22 additions & 13 deletions gaphas/segment.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"""Allow for easily adding segments to lines."""

from functools import singledispatch

from cairo import ANTIALIAS_NONE
from typing import Optional

from gaphas.connector import Handle, LinePort
from gaphas.cursor import DEFAULT_CURSOR, LINE_CURSOR, cursor, line_hover
from gaphas.geometry import distance_point_point_fast
from gaphas.item import Line, matrix_i2i
from gaphas.model import Model
from gaphas.painter.handlepainter import GREEN_4, draw_handle
from gaphas.selection import Selection
from gaphas.solver import WEAK
from gaphas.types import Pos


@singledispatch
Expand Down Expand Up @@ -204,14 +206,21 @@ def paint(self, _items, cairo):
cx = (p1.x + p2.x) / 2
cy = (p1.y + p2.y) / 2
vx, vy = cairo.user_to_device(*item.matrix_i2c.transform_point(cx, cy))
cairo.save()
cairo.set_antialias(ANTIALIAS_NONE)
cairo.identity_matrix()
cairo.translate(vx, vy)
cairo.rectangle(-3, -3, 6, 6)
cairo.set_source_rgba(0, 0.5, 0, 0.4)
cairo.fill_preserve()
cairo.set_source_rgba(0.25, 0.25, 0.25, 0.6)
cairo.set_line_width(1)
cairo.stroke()
cairo.restore()
cairo.set_source_rgba(*GREEN_4, 0.4)
draw_handle(cairo, vx, vy, size=9.0, corner=1.5)


@cursor.register
def line_segment_hover(item: Line, handle: Optional[Handle], pos: Pos) -> str:
if not handle:
handles = item.handles()
if any(
distance_point_point_fast(
pos, ((h1.pos.x + h2.pos.x) / 2, (h1.pos.y + h2.pos.y) / 2)
)
<= 4.5
for h1, h2 in zip(handles, handles[1:])
):
return LINE_CURSOR
return DEFAULT_CURSOR
return line_hover(item, handle, pos)
33 changes: 21 additions & 12 deletions gaphas/tool/hover.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from typing import Optional
from gi.repository import Gdk, Gtk

from gi.repository import Gtk

from gaphas.item import Item
from gaphas.tool.itemtool import handle_at_point, item_at_point
from gaphas.types import Pos
from gaphas.cursor import cursor
from gaphas.tool.itemtool import find_item_and_handle_at_point
from gaphas.view import GtkView


Expand All @@ -21,9 +18,21 @@ def hover_tool(view: GtkView) -> Gtk.EventController:

def on_motion(ctrl, x, y):
view = ctrl.get_widget()
view.selection.hovered_item = find_item_at_point(view, (x, y))


def find_item_at_point(view: GtkView, pos: Pos) -> Optional[Item]:
item, handle = handle_at_point(view, pos)
return item or next(item_at_point(view, pos), None) # type: ignore[call-overload]
pos = (x, y)
item, handle = find_item_and_handle_at_point(view, pos)
view.selection.hovered_item = item

if item:
v2i = view.get_matrix_v2i(item)
pos = v2i.transform_point(x, y)
set_cursor(view, cursor(item, handle, pos))


def set_cursor(view, cursor_name):
if Gtk.get_major_version() == 3:
display = view.get_display()
cursor = Gdk.Cursor.new_from_name(display, cursor_name)
view.get_window().set_cursor(cursor)
else:
cursor = Gdk.Cursor.new_from_name(cursor_name)
view.set_cursor(cursor)
2 changes: 1 addition & 1 deletion gaphas/view/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from gaphas import model
from gaphas.selection import Selection
from gaphas.view.gtkview import DEFAULT_CURSOR, GtkView
from gaphas.view.gtkview import GtkView
7 changes: 0 additions & 7 deletions gaphas/view/gtkview.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
DEBUG_DRAW_BOUNDING_BOX = False
DEBUG_DRAW_QUADTREE = False

# The default cursor (use in case of a cursor reset)
DEFAULT_CURSOR = (
Gdk.CursorType.LEFT_PTR
if Gtk.get_major_version() == 3
else Gdk.Cursor.new_from_name("default")
)

# The tolerance for Cairo. Bigger values increase speed and reduce accuracy
# (default: 0.1)
PAINT_TOLERANCE = 0.8
Expand Down

0 comments on commit c996f75

Please sign in to comment.