Files
PixelBash/src/widgets/draw_area.py

226 lines
7.9 KiB
Python

# 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 data.events import brushes
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()