Merge branch 'painter-package'

This commit is contained in:
Arjan Molenaar 2020-10-27 21:56:17 +01:00
commit 03e1b72fbf
20 changed files with 435 additions and 396 deletions

View File

@ -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,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)))
.append(HandlePainter(view))
.append(FocusedItemPainter(view))
.append(ToolPainter(view))
)
view.bounding_box_painter = BoundingBoxPainter(
FreeHandPainter(ItemPainter(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)

View File

@ -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)

View File

@ -325,7 +325,7 @@ class ItemPaintFocused:
self.item = item
self.view = view
def paint(self, context):
def paint(self, cairo):
pass

View File

@ -291,13 +291,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

View File

@ -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())
)

View File

@ -0,0 +1,19 @@
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
from gaphas.painter.toolpainter import ToolPainter
def DefaultPainter(view) -> Painter:
"""Default painter, containing item, handle and tool painters."""
return (
PainterChain()
.append(ItemPainter(view))
.append(HandlePainter(view))
.append(FocusedItemPainter(view))
.append(ToolPainter(view))
)

View File

@ -0,0 +1,123 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Sequence
from gaphas.geometry import Rectangle
if TYPE_CHECKING:
from gaphas.item import Item
from gaphas.painter.painter import ItemPainterType
from gaphas.view import View
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: Optional[Rectangle] = 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:
"""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: ItemPainterType, view: View):
self.item_painter = item_painter
self.view = view
def paint_item(self, item, cairo):
cairo = CairoBoundingBoxContext(cairo)
self.item_painter.paint_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 paint(self, items: Sequence[Item], cairo):
"""Draw the items."""
for item in items:
self.paint_item(item, cairo)

31
gaphas/painter/chain.py Normal file
View File

@ -0,0 +1,31 @@
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.
like ToolChain.
"""
def __init__(self):
self._painters: List[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: Painter) -> PainterChain:
"""Add a painter to the beginning of the list of painters."""
self._painters.insert(0, painter)
return self
def paint(self, items: Sequence[Item], cairo):
"""See Painter.paint()."""
for painter in self._painters:
painter.paint(items, cairo)

View File

@ -0,0 +1,19 @@
from typing import Sequence
from gaphas.aspect import PaintFocused
from gaphas.item import Item
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, 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(cairo)

View File

@ -11,8 +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.painter import Context, Painter
from gaphas.item import Item
from gaphas.painter.painter import ItemPainterType
class FreeHandCairoContext:
@ -131,23 +133,16 @@ class FreeHandCairoContext:
self.close_path()
class FreeHandPainter(Painter):
def __init__(self, subpainter, sloppiness=1.0, view=None):
class FreeHandPainter:
def __init__(self, subpainter: ItemPainterType, 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 paint_item(self, item: Item, cairo):
# Bounding painter requires painting per item
self.subpainter.paint_item(item, cairo)
def draw_item(self, item, cairo):
# Bounding box painter requires painting per item
self.subpainter.draw_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)

View File

@ -0,0 +1,73 @@
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."""
def __init__(self, view):
assert view
self.view = 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, items: Sequence[Item], cairo):
view = self.view
canvas = view.canvas
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)

View File

@ -0,0 +1,80 @@
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
# 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:
draw_all = False
def __init__(self, view):
assert view
self.view = view
def paint_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_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, items: Sequence[Item], cairo):
"""Draw the items."""
cairo.set_tolerance(TOLERANCE)
cairo.set_line_join(LINE_JOIN_ROUND)
for item in items:
self.paint_item(item, cairo)
if DEBUG_DRAW_BOUNDING_BOX:
self._draw_bounds(item, cairo)

30
gaphas/painter/painter.py Normal file
View File

@ -0,0 +1,30 @@
"""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 typing import Sequence
from typing_extensions import Protocol
from gaphas.item import Item
class Painter(Protocol):
"""Painter interface."""
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

View File

@ -0,0 +1,21 @@
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."""
def __init__(self, view):
assert view
self.view = view
def paint(self, items: Sequence[Item], cairo):
view = self.view
if view.tool:
cairo.save()
cairo.identity_matrix()
view.tool.draw(Context(items=items, cairo=cairo))
cairo.restore()

View File

@ -238,11 +238,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

View File

@ -5,7 +5,7 @@ from typing import Optional, Set
import cairo
from gi.repository import Gdk, GLib, GObject, Gtk
from gaphas.canvas import Canvas, Context, instant_cairo_context
from gaphas.canvas import Canvas, instant_cairo_context
from gaphas.decorators import AsyncIO
from gaphas.geometry import Rectangle, distance_point_point_fast
from gaphas.item import Item
@ -471,7 +471,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()

View File

@ -1,10 +1,10 @@
from typing import Optional, Tuple
from gaphas.canvas import Canvas, Context
from gaphas.canvas import Canvas
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,20 +41,18 @@ 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.
"""
self._painter = painter
painter.set_view(self)
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
painter.set_view(self)
bounding_box_painter = property(
lambda s: s._bounding_box_painter, _set_bounding_box_painter
@ -151,7 +151,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``."""

4
poetry.lock generated
View File

@ -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 = [

View File

@ -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"

View File

@ -1,6 +1,6 @@
import cairo
from gaphas.freehand import FreeHandCairoContext
from gaphas.painter.freehand import FreeHandCairoContext
def test_drawing_lines():