From: mar77i Date: Tue, 18 Feb 2025 00:59:54 +0000 (+0100) Subject: basic color chooser, basic drawing functionality X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=43d3d02b8b6b5406df36ef39c4a1fcbf3fb8e57f;p=zenbook_gui basic color chooser, basic drawing functionality --- diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py index 6af8a4c..1468f9e 100644 --- a/bookpaint/bookpaint.py +++ b/bookpaint/bookpaint.py @@ -4,7 +4,7 @@ from ui import Root from zenbook_conf.xrandr import XrandrConf from .bookpaint_menu import BookPaintMenu -from .draw_image import DrawImage +from .draw_image import DrawImage, StrokeMethod class BookPaint(Root): @@ -22,9 +22,12 @@ class BookPaint(Root): ) self.background_color = self.BACKGROUND_COLOR self.foreground_color = "white" + self.drawing_color = "foreground_color" + self.line_width = 1 + self.stroke_method = StrokeMethod.PYGAME self.menu = BookPaintMenu(self) self.draw_image = DrawImage(self) - self.draw_image.clear(self.background_color) + self.clear() self.xrandr_conf = XrandrConf(True) def key_escape(self): @@ -54,3 +57,6 @@ class BookPaint(Root): (0, 0), self.display_flags, display=self.current_display ) self.dirty = True + + def clear(self): + self.draw_image.clear(self.background_color) diff --git a/bookpaint/bookpaint_menu.py b/bookpaint/bookpaint_menu.py index dd489a7..80a2dc8 100644 --- a/bookpaint/bookpaint_menu.py +++ b/bookpaint/bookpaint_menu.py @@ -1,3 +1,4 @@ +from functools import partial from pathlib import Path import pygame @@ -16,8 +17,17 @@ from ui import ( TextInput, ) +from .color_chooser import ColorChooser +from .draw_image import StrokeMethod + class BookPaintMenu(Modal): + def get_stroke_methods(self): + return ( + self.root.stroke_method.name.title(), + [stroke_method.name.title() for stroke_method in StrokeMethod], + ) + def __init__(self, parent): super().__init__(parent) Button( @@ -81,37 +91,43 @@ class BookPaintMenu(Modal): str(Path("book/").absolute()), unfocused_align=TextInput.RIGHT, ) - ColorButton( + color_chooser = ColorChooser(self) + self.background_color = ColorButton( self, grid_layout.get_rect((1, 1)).inflate((pad, pad)), self.root.background_color, - lambda: None, + partial(color_chooser.activate_for, "background_color"), ) Button( self, grid_layout.get_rect((2, 1)).inflate((pad, pad)), "Fill Page", - lambda: None, + self.root.clear, ) - ColorButton( + self.foreground_color = ColorButton( self, grid_layout.get_rect((1, 2)).inflate((pad, pad)), self.root.foreground_color, - lambda: None, + partial(color_chooser.activate_for, "foreground_color"), ) - Slider( + self.width_slider = Slider( self, grid_layout.get_rect((1, 3)).inflate((pad, pad)), Slider.HORIZONTAL, - handle_size=96, - callback=lambda _: None, - ), - DropDown( + self.root.line_width - 1, + 96, + self.set_width, + ) + self.width_label = CenterLabel( + self, + grid_layout.get_rect((2, 3)), + "1", + ) + self.stroke_method = DropDown( self, grid_layout.get_rect((1, 4)).inflate((pad, pad)), - "", - ["a", "b", "c"], - lambda _: None, + *self.get_stroke_methods(), + self.set_stroke_method, ) DropDown( self, @@ -144,36 +160,54 @@ class BookPaintMenu(Modal): button_bar_layout.get_rect(), "|<", lambda: None, - ), + ) RepeatButton( self, button_bar_layout.get_rect(), "<", lambda: None, - ), + ) RepeatButton( self, button_bar_layout.get_rect(), ">", lambda: None, - ), + ) Button( self, button_bar_layout.get_rect(), ">|", lambda: None, - ), + ) Button( self, button_bar_layout.get_rect(), "New", lambda: None, - ), - - def key_escape(self): - self.deactivate() + ) - KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}} + KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}} def set_book_path(self, value): print("book path", value) + + def set_color(self, attr, color): + setattr(self.root, attr, color) + getattr(self, attr).color = color + self.dirty = True + + def set_width(self, slider_width): + if slider_width < 0: + slider_width = 0 + if self.width_slider.value != slider_width: + self.width_slider.value = slider_width + self.dirty = True + width = (slider_width * 256 // self.width_slider.extent) + 1 + if self.root.line_width != width: + self.root.line_width = width + self.width_label.value = str(width) + self.dirty = True + + def set_stroke_method(self, i): + self.root.stroke_method = StrokeMethod(i + 1) + self.stroke_method.value = self.root.stroke_method.name.title() diff --git a/bookpaint/color_chooser.py b/bookpaint/color_chooser.py new file mode 100644 index 0000000..0887fc5 --- /dev/null +++ b/bookpaint/color_chooser.py @@ -0,0 +1,86 @@ +from colorsys import hsv_to_rgb +from functools import partial + +import pygame + +from ui import Modal, Rect + +from ui import ColorButton + + +def hsv_to_color(hsv): + return pygame.Color(*(int(c * 255) for c in hsv_to_rgb(*(c / 255 for c in hsv)))) + + +class ColorChooser(Modal): + def get_color_grid(self, lefttop, cell_size): + """ + 8 * 8 * 8 hsv colors => 351 unique colors of that cube. + Arrange them in a visually pleasing way. + """ + x = y = 0 + pos = list(lefttop) + colors_seen = set() + grays = [] + grays_pos = None + for v_range in (range(7, 3, -1), range(3, -1, -1)): + for s in range(7, -1, -1): + for v in v_range: + for h in range(8): + color = hsv_to_color((h * 255 / 7, s * 255 / 7, v * 255 / 7)) + int_color = (color.r << 16) | (color.g << 8) | color.b + if int_color not in colors_seen: + colors_seen.add(int_color) + if v == 0: + grays_pos = pos.copy() + grays.insert(0, color) + elif s == 0: + grays.append(color) + else: + yield (pygame.Rect(pos, cell_size), color) + x += 1 + pos[0] += cell_size[0] // (2 - bool(x % 8)) + if x < 32: + continue + x, pos[0] = 0, lefttop[0] + y += 1 + pos[1] += cell_size[1] // (2 - bool(y % 8)) + for i, gray in enumerate((*grays[1:], grays[0])): + yield ( + pygame.Rect( + ( + grays_pos[0] + (i % 4) * cell_size[0], + grays_pos[1] + (i // 4) * cell_size[1], + ), + cell_size, + ), + gray, + ) + + def __init__(self, parent): + super().__init__(parent) + # self.rect = rect + size = self.surf.get_size() + rect = pygame.Rect( + (448, 192), + (size[0] - 896, size[1] - 384), + ) + Rect(self, rect, "black", "gray34") + self.attr = None + self.color = None + for r, color in self.get_color_grid((rect.left + 48, rect.top + 192), (64, 64)): + ColorButton(self, r, color, partial(self.set_color, color)) + + KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}} + + def activate_for(self, attr): + self.attr = attr + super().activate() + + def set_color(self, color): + self.color = color + self.deactivate() + + def deactivate(self): + super().deactivate() + self.parent.set_color(self.attr, self.color) diff --git a/bookpaint/draw_image.py b/bookpaint/draw_image.py index 1179854..af7bb3f 100644 --- a/bookpaint/draw_image.py +++ b/bookpaint/draw_image.py @@ -1,6 +1,15 @@ +from enum import Enum, auto + import pygame from ui import Child +from vectors import StrokeRoundLine, StrokeSquareLine + + +class StrokeMethod(Enum): + PYGAME = auto() + ROUND = auto() + SQUARE = auto() class DrawImage(Child): @@ -8,9 +17,55 @@ class DrawImage(Child): super().__init__(parent) self.surface = pygame.Surface(self.surf.get_size(), 0, 24) self.clear(self.root.BACKGROUND_COLOR) + self.prev_pos = None def clear(self, background_color): self.surface.fill(background_color) def draw(self): self.surf.blit(self.surface, (0, 0)) + + def pygame_stroke(self, a, b): + root = self.root + pygame.draw.line( + self.surface, getattr(root, root.drawing_color), a, b, root.line_width + ) + + def round_stroke(self, a, b): + root = self.root + StrokeRoundLine( + a, b, root.line_width + ).draw(self.surface, getattr(root, root.drawing_color)) + + def square_stroke(self, a, b): + root = self.root + StrokeSquareLine( + a, b, root.line_width + ).draw(self.surface, getattr(root, root.drawing_color)) + + STROKE_MAPPING = { + StrokeMethod.PYGAME: pygame_stroke, + StrokeMethod.ROUND: round_stroke, + StrokeMethod.SQUARE: square_stroke + } + + def handle_mousebuttondown(self, ev): + if ev.button == 1: + self.root.drawing_color = "foreground_color" + elif ev.button == 3: + self.root.drawing_color = "background_color" + else: + return + self.prev_pos = ev.pos + + def handle_mousemotion(self, ev): + if self.prev_pos is not None: + self.STROKE_MAPPING[self.root.stroke_method](self, self.prev_pos, ev.pos) + self.prev_pos = ev.pos + self.dirty = True + + def handle_mousebuttonup(self, ev): + if self.prev_pos is not None: + self.STROKE_MAPPING[self.root.stroke_method](self, self.prev_pos, ev.pos) + self.prev_pos = None + self.dirty = True