2025-09-12 12:07:47 -05:00
|
|
|
# Python imports
|
|
|
|
from copy import copy, deepcopy
|
|
|
|
|
|
|
|
# Lib imports
|
|
|
|
import cairo
|
|
|
|
import gi
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
|
|
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from gi.repository import Gdk
|
|
|
|
|
|
|
|
# Application imports
|
|
|
|
from libs.surface_manager import SurfaceManager
|
|
|
|
from libs.event_collection import EventCollection
|
|
|
|
|
|
|
|
from data.mouse_buttons import MouseButton
|
2025-09-17 16:22:04 -05:00
|
|
|
from data.events import brushes
|
2025-09-12 12:07:47 -05:00
|
|
|
|
|
|
|
from .surface import Surface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DrawArea(Gtk.DrawingArea):
|
|
|
|
def __init__(self):
|
|
|
|
super(DrawArea, self).__init__()
|
|
|
|
|
|
|
|
self.background_surface: Surface = None
|
|
|
|
self.primary_surface: Surface = None
|
|
|
|
self.intermediate_surface: Surface = None
|
|
|
|
self.surface_manager: SurfaceManager = SurfaceManager()
|
|
|
|
self.event_collection: EventCollection = EventCollection()
|
|
|
|
|
|
|
|
self.is_drawing: bool = False
|
|
|
|
self.brush_mode: str = "Line"
|
|
|
|
self.grid_brush: BrushBase = getattr(brushes, "Grid")()
|
|
|
|
self.brush: BrushBase = getattr(brushes, self.brush_mode)()
|
|
|
|
self.brush_color = self.brush.color
|
|
|
|
self.brush_size = self.brush.size
|
|
|
|
|
|
|
|
self._setup_styling()
|
|
|
|
self._setup_signals()
|
|
|
|
self._subscribe_to_events()
|
|
|
|
self._load_widgets()
|
|
|
|
|
|
|
|
self._set_new_surface()
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_styling(self):
|
|
|
|
margin_px = 24
|
|
|
|
self.set_margin_top(margin_px)
|
|
|
|
self.set_margin_left(margin_px)
|
|
|
|
self.set_margin_right(margin_px)
|
|
|
|
self.set_margin_end(margin_px)
|
|
|
|
|
|
|
|
def _setup_signals(self):
|
|
|
|
self.set_can_focus(True)
|
|
|
|
self.set_size_request(800, 600)
|
|
|
|
|
|
|
|
self.add_events(Gdk.EventMask.KEY_PRESS_MASK)
|
|
|
|
self.add_events(Gdk.EventMask.KEY_RELEASE_MASK)
|
|
|
|
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
|
|
|
|
self.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK)
|
|
|
|
self.add_events(Gdk.EventMask.BUTTON1_MOTION_MASK)
|
|
|
|
|
|
|
|
self.connect("key-release-event", self._key_release_event)
|
|
|
|
self.connect("button-press-event", self._button_press_event)
|
|
|
|
self.connect("button-release-event", self._button_release_event)
|
|
|
|
self.connect("motion-notify-event", self._motion_notify_event)
|
|
|
|
self.connect("draw", self._draw)
|
|
|
|
|
|
|
|
|
|
|
|
def _subscribe_to_events(self):
|
|
|
|
event_system.subscribe("save-image", self._save_image)
|
|
|
|
event_system.subscribe("set-brush-color", self._set_brush_color)
|
|
|
|
event_system.subscribe("set-brush-size", self._set_brush_size)
|
|
|
|
event_system.subscribe("set-brush-mode", self._set_brush_mode)
|
|
|
|
|
|
|
|
def _load_widgets(self):
|
|
|
|
...
|
|
|
|
|
|
|
|
def _set_new_surface(self):
|
|
|
|
size_w, \
|
|
|
|
size_h, \
|
|
|
|
image_surface = event_system.emit_and_await("get-image-size")
|
|
|
|
|
|
|
|
self.grid_brush.width = size_w
|
|
|
|
self.grid_brush.height = size_h
|
|
|
|
self.background_surface = Surface(size_w, size_h)
|
|
|
|
self.primary_surface = Surface(size_w, size_h)
|
|
|
|
self.intermediate_surface = Surface(size_w, size_h)
|
|
|
|
|
|
|
|
if image_surface:
|
|
|
|
self.primary_surface.create_new_area(image_surface)
|
|
|
|
self.primary_surface.set_image_data(image_surface.get_data())
|
|
|
|
|
|
|
|
self.event_collection.clear()
|
|
|
|
self.surface_manager.clear()
|
|
|
|
|
|
|
|
self.grid_brush.update(None)
|
|
|
|
self.set_size_request(size_w, size_h)
|
|
|
|
self.background_surface.update(self.grid_brush)
|
|
|
|
self.surface_manager.append( self.primary_surface )
|
|
|
|
|
|
|
|
def _save_image(self, fpath: str):
|
|
|
|
self.primary_surface.area.write_to_png(fpath)
|
|
|
|
|
|
|
|
def _do_save_image(self):
|
|
|
|
fpath = event_system.emit_and_await("save-as")
|
|
|
|
if not fpath: return
|
|
|
|
self._save_image(fpath)
|
|
|
|
|
|
|
|
def _set_brush_color(self, color: Gdk.RGBA):
|
|
|
|
self.brush = getattr(brushes, self.brush_mode)()
|
|
|
|
self.brush_color = [color.red, color.green, color.blue, color.alpha]
|
|
|
|
self.brush.color = self.brush_color
|
|
|
|
|
|
|
|
def _set_brush_size(self, size: int):
|
|
|
|
self.brush_size = size
|
|
|
|
self.brush = getattr(brushes, self.brush_mode)()
|
|
|
|
self.brush.size = self.brush_size
|
|
|
|
|
|
|
|
def _set_brush_mode(self, mode: str):
|
|
|
|
self.brush_mode = mode
|
|
|
|
self.brush = getattr(brushes, self.brush_mode)()
|
|
|
|
self.brush.color = self.brush_color
|
|
|
|
self.brush.size = self.brush_size
|
|
|
|
|
|
|
|
def _button_press_event(self, widget, eve):
|
|
|
|
if not self.has_focus():
|
|
|
|
self.grab_focus()
|
|
|
|
|
|
|
|
if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == MouseButton.LEFT_BUTTON:
|
|
|
|
self.event_collection.clear()
|
|
|
|
|
|
|
|
self.is_drawing = True
|
|
|
|
self._set_brush_mode(self.brush_mode)
|
|
|
|
self.intermediate_surface.update(self.brush)
|
|
|
|
self.brush.update(eve)
|
|
|
|
|
|
|
|
|
|
|
|
if eve.type == Gdk.EventType.BUTTON_PRESS and eve.button == MouseButton.RIGHT_BUTTON:
|
|
|
|
...
|
|
|
|
|
|
|
|
def _button_release_event(self, widget, eve):
|
|
|
|
if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == MouseButton.LEFT_BUTTON:
|
|
|
|
self.is_drawing = False
|
|
|
|
self.intermediate_surface.history_manager.clear()
|
|
|
|
|
|
|
|
if not self.brush.is_valid: return
|
|
|
|
|
|
|
|
self.primary_surface.update(self.brush)
|
|
|
|
self.queue_draw()
|
|
|
|
|
|
|
|
if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == MouseButton.RIGHT_BUTTON:
|
|
|
|
...
|
|
|
|
|
|
|
|
def _key_release_event(self, widget, eve):
|
|
|
|
keyname = Gdk.keyval_name(eve.keyval)
|
|
|
|
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
|
|
|
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
|
|
|
|
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
|
|
|
|
|
|
|
|
if is_control:
|
|
|
|
if keyname == "z":
|
|
|
|
if len(self.primary_surface.history_manager) == 0: return
|
|
|
|
self.event_collection.append( self.primary_surface.history_manager.pop() )
|
|
|
|
elif keyname == "y":
|
|
|
|
if len(self.event_collection) == 0: return
|
|
|
|
self.primary_surface.history_manager.append( self.event_collection.pop() )
|
|
|
|
elif keyname == "n":
|
|
|
|
self._set_new_surface()
|
|
|
|
elif keyname == "s":
|
|
|
|
self._do_save_image()
|
|
|
|
|
|
|
|
self.queue_draw()
|
|
|
|
|
|
|
|
|
|
|
|
def _motion_notify_event(self, area, eve):
|
|
|
|
if not self.is_drawing: return
|
|
|
|
self.brush.update(eve)
|
|
|
|
self.queue_draw()
|
|
|
|
|
|
|
|
def _draw(self, area, brush: cairo.Context):
|
|
|
|
self._draw_background(area, brush)
|
|
|
|
self._draw_surfaces(area, brush)
|
|
|
|
self._draw_overlay(area, brush)
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _draw_background(self, area, brush: cairo.Context):
|
|
|
|
# Note: While drawing, only overlay needs to re-calculate its stack.
|
|
|
|
# This can just copy what exists than re-calculating the surface.
|
|
|
|
if self.is_drawing:
|
|
|
|
brush.set_source_surface(self.background_surface.area, 0.0, 0.0)
|
|
|
|
brush.paint()
|
|
|
|
return
|
|
|
|
|
|
|
|
brush.set_source_surface(self.background_surface.area, 0.0, 0.0)
|
|
|
|
self.background_surface.draw()
|
|
|
|
brush.paint()
|
|
|
|
|
|
|
|
def _draw_surfaces(self, area, brush: cairo.Context):
|
|
|
|
# Note: While drawing, only overlay needs to re-calculate its stack.
|
|
|
|
# This can just copy what exists than re-calculating each surface.
|
|
|
|
if self.is_drawing:
|
|
|
|
for surface in self.surface_manager:
|
|
|
|
brush.set_source_surface(surface.area, 0.0, 0.0)
|
|
|
|
brush.paint()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
for surface in self.surface_manager:
|
|
|
|
brush.set_source_surface(surface.area, 0.0, 0.0)
|
|
|
|
surface.draw()
|
|
|
|
brush.paint()
|
|
|
|
|
|
|
|
def _draw_overlay(self, area, brush: cairo.Context):
|
|
|
|
# Note: When NOT drawing, no overlay data should exist nor be processed...
|
|
|
|
if not self.is_drawing:
|
|
|
|
self.intermediate_surface.clear_surface()
|
|
|
|
return
|
|
|
|
|
|
|
|
brush.set_source_surface(self.intermediate_surface.area, 0.0, 0.0)
|
|
|
|
self.intermediate_surface.draw()
|
|
|
|
brush.paint()
|