class GraphicsScene(core.ObjectMixin, widgets.QGraphicsScene):
"""Surface for managing a large number of 2D graphical items."""
class GridType(enum.IntEnum):
"""Grid type for background."""
NoGrid = 0
DotGrid = 1
LineGrid = 2
def __init__(self, parent=None):
super().__init__(parent)
self._grid_mode = self.GridType.LineGrid
self._grid_size = 50
self._pen_width = 0.65
self._grid_color = self.get_palette().get_color("text")
self._bg_color = self.get_palette().get_color("window")
# self.setBackgroundBrush(self._bg_color)
def __repr__(self):
cls_name = str(self.__class__.__name__)
return f'<{cls_name}("{self.viewer()}") object at {hex(id(self))}>'
def __getitem__(self, index: int) -> widgets.QGraphicsItem:
return self.items()[index]
def _get_map(self):
maps = super()._get_map()
maps |= {"itemIndexMethod": ITEM_INDEX_METHOD}
return maps
def get_palette(self) -> gui.Palette:
return gui.Palette(self.palette())
def get_font(self) -> gui.Font:
return gui.Font(self.font())
def get_background_brush(self) -> gui.Brush:
return gui.Brush(self.backgroundBrush())
def get_foreground_brush(self) -> gui.Brush:
return gui.Brush(self.foregroundBrush())
def add(self, item) -> widgets.QGraphicsItem:
match item:
case widgets.QGraphicsItem():
self.addItem(item)
return item
case gui.QPixmap():
return self.add_pixmap(item)
case gui.QPainterPath():
return self.add_path(item)
case gui.QPolygonF():
return self.add_polygon(item)
case core.QRectF():
return self.add_rect(item)
case core.QLine():
return self.add_line(item)
case str():
return self.add_text(item)
case widgets.QWidget():
return self.add_widget(item)
case _:
raise TypeError(item)
def add_pixmap(self, pixmap: gui.QPixmap) -> widgets.GraphicsPixmapItem:
g_item = widgets.GraphicsPixmapItem(pixmap)
self.addItem(g_item)
return g_item
def add_polygon(
self,
polygon: gui.QPolygonF | gui.QPolygon,
pen: gui.QPen | None = None,
brush: gui.QBrush | None = None,
) -> widgets.GraphicsPolygonItem:
if isinstance(polygon, gui.QPolygon):
polygon = gui.PolygonF(polygon)
g_item = widgets.GraphicsPolygonItem(polygon)
if brush is not None:
g_item.setBrush(brush)
if pen is not None:
g_item.setPen(pen)
self.addItem(g_item)
return g_item
def add_path(
self,
path: gui.QPainterPath,
pen: gui.QPen | None = None,
brush: gui.QBrush | None = None,
) -> widgets.GraphicsPathItem:
g_item = widgets.GraphicsPathItem(path)
if brush is not None:
g_item.setBrush(brush)
if pen is not None:
g_item.setPen(pen)
self.addItem(g_item)
return g_item
def add_rect(
self,
rect: datatypes.RectType | datatypes.RectFType,
pen: gui.QPen | None = None,
brush: gui.QBrush | None = None,
) -> widgets.GraphicsRectItem:
g_item = widgets.GraphicsRectItem(datatypes.to_rectf(rect))
if brush is not None:
g_item.setBrush(brush)
if pen is not None:
g_item.setPen(pen)
self.addItem(g_item)
return g_item
def add_line(
self,
line: core.QLineF | core.QLine | tuple[float, float, float, float],
pen: gui.QPen | None = None,
) -> widgets.GraphicsLineItem:
g_item = widgets.GraphicsLineItem(datatypes.to_linef(line))
if pen is not None:
g_item.setPen(pen)
self.addItem(g_item)
return g_item
def add_ellipse(
self,
ellipse: datatypes.RectType | datatypes.RectFType,
pen: gui.QPen | None = None,
brush: gui.QBrush | None = None,
) -> widgets.GraphicsEllipseItem:
g_item = widgets.GraphicsEllipseItem(datatypes.to_rectf(ellipse))
if brush is not None:
g_item.setBrush(brush)
if pen is not None:
g_item.setPen(pen)
self.addItem(g_item)
return g_item
def add_text(
self, text: str, font: gui.QFont | None = None
) -> widgets.GraphicsTextItem:
g_item = widgets.GraphicsTextItem(text)
if font is not None:
g_item.setFont(font)
self.addItem(g_item)
return g_item
def add_simple_text(
self, text: str, font: gui.QFont | None = None
) -> widgets.GraphicsSimpleTextItem:
g_item = widgets.GraphicsSimpleTextItem(text)
if font is not None:
g_item.setFont(font)
self.addItem(g_item)
return g_item
def add_widget(self, widget: widgets.QWidget) -> widgets.GraphicsProxyWidget:
g_item = widgets.GraphicsProxyWidget()
g_item.setWidget(widget)
self.addItem(g_item)
return g_item
def colliding_items(
self,
item: widgets.QGraphicsItem,
mode: constants.ItemSelectionModeStr
| constants.ItemSelectionMode = "intersects_shape",
) -> listdelegators.ListDelegator[widgets.QGraphicsItem]:
items = self.collidingItems(item, constants.ITEM_SELECTION_MODE[mode])
return listdelegators.ListDelegator(items)
def add_item_group(self, *items: widgets.QGraphicsItem) -> widgets.GraphicsItemGroup:
group = widgets.GraphicsItemGroup()
for item in items:
group.addToGroup(item)
return group
def _draw_grid(
self,
painter: gui.QPainter,
rect: core.QRectF,
pen: gui.QPen,
grid_size: int,
):
left = int(rect.left())
right = int(rect.right())
top = int(rect.top())
bottom = int(rect.bottom())
first_left = left - (left % grid_size)
first_top = top - (top % grid_size)
lines = [
core.QLineF(x, top, x, bottom) for x in range(first_left, right, grid_size)
]
lines.extend(
[core.QLineF(left, y, right, y) for y in range(first_top, bottom, grid_size)]
)
painter.setPen(pen)
painter.drawLines(lines)
def _draw_dots(
self,
painter: gui.QPainter,
rect: core.QRectF,
pen: gui.QPen,
grid_size: int,
):
if (zoom := self._get_viewer_zoom()) < 0:
grid_size *= int(abs(zoom) / 0.3 + 1)
left = int(rect.left())
right = int(rect.right())
top = int(rect.top())
bottom = int(rect.bottom())
first_left = left - (left % grid_size)
first_top = top - (top % grid_size)
pen.setWidth(grid_size / 10)
painter.setPen(pen)
[
painter.drawPoint(int(x), int(y))
for x in range(first_left, right, grid_size)
for y in range(first_top, bottom, grid_size)
]
def drawBackground(self, painter: gui.QPainter, rect: core.QRect):
super().drawBackground(painter, rect)
painter.save()
painter.setRenderHint(gui.QPainter.RenderHint.Antialiasing, False)
painter.setBrush(self.backgroundBrush())
if self._grid_mode == self.GridType.DotGrid:
pen = gui.QPen(self.grid_color, self._pen_width)
self._draw_dots(painter, rect, pen, self._grid_size)
elif self._grid_mode == self.GridType.LineGrid:
zoom = self._get_viewer_zoom()
if zoom > -0.5:
pen = gui.QPen(self.grid_color, self._pen_width)
self._draw_grid(painter, rect, pen, self._grid_size)
color = self._bg_color.darker(150)
if zoom < -0.0:
color = color.darker(100 - int(zoom * 110))
pen = gui.QPen(color, self._pen_width)
self._draw_grid(painter, rect, pen, self._grid_size * 8)
painter.restore()
def _get_viewer_zoom(self):
viewer = self.viewer()
if viewer is None:
return 1.0
transform = viewer.transform()
cur_scale = (transform.m11(), transform.m22())
return float(f"{cur_scale[0] - 1.0:0.2f}")
# def mousePressEvent(self, event):
# selected = self.viewer().selectedItems()
# if viewer := self.viewer():
# viewer.sceneMousePressEvent(event)
# super().mousePressEvent(event)
# keep_selection = any(
# [
# event.button() == core.Qt.MiddleButton,
# event.button() == core.Qt.RightButton,
# event.modifiers() == core.Qt.AltModifier,
# ]
# )
# if keep_selection:
# for node in selected:
# node.setSelected(True)
# def mouseMoveEvent(self, event):
# if viewer := self.viewer():
# viewer.sceneMouseMoveEvent(event)
# super().mouseMoveEvent(event)
# def mouseReleaseEvent(self, event):
# if viewer := self.viewer():
# viewer.sceneMouseReleaseEvent(event)
# super().mouseReleaseEvent(event)
def viewer(self):
return self.views()[0] if self.views() else None
def get_grid_mode(self) -> GridType:
return self._grid_mode
def set_grid_mode(self, mode: GridType | None = None):
# alternative?
# brush = gui.Brush()
# brush.set_style("cross")
# scene.setBackgroundBrush(brush)
if mode is None:
mode = self.GridType.NoGrid
self._grid_mode = mode
def get_grid_color(self) -> gui.Color:
return self._grid_color
def set_grid_color(self, color: datatypes.ColorType):
self._grid_color = colors.get_color(color)
def get_background_color(self) -> gui.Color:
return self._bg_color
def set_background_color(self, color: datatypes.ColorType):
self._bg_color = colors.get_color(color)
self.setBackgroundBrush(self._bg_color)
def set_item_index_method(
self, method: ItemIndexMethodStr | widgets.QGraphicsScene.ItemIndexMethod
):
"""Set item index method.
Args:
method: item index method to use
"""
self.setItemIndexMethod(ITEM_INDEX_METHOD.get_enum_value(method))
def get_item_index_method(self) -> ItemIndexMethodStr:
"""Return item index method.
Returns:
item index method
"""
return ITEM_INDEX_METHOD.inverse[self.itemIndexMethod()]
bg_color = core.Property(
gui.QColor,
get_background_color,
set_background_color,
doc="Color for the scene background",
)
grid_color = core.Property(
gui.QColor,
get_grid_color,
set_grid_color,
doc="Color for the grid",
)
grid_mode = core.Property(
int,
get_grid_mode,
set_grid_mode,
doc="Grid mode",
)