# 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 from .surface import Surface from . import brushes 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()