From 6d5f00fb2cad4756445678a165400ec76b9d0f27 Mon Sep 17 00:00:00 2001 From: mar77i Date: Thu, 8 May 2025 02:32:20 +0200 Subject: [PATCH] consolidate label, start over with bookpaint --- bookpaint/book_manager.py | 66 ++++++++++ bookpaint/bookpaint.py | 80 +++++++------ bookpaint/bookpaint_menu.py | 230 ----------------------------------- bookpaint/color_chooser.py | 233 ------------------------------------ bookpaint/color_circle.py | 169 -------------------------- bookpaint/color_plane.py | 72 ----------- bookpaint/draw_image.py | 185 +++++++++------------------- bookpaint/menu.py | 21 ++++ bookpaint/utils.py | 27 ----- bookpaint/value_slider.py | 5 - memory.py | 15 ++- ui/__init__.py | 5 +- ui/drop_down.py | 2 +- ui/label.py | 43 ++++--- ui/text_input.py | 2 +- vs_memory.py | 7 +- 16 files changed, 231 insertions(+), 931 deletions(-) create mode 100644 bookpaint/book_manager.py delete mode 100644 bookpaint/bookpaint_menu.py delete mode 100644 bookpaint/color_chooser.py delete mode 100644 bookpaint/color_circle.py delete mode 100644 bookpaint/color_plane.py create mode 100644 bookpaint/menu.py delete mode 100644 bookpaint/utils.py delete mode 100644 bookpaint/value_slider.py diff --git a/bookpaint/book_manager.py b/bookpaint/book_manager.py new file mode 100644 index 0000000..39fd1f7 --- /dev/null +++ b/bookpaint/book_manager.py @@ -0,0 +1,66 @@ +from pathlib import Path + +import pygame + + +class BookManager: + SUFFIX = ".png" + + def get_pages(self): + free_page_no = 1 + pages = [] + numbered_pages = [] + for p in self.path.iterdir(): + if p.suffix != self.SUFFIX: + continue + try: + page_no = int(p.stem) + except ValueError: + pages.append(p) + else: + numbered_pages.append(p) + free_page_no = max(free_page_no, page_no + 1) + pages = sorted(pages) + pages.extend(sorted(numbered_pages)) + return free_page_no, pages + + def __init__(self, path: Path): + self.path = path + self.free_page_no, self.pages = self.get_pages() + self.current_file = self.get_new_file() + + def get_new_file(self): + return self.path / f"{self.free_page_no}{self.SUFFIX}" + + def save_file(self, surf): + if self.current_file not in self.pages: + if self.current_file.exists(): + self.pages.clear() + self.free_page_no, pages = self.get_pages() + self.pages.extend(pages) + self.current_file = self.get_new_file() + self.pages.append(self.current_file) + self.free_page_no += 1 + pygame.image.save(surf, self.current_file) + + NewFile = type("NewFileType", (), {})() + + def next_file(self): + if self.current_file not in self.pages: + return None + i = self.pages.index(self.current_file) + if i == len(self.pages) - 1: + self.current_file = self.get_new_file() + return self.NewFile + self.current_file = self.pages[i + 1] + return pygame.image.load(self.current_file) + + def prev_file(self): + if self.current_file not in self.pages: + i = len(self.pages) + else: + i = self.pages.index(self.current_file) + if i == 0: + return None + self.current_file = self.pages[i - 1] + return pygame.image.load(self.current_file) diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py index 20b4f3b..473f03d 100644 --- a/bookpaint/bookpaint.py +++ b/bookpaint/bookpaint.py @@ -1,10 +1,19 @@ +from pathlib import Path + import pygame from ui import Root -from zenbook_conf.xrandr import XrandrConf -from .bookpaint_menu import BookPaintMenu +from .book_manager import BookManager from .draw_image import DrawImage +from .menu import Menu + + +# - color menu; set background (default for new pages) and foreground color +# - stub with a 6 hex-digit textinputs and a little rectangle +# - page menu; browse through next and prev pages using stylus +# - info layer (F3) +# - list color and page menus in the esc menu but also using keyboard shortcuts c/p class BookPaint(Root): @@ -12,42 +21,45 @@ class BookPaint(Root): def __init__(self): pygame.init() - self.current_display = 0 - self.display_flags = pygame.FULLSCREEN super().__init__( - pygame.display.set_mode( - (0, 0), self.display_flags, display=self.current_display - ), + pygame.display.set_mode((0, 0), pygame.FULLSCREEN), pygame.font.Font(None, size=96), ) - self.draw_image = DrawImage(self, self.BACKGROUND_COLOR, "white") - self.menu = BookPaintMenu(self, self.draw_image) - self.xrandr_conf = XrandrConf(True) + self.menu = Menu(self) + self.draw_image = DrawImage(self, "white") + book_path = Path("/dev/shm/book") + if not book_path.exists(): + book_path.mkdir(0o755) + self.book_manager = BookManager(book_path) def key_escape(self): self.menu.activate() - KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}} - - def quit(self): - self.running = False - - def next_display(self): - self.xrandr_conf.renew() - if self.xrandr_conf.count_active() != len(pygame.display.get_desktop_sizes()): - pygame.display.quit() - pygame.display.init() - reset = True - else: - reset = False - num_displays = len(pygame.display.get_desktop_sizes()) - current_display = (self.current_display + 1) % num_displays - if current_display != self.current_display: - self.current_display = current_display - reset = True - if not reset: - return - self.surf = pygame.display.set_mode( - (0, 0), self.display_flags, display=self.current_display - ) - self.dirty = True + def save_file(self): + self.book_manager.save_file(self.draw_image.surface) + self.draw_image.image_dirty = False + + def set_image(self, surf): + if surf == BookManager.NewFile: + self.draw_image.clear() + elif surf: + self.draw_image.set_image(surf) + + def next_file(self): + if self.draw_image.image_dirty: + self.save_file() + self.set_image(self.book_manager.next_file()) + + def prev_file(self): + if self.draw_image.image_dirty: + self.save_file() + self.set_image(self.book_manager.prev_file()) + + KEY_METHODS = { + frozenset(): {pygame.K_ESCAPE: key_escape}, + frozenset({pygame.KMOD_CTRL}): { + pygame.K_s: save_file, + pygame.K_PAGEDOWN: next_file, + pygame.K_PAGEUP: prev_file, + }, + } diff --git a/bookpaint/bookpaint_menu.py b/bookpaint/bookpaint_menu.py deleted file mode 100644 index 44a364c..0000000 --- a/bookpaint/bookpaint_menu.py +++ /dev/null @@ -1,230 +0,0 @@ -from functools import partial -from pathlib import Path - -import pygame - -from layout import BarLayout, GridLayout -from ui import ( - Button, - CenterLabel, - ColorButton, - DropDown, - Modal, - Rect, - RepeatButton, - RightLabel, - Slider, - TextInput, -) - -from .color_chooser import ColorChooser -from .draw_image import InputMethod, StrokeMethod -from .utils import color_to_hex - - -class BookPaintMenu(Modal): - def get_stroke_methods(self): - return ( - self.draw_image.stroke_method.name.title(), - [stroke_method.name.title() for stroke_method in StrokeMethod], - ) - - def get_input_methods(self): - labels = InputMethod.labels() - return ( - labels[tuple(InputMethod).index(self.draw_image.input_method)], - labels, - ) - - def __init__(self, parent, draw_image): - super().__init__(parent) - self.draw_image = draw_image - Button( - self, - pygame.Rect((self.surf.get_width() - 128, 0), (128, 128)), - "×", - self.root.quit, - ) - Button( - self, - pygame.Rect((self.surf.get_width() - 256, 0), (128, 128)), - "_", - pygame.display.iconify, - ) - Button( - self, - pygame.Rect((self.surf.get_width() - 384, 0), (128, 128)), - "»", - self.root.next_display, - ) - size = self.surf.get_size() - grid_layout = GridLayout( - pygame.Rect((512, 256), (size[0] - 1024, size[1] - 768)), (3, 6) - ) - RightLabel( - self, - grid_layout.get_rect((0, 0)), - "Book Path", - ) - RightLabel( - self, - grid_layout.get_rect((0, 1)), - "New Page Color", - ) - RightLabel( - self, - grid_layout.get_rect((0, 2)), - "Line Color", - ) - RightLabel( - self, - grid_layout.get_rect((0, 3)), - "Line Width", - ) - RightLabel( - self, - grid_layout.get_rect((0, 4)), - "Stroke Method", - ) - RightLabel( - self, - grid_layout.get_rect((0, 5)), - "Input", - ) - rect = grid_layout.get_rect((1, 0), (2, 1)) - pad = 96 - rect.height - self.path_input = TextInput( - self, - rect.inflate((pad, pad)), - self.set_book_path, - str(Path("book/").absolute()), - unfocused_align=TextInput.RIGHT, - ) - color_chooser = ColorChooser(self) - self.background_color = ColorButton( - self, - grid_layout.get_rect((1, 1)).inflate((pad, pad)), - self.draw_image.background_color, - partial(color_chooser.activate_for, "background_color"), - ) - Button( - self, - grid_layout.get_rect((2, 1)).inflate((pad, pad)), - "Fill Page", - self.draw_image.clear, - ) - self.foreground_color = ColorButton( - self, - grid_layout.get_rect((1, 2)).inflate((pad, pad)), - self.draw_image.foreground_color, - partial(color_chooser.activate_for, "foreground_color"), - ) - self.foreground_color_value = CenterLabel( - self, - grid_layout.get_rect((2, 2)), - color_to_hex(self.draw_image.foreground_color), - ) - self.width_slider = Slider( - self, - grid_layout.get_rect((1, 3)).inflate((pad, pad)), - Slider.HORIZONTAL, - self.draw_image.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)), - *self.get_stroke_methods(), - self.set_stroke_method, - ) - self.input_method = DropDown( - self, - grid_layout.get_rect((1, 5), (2, 1)).inflate((pad, pad)), - *self.get_input_methods(), - self.set_input_method, - ) - rect = pygame.Rect( - (512, size[1] - 384), - (size[0] - 1024, 256), - ) - Rect(self, rect, "black", "darkgray") - - self.page_label = CenterLabel( - self, - pygame.Rect( - (rect.left, rect.top + rect.height // 4 - 48), (rect.width, 96) - ), - "0 / 0", - ) - button_bar_layout = BarLayout( - pygame.Rect( - (rect.left, rect.top + rect.height * 3 // 4 - 48), (rect.width, 96) - ), - abs(pad), - ) - Button( - self, - 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, - ) - - 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.draw_image, 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.draw_image.line_width != width: - self.draw_image.line_width = width - self.width_label.value = str(width) - self.dirty = True - - def set_stroke_method(self, i): - self.draw_image.stroke_method = StrokeMethod(i + 1) - self.stroke_method.value = self.draw_image.stroke_method.name.title() - - def set_input_method(self, i): - self.draw_image.input_method = InputMethod(i + 1) - self.input_method.value = InputMethod.labels()[i] diff --git a/bookpaint/color_chooser.py b/bookpaint/color_chooser.py deleted file mode 100644 index a0ce42a..0000000 --- a/bookpaint/color_chooser.py +++ /dev/null @@ -1,233 +0,0 @@ -from functools import partial - -import pygame - -from ui import Button, ColorButton, Label, Modal, Rect, TabBar - -from .color_circle import ColorCircle -from .color_plane import ColorPlane -from .utils import hsv_to_color - - -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(7): - 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] - if x % 7 == 0: - pos[0] += cell_size[0] // 2 - if x < 28: - continue - x, pos[0] = 0, lefttop[0] - y += 1 - if y % 8 == 0: - pos[1] += cell_size[1] // 2 - else: - pos[1] += cell_size[1] - 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) - 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 = pygame.Color("red") - buttons = [] - for r, color in self.get_color_grid((rect.left + 48, rect.top + 192), (64, 64)): - buttons.append(ColorButton(self, r, color, partial(self.set_color, color))) - self.color_circle = ColorCircle( - self, - pygame.Rect((rect.left + 1024, rect.top + 256), (512, 512)), - self.chooser_set_hsv, - self.color, - ) - self.chooser_button = ColorButton( - self, - pygame.Rect((rect.right - 1024, rect.bottom - 192), (768, 128)), - self.color, - self.deactivate, - ) - #self.hsv_rgb_buttons = ( - # Button( - # self, - # pygame.Rect((rect.left + 1024, rect.top + 160), (96, 96)), - # "H", - # partial(self.set_plane_axis, 0, AxisSetting.HUE), - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1120, rect.top + 160), (96, 96)), - # "S", - # partial(self.set_plane_axis, 0, AxisSetting.SATURATION), - # True, - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1216, rect.top + 160), (96, 96)), - # "V", - # partial(self.set_plane_axis, 0, AxisSetting.VALUE) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1312, rect.top + 160), (96, 96)), - # "R", - # partial(self.set_plane_axis, 0, AxisSetting.RED) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1408, rect.top + 160), (96, 96)), - # "G", - # partial(self.set_plane_axis, 0, AxisSetting.GREEN) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1504, rect.top + 160), (96, 96)), - # "B", - # partial(self.set_plane_axis, 0, AxisSetting.BLUE) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 256), (96, 96)), - # "H", - # partial(self.set_plane_axis, 1, AxisSetting.HUE) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 352), (96, 96)), - # "S", - # partial(self.set_plane_axis, 1, AxisSetting.SATURATION) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 448), (96, 96)), - # "V", - # partial(self.set_plane_axis, 1, AxisSetting.VALUE), - # True, - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 544), (96, 96)), - # "R", - # partial(self.set_plane_axis, 1, AxisSetting.RED) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 640), (96, 96)), - # "G", - # partial(self.set_plane_axis, 1, AxisSetting.GREEN) - # ), - # Button( - # self, - # pygame.Rect((rect.left + 1792, rect.top + 736), (96, 96)), - # "B", - # partial(self.set_plane_axis, 1, AxisSetting.BLUE) - # ), - #) - #self.hsv_rgb_slider = Slider( - # self, - # pygame.Rect((rect.left + 928, rect.top + 256), (96, 768)), - # Slider.VERTICAL, - # 0, - # 96, - # self.set_hsv_rgb_slider, - #) - #self.hsv_rgb_slider.value = self.hsv_rgb_slider.extent - #self.hsv_rgb_slider_label = Label( - # self, pygame.Rect((rect.left + 928, rect.top + 160), (96, 96)), "H" - #) - #self.color_plane = ColorPlane( - # self, - # pygame.Rect((rect.left + 1024, rect.top + 256), (768, 768)), - # self.color, - #) - TabBar( - self, - pygame.Rect(rect.topleft, (1024, 128)), - ("Grid", "Chooser"), - ( - buttons, - ( - self.color_circle, - #self.hsv_rgb_slider_label, - #self.hsv_rgb_slider, - #*self.hsv_rgb_buttons, - #self.color_plane, - self.chooser_button, - ), - ), - 1, - ) - - 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) - - def chooser_set_hsv(self, hsv): - self.chooser_button.color = self.color = hsv_to_color(hsv) - self.dirty = True - - def set_plane_axis(self, axis, axis_setting): - if axis == 0: - self.color_plane.set_horizontal(axis_setting) - else: - self.color_plane.set_vertical(axis_setting) - #for button in self.hsv_rgb_buttons: - # if button.callback.args[0] == 0: - # button.highlight = button.callback.args[1] == self.color_plane.horizontal_setting - # else: # button.callback.args[0] == 1 - # button.highlight = button.callback.args[1] == self.color_plane.vertical_setting - - def set_hsv_rgb_slider(self, value): - if value < 0: - value = 0 - elif value > self.hsv_rgb_slider.extent: - value = self.hsv_rgb_slider.extent - self.hsv_rgb_slider.value = value - self.color_plane.set_base_value(value * 255 / self.hsv_rgb_slider.extent) diff --git a/bookpaint/color_circle.py b/bookpaint/color_circle.py deleted file mode 100644 index 45334b9..0000000 --- a/bookpaint/color_circle.py +++ /dev/null @@ -1,169 +0,0 @@ -from math import atan2, cos, sin, tau -from operator import itemgetter - -import pygame - -from ui import Child - -from .utils import distance, hsv_to_color, color_to_hsv - - -class ColorCircle(Child): - @staticmethod - def get_hue_circle(size): - surf = pygame.Surface(size, pygame.SRCALPHA, 32) - surf.fill(pygame.Color(0, 0, 0, 0)) - center = tuple(x // 2 for x in size) - outer_radius = min(center) - inner_radius = outer_radius * 3 // 4 - inner_radius_squared = inner_radius ** 2 - outer_radius_squared = outer_radius ** 2 - with pygame.PixelArray(surf) as pa: - for x, col in enumerate(pa): - for y in range(len(col)): - pos = (x - center[0]), (y - center[1]) - distance_squared = pos[0] ** 2 + pos[1] ** 2 - if inner_radius_squared <= distance_squared <= outer_radius_squared: - angle = atan2(pos[1], pos[0]) - if angle < 0: - angle += tau - pa[x][y] = hsv_to_color((angle * 255 / tau, 255, 255)) - return inner_radius, surf - - get_alphas = (itemgetter(1), itemgetter(0)) - - @classmethod - def get_overlay_surfs(cls, size): - overlay_surfs = ( - pygame.Surface(size, pygame.SRCALPHA, 32), - pygame.Surface(size, pygame.SRCALPHA, 32), - ) - for surf, get_alpha in zip(overlay_surfs, cls.get_alphas): - with pygame.PixelArray(surf) as pa: - for x, col in enumerate(pa): - for y in range(len(col)): - pa[x][y] = pygame.Color( - y // 2, y // 2, y // 2, 255 - get_alpha((x, y)), - ) - return overlay_surfs - - def __init__(self, parent, rect, callback, color): - super().__init__(parent) - self.rect = rect - min_size = min(rect.size) // 2 - self.inner_radius, self.hue_circle = self.get_hue_circle(rect.size) - self.overlay_surfs = self.get_overlay_surfs((min_size, min_size)) - self.callback = callback - self.hsv = color_to_hsv(color) - self.sv_surf = None - self.pushed = None - - def set_hue(self, rel_pos): - angle = atan2(rel_pos[1], rel_pos[0]) - if angle < 0: - angle += tau - hue = angle * 256 / tau - if hue >= 256: - hue -= 256 - if hue == self.hsv[0]: - return - self.sv_surf = None - self.hsv = (hue, *self.hsv[1:]) - self.callback(self.hsv) - self.dirty = True - - def set_sv(self, rel_pos): - hue_angle = self.hsv[0] * tau / 255 - sv_surf_angle = tau * 9 / 8 - hue_angle - if sv_surf_angle >= tau: - sv_surf_angle -= tau - center = tuple(a // 2 for a in self.overlay_surfs[0].get_size()) - dist = distance(rel_pos, (0, 0)) - angle = atan2(rel_pos[1], rel_pos[0]) + sv_surf_angle - self.hsv = ( - self.hsv[0], - min(max(0, center[0] + cos(angle) * dist), 255), - min(max(0, center[1] + sin(angle) * dist), 255), - ) - self.callback(self.hsv) - self.dirty = True - - def handle_mousebuttondown(self, ev): - if ev.button != 1 or not self.rect.collidepoint(ev.pos): - return - rel_pos = (ev.pos[0] - self.rect.centerx, ev.pos[1] - self.rect.centery) - self.pushed = distance(rel_pos, (0, 0)) >= self.inner_radius - if self.pushed: - self.set_hue(rel_pos) - else: - self.set_sv(rel_pos) - - def handle_mousemotion(self, ev): - if self.pushed is None: - return - rel_pos = tuple(p - c for p, c in zip(ev.pos, self.rect.center)) - if self.pushed: - self.set_hue(rel_pos) - else: - self.set_sv(rel_pos) - - def handle_mousebuttonup(self, ev): - if self.pushed is None: - return - rel_pos = tuple(p - c for p, c in zip(ev.pos, self.rect.center)) - if self.pushed is True: - self.set_hue(rel_pos) - else: - self.set_sv(rel_pos) - self.pushed = None - - def draw_cursor(self, pos): - pygame.draw.circle(self.surf, "black", pos, 12) - pygame.draw.circle(self.surf, "darkgray", pos, 12, 2) - - def draw_sv_surf(self, hue_angle): - sv_surf_size = self.overlay_surfs[0].get_size() - if self.sv_surf is None: - self.sv_surf = pygame.Surface(sv_surf_size, pygame.SRCALPHA, 32) - self.sv_surf.fill(hsv_to_color((self.hsv[0], 255, 255))) - for overlay_surf in self.overlay_surfs: - self.sv_surf.blit(overlay_surf, (0, 0)) - sv_surf_angle = tau * 9 / 8 - hue_angle - if sv_surf_angle >= tau: - sv_surf_angle -= tau - sv_surf = pygame.transform.rotate(self.sv_surf, sv_surf_angle * 360 / tau) - self.surf.blit( - sv_surf, - tuple( - rc - sc - for rc, sc in zip( - self.rect.center, tuple(a // 2 for a in sv_surf.get_size()) - ) - ) - ) - - center = tuple(a // 2 for a in sv_surf_size) - cursor_distance = distance(self.hsv[1:], center) - cursor_angle = atan2( - self.hsv[2] - center[0], self.hsv[1] - center[1] - ) - sv_surf_angle - while cursor_angle < 0: - cursor_angle += tau - self.draw_cursor( - tuple( - c + f(cursor_angle) * cursor_distance - for c, f in zip(self.rect.center, (cos, sin)) - ) - ) - - def draw(self): - self.surf.blit(self.hue_circle, self.rect.topleft) - hue_angle = self.hsv[0] * tau / 255 - radius = min(x // 2 for x in self.rect.size) * 7 // 8 - self.draw_cursor( - ( - self.rect.centerx + cos(hue_angle) * radius, - self.rect.centery + sin(hue_angle) * radius, - ) - ) - self.draw_sv_surf(hue_angle) diff --git a/bookpaint/color_plane.py b/bookpaint/color_plane.py deleted file mode 100644 index 84a26a4..0000000 --- a/bookpaint/color_plane.py +++ /dev/null @@ -1,72 +0,0 @@ -from enum import Enum, auto - -import pygame - -from ui import Child -from .utils import color_to_hsv, hsv_to_color - - -class AxisSetting(Enum): - HUE = auto() - SATURATION = auto() - VALUE = auto() - RED = auto() - GREEN = auto() - BLUE = auto() - - -class ColorPlane(Child): - """ - 6 modes at the top R/G/B/H/S/V - 6 modes on the side R/G/B/H/S/V - - Both modes can probably be the same, but they must at all times be - from the same group: R/G/B or H/S/V - - So when you turn on a new group, we can set the other dimension - to the same - or not the same - value. - """ - def get_surface(self): - size = self.rect.size - surf = pygame.Surface(size) - with pygame.PixelArray(surf) as pa: - for x, col in enumerate(pa): - for y in range(size[1]): - col[y] = hsv_to_color( - (int(y * 256 / size[1]), int(x * 256 / size[0]), 255) - ) - return surf - - def __init__(self, parent, rect, color): - super().__init__(parent) - self.rect = rect - self.horizontal_setting = AxisSetting.SATURATION - self.vertical_setting = AxisSetting.VALUE - hsv = color_to_hsv(color) - self.values = hsv[1:] - self.base = hsv[0] - self.surface = self.get_surface() - - def draw(self): - self.surf.blit(self.surface, self.rect.topleft) - - HSV = (AxisSetting.HUE, AxisSetting.SATURATION, AxisSetting.VALUE) - RGB = (AxisSetting.RED, AxisSetting.GREEN, AxisSetting.BLUE) - - def set_horizontal(self, axis_setting): - self.horizontal_setting = axis_setting - group = self.HSV if self.horizontal_setting in self.HSV else self.RGB - if self.vertical_setting not in group: - self.vertical_setting = group[group[0] == self.horizontal_setting] - - def set_vertical(self, axis_setting): - self.vertical_setting = axis_setting - group = self.HSV if self.vertical_setting in self.HSV else self.RGB - if self.horizontal_setting not in group: - self.horizontal_setting = group[group[0] == self.vertical_setting] - - def set_base_value(self, value): - if value == self.base: - return - self.base = value - self.surface = self.get_surface() diff --git a/bookpaint/draw_image.py b/bookpaint/draw_image.py index 141c56a..46b9075 100644 --- a/bookpaint/draw_image.py +++ b/bookpaint/draw_image.py @@ -1,5 +1,4 @@ from enum import Enum, auto -from time import time import pygame @@ -13,149 +12,79 @@ class StrokeMethod(Enum): PYGAME = auto() -class InputMethod(Enum): - SDL_ANY = auto() - SDL_MOUSE = auto() - SDL_FINGERS = auto() - SDL_KEYBOARD = auto() - - @staticmethod - def labels(): - return ( - "Mouse or Fingers (SDL)", - "Mouse/Stylus (SDL)", - "Fingers (SDL)", - "Keyboard (SDL)", - ) - - class DrawImage(Child): FINGER_TIMEOUT = 30 - def __init__(self, parent, background_color, foreground_color): + def __init__(self, parent, color): super().__init__(parent) - self.background_color = background_color - self.foreground_color = foreground_color - self.drawing_color = "foreground_color" + self.color = color self.line_width = 1 self.surface = pygame.Surface(self.surf.get_size(), 0, 24) self.clear() + self.image_dirty = False self.stroke_method = next(iter(StrokeMethod)) - self.input_method = next(iter(InputMethod)) - self.mouse_pos = None - self.fingers = {} + self.prev_pos = None def clear(self): - self.surface.fill(self.background_color) - - def handle_mousebuttondown(self, ev): - if self.input_method in (InputMethod.SDL_ANY, InputMethod.SDL_MOUSE): - if ev.button == 1: - self.drawing_color = "foreground_color" - self.mouse_pos = ev.pos - elif ev.button == 3: - self.drawing_color = "background_color" - self.mouse_pos = ev.pos - - def mouse_stroke(self, buttons, pos): - if ( - self.input_method in (InputMethod.SDL_ANY, InputMethod.SDL_MOUSE) - and self.mouse_pos is not None - and (buttons[0] or buttons[2]) - ): - self.STROKE_MAPPING[self.stroke_method](self, self.mouse_pos, pos) - self.mouse_pos = pos - self.dirty = True - - def handle_mousemotion(self, ev): - self.mouse_stroke(ev.buttons, ev.pos) - - def handle_mousebuttonup(self, ev): - self.mouse_stroke(tuple(ev.button == i for i in range(1, 4)), ev.pos) - - def get_finger_pos(self, key): - item = self.fingers.get(key) - if item is None: - return None - prev_pos, t = item - if t < time(): - self.fingers.pop(key) - return None - return prev_pos - - def handle_fingerdown(self, ev): - if self.input_method not in (InputMethod.SDL_ANY, InputMethod.SDL_FINGERS): - self.fingers.clear() - return - key = (ev.touch_id, ev.finger_id) - size = self.surface.get_size() - pos = (int(size[0] * ev.x), int(size[1] * ev.y)) - prev_pos = self.get_finger_pos(key) - if prev_pos is not None: - self.STROKE_MAPPING[self.stroke_method](self, prev_pos, pos) - self.dirty = True - if ev.type == pygame.FINGERUP: - self.fingers.pop(key, None) - else: - self.fingers[key] = pos, time() + self.FINGER_TIMEOUT - - handle_fingermotion = handle_fingerdown - handle_fingerup = handle_fingerdown + self.surface.fill("black") + self.image_dirty = False + self.dirty = True - def update(self): - if not self.root.focused: - if self.mouse_pos is not None: - self.mouse_pos = None - self.dirty = True - if len(self.fingers) > 0: - self.fingers.clear() - self.dirty = True - return - buttons = pygame.mouse.get_pressed() - if buttons[0] or buttons[2]: - return - pos = pygame.mouse.get_pos() - if pos != self.mouse_pos: - self.mouse_pos = pos - self.dirty = True + def dot(self, pos): + pygame.draw.circle(self.surf, self.color, pos, self.line_width // 2) def draw(self): self.surf.blit(self.surface, (0, 0)) - if self.mouse_pos is not None: - pygame.draw.circle( - self.surf, - getattr(self, self.drawing_color), - self.mouse_pos, - self.line_width // 2, - ) - pygame.draw.circle( - self.surf, - "black", - self.mouse_pos, - self.line_width // 2, - 2, - ) + self.dot(pygame.mouse.get_pos()) - def pygame_stroke(self, a, b): - pygame.draw.line( - self.surface, getattr(self, self.drawing_color), a, b, self.line_width - ) + def handle_mousebuttondown(self, ev): + if ev.button == 1: + self.prev_pos = ev.pos + self.dirty = True - def round_stroke(self, a, b): - StrokeRoundLine( - a, b, self.line_width - ).draw(self.surface, getattr(self, self.drawing_color)) + def handle_mousemotion(self, ev): + if self.prev_pos is not None: + if self.stroke_method == StrokeMethod.ROUND: + if ev.pos == self.prev_pos: + self.dot(ev.pos) + else: + StrokeRoundLine( + self.prev_pos, ev.pos, self.line_width + ).draw(self.surface, self.color) + elif self.stroke_method == StrokeMethod.SQUARE: + if ev.pos == self.prev_pos: + pygame.draw.rect( + self.surface, + self.color, + pygame.Rect( + ( + ev.pos[0] - self.line_width // 2, + ev.pos[1] - self.line_width // 2, + ), + (self.line_width, self.line_width), + ) + ) + else: + StrokeSquareLine( + self.prev_pos, ev.pos, self.line_width + ).draw(self.surface, self.color) + elif self.stroke_method == StrokeMethod.PYGAME: + if ev.pos == self.prev_pos: + self.dot(ev.pos) + else: + pygame.draw.line( + self.surface, self.color, self.prev_pos, ev.pos, self.line_width + ) + self.prev_pos = ev.pos + self.image_dirty = True + self.dirty = True - def square_stroke(self, a, b): - if a == b: - return - StrokeSquareLine( - a, b, self.line_width - ).draw(self.surface, getattr(self, self.drawing_color)) + def handle_mousebuttonup(self, ev): + if ev.button == 1 and self.prev_pos is not None: + self.handle_mousemotion(ev) + self.prev_pos = None - STROKE_MAPPING = { - StrokeMethod.PYGAME: pygame_stroke, - StrokeMethod.ROUND: round_stroke, - StrokeMethod.SQUARE: square_stroke - } + def set_image(self, surf): + self.surface.blit(surf, (0, 0)) + self.dirty = True diff --git a/bookpaint/menu.py b/bookpaint/menu.py new file mode 100644 index 0000000..3b836b6 --- /dev/null +++ b/bookpaint/menu.py @@ -0,0 +1,21 @@ +import pygame + +from ui import ( + Button, + Modal, +) + + +class Menu(Modal): + def __init__(self, parent): + super().__init__(parent) + size = self.root.surf.get_size() + width_third = size[0] // 3 + Button( + self, + pygame.Rect((width_third, size[1] * 2 // 3), (width_third, 128)), + "Quit", + self.root.handle_quit, + ) + + KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}} diff --git a/bookpaint/utils.py b/bookpaint/utils.py deleted file mode 100644 index bd94bc7..0000000 --- a/bookpaint/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -from colorsys import hsv_to_rgb, rgb_to_hsv -from math import sqrt - -import pygame - - -def distance(a, b): - return sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) - - -def hsv_to_color(hsv): - return pygame.Color( - *(int(c * 255) for c in hsv_to_rgb(hsv[0] / 256, hsv[1] / 255, hsv[2] / 255)) - ) - - -def color_to_hsv(color): - hsv = rgb_to_hsv(color.r / 255, color.g / 255, color.b / 255) - hue = hsv[0] * 256 - if hue >= 256: - hue -= 256 - return (hue, hsv[1] * 255, hsv[2] * 255) - - -def color_to_hex(color): - assert isinstance(color, pygame.Color) - return str(, 16) diff --git a/bookpaint/value_slider.py b/bookpaint/value_slider.py deleted file mode 100644 index c19d71e..0000000 --- a/bookpaint/value_slider.py +++ /dev/null @@ -1,5 +0,0 @@ -from ui import Slider - - -class ValueSlider(Slider): - pass diff --git a/memory.py b/memory.py index dfea625..a1611f0 100755 --- a/memory.py +++ b/memory.py @@ -6,7 +6,7 @@ from secrets import choice import pygame -from ui import Button, CenterLabel, Child, Modal, Root, TextInput +from ui import Button, Child, Label, Modal, Root, TextInput class QuittableModal(Modal): @@ -22,10 +22,11 @@ class NameMenu(QuittableModal): # add exit button super().__init__(parent) self.lw = parent.surf.get_width() / 3 - CenterLabel( + Label( self, pygame.Rect((int(self.lw / 2), 144), (self.lw, 128)), "Enter player names", + Label.HAlign.CENTER, ) self.inputs = [ TextInput( @@ -106,11 +107,17 @@ class EndGameMenu(QuittableModal): self.rect = rect th = rect.height // 2 self.labels = ( - CenterLabel(self, pygame.Rect(rect.topleft, (rect.width, th)), ""), - CenterLabel( + Label( + self, + pygame.Rect(rect.topleft, (rect.width, th)), + "", + Label.HAlign.CENTER, + ), + Label( self, pygame.Rect((rect.left, rect.top + th), (rect.width, th)), "NEW GAME", + Label.HAlign.CENTER, ), ) diff --git a/ui/__init__.py b/ui/__init__.py index 13086f2..824209f 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -6,7 +6,7 @@ from .event_method_dispatcher import EventMethodDispatcher from .fps_widget import FPSWidget from .icon import Icon from .icon_button import IconButton -from .label import CenterLabel, Label, LeftLabel, RightLabel +from .label import Label from .message_box import MessageBox from .modal import Modal from .parent import Parent @@ -21,7 +21,6 @@ from .text_input import TextInput __all__ = [ "Button", - "CenterLabel", "Child", "ColorButton", "DropDown", @@ -31,13 +30,11 @@ __all__ = [ "Icon", "IconButton", "Label", - "LeftLabel", "MessageBox", "Modal", "Parent", "Rect", "RepeatButton", - "RightLabel", "Root", "Scroll", "Slider", diff --git a/ui/drop_down.py b/ui/drop_down.py index 61ce3d6..f78fe0f 100644 --- a/ui/drop_down.py +++ b/ui/drop_down.py @@ -23,7 +23,7 @@ class DropDownMenu(Modal): rect.bottomleft, (rect.width, rect.height * len(entries)) ) - KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}} + KEY_METHODS = {frozenset(): {pygame.K_ESCAPE: Modal.deactivate}} def choose(self, i=None): self.deactivate() diff --git a/ui/label.py b/ui/label.py index 343ac9a..e76c5de 100644 --- a/ui/label.py +++ b/ui/label.py @@ -1,35 +1,40 @@ +from enum import Enum + from .child import Child -class CenterLabel(Child): - def __init__(self, parent, rect, value): +class Label(Child): + class HAlign(Enum): + LEFT = "left" + CENTER = "center" + RIGHT = "right" + + def __init__(self, parent, rect, value, align=HAlign.LEFT): super().__init__(parent) self.rect = rect self.value = value + self.get_blit_pos = { + self.HAlign.LEFT: self.get_blit_pos_left, + self.HAlign.CENTER: self.get_blit_pos_center, + self.HAlign.RIGHT: self.get_blit_pos_right, + }[align] - def get_blit_pos(self): - fs_size = self.font.size(self.value) - return ( - self.rect.centerx - fs_size[0] // 2, self.rect.centery - fs_size[1] // 2 - ) - - def draw(self): - fs = self.font.render(self.value, True, "gray") - self.surf.blit(fs, self.get_blit_pos()) - - -class LeftLabel(CenterLabel): - def get_blit_pos(self): + def get_blit_pos_left(self): return ( self.rect.left + 16, self.rect.centery - self.font.size(self.value)[1] // 2, ) + def get_blit_pos_center(self): + fs_size = self.font.size(self.value) + return ( + self.rect.centerx - fs_size[0] // 2, self.rect.centery - fs_size[1] // 2 + ) -class RightLabel(CenterLabel): - def get_blit_pos(self): + def get_blit_pos_right(self): fs_size = self.font.size(self.value) return (self.rect.right - 16 - fs_size[0], self.rect.centery - fs_size[1] // 2) - -Label = LeftLabel + def draw(self): + fs = self.font.render(self.value, True, "gray") + self.surf.blit(fs, self.get_blit_pos()) diff --git a/ui/text_input.py b/ui/text_input.py index 51fa4a3..7b5d5c8 100644 --- a/ui/text_input.py +++ b/ui/text_input.py @@ -273,7 +273,7 @@ class TextInput(Focusable, Child): super().deactivate() KEY_METHODS = { - frozenset(set()): { + frozenset(): { pygame.K_LEFT: key_left, pygame.K_RIGHT: key_right, pygame.K_HOME: key_home, diff --git a/vs_memory.py b/vs_memory.py index 7a4c52c..42ba26f 100755 --- a/vs_memory.py +++ b/vs_memory.py @@ -1,14 +1,11 @@ #!/usr/bin/env python3 -import sys from pathlib import Path from secrets import choice, randbelow from time import time import pygame -sys.path.append(str(Path(__file__).parents[1])) - from ui import Button, Child, FloatSpinner, Label, Root, Spinner @@ -108,7 +105,9 @@ class VSMemory(Root): pygame.font.Font(None, size=96), ) self.teams = tuple( - Team(file) for file in (Path(__file__).parent / "logos").iterdir() + Team(file) + for file in (Path(__file__).parent / "logos").iterdir() + if file.stem != "National League" ) len_teams = len(self.teams) # fits one column of memory destinations (todo) -- 2.51.0