From e6e391ad06233bde479543cfe2a51623501389ca Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 25 Oct 2020 13:51:24 +0100 Subject: [PATCH 1/5] Split painter module in smaller ones --- gaphas/freehand.py | 3 +- gaphas/painter.py | 351 --------------------------- gaphas/painter/__init__.py | 18 ++ gaphas/painter/boundingboxpainter.py | 123 ++++++++++ gaphas/painter/chain.py | 32 +++ gaphas/painter/focuseditempainter.py | 13 + gaphas/painter/handlepainter.py | 68 ++++++ gaphas/painter/itempainter.py | 77 ++++++ gaphas/painter/painter.py | 21 ++ gaphas/painter/toolpainter.py | 15 ++ 10 files changed, 369 insertions(+), 352 deletions(-) delete mode 100644 gaphas/painter.py create mode 100644 gaphas/painter/__init__.py create mode 100644 gaphas/painter/boundingboxpainter.py create mode 100644 gaphas/painter/chain.py create mode 100644 gaphas/painter/focuseditempainter.py create mode 100644 gaphas/painter/handlepainter.py create mode 100644 gaphas/painter/itempainter.py create mode 100644 gaphas/painter/painter.py create mode 100644 gaphas/painter/toolpainter.py diff --git a/gaphas/freehand.py b/gaphas/freehand.py index 1be289a..2dae2e2 100644 --- a/gaphas/freehand.py +++ b/gaphas/freehand.py @@ -12,7 +12,8 @@ See: http://stevehanov.ca/blog/index.php?id=33 and from math import sqrt from random import Random -from gaphas.painter import Context, Painter +from gaphas.canvas import Context +from gaphas.painter import Painter class FreeHandCairoContext: diff --git a/gaphas/painter.py b/gaphas/painter.py deleted file mode 100644 index 3636950..0000000 --- a/gaphas/painter.py +++ /dev/null @@ -1,351 +0,0 @@ -"""The painter module provides different painters for parts of the canvas. - -Painters can be swapped in and out. - -Each painter takes care of a layer in the canvas (such as grid, items -and handles). -""" -from cairo import ANTIALIAS_NONE, LINE_JOIN_ROUND - -from gaphas.aspect import PaintFocused -from gaphas.canvas import Context -from gaphas.geometry import Rectangle - -DEBUG_DRAW_BOUNDING_BOX = False - -# The tolerance for Cairo. Bigger values increase speed and reduce accuracy -# (default: 0.1) -TOLERANCE = 0.8 - - -class Painter: - """Painter interface.""" - - def __init__(self, view=None): - self.view = view - - def set_view(self, view): - self.view = view - - def paint(self, context): - """Do the paint action (called from the View).""" - pass - - -class PainterChain(Painter): - """Chain up a set of painters. - - like ToolChain. - """ - - def __init__(self, view=None): - super().__init__(view) - self._painters = [] - - def set_view(self, view): - self.view = view - for painter in self._painters: - painter.set_view(self.view) - - def append(self, painter): - """Add a painter to the list of painters.""" - self._painters.append(painter) - painter.set_view(self.view) - return self - - def prepend(self, painter): - """Add a painter to the beginning of the list of painters.""" - self._painters.insert(0, painter) - - def paint(self, context): - """See Painter.paint().""" - for painter in self._painters: - painter.paint(context) - - -class DrawContext(Context): - """Special context for draw()'ing the item. - - The draw-context contains stuff like the cairo context and - properties like selected and focused. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - -class ItemPainter(Painter): - - draw_all = False - - def draw_item(self, item, cairo): - view = self.view - cairo.save() - try: - cairo.set_matrix(view.matrix.to_cairo()) - cairo.transform(view.canvas.get_matrix_i2c(item).to_cairo()) - - item.draw( - DrawContext( - painter=self, - cairo=cairo, - _item=item, - selected=(item in view.selection.selected_items), - focused=(item is view.selection.focused_item), - hovered=(item is view.selection.hovered_item), - dropzone=(item is view.selection.dropzone_item), - draw_all=self.draw_all, - ) - ) - - finally: - cairo.restore() - - def draw_items(self, items, cairo): - """Draw the items.""" - for item in items: - self.draw_item(item, cairo) - if DEBUG_DRAW_BOUNDING_BOX: - self._draw_bounds(item, cairo) - - def _draw_bounds(self, item, cairo): - view = self.view - try: - b = view.get_item_bounding_box(item) - except KeyError: - pass # No bounding box right now.. - else: - cairo.save() - cairo.identity_matrix() - cairo.set_source_rgb(0.8, 0, 0) - cairo.set_line_width(1.0) - cairo.rectangle(*b) - cairo.stroke() - cairo.restore() - - def paint(self, context): - cairo = context.cairo - cairo.set_tolerance(TOLERANCE) - cairo.set_line_join(LINE_JOIN_ROUND) - self.draw_items(context.items, cairo) - - -class CairoBoundingBoxContext: - """Delegate all calls to the wrapped CairoBoundingBoxContext, intercept - ``stroke()``, ``fill()`` and a few others so the bounding box of the item - involved can be calculated.""" - - def __init__(self, cairo): - self._cairo = cairo - self._bounds = None # a Rectangle object - - def __getattr__(self, key): - return getattr(self._cairo, key) - - def get_bounds(self): - """Return the bounding box.""" - return self._bounds or Rectangle() - - def _update_bounds(self, bounds): - if bounds: - if not self._bounds: - self._bounds = bounds - else: - self._bounds += bounds - - def _extents(self, extents_func, line_width=False): - """Calculate the bounding box for a given drawing operation. - - if ``line_width`` is True, the current line-width is taken into - account. - """ - cr = self._cairo - cr.save() - cr.identity_matrix() - x0, y0, x1, y1 = extents_func() - b = Rectangle(x0, y0, x1=x1, y1=y1) - cr.restore() - if b and line_width: - # Do this after the restore(), so we can get the proper width. - lw = cr.get_line_width() / 2 - d = cr.user_to_device_distance(lw, lw) - b.expand(d[0] + d[1]) - self._update_bounds(b) - return b - - def fill(self, b=None): - """Interceptor for Cairo drawing method.""" - cr = self._cairo - if not b: - b = self._extents(cr.fill_extents) - cr.fill() - - def fill_preserve(self, b=None): - """Interceptor for Cairo drawing method.""" - if not b: - cr = self._cairo - b = self._extents(cr.fill_extents) - - def stroke(self, b=None): - """Interceptor for Cairo drawing method.""" - cr = self._cairo - if not b: - b = self._extents(cr.stroke_extents, line_width=True) - cr.stroke() - - def stroke_preserve(self, b=None): - """Interceptor for Cairo drawing method.""" - if not b: - cr = self._cairo - b = self._extents(cr.stroke_extents, line_width=True) - - def show_text(self, utf8, b=None): - """Interceptor for Cairo drawing method.""" - cr = self._cairo - if not b: - x, y = cr.get_current_point() - e = cr.text_extents(utf8) - x0, y0 = cr.user_to_device(x + e[0], y + e[1]) - x1, y1 = cr.user_to_device(x + e[0] + e[2], y + e[1] + e[3]) - b = Rectangle(x0, y0, x1=x1, y1=y1) - self._update_bounds(b) - cr.show_text(utf8) - - -class BoundingBoxPainter(Painter): - """This specific case of an ItemPainter is used to calculate the bounding - boxes (in canvas coordinates) for the items.""" - - draw_all = True - - def __init__(self, item_painter=None, view=None): - super().__init__(view) - self.item_painter = item_painter or ItemPainter(view) - - def set_view(self, view): - super().set_view(view) - self.item_painter.set_view(view) - - def draw_item(self, item, cairo): - cairo = CairoBoundingBoxContext(cairo) - self.item_painter.draw_item(item, cairo) - bounds = cairo.get_bounds() - - # Update bounding box with handles. - view = self.view - i2v = view.get_matrix_i2v(item).transform_point - for h in item.handles(): - cx, cy = i2v(*h.pos) - bounds += (cx - 5, cy - 5, 9, 9) - - bounds.expand(1) - view.set_item_bounding_box(item, bounds) - - def draw_items(self, items, cairo): - """Draw the items.""" - for item in items: - self.draw_item(item, cairo) - - def paint(self, context): - self.draw_items(context.items, context.cairo) - - -class HandlePainter(Painter): - """Draw handles of items that are marked as selected in the view.""" - - def _draw_handles(self, item, cairo, opacity=None, inner=False): - """Draw handles for an item. - - The handles are drawn in non-antialiased mode for clarity. - """ - view = self.view - cairo.save() - i2v = view.get_matrix_i2v(item) - if not opacity: - opacity = (item is view.selection.focused_item) and 0.7 or 0.4 - - cairo.set_line_width(1) - - get_connection = view.canvas.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 - # connected but being moved, see HandleTool.on_button_press - elif get_connection(h): - r, g, b = 1, 0.6, 0 - elif h.movable: - r, g, b = 0, 1, 0 - else: - r, g, b = 0, 0, 1 - - cairo.identity_matrix() - cairo.set_antialias(ANTIALIAS_NONE) - cairo.translate(*i2v.transform_point(*h.pos)) - cairo.rectangle(-4, -4, 8, 8) - if inner: - cairo.rectangle(-3, -3, 6, 6) - cairo.set_source_rgba(r, g, b, opacity) - cairo.fill_preserve() - if h.connectable: - cairo.move_to(-2, -2) - cairo.line_to(2, 3) - cairo.move_to(2, -2) - cairo.line_to(-2, 3) - cairo.set_source_rgba(r / 4.0, g / 4.0, b / 4.0, opacity * 1.3) - cairo.stroke() - cairo.restore() - - def paint(self, context): - view = self.view - canvas = view.canvas - cairo = context.cairo - selection = view.selection - # Order matters here: - for item in canvas.sort(selection.selected_items): - self._draw_handles(item, cairo) - # Draw nice opaque handles when hovering an item: - item = selection.hovered_item - if item and item not in selection.selected_items: - self._draw_handles(item, cairo, opacity=0.25) - item = selection.dropzone_item - if item and item not in selection.selected_items: - self._draw_handles(item, cairo, opacity=0.25, inner=True) - - -class ToolPainter(Painter): - """ToolPainter allows the Tool defined on a view to do some special - drawing.""" - - def paint(self, context): - view = self.view - if view.tool: - cairo = context.cairo - cairo.save() - cairo.identity_matrix() - view.tool.draw(context) - cairo.restore() - - -class FocusedItemPainter(Painter): - """This painter allows for drawing on top of all the other layers for the - focused item.""" - - def paint(self, context): - view = self.view - item = view.selection.hovered_item - if item and item is view.selection.focused_item: - PaintFocused(item, view).paint(context) - - -def DefaultPainter(view=None): - """Default painter, containing item, handle and tool painters.""" - return ( - PainterChain(view) - .append(ItemPainter()) - .append(HandlePainter()) - .append(FocusedItemPainter()) - .append(ToolPainter()) - ) diff --git a/gaphas/painter/__init__.py b/gaphas/painter/__init__.py new file mode 100644 index 0000000..8a8642c --- /dev/null +++ b/gaphas/painter/__init__.py @@ -0,0 +1,18 @@ +from gaphas.painter.boundingboxpainter import BoundingBoxPainter +from gaphas.painter.chain import PainterChain +from gaphas.painter.focuseditempainter import FocusedItemPainter +from gaphas.painter.handlepainter import HandlePainter +from gaphas.painter.itempainter import ItemPainter +from gaphas.painter.painter import Painter +from gaphas.painter.toolpainter import ToolPainter + + +def DefaultPainter(view=None): + """Default painter, containing item, handle and tool painters.""" + return ( + PainterChain(view) + .append(ItemPainter()) + .append(HandlePainter()) + .append(FocusedItemPainter()) + .append(ToolPainter()) + ) diff --git a/gaphas/painter/boundingboxpainter.py b/gaphas/painter/boundingboxpainter.py new file mode 100644 index 0000000..61eb0da --- /dev/null +++ b/gaphas/painter/boundingboxpainter.py @@ -0,0 +1,123 @@ +from gaphas.geometry import Rectangle +from gaphas.painter.itempainter import ItemPainter +from gaphas.painter.painter import Painter + + +class CairoBoundingBoxContext: + """Delegate all calls to the wrapped CairoBoundingBoxContext, intercept + ``stroke()``, ``fill()`` and a few others so the bounding box of the item + involved can be calculated.""" + + def __init__(self, cairo): + self._cairo = cairo + self._bounds = None # a Rectangle object + + def __getattr__(self, key): + return getattr(self._cairo, key) + + def get_bounds(self): + """Return the bounding box.""" + return self._bounds or Rectangle() + + def _update_bounds(self, bounds): + if bounds: + if not self._bounds: + self._bounds = bounds + else: + self._bounds += bounds + + def _extents(self, extents_func, line_width=False): + """Calculate the bounding box for a given drawing operation. + + if ``line_width`` is True, the current line-width is taken into + account. + """ + cr = self._cairo + cr.save() + cr.identity_matrix() + x0, y0, x1, y1 = extents_func() + b = Rectangle(x0, y0, x1=x1, y1=y1) + cr.restore() + if b and line_width: + # Do this after the restore(), so we can get the proper width. + lw = cr.get_line_width() / 2 + d = cr.user_to_device_distance(lw, lw) + b.expand(d[0] + d[1]) + self._update_bounds(b) + return b + + def fill(self, b=None): + """Interceptor for Cairo drawing method.""" + cr = self._cairo + if not b: + b = self._extents(cr.fill_extents) + cr.fill() + + def fill_preserve(self, b=None): + """Interceptor for Cairo drawing method.""" + if not b: + cr = self._cairo + b = self._extents(cr.fill_extents) + + def stroke(self, b=None): + """Interceptor for Cairo drawing method.""" + cr = self._cairo + if not b: + b = self._extents(cr.stroke_extents, line_width=True) + cr.stroke() + + def stroke_preserve(self, b=None): + """Interceptor for Cairo drawing method.""" + if not b: + cr = self._cairo + b = self._extents(cr.stroke_extents, line_width=True) + + def show_text(self, utf8, b=None): + """Interceptor for Cairo drawing method.""" + cr = self._cairo + if not b: + x, y = cr.get_current_point() + e = cr.text_extents(utf8) + x0, y0 = cr.user_to_device(x + e[0], y + e[1]) + x1, y1 = cr.user_to_device(x + e[0] + e[2], y + e[1] + e[3]) + b = Rectangle(x0, y0, x1=x1, y1=y1) + self._update_bounds(b) + cr.show_text(utf8) + + +class BoundingBoxPainter(Painter): + """This specific case of an ItemPainter is used to calculate the bounding + boxes (in canvas coordinates) for the items.""" + + draw_all = True + + def __init__(self, item_painter=None, view=None): + super().__init__(view) + self.item_painter = item_painter or ItemPainter(view) + + def set_view(self, view): + super().set_view(view) + self.item_painter.set_view(view) + + def draw_item(self, item, cairo): + cairo = CairoBoundingBoxContext(cairo) + self.item_painter.draw_item(item, cairo) + bounds = cairo.get_bounds() + + # Update bounding box with handles. + view = self.view + i2v = view.get_matrix_i2v(item).transform_point + for h in item.handles(): + cx, cy = i2v(*h.pos) + bounds += (cx - 5, cy - 5, 9, 9) + + bounds.expand(1) + view.set_item_bounding_box(item, bounds) + + def draw_items(self, items, cairo): + """Draw the items.""" + for item in items: + self.draw_item(item, cairo) + + def paint(self, context): + self.draw_items(context.items, context.cairo) diff --git a/gaphas/painter/chain.py b/gaphas/painter/chain.py new file mode 100644 index 0000000..aa99335 --- /dev/null +++ b/gaphas/painter/chain.py @@ -0,0 +1,32 @@ +from gaphas.painter.painter import Painter + + +class PainterChain(Painter): + """Chain up a set of painters. + + like ToolChain. + """ + + def __init__(self, view=None): + super().__init__(view) + self._painters = [] + + def set_view(self, view): + self.view = view + for painter in self._painters: + painter.set_view(self.view) + + def append(self, painter): + """Add a painter to the list of painters.""" + self._painters.append(painter) + painter.set_view(self.view) + return self + + def prepend(self, painter): + """Add a painter to the beginning of the list of painters.""" + self._painters.insert(0, painter) + + def paint(self, context): + """See Painter.paint().""" + for painter in self._painters: + painter.paint(context) diff --git a/gaphas/painter/focuseditempainter.py b/gaphas/painter/focuseditempainter.py new file mode 100644 index 0000000..28100cb --- /dev/null +++ b/gaphas/painter/focuseditempainter.py @@ -0,0 +1,13 @@ +from gaphas.aspect import PaintFocused +from gaphas.painter.painter import Painter + + +class FocusedItemPainter(Painter): + """This painter allows for drawing on top of all the other layers for the + focused item.""" + + def paint(self, context): + view = self.view + item = view.selection.hovered_item + if item and item is view.selection.focused_item: + PaintFocused(item, view).paint(context) diff --git a/gaphas/painter/handlepainter.py b/gaphas/painter/handlepainter.py new file mode 100644 index 0000000..6f79086 --- /dev/null +++ b/gaphas/painter/handlepainter.py @@ -0,0 +1,68 @@ +from cairo import ANTIALIAS_NONE + +from gaphas.painter.painter import Painter + + +class HandlePainter(Painter): + """Draw handles of items that are marked as selected in the view.""" + + def _draw_handles(self, item, cairo, opacity=None, inner=False): + """Draw handles for an item. + + The handles are drawn in non-antialiased mode for clarity. + """ + view = self.view + cairo.save() + i2v = view.get_matrix_i2v(item) + if not opacity: + opacity = (item is view.selection.focused_item) and 0.7 or 0.4 + + cairo.set_line_width(1) + + get_connection = view.canvas.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 + # connected but being moved, see HandleTool.on_button_press + elif get_connection(h): + r, g, b = 1, 0.6, 0 + elif h.movable: + r, g, b = 0, 1, 0 + else: + r, g, b = 0, 0, 1 + + cairo.identity_matrix() + cairo.set_antialias(ANTIALIAS_NONE) + cairo.translate(*i2v.transform_point(*h.pos)) + cairo.rectangle(-4, -4, 8, 8) + if inner: + cairo.rectangle(-3, -3, 6, 6) + cairo.set_source_rgba(r, g, b, opacity) + cairo.fill_preserve() + if h.connectable: + cairo.move_to(-2, -2) + cairo.line_to(2, 3) + cairo.move_to(2, -2) + cairo.line_to(-2, 3) + cairo.set_source_rgba(r / 4.0, g / 4.0, b / 4.0, opacity * 1.3) + cairo.stroke() + cairo.restore() + + def paint(self, context): + view = self.view + canvas = view.canvas + cairo = context.cairo + selection = view.selection + # Order matters here: + for item in canvas.sort(selection.selected_items): + self._draw_handles(item, cairo) + # Draw nice opaque handles when hovering an item: + item = selection.hovered_item + if item and item not in selection.selected_items: + self._draw_handles(item, cairo, opacity=0.25) + item = selection.dropzone_item + if item and item not in selection.selected_items: + self._draw_handles(item, cairo, opacity=0.25, inner=True) diff --git a/gaphas/painter/itempainter.py b/gaphas/painter/itempainter.py new file mode 100644 index 0000000..7b451f2 --- /dev/null +++ b/gaphas/painter/itempainter.py @@ -0,0 +1,77 @@ +from cairo import LINE_JOIN_ROUND + +from gaphas.canvas import Context +from gaphas.painter.painter import Painter + +DEBUG_DRAW_BOUNDING_BOX = False + +# The tolerance for Cairo. Bigger values increase speed and reduce accuracy +# (default: 0.1) +TOLERANCE = 0.8 + + +class DrawContext(Context): + """Special context for draw()'ing the item. + + The draw-context contains stuff like the cairo context and + properties like selected and focused. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class ItemPainter(Painter): + + draw_all = False + + def draw_item(self, item, cairo): + view = self.view + cairo.save() + try: + cairo.set_matrix(view.matrix.to_cairo()) + cairo.transform(view.canvas.get_matrix_i2c(item).to_cairo()) + + item.draw( + DrawContext( + painter=self, + cairo=cairo, + _item=item, + selected=(item in view.selection.selected_items), + focused=(item is view.selection.focused_item), + hovered=(item is view.selection.hovered_item), + dropzone=(item is view.selection.dropzone_item), + draw_all=self.draw_all, + ) + ) + + finally: + cairo.restore() + + def draw_items(self, items, cairo): + """Draw the items.""" + for item in items: + self.draw_item(item, cairo) + if DEBUG_DRAW_BOUNDING_BOX: + self._draw_bounds(item, cairo) + + def _draw_bounds(self, item, cairo): + view = self.view + try: + b = view.get_item_bounding_box(item) + except KeyError: + pass # No bounding box right now.. + else: + cairo.save() + cairo.identity_matrix() + cairo.set_source_rgb(0.8, 0, 0) + cairo.set_line_width(1.0) + cairo.rectangle(*b) + cairo.stroke() + cairo.restore() + + def paint(self, context): + cairo = context.cairo + cairo.set_tolerance(TOLERANCE) + cairo.set_line_join(LINE_JOIN_ROUND) + self.draw_items(context.items, cairo) diff --git a/gaphas/painter/painter.py b/gaphas/painter/painter.py new file mode 100644 index 0000000..5d01197 --- /dev/null +++ b/gaphas/painter/painter.py @@ -0,0 +1,21 @@ +"""The painter module provides different painters for parts of the canvas. + +Painters can be swapped in and out. + +Each painter takes care of a layer in the canvas (such as grid, items +and handles). +""" + + +class Painter: + """Painter interface.""" + + def __init__(self, view=None): + self.view = view + + def set_view(self, view): + self.view = view + + def paint(self, context): + """Do the paint action (called from the View).""" + pass diff --git a/gaphas/painter/toolpainter.py b/gaphas/painter/toolpainter.py new file mode 100644 index 0000000..c58f0db --- /dev/null +++ b/gaphas/painter/toolpainter.py @@ -0,0 +1,15 @@ +from gaphas.painter.painter import Painter + + +class ToolPainter(Painter): + """ToolPainter allows the Tool defined on a view to do some special + drawing.""" + + def paint(self, context): + view = self.view + if view.tool: + cairo = context.cairo + cairo.save() + cairo.identity_matrix() + view.tool.draw(context) + cairo.restore() From 79eb85982a78606447592defeeb482fb688086de Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 25 Oct 2020 14:13:11 +0100 Subject: [PATCH 2/5] Set view at construction time for Painters Can no longer set view afterwards. --- examples/demo.py | 16 +++++++++------- examples/simple-box.py | 2 -- gaphas/freehand.py | 7 +------ gaphas/painter/__init__.py | 12 ++++++------ gaphas/painter/boundingboxpainter.py | 4 ---- gaphas/painter/chain.py | 14 ++------------ gaphas/painter/painter.py | 3 --- gaphas/tool.py | 2 +- gaphas/view/view.py | 2 -- 9 files changed, 19 insertions(+), 43 deletions(-) diff --git a/examples/demo.py b/examples/demo.py index 08898e8..f5275f5 100755 --- a/examples/demo.py +++ b/examples/demo.py @@ -112,12 +112,14 @@ def create_window(canvas, title, zoom=1.0): # noqa too complex view = GtkView() view.painter = ( PainterChain() - .append(FreeHandPainter(ItemPainter())) - .append(HandlePainter()) - .append(FocusedItemPainter()) - .append(ToolPainter()) + .append(FreeHandPainter(ItemPainter(view), view)) + .append(HandlePainter(view)) + .append(FocusedItemPainter(view)) + .append(ToolPainter(view)) + ) + view.bounding_box_painter = BoundingBoxPainter( + FreeHandPainter(ItemPainter(view), view), view=view ) - view.bounding_box_painter = BoundingBoxPainter(FreeHandPainter(ItemPainter())) w = Gtk.Window() w.set_title(title) w.set_default_size(400, 120) @@ -235,7 +237,7 @@ def create_window(canvas, title, zoom=1.0): # noqa too complex def on_write_demo_png_clicked(button): svgview = View(view.canvas) - svgview.painter = ItemPainter() + svgview.painter = ItemPainter(svgview) # Update bounding boxes with a temporary CairoContext # (used for stuff like calculating font metrics) @@ -263,7 +265,7 @@ def create_window(canvas, title, zoom=1.0): # noqa too complex def on_write_demo_svg_clicked(button): svgview = View(view.canvas) - svgview.painter = ItemPainter() + svgview.painter = ItemPainter(svgview) # Update bounding boxes with a temporaly CairoContext # (used for stuff like calculating font metrics) diff --git a/examples/simple-box.py b/examples/simple-box.py index 1942a42..4529477 100755 --- a/examples/simple-box.py +++ b/examples/simple-box.py @@ -5,7 +5,6 @@ import gi from examples.exampleitems import Box from gaphas import Canvas, GtkView from gaphas.item import Line -from gaphas.painter import DefaultPainter # fmt: off gi.require_version("Gtk", "3.0") # noqa: isort:skip @@ -16,7 +15,6 @@ from gi.repository import Gtk # noqa: isort:skip def create_canvas(canvas, title): # Setup drawing window view = GtkView() - view.painter = DefaultPainter() view.canvas = canvas window = Gtk.Window() window.set_title(title) diff --git a/gaphas/freehand.py b/gaphas/freehand.py index 2dae2e2..6e26781 100644 --- a/gaphas/freehand.py +++ b/gaphas/freehand.py @@ -133,14 +133,9 @@ class FreeHandCairoContext: class FreeHandPainter(Painter): - def __init__(self, subpainter, sloppiness=1.0, view=None): + def __init__(self, subpainter, view, sloppiness=1.0): self.subpainter = subpainter self.sloppiness = sloppiness - if view: - self.set_view(view) - - def set_view(self, view): - self.subpainter.set_view(view) def draw_item(self, item, cairo): # Bounding box painter requires painting per item diff --git a/gaphas/painter/__init__.py b/gaphas/painter/__init__.py index 8a8642c..4f52688 100644 --- a/gaphas/painter/__init__.py +++ b/gaphas/painter/__init__.py @@ -7,12 +7,12 @@ from gaphas.painter.painter import Painter from gaphas.painter.toolpainter import ToolPainter -def DefaultPainter(view=None): +def DefaultPainter(view): """Default painter, containing item, handle and tool painters.""" return ( - PainterChain(view) - .append(ItemPainter()) - .append(HandlePainter()) - .append(FocusedItemPainter()) - .append(ToolPainter()) + PainterChain() + .append(ItemPainter(view)) + .append(HandlePainter(view)) + .append(FocusedItemPainter(view)) + .append(ToolPainter(view)) ) diff --git a/gaphas/painter/boundingboxpainter.py b/gaphas/painter/boundingboxpainter.py index 61eb0da..80793e5 100644 --- a/gaphas/painter/boundingboxpainter.py +++ b/gaphas/painter/boundingboxpainter.py @@ -95,10 +95,6 @@ class BoundingBoxPainter(Painter): super().__init__(view) self.item_painter = item_painter or ItemPainter(view) - def set_view(self, view): - super().set_view(view) - self.item_painter.set_view(view) - def draw_item(self, item, cairo): cairo = CairoBoundingBoxContext(cairo) self.item_painter.draw_item(item, cairo) diff --git a/gaphas/painter/chain.py b/gaphas/painter/chain.py index aa99335..74470e3 100644 --- a/gaphas/painter/chain.py +++ b/gaphas/painter/chain.py @@ -1,25 +1,15 @@ -from gaphas.painter.painter import Painter - - -class PainterChain(Painter): +class PainterChain: """Chain up a set of painters. like ToolChain. """ - def __init__(self, view=None): - super().__init__(view) + def __init__(self): self._painters = [] - def set_view(self, view): - self.view = view - for painter in self._painters: - painter.set_view(self.view) - def append(self, painter): """Add a painter to the list of painters.""" self._painters.append(painter) - painter.set_view(self.view) return self def prepend(self, painter): diff --git a/gaphas/painter/painter.py b/gaphas/painter/painter.py index 5d01197..68fb933 100644 --- a/gaphas/painter/painter.py +++ b/gaphas/painter/painter.py @@ -13,9 +13,6 @@ class Painter: def __init__(self, view=None): self.view = view - def set_view(self, view): - self.view = view - def paint(self, context): """Do the paint action (called from the View).""" pass diff --git a/gaphas/tool.py b/gaphas/tool.py index 6aa5ac4..ce93f80 100644 --- a/gaphas/tool.py +++ b/gaphas/tool.py @@ -625,7 +625,7 @@ class PlacementTool(Tool): new_item = self._create_item(pos) # Enforce matrix update, as a good matrix is required for the handle # positioning: - canvas.get_matrix_i2c(new_item, calculate=True) + canvas.get_matrix_i2c(new_item) self._new_item = new_item view.selection.set_focused_item(new_item) diff --git a/gaphas/view/view.py b/gaphas/view/view.py index 0fc7c7c..1ee08e4 100644 --- a/gaphas/view/view.py +++ b/gaphas/view/view.py @@ -45,14 +45,12 @@ class View: Painters should implement painter.Painter. """ self._painter = painter - painter.set_view(self) painter = property(lambda s: s._painter, _set_painter) def _set_bounding_box_painter(self, painter): """Set the painter to use for bounding box calculations.""" self._bounding_box_painter = painter - painter.set_view(self) bounding_box_painter = property( lambda s: s._bounding_box_painter, _set_bounding_box_painter From ef9ba7f1521bc333bb94750c02f33a49a56562f5 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 25 Oct 2020 14:26:00 +0100 Subject: [PATCH 3/5] Turn Painter into a protocol --- gaphas/painter/boundingboxpainter.py | 6 +++--- gaphas/painter/focuseditempainter.py | 7 +++++-- gaphas/painter/handlepainter.py | 8 +++++--- gaphas/painter/itempainter.py | 7 +++++-- gaphas/painter/painter.py | 7 +++---- gaphas/painter/toolpainter.py | 9 +++++---- gaphas/view/view.py | 12 +++++++----- poetry.lock | 4 ++-- pyproject.toml | 1 + 9 files changed, 36 insertions(+), 25 deletions(-) diff --git a/gaphas/painter/boundingboxpainter.py b/gaphas/painter/boundingboxpainter.py index 80793e5..ad1e29c 100644 --- a/gaphas/painter/boundingboxpainter.py +++ b/gaphas/painter/boundingboxpainter.py @@ -1,6 +1,5 @@ from gaphas.geometry import Rectangle from gaphas.painter.itempainter import ItemPainter -from gaphas.painter.painter import Painter class CairoBoundingBoxContext: @@ -85,14 +84,15 @@ class CairoBoundingBoxContext: cr.show_text(utf8) -class BoundingBoxPainter(Painter): +class BoundingBoxPainter: """This specific case of an ItemPainter is used to calculate the bounding boxes (in canvas coordinates) for the items.""" draw_all = True def __init__(self, item_painter=None, view=None): - super().__init__(view) + assert view + self.view = view self.item_painter = item_painter or ItemPainter(view) def draw_item(self, item, cairo): diff --git a/gaphas/painter/focuseditempainter.py b/gaphas/painter/focuseditempainter.py index 28100cb..46bfe12 100644 --- a/gaphas/painter/focuseditempainter.py +++ b/gaphas/painter/focuseditempainter.py @@ -1,11 +1,14 @@ from gaphas.aspect import PaintFocused -from gaphas.painter.painter import Painter -class FocusedItemPainter(Painter): +class FocusedItemPainter: """This painter allows for drawing on top of all the other layers for the focused item.""" + def __init__(self, view): + assert view + self.view = view + def paint(self, context): view = self.view item = view.selection.hovered_item diff --git a/gaphas/painter/handlepainter.py b/gaphas/painter/handlepainter.py index 6f79086..07f677b 100644 --- a/gaphas/painter/handlepainter.py +++ b/gaphas/painter/handlepainter.py @@ -1,11 +1,13 @@ from cairo import ANTIALIAS_NONE -from gaphas.painter.painter import Painter - -class HandlePainter(Painter): +class HandlePainter: """Draw handles of items that are marked as selected in the view.""" + def __init__(self, view): + assert view + self.view = view + def _draw_handles(self, item, cairo, opacity=None, inner=False): """Draw handles for an item. diff --git a/gaphas/painter/itempainter.py b/gaphas/painter/itempainter.py index 7b451f2..a1e81be 100644 --- a/gaphas/painter/itempainter.py +++ b/gaphas/painter/itempainter.py @@ -1,7 +1,6 @@ from cairo import LINE_JOIN_ROUND from gaphas.canvas import Context -from gaphas.painter.painter import Painter DEBUG_DRAW_BOUNDING_BOX = False @@ -21,10 +20,14 @@ class DrawContext(Context): super().__init__(**kwargs) -class ItemPainter(Painter): +class ItemPainter: draw_all = False + def __init__(self, view): + assert view + self.view = view + def draw_item(self, item, cairo): view = self.view cairo.save() diff --git a/gaphas/painter/painter.py b/gaphas/painter/painter.py index 68fb933..274c434 100644 --- a/gaphas/painter/painter.py +++ b/gaphas/painter/painter.py @@ -6,13 +6,12 @@ Each painter takes care of a layer in the canvas (such as grid, items and handles). """ +from typing_extensions import Protocol -class Painter: + +class Painter(Protocol): """Painter interface.""" - def __init__(self, view=None): - self.view = view - def paint(self, context): """Do the paint action (called from the View).""" pass diff --git a/gaphas/painter/toolpainter.py b/gaphas/painter/toolpainter.py index c58f0db..641356d 100644 --- a/gaphas/painter/toolpainter.py +++ b/gaphas/painter/toolpainter.py @@ -1,10 +1,11 @@ -from gaphas.painter.painter import Painter - - -class ToolPainter(Painter): +class ToolPainter: """ToolPainter allows the Tool defined on a view to do some special drawing.""" + def __init__(self, view): + assert view + self.view = view + def paint(self, context): view = self.view if view.tool: diff --git a/gaphas/view/view.py b/gaphas/view/view.py index 1ee08e4..cbcbb3b 100644 --- a/gaphas/view/view.py +++ b/gaphas/view/view.py @@ -4,7 +4,7 @@ from gaphas.canvas import Context from gaphas.geometry import Rectangle from gaphas.item import Item from gaphas.matrix import Matrix -from gaphas.painter import BoundingBoxPainter, DefaultPainter, ItemPainter +from gaphas.painter import BoundingBoxPainter, DefaultPainter, ItemPainter, Painter from gaphas.quadtree import Quadtree @@ -13,8 +13,10 @@ class View: def __init__(self, canvas=None): self._matrix = Matrix() - self._painter = DefaultPainter(self) - self._bounding_box_painter = BoundingBoxPainter(ItemPainter(self), self) + self._painter: Painter = DefaultPainter(self) + self._bounding_box_painter: Painter = BoundingBoxPainter( + ItemPainter(self), self + ) self._qtree: Quadtree[Item, Tuple[float, float, float, float]] = Quadtree() @@ -39,7 +41,7 @@ class View: canvas = property(lambda s: s._canvas, _set_canvas) - def _set_painter(self, painter): + def _set_painter(self, painter: Painter): """Set the painter to use. Painters should implement painter.Painter. @@ -48,7 +50,7 @@ class View: painter = property(lambda s: s._painter, _set_painter) - def _set_bounding_box_painter(self, painter): + def _set_bounding_box_painter(self, painter: Painter): """Set the painter to use for bounding box calculations.""" self._bounding_box_painter = painter diff --git a/poetry.lock b/poetry.lock index e7ab3db..eb66f14 100644 --- a/poetry.lock +++ b/poetry.lock @@ -399,7 +399,7 @@ python-versions = "*" name = "typing-extensions" version = "3.7.4.3" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -438,7 +438,7 @@ testing = ["jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "c9f4a89ddb95ef0dd32d318103d592314f44986c3f583cf199de777d6bd3e92f" +content-hash = "49eac21141f96b333fb34b2b1a1331bb93e58a18f77aae9d89f4f8adad481778" [metadata.files] appdirs = [ diff --git a/pyproject.toml b/pyproject.toml index e7cfe3e..35af5f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ python = "^3.6" PyGObject = "^3.20.0" pycairo = "^1.13.0" importlib_metadata = ">=1.3,<3.0" +typing-extensions = "^3.7.4" [tool.poetry.dev-dependencies] pytest = "^6.1" From 9d703479f793c010d85e48840cb6804c21ea9010 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 25 Oct 2020 14:49:37 +0100 Subject: [PATCH 4/5] Update Painter.paint protocol --- gaphas/painter/__init__.py | 2 +- gaphas/painter/boundingboxpainter.py | 12 ++++++------ gaphas/painter/chain.py | 19 ++++++++++++++----- gaphas/painter/focuseditempainter.py | 7 +++++-- gaphas/painter/handlepainter.py | 7 +++++-- gaphas/painter/itempainter.py | 20 ++++++++++---------- gaphas/painter/painter.py | 6 +++++- gaphas/painter/toolpainter.py | 11 ++++++++--- gaphas/view/view.py | 3 +-- 9 files changed, 55 insertions(+), 32 deletions(-) diff --git a/gaphas/painter/__init__.py b/gaphas/painter/__init__.py index 4f52688..324ca8c 100644 --- a/gaphas/painter/__init__.py +++ b/gaphas/painter/__init__.py @@ -7,7 +7,7 @@ from gaphas.painter.painter import Painter from gaphas.painter.toolpainter import ToolPainter -def DefaultPainter(view): +def DefaultPainter(view) -> Painter: """Default painter, containing item, handle and tool painters.""" return ( PainterChain() diff --git a/gaphas/painter/boundingboxpainter.py b/gaphas/painter/boundingboxpainter.py index ad1e29c..38394d0 100644 --- a/gaphas/painter/boundingboxpainter.py +++ b/gaphas/painter/boundingboxpainter.py @@ -1,4 +1,7 @@ +from typing import Optional, Sequence + from gaphas.geometry import Rectangle +from gaphas.item import Item from gaphas.painter.itempainter import ItemPainter @@ -9,7 +12,7 @@ class CairoBoundingBoxContext: def __init__(self, cairo): self._cairo = cairo - self._bounds = None # a Rectangle object + self._bounds: Optional[Rectangle] = None # a Rectangle object def __getattr__(self, key): return getattr(self._cairo, key) @@ -90,7 +93,7 @@ class BoundingBoxPainter: draw_all = True - def __init__(self, item_painter=None, view=None): + def __init__(self, item_painter: ItemPainter = None, view=None): assert view self.view = view self.item_painter = item_painter or ItemPainter(view) @@ -110,10 +113,7 @@ class BoundingBoxPainter: bounds.expand(1) view.set_item_bounding_box(item, bounds) - def draw_items(self, items, cairo): + def paint(self, items: Sequence[Item], cairo): """Draw the items.""" for item in items: self.draw_item(item, cairo) - - def paint(self, context): - self.draw_items(context.items, context.cairo) diff --git a/gaphas/painter/chain.py b/gaphas/painter/chain.py index 74470e3..0a96392 100644 --- a/gaphas/painter/chain.py +++ b/gaphas/painter/chain.py @@ -1,3 +1,11 @@ +from __future__ import annotations + +from typing import List, Sequence + +from gaphas.item import Item +from gaphas.painter.painter import Painter + + class PainterChain: """Chain up a set of painters. @@ -5,18 +13,19 @@ class PainterChain: """ def __init__(self): - self._painters = [] + self._painters: List[Painter] = [] - def append(self, painter): + def append(self, painter: Painter) -> PainterChain: """Add a painter to the list of painters.""" self._painters.append(painter) return self - def prepend(self, painter): + def prepend(self, painter: Painter) -> PainterChain: """Add a painter to the beginning of the list of painters.""" self._painters.insert(0, painter) + return self - def paint(self, context): + def paint(self, items: Sequence[Item], cairo): """See Painter.paint().""" for painter in self._painters: - painter.paint(context) + painter.paint(items, cairo) diff --git a/gaphas/painter/focuseditempainter.py b/gaphas/painter/focuseditempainter.py index 46bfe12..b3a1f70 100644 --- a/gaphas/painter/focuseditempainter.py +++ b/gaphas/painter/focuseditempainter.py @@ -1,4 +1,7 @@ +from typing import Sequence + from gaphas.aspect import PaintFocused +from gaphas.item import Item class FocusedItemPainter: @@ -9,8 +12,8 @@ class FocusedItemPainter: assert view self.view = view - def paint(self, context): + def paint(self, items: Sequence[Item], cairo): view = self.view item = view.selection.hovered_item if item and item is view.selection.focused_item: - PaintFocused(item, view).paint(context) + PaintFocused(item, view).paint(items, cairo) diff --git a/gaphas/painter/handlepainter.py b/gaphas/painter/handlepainter.py index 07f677b..8fe9644 100644 --- a/gaphas/painter/handlepainter.py +++ b/gaphas/painter/handlepainter.py @@ -1,5 +1,9 @@ +from typing import Sequence + from cairo import ANTIALIAS_NONE +from gaphas.item import Item + class HandlePainter: """Draw handles of items that are marked as selected in the view.""" @@ -53,10 +57,9 @@ class HandlePainter: cairo.stroke() cairo.restore() - def paint(self, context): + def paint(self, items: Sequence[Item], cairo): view = self.view canvas = view.canvas - cairo = context.cairo selection = view.selection # Order matters here: for item in canvas.sort(selection.selected_items): diff --git a/gaphas/painter/itempainter.py b/gaphas/painter/itempainter.py index a1e81be..78e9a59 100644 --- a/gaphas/painter/itempainter.py +++ b/gaphas/painter/itempainter.py @@ -1,6 +1,9 @@ +from typing import Sequence + from cairo import LINE_JOIN_ROUND from gaphas.canvas import Context +from gaphas.item import Item DEBUG_DRAW_BOUNDING_BOX = False @@ -51,13 +54,6 @@ class ItemPainter: finally: cairo.restore() - def draw_items(self, items, cairo): - """Draw the items.""" - for item in items: - self.draw_item(item, cairo) - if DEBUG_DRAW_BOUNDING_BOX: - self._draw_bounds(item, cairo) - def _draw_bounds(self, item, cairo): view = self.view try: @@ -73,8 +69,12 @@ class ItemPainter: cairo.stroke() cairo.restore() - def paint(self, context): - cairo = context.cairo + def paint(self, items: Sequence[Item], cairo): + """Draw the items.""" cairo.set_tolerance(TOLERANCE) cairo.set_line_join(LINE_JOIN_ROUND) - self.draw_items(context.items, cairo) + + for item in items: + self.draw_item(item, cairo) + if DEBUG_DRAW_BOUNDING_BOX: + self._draw_bounds(item, cairo) diff --git a/gaphas/painter/painter.py b/gaphas/painter/painter.py index 274c434..c651ff9 100644 --- a/gaphas/painter/painter.py +++ b/gaphas/painter/painter.py @@ -6,12 +6,16 @@ Each painter takes care of a layer in the canvas (such as grid, items and handles). """ +from typing import Sequence + from typing_extensions import Protocol +from gaphas.item import Item + class Painter(Protocol): """Painter interface.""" - def paint(self, context): + def paint(self, items: Sequence[Item], cairo): """Do the paint action (called from the View).""" pass diff --git a/gaphas/painter/toolpainter.py b/gaphas/painter/toolpainter.py index 641356d..3dc76a0 100644 --- a/gaphas/painter/toolpainter.py +++ b/gaphas/painter/toolpainter.py @@ -1,3 +1,9 @@ +from typing import Sequence + +from gaphas.canvas import Context +from gaphas.item import Item + + class ToolPainter: """ToolPainter allows the Tool defined on a view to do some special drawing.""" @@ -6,11 +12,10 @@ class ToolPainter: assert view self.view = view - def paint(self, context): + def paint(self, items: Sequence[Item], cairo): view = self.view if view.tool: - cairo = context.cairo cairo.save() cairo.identity_matrix() - view.tool.draw(context) + view.tool.draw(Context(items=items, cairo=cairo)) cairo.restore() diff --git a/gaphas/view/view.py b/gaphas/view/view.py index cbcbb3b..250d772 100644 --- a/gaphas/view/view.py +++ b/gaphas/view/view.py @@ -1,6 +1,5 @@ from typing import Tuple -from gaphas.canvas import Context from gaphas.geometry import Rectangle from gaphas.item import Item from gaphas.matrix import Matrix @@ -151,7 +150,7 @@ class View: items = self.canvas.get_all_items() # The painter calls set_item_bounding_box() for each rendered item. - painter.paint(Context(cairo=cr, items=items)) + painter.paint(items, cr) def get_matrix_i2v(self, item): """Get Item to View matrix for ``item``.""" From 52dd793578d954972f8c3247d34ecd6cf483fe81 Mon Sep 17 00:00:00 2001 From: Arjan Molenaar Date: Sun, 25 Oct 2020 20:09:01 +0100 Subject: [PATCH 5/5] Move freehand painter to painter package Add some more type info. --- examples/demo.py | 6 +++--- gaphas/aspect.py | 2 +- gaphas/guide.py | 3 +-- gaphas/painter/__init__.py | 1 + gaphas/painter/boundingboxpainter.py | 22 +++++++++++++--------- gaphas/painter/focuseditempainter.py | 2 +- gaphas/{ => painter}/freehand.py | 23 +++++++++++------------ gaphas/painter/itempainter.py | 4 ++-- gaphas/painter/painter.py | 9 +++++++++ gaphas/segment.py | 3 +-- gaphas/view/gtkview.py | 4 ++-- tests/test_freehand.py | 2 +- 12 files changed, 46 insertions(+), 35 deletions(-) rename gaphas/{ => painter}/freehand.py (88%) diff --git a/examples/demo.py b/examples/demo.py index f5275f5..c7bf2ad 100755 --- a/examples/demo.py +++ b/examples/demo.py @@ -19,11 +19,11 @@ import gi from examples.exampleitems import Box, Circle, FatLine, PortoBox, Text from gaphas import Canvas, GtkView, View, state from gaphas.canvas import Context -from gaphas.freehand import FreeHandPainter from gaphas.item import Line from gaphas.painter import ( BoundingBoxPainter, FocusedItemPainter, + FreeHandPainter, HandlePainter, ItemPainter, PainterChain, @@ -112,13 +112,13 @@ def create_window(canvas, title, zoom=1.0): # noqa too complex view = GtkView() view.painter = ( PainterChain() - .append(FreeHandPainter(ItemPainter(view), view)) + .append(FreeHandPainter(ItemPainter(view))) .append(HandlePainter(view)) .append(FocusedItemPainter(view)) .append(ToolPainter(view)) ) view.bounding_box_painter = BoundingBoxPainter( - FreeHandPainter(ItemPainter(view), view), view=view + FreeHandPainter(ItemPainter(view)), view ) w = Gtk.Window() w.set_title(title) diff --git a/gaphas/aspect.py b/gaphas/aspect.py index 327c89e..a8559e4 100644 --- a/gaphas/aspect.py +++ b/gaphas/aspect.py @@ -349,7 +349,7 @@ class ItemPaintFocused: self.item = item self.view = view - def paint(self, context): + def paint(self, cairo): pass diff --git a/gaphas/guide.py b/gaphas/guide.py index c93e800..4e7074e 100644 --- a/gaphas/guide.py +++ b/gaphas/guide.py @@ -290,13 +290,12 @@ class GuidedItemHandleInMotion(GuideMixin, ItemHandleInMotion): @PaintFocused.register(Item) class GuidePainter(ItemPaintFocused): - def paint(self, context): + def paint(self, cr): try: guides = self.view.guides except AttributeError: return - cr = context.cairo view = self.view allocation = view.get_allocation() w, h = allocation.width, allocation.height diff --git a/gaphas/painter/__init__.py b/gaphas/painter/__init__.py index 324ca8c..183a315 100644 --- a/gaphas/painter/__init__.py +++ b/gaphas/painter/__init__.py @@ -1,6 +1,7 @@ from gaphas.painter.boundingboxpainter import BoundingBoxPainter from gaphas.painter.chain import PainterChain from gaphas.painter.focuseditempainter import FocusedItemPainter +from gaphas.painter.freehand import FreeHandPainter from gaphas.painter.handlepainter import HandlePainter from gaphas.painter.itempainter import ItemPainter from gaphas.painter.painter import Painter diff --git a/gaphas/painter/boundingboxpainter.py b/gaphas/painter/boundingboxpainter.py index 38394d0..2e0d01e 100644 --- a/gaphas/painter/boundingboxpainter.py +++ b/gaphas/painter/boundingboxpainter.py @@ -1,8 +1,13 @@ -from typing import Optional, Sequence +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional, Sequence from gaphas.geometry import Rectangle -from gaphas.item import Item -from gaphas.painter.itempainter import ItemPainter + +if TYPE_CHECKING: + from gaphas.item import Item + from gaphas.painter.painter import ItemPainterType + from gaphas.view import View class CairoBoundingBoxContext: @@ -93,14 +98,13 @@ class BoundingBoxPainter: draw_all = True - def __init__(self, item_painter: ItemPainter = None, view=None): - assert view + def __init__(self, item_painter: ItemPainterType, view: View): + self.item_painter = item_painter self.view = view - self.item_painter = item_painter or ItemPainter(view) - def draw_item(self, item, cairo): + def paint_item(self, item, cairo): cairo = CairoBoundingBoxContext(cairo) - self.item_painter.draw_item(item, cairo) + self.item_painter.paint_item(item, cairo) bounds = cairo.get_bounds() # Update bounding box with handles. @@ -116,4 +120,4 @@ class BoundingBoxPainter: def paint(self, items: Sequence[Item], cairo): """Draw the items.""" for item in items: - self.draw_item(item, cairo) + self.paint_item(item, cairo) diff --git a/gaphas/painter/focuseditempainter.py b/gaphas/painter/focuseditempainter.py index b3a1f70..4bfa380 100644 --- a/gaphas/painter/focuseditempainter.py +++ b/gaphas/painter/focuseditempainter.py @@ -16,4 +16,4 @@ class FocusedItemPainter: view = self.view item = view.selection.hovered_item if item and item is view.selection.focused_item: - PaintFocused(item, view).paint(items, cairo) + PaintFocused(item, view).paint(cairo) diff --git a/gaphas/freehand.py b/gaphas/painter/freehand.py similarity index 88% rename from gaphas/freehand.py rename to gaphas/painter/freehand.py index 6e26781..70b6397 100644 --- a/gaphas/freehand.py +++ b/gaphas/painter/freehand.py @@ -11,9 +11,10 @@ See: http://stevehanov.ca/blog/index.php?id=33 and """ from math import sqrt from random import Random +from typing import Sequence -from gaphas.canvas import Context -from gaphas.painter import Painter +from gaphas.item import Item +from gaphas.painter.painter import ItemPainterType class FreeHandCairoContext: @@ -132,18 +133,16 @@ class FreeHandCairoContext: self.close_path() -class FreeHandPainter(Painter): - def __init__(self, subpainter, view, sloppiness=1.0): +class FreeHandPainter: + def __init__(self, subpainter: ItemPainterType, sloppiness=1.0): self.subpainter = subpainter self.sloppiness = sloppiness - def draw_item(self, item, cairo): - # Bounding box painter requires painting per item - self.subpainter.draw_item(item, cairo) + def paint_item(self, item: Item, cairo): + # Bounding painter requires painting per item + self.subpainter.paint_item(item, cairo) - def paint(self, context): - subcontext = Context( - cairo=FreeHandCairoContext(context.cairo, self.sloppiness), - items=context.items, + def paint(self, items: Sequence[Item], cairo): + self.subpainter.paint( + items, FreeHandCairoContext(cairo, self.sloppiness), ) - self.subpainter.paint(subcontext) diff --git a/gaphas/painter/itempainter.py b/gaphas/painter/itempainter.py index 78e9a59..58e52ac 100644 --- a/gaphas/painter/itempainter.py +++ b/gaphas/painter/itempainter.py @@ -31,7 +31,7 @@ class ItemPainter: assert view self.view = view - def draw_item(self, item, cairo): + def paint_item(self, item, cairo): view = self.view cairo.save() try: @@ -75,6 +75,6 @@ class ItemPainter: cairo.set_line_join(LINE_JOIN_ROUND) for item in items: - self.draw_item(item, cairo) + self.paint_item(item, cairo) if DEBUG_DRAW_BOUNDING_BOX: self._draw_bounds(item, cairo) diff --git a/gaphas/painter/painter.py b/gaphas/painter/painter.py index c651ff9..400366b 100644 --- a/gaphas/painter/painter.py +++ b/gaphas/painter/painter.py @@ -19,3 +19,12 @@ class Painter(Protocol): def paint(self, items: Sequence[Item], cairo): """Do the paint action (called from the View).""" pass + + +class ItemPainterType(Protocol): + def paint_item(self, item: Item, cairo): + """Draw a single item.""" + + def paint(self, items: Sequence[Item], cairo): + """Do the paint action (called from the View).""" + pass diff --git a/gaphas/segment.py b/gaphas/segment.py index c9a8ab1..baf10f9 100644 --- a/gaphas/segment.py +++ b/gaphas/segment.py @@ -242,11 +242,10 @@ class LineSegmentPainter(ItemPaintFocused): required for this feature. """ - def paint(self, context): + def paint(self, cr): view = self.view item = view.selection.hovered_item if item and item is view.selection.focused_item: - cr = context.cairo h = item.handles() for h1, h2 in zip(h[:-1], h[1:]): p1, p2 = h1.pos, h2.pos diff --git a/gaphas/view/gtkview.py b/gaphas/view/gtkview.py index 9dc9878..99e253c 100644 --- a/gaphas/view/gtkview.py +++ b/gaphas/view/gtkview.py @@ -3,7 +3,7 @@ import cairo from gi.repository import Gdk, GLib, GObject, Gtk -from gaphas.canvas import Context, instant_cairo_context +from gaphas.canvas import instant_cairo_context from gaphas.decorators import AsyncIO from gaphas.geometry import Rectangle, distance_point_point_fast from gaphas.matrix import Matrix @@ -461,7 +461,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View): (0, 0, allocation.width, allocation.height) ) - self.painter.paint(Context(cairo=cr, items=items)) + self.painter.paint(items, cr) if DEBUG_DRAW_BOUNDING_BOX: cr.save() diff --git a/tests/test_freehand.py b/tests/test_freehand.py index c05a4eb..4c144d8 100644 --- a/tests/test_freehand.py +++ b/tests/test_freehand.py @@ -1,6 +1,6 @@ import cairo -from gaphas.freehand import FreeHandCairoContext +from gaphas.painter.freehand import FreeHandCairoContext def test_drawing_lines():