From: mar77i Date: Fri, 16 May 2025 06:55:58 +0000 (+0200) Subject: revamp bookpaint. some more improvements in ui, too X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=003332be343e597e2c80f78e09160b09e9a4d65f;p=zenbook_gui revamp bookpaint. some more improvements in ui, too --- diff --git a/bookpaint.py b/bookpaint.py index cba34b7..39415ea 100755 --- a/bookpaint.py +++ b/bookpaint.py @@ -1,16 +1,6 @@ #!/usr/bin/env python3 -import sys -from contextlib import redirect_stdout -from io import StringIO -from pathlib import Path +from launch import run -sys.path.append(str(Path(__file__).parent)) - -from bookpaint.bookpaint import BookPaint - -with redirect_stdout(StringIO()): - # ruff: noqa: F401 - import pygame # type: ignore - -BookPaint().run() +if __name__ == "__main__": + run() diff --git a/bookpaint/base_menu.py b/bookpaint/base_menu.py new file mode 100644 index 0000000..0411177 --- /dev/null +++ b/bookpaint/base_menu.py @@ -0,0 +1,10 @@ +import pygame + +from ui import Modal + + +class BaseMenu(Modal): + def handle_quit(self, _): + self.root.running = False + + key_methods = {frozenset(): {pygame.K_ESCAPE: Modal.deactivate}} diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py index addb0d0..002531d 100644 --- a/bookpaint/bookpaint.py +++ b/bookpaint/bookpaint.py @@ -28,12 +28,12 @@ class BookPaint(Root): pygame.display.set_mode((0, 0), pygame.FULLSCREEN), pygame.font.Font(None, size=96), ) + self.draw_image = DrawImage(self, pygame.Color("white")) self.color_menu = ColorMenu(self) - self.draw_image = DrawImage(self, "white") self.line_menu = LineMenu(self) + self.book_manager = BookManager(self, Path("book")) self.page_menu = PageMenu(self) self.menu = Menu(self) - self.book_manager = BookManager(self, Path("book")) self.page_menu.update_label() self.setup_deactivate_keys() diff --git a/bookpaint/color_menu.py b/bookpaint/color_menu.py index 4067db6..992ce63 100644 --- a/bookpaint/color_menu.py +++ b/bookpaint/color_menu.py @@ -1,67 +1,287 @@ +from colorsys import hsv_to_rgb, rgb_to_hsv +from functools import partial + import pygame -from ui import Button, Label, Modal, TextInput +from ui import Child, Label, Slider, Spinner, TextInput + +from .base_menu import BaseMenu + +class ColorLabel(Child): + def __init__(self, parent, rect, color): + super().__init__(parent) + self.rect = rect + self.color = color + + def draw(self): + pygame.draw.rect(self.surf, self.color, self.rect) -class ColorMenu(Modal): + +class ColorMenu(BaseMenu): def __init__(self, parent): super().__init__(parent) size = self.surf.get_size() - self.size_third = tuple(a // 3 for a in size) + self.modal_rect = pygame.Rect( + (size[0] // 8, size[1] // 8), + (size[0] * 3 // 4, size[1] * 3 // 4), + ) + y = self.modal_rect.top button_height = 128 - y = self.size_third[1] Label( self, - pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)), + pygame.Rect( + (self.modal_rect.left, y), (self.modal_rect.width, button_height) + ), "Color Menu", Label.HAlign.CENTER, ) + label_width = self.modal_rect.width // 16 + slider_width = self.modal_rect.width * 5 // 16 + input_width = self.modal_rect.width * 2 // 16 + label_left = self.modal_rect.left + slider_left = label_left + label_width + input_left = slider_left + slider_width + label_right = self.modal_rect.centerx + slider_right = label_right + label_width + input_right = slider_right + slider_width + + y += button_height * 2 + Label( + self, + pygame.Rect((label_left, y), (label_width, button_height)), + "R", + Label.HAlign.CENTER, + ) + self.red_slider = Slider( + self, + pygame.Rect((slider_left, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_rgb, dest="r", sender_type=Slider), + ) + self.red_input = Spinner( + self, + pygame.Rect((input_left, y), (input_width, button_height)), + partial(self.set_rgb, dest="r", sender_type=Spinner), + ) + Label( + self, + pygame.Rect((label_right, y), (label_width, button_height)), + "H", + Label.HAlign.CENTER, + ) + self.hue_slider = Slider( + self, + pygame.Rect((slider_right, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_hsv, dest=0, sender_type=Slider), + ) + self.hue_input = Spinner( + self, + pygame.Rect((input_right, y), (input_width, button_height)), + partial(self.set_hsv, dest=0, sender_type=Spinner), + ) + + y += button_height + 16 + Label( + self, + pygame.Rect((label_left, y), (label_width, button_height)), + "G", + Label.HAlign.CENTER, + ) + self.green_slider = Slider( + self, + pygame.Rect((slider_left, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_rgb, dest="g", sender_type=Slider), + ) + self.green_input = Spinner( + self, + pygame.Rect((input_left, y), (input_width, button_height)), + partial(self.set_rgb, dest="g", sender_type=Spinner), + ) + Label( + self, + pygame.Rect((label_right, y), (label_width, button_height)), + "S", + Label.HAlign.CENTER, + ) + self.saturation_slider = Slider( + self, + pygame.Rect((slider_right, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_hsv, dest=1, sender_type=Slider), + ) + self.saturation_input = Spinner( + self, + pygame.Rect((input_right, y), (input_width, button_height)), + partial(self.set_hsv, dest=1, sender_type=Spinner), + ) + y += button_height + 16 - self.input = TextInput( + Label( + self, + pygame.Rect((label_left, y), (label_width, button_height)), + "B", + Label.HAlign.CENTER, + ) + self.blue_slider = Slider( + self, + pygame.Rect((slider_left, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_rgb, dest="b", sender_type=Slider), + ) + self.blue_input = Spinner( + self, + pygame.Rect((input_left, y), (input_width, button_height)), + partial(self.set_rgb, dest="b", sender_type=Spinner), + ) + Label( + self, + pygame.Rect((label_right, y), (label_width, button_height)), + "V", + Label.HAlign.CENTER, + ) + self.value_slider = Slider( + self, + pygame.Rect((slider_right, y), (slider_width, button_height)), + Slider.Direction.HORIZONTAL, + 0, + button_height, + partial(self.set_hsv, dest=2, sender_type=Slider), + ) + self.value_input = Spinner( + self, + pygame.Rect((input_right, y), (input_width, button_height)), + partial(self.set_hsv, dest=2, sender_type=Spinner), + ) + + y += button_height * 2 + half_width = self.modal_rect.width // 2 + self.hex_input = TextInput( self, - pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)), + pygame.Rect( + (self.modal_rect.left + 128, y), (half_width - 256, button_height) + ), self.set_color, "", ) - y += (button_height + 16) * 2 - Button( + self.color_label = ColorLabel( self, - pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)), - "Close", - self.deactivate + pygame.Rect( + (self.modal_rect.centerx + 128, y), + (half_width - 256, button_height), + ), + pygame.Color("white"), ) def activate(self): super().activate() - self.input.value = f"{int(self.parent.draw_image.color) >> 8:06x}" + self.update_controls() - def set_color(self, value): + @staticmethod + def color_to_hsv(color): + return tuple( + max(min(x * 255, 255), 0) + for x in rgb_to_hsv(color.r / 255, color.g / 255, color.b / 255) + ) + + @staticmethod + def hsv_to_color(hsv): + return pygame.Color( + *(int(c * 255) for c in hsv_to_rgb(*(x / 255 for x in hsv))) + ) + + def update_controls(self, skip_hsv=False): color = self.parent.draw_image.color - try: - color = pygame.Color(f"0x{value}") - except ValueError: - try: - color = pygame.Color(value) - except ValueError: - pass - self.parent.draw_image.color = color + self.color_label.color = color + self.hex_input.value = f"{int(color) >> 8:06x}" + controls = ( + (self.red_slider, self.red_input, color.r), + (self.green_slider, self.green_input, color.g), + (self.blue_slider, self.blue_input, color.b), + ) + if not skip_hsv: + hsv = self.color_to_hsv(color) + controls = ( + *controls, + (self.hue_slider, self.hue_input, int(hsv[0])), + (self.saturation_slider, self.saturation_input, int(hsv[1])), + (self.value_slider, self.value_input, int(hsv[2])), + ) + for slider, spinner, value in controls: + slider.value = slider.extent * value // 255 + spinner.value = value def draw_modal(self): super().draw_modal() - rect = pygame.Rect(self.size_third, self.size_third) + rect = self.modal_rect.copy() rect.inflate_ip((32, 32)) pygame.draw.rect(self.surf, "black", rect) pygame.draw.rect(self.surf, "gray", rect, 1) - def draw(self): - super().draw() - pygame.draw.rect( - self.surf, - self.parent.draw_image.color, - pygame.Rect( - (self.size_third[0], self.size_third[1] + 288), - (self.size_third[0], 128), - ), + def set_rgb(self, value, dest, sender_type): + slider, spinner = { + "r": (self.red_slider, self.red_input), + "g": (self.green_slider, self.green_input), + "b": (self.blue_slider, self.blue_input), + }[dest] + if sender_type is Slider: + value = value * 255 // slider.extent + elif sender_type is Spinner: + pass + else: + raise KeyError(sender_type) + setattr(self.parent.draw_image.color, dest, max(min(value, 255), 0)) + self.update_controls() + self.dirty = True + + def set_hsv(self, value, dest, sender_type): + slider, spinner = ( + (self.hue_slider, self.hue_input), + (self.saturation_slider, self.saturation_input), + (self.value_slider, self.value_input), + )[dest] + if sender_type is Slider: + limited_value = max(min(value, slider.extent), 0) + if limited_value != value: + value = limited_value + slider.value = value + value = value * 255 // slider.extent + spinner.value = int(value) + elif sender_type is Spinner: + limited_value = max(min(int(value), 255), 0) + if limited_value != value: + value = limited_value + spinner.value = value + slider.value = value * slider.extent // 255 + else: + raise KeyError(sender_type) + self.parent.draw_image.color = self.hsv_to_color( + ( + value if dest == 0 else self.hue_input.value, + value if dest == 1 else self.saturation_input.value, + value if dest == 2 else self.value_input.value, + ) ) + self.update_controls(True) + self.dirty = True - key_methods = {frozenset(): {pygame.K_ESCAPE: Modal.deactivate}} + def set_color(self, value): + try: + color = pygame.Color(value) + except: + color = pygame.Color(f"0x{value}") + self.parent.draw_image.color = color + self.update_controls() + self.dirty = True diff --git a/bookpaint/draw_image.py b/bookpaint/draw_image.py index 20706de..7244da6 100644 --- a/bookpaint/draw_image.py +++ b/bookpaint/draw_image.py @@ -14,8 +14,6 @@ class DrawImage(Child): def __init__(self, parent, color): super().__init__(parent) - if not isinstance(color, pygame.Color): - color = pygame.Color(color) self.color = color self.line_width = 1 self.image = pygame.Surface(self.surf.get_size(), 0, 24) @@ -38,7 +36,7 @@ class DrawImage(Child): self.dirty = True def dot(self, surf, pos): - if self.stroke_method == self.StrokeMethod.SQUARE: + if self.stroke_method is self.StrokeMethod.SQUARE: pass elif self.line_width // 2 == 0: surf.set_at(pos, self.color) @@ -63,18 +61,20 @@ class DrawImage(Child): if ev.pos == self.prev_pos: self.dot(self.image, ev.pos) return - if self.stroke_method == self.StrokeMethod.ROUND: + if self.stroke_method is self.StrokeMethod.ROUND: StrokeRoundLine( self.prev_pos, ev.pos, self.line_width ).draw(self.image, self.color) - elif self.stroke_method == self.StrokeMethod.SQUARE: + elif self.stroke_method is self.StrokeMethod.SQUARE: StrokeSquareLine( self.prev_pos, ev.pos, self.line_width ).draw(self.image, self.color) - elif self.stroke_method == self.StrokeMethod.PYGAME: + elif self.stroke_method is self.StrokeMethod.PYGAME: pygame.draw.line( self.image, self.color, self.prev_pos, ev.pos, self.line_width ) + else: + raise IndexError(self.stroke_method) self.prev_pos = ev.pos def handle_mousebuttonup(self, ev): diff --git a/bookpaint/line_menu.py b/bookpaint/line_menu.py index 21536ad..6982cd3 100644 --- a/bookpaint/line_menu.py +++ b/bookpaint/line_menu.py @@ -1,11 +1,12 @@ import pygame -from ui import DropDown, Label, Modal, Slider, TextInput +from ui import DropDown, Label, Slider, TextInput from .draw_image import DrawImage +from .base_menu import BaseMenu -class LineMenu(Modal): +class LineMenu(BaseMenu): def __init__(self, parent): super().__init__(parent) size = self.surf.get_size() @@ -75,5 +76,3 @@ class LineMenu(Modal): rect.inflate_ip((32, 32)) pygame.draw.rect(self.surf, "black", rect) pygame.draw.rect(self.surf, "gray", rect, 1) - - key_methods = {frozenset(): {pygame.K_ESCAPE: Modal.deactivate}} diff --git a/bookpaint/page_menu.py b/bookpaint/page_menu.py index e1b0852..5dd9f96 100644 --- a/bookpaint/page_menu.py +++ b/bookpaint/page_menu.py @@ -2,12 +2,13 @@ from functools import partial import pygame -from ui import Button, Label, Modal +from ui import Button, Label from .book_manager import BookManager +from .base_menu import BaseMenu -class PageMenu(Modal): +class PageMenu(BaseMenu): def __init__(self, parent): super().__init__(parent) size = self.surf.get_size() @@ -25,11 +26,11 @@ class PageMenu(Modal): ) y += button_height + 16 callbacks_per_value = ( - ("|<", partial(self.parent.nav, BookManager.Nav.FIRST)), - ("<", partial(self.parent.nav, BookManager.Nav.PREV)), - (">", partial(self.parent.nav, BookManager.Nav.NEXT)), - (">|", partial(self.parent.nav, BookManager.Nav.LAST)), - ("NEW", partial(self.parent.nav, BookManager.Nav.NEW)), + ("|<", partial(self.nav, BookManager.Nav.FIRST)), + ("<", partial(self.nav, BookManager.Nav.PREV)), + (">", partial(self.nav, BookManager.Nav.NEXT)), + (">|", partial(self.nav, BookManager.Nav.LAST)), + ("NEW", partial(self.nav, BookManager.Nav.NEW)), ) button_width = size[0] // (2 * len(callbacks_per_value)) x = self.base_rect.left @@ -48,12 +49,24 @@ class PageMenu(Modal): pygame.draw.rect(self.surf, "black", rect) pygame.draw.rect(self.surf, "gray", rect, 1) - key_methods = {frozenset(): {pygame.K_ESCAPE: Modal.deactivate}} - def update_label(self): self.label.value = " / ".join( ( str(self.parent.book_manager.get_current_index() + 1), str(len(self.parent.book_manager.pages)), ) - ) \ No newline at end of file + ) + + def nav(self, nav): + self.parent.nav(nav) + + key_methods = { + **BaseMenu.key_methods, + frozenset({pygame.KMOD_CTRL}): { + pygame.K_HOME: partial(nav, nav=BookManager.Nav.FIRST), + pygame.K_PAGEUP: partial(nav, nav=BookManager.Nav.PREV), + pygame.K_PAGEDOWN: partial(nav, nav=BookManager.Nav.NEXT), + pygame.K_END: partial(nav, nav=BookManager.Nav.LAST), + pygame.K_n: partial(nav, nav=BookManager.Nav.NEW), + } + } diff --git a/connect_four.py b/connect_four.py index afd7248..92f4a29 100755 --- a/connect_four.py +++ b/connect_four.py @@ -4,8 +4,7 @@ from functools import partial from math import pi, sqrt from time import time -import pygame - +from launch import run, pygame from ui import Button, MessageBox, Rect, Root from vectors import StrokeCircleSegment @@ -285,4 +284,4 @@ class ConnectFour(Root): if __name__ == "__main__": - ConnectFour().run() + run() diff --git a/create_desktop_file.sh b/create_desktop_file.sh index 366fe36..77dc1ac 100755 --- a/create_desktop_file.sh +++ b/create_desktop_file.sh @@ -3,7 +3,6 @@ script_dir="$(realpath -Pe "$(dirname "${0}")")" executable="${script_dir}/zenbook_conf.py" icon="${script_dir}/laptop-single.svg" -virtual_env= help() { echo " ${0} [--executable PATH] [--icon PATH] [--virtual-env PATH] | -h|--help" @@ -27,10 +26,6 @@ while (( $# )); do icon="${2}" shift ;; - --virtual-env) - virtual_env="${2}" - shift - ;; -h|--help) help=1 ;; @@ -49,13 +44,7 @@ fi echo "[Desktop Entry]" echo "Type=Application" -if [[ "${virtual_env}" ]]; then - virtual_env="$(realpath -Pe "${virtual_env}")" - env="VIRTUAL_ENV=${virtual_env} PATH=\$VIRTUAL_ENV/bin:\$PATH " -else - env= -fi -echo "Exec=/usr/bin/sh -c '${env}$(realpath -Pe "${executable}")'" +echo "Exec=$(realpath -Pe "${executable}")" echo "Icon=$(realpath -Pe "${icon}")" echo "" echo "Name=Zenbook Duo settings" diff --git a/launch/__init__.py b/launch/__init__.py new file mode 100644 index 0000000..8f59c15 --- /dev/null +++ b/launch/__init__.py @@ -0,0 +1,77 @@ +import os +import shlex +import sys +from contextlib import redirect_stdout +from importlib import import_module +from inspect import stack +from io import StringIO +from pathlib import Path +from time import time + + +def setup_venv(): + venv_dir = (Path(__file__).parents[1] / "venv").absolute() + if venv_dir.is_dir(): + install_venv = max( + p.stat().st_mtime + for p in ( + venv_dir, + *( + base / ent + for base, dirs, files in venv_dir.walk() + for ent in (*dirs, *files) + ) + ) + ) < time() - 86400 + else: + if venv_dir.exists(): + venv_dir.unlink() + assert not venv_dir.exists() + os.system(shlex.join((sys.executable, "-m", "venv", str(venv_dir)))) + install_venv = True + new_env = {"VIRTUAL_ENV": str(venv_dir)} + venv_bin_str = str(venv_dir / "bin") + if venv_bin_str not in os.environ['PATH'].split(os.pathsep): + new_env["PATH"] = f"{venv_bin_str}{os.pathsep}{os.environ['PATH']}" + os.environ.update(new_env) + if install_venv: + os.system("python -m pip install -qU pip") + os.system("python -m pip install -qU pygame") + venv_dir.touch(0o755, True) + os.execl(sys.executable, Path(sys.executable).name, *sys.argv) + exit(1) + + +try: + with redirect_stdout(StringIO()): + # ruff: noqa: F401 + import pygame # type: ignore +except ImportError: + setup_venv() + raise + + +def find_root(module_globals): + from ui import Root + + for key, value in module_globals.items(): + if key.startswith("__") and key.endswith("__"): + continue + if value is not Root and isinstance(value, type) and issubclass(value, Root): + return value + return None + + +def run(): + for fi in stack(): + caller_name = fi.frame.f_globals["__name__"] + if caller_name == "__main__" or caller_name.endswith(".__main__"): + module_globals = fi.frame.f_globals + break + else: + raise ValueError("Could not determine caller name") + root_class = find_root(module_globals) + if root_class is None: + package = Path(module_globals["__file__"]).stem + root_class = find_root(import_module(f"{package}.{package}").__dict__) + root_class().run() diff --git a/memory.py b/memory.py index 950bc2a..2771efc 100755 --- a/memory.py +++ b/memory.py @@ -4,8 +4,7 @@ from functools import partial from pathlib import Path from secrets import choice -import pygame - +from launch import run, pygame from ui import Button, Child, Label, Modal, Root, TextInput @@ -305,4 +304,4 @@ class MemoryGame(Root): if __name__ == "__main__": - MemoryGame().run() + run() diff --git a/rps.py b/rps.py index 7cf8645..39415ea 100755 --- a/rps.py +++ b/rps.py @@ -1,18 +1,6 @@ #!/usr/bin/env python3 -import sys -from contextlib import redirect_stdout -from io import StringIO -from pathlib import Path - -sys.path.append(str(Path(__file__).parent)) - -from rps.rps import RockPaperScissors - -with redirect_stdout(StringIO()): - # ruff: noqa: F401 - import pygame # type: ignore - +from launch import run if __name__ == "__main__": - RockPaperScissors().run() + run() diff --git a/ui/event_method_dispatcher.py b/ui/event_method_dispatcher.py index 2bdd8d7..bc0c049 100644 --- a/ui/event_method_dispatcher.py +++ b/ui/event_method_dispatcher.py @@ -1,15 +1,14 @@ +from copy import deepcopy from functools import partial import pygame -from .utils import CowDict - class EventMethodDispatcher: MODS = (pygame.KMOD_CTRL, pygame.KMOD_ALT, pygame.KMOD_META, pygame.KMOD_SHIFT) def __init__(self): - self.key_methods = CowDict(getattr(type(self), "key_methods", {})) + self.key_methods = deepcopy(getattr(type(self), "key_methods", {})) def get_key_method(self, key, mod): mods = set() diff --git a/ui/slider.py b/ui/slider.py index 7ce43e1..176f624 100644 --- a/ui/slider.py +++ b/ui/slider.py @@ -4,13 +4,29 @@ import pygame from .child import Child -class HorizontalSlider(Child): - def _get_extent_base(self): - return self.rect.width +class Slider(Child): + class Direction(Enum): + HORIZONTAL = auto() + VERTICAL = auto() - def __init__(self, parent, rect, value=0, handle_size=None, callback=None): + def _get_extent_base(self): + return { + self.Direction.HORIZONTAL: self.rect.width, + self.Direction.VERTICAL: self.rect.height, + }[self.direction] + + def __init__( + self, + parent, + rect, + direction, + value=0, + handle_size=None, + callback=None, + ): super().__init__(parent) self.rect = rect + self.direction = direction self.extent = ( self._get_extent_base() - (1 if handle_size is None else handle_size) ) @@ -20,34 +36,60 @@ class HorizontalSlider(Child): self._pushed = None def value_from_pos(self, pos): - return pos[0] + return { + self.Direction.HORIZONTAL: pos[0], + self.Direction.VERTICAL: pos[1], + }[self.direction] def set_rel_value(self, value): - self.set_value(value - self.rect.left) + self.set_value( + { + self.Direction.HORIZONTAL: value - self.rect.left, + self.Direction.VERTICAL: self.extent - (value - self.rect.top), + }[self.direction] + ) def get_cursor_rect(self): value, limited = self.map_value() - return pygame.Rect( - (self.rect.left + value, self.rect.top), - (self.handle_size, self.rect.height), - ), limited + if self.direction is self.Direction.HORIZONTAL: + rect = pygame.Rect( + (self.rect.left + value, self.rect.top), + (self.handle_size, self.rect.height), + ) + elif self.direction is self.Direction.VERTICAL: + rect = pygame.Rect( + (self.rect.left, self.rect.top + self.extent - value), + (self.rect.width, self.handle_size), + ) + else: + raise IndexError(self.direction) + return rect, limited def draw_cursor_line(self): value, limited = self.map_value() - x = self.rect.left + value - pygame.draw.line( - self.surf, - "dimgray" if limited else "gray", - (x, self.rect.top), - (x, self.rect.bottom), - 8, - ) + if self.direction is self.Direction.HORIZONTAL: + x = self.rect.left + value + points = (x, self.rect.top), (x, self.rect.bottom) + elif self.direction is self.Direction.VERTICAL: + y = self.rect.top + self.extent - value + points = (self.rect.left, y), (self.rect.right, y) + else: + raise IndexError(self.direction) + pygame.draw.line(self.surf, "dimgray" if limited else "gray", *points, 8) def page_flip(self, cursor_rect, value): - if value < cursor_rect.left: - return max(self.value - cursor_rect.width, 0) + if self.direction is self.Direction.HORIZONTAL: + if value < cursor_rect.left: + return max(self.value - cursor_rect.width, 0) + else: + return min(self.value + cursor_rect.width, self.extent) + elif self.direction is self.Direction.VERTICAL: + if value < cursor_rect.top: + return min(self.value + cursor_rect.height, self.extent) + else: + return max(self.value - cursor_rect.height, 0) else: - return min(self.value + cursor_rect.width, self.extent) + raise IndexError(self.direction) def set_value(self, value): if value == self.value: @@ -84,7 +126,11 @@ class HorizontalSlider(Child): self.pushed = value def get_rel(self, value): - return self.pushed - value + if self.direction is self.Direction.HORIZONTAL: + return self.pushed - value + elif self.direction is self.Direction.VERTICAL: + return value - self.pushed + raise IndexError(self.direction) def handle_mousemotion(self, ev): if self.pushed is None: @@ -125,7 +171,7 @@ class HorizontalSlider(Child): value = -value limited = True if value > self.extent: - value = int(self.extent * (self.extent / value)) + value = self.extent ** 2 // value limited = True return value, limited @@ -138,54 +184,3 @@ class HorizontalSlider(Child): else: color = "gray" pygame.draw.rect(self.surf, color, rect) - - -class VerticalSlider(HorizontalSlider): - def _get_extent_base(self): - return self.rect.height - - def value_from_pos(self, pos): - return pos[1] - - def set_rel_value(self, value): - self.set_value(self.extent - (value - self.rect.top)) - - def get_cursor_rect(self): - value, limited = self.map_value() - return pygame.Rect( - (self.rect.left, self.rect.top + self.extent - value), - (self.rect.width, self.handle_size), - ), limited - - def draw_cursor_line(self): - value, limited = self.map_value() - y = self.rect.top + self.extent - value - pygame.draw.line( - self.surf, - "dimgray" if limited else "gray", - (self.rect.left, y), - (self.rect.right, y), - 8, - ) - - def page_flip(self, cursor_rect, value): - if value < cursor_rect.top: - return min(self.value + cursor_rect.height, self.extent) - else: - return max(self.value - cursor_rect.height, 0) - - def get_rel(self, value): - return value - self.pushed - - -class Slider: - class Direction(Enum): - HORIZONTAL = auto() - VERTICAL = auto() - - def __new__(cls, parent, rect, direction, value=0, handle_size=None, callback=None): - if direction == cls.Direction.HORIZONTAL: - klass = HorizontalSlider - else: # direction == cls.Direction.VERTICAL - klass = VerticalSlider - return klass(parent, rect, value, handle_size, callback) diff --git a/ui/spinner.py b/ui/spinner.py index 40b0b21..43547df 100644 --- a/ui/spinner.py +++ b/ui/spinner.py @@ -81,6 +81,7 @@ class Spinner(Parent, Child): def __init__(self, parent, rect, callback, value=0): super().__init__(parent) self.callback = callback + self._value = 0 self.value = value button_size = (rect.height // 2, rect.height // 2) self.text_input = TextInput( @@ -111,38 +112,51 @@ class Spinner(Parent, Child): partial(self.spin_callback, -1), ) - def call_callback(self, value): + @property + def value(self): + return self._value + + @value.setter + def value(self, value): try: int_value = int(value) except ValueError: - pass - else: - if int_value != self.value: - self.value = int_value - self.callback(int_value) + return + if int_value == self.value: + return + self.value = int_value str_value = str(self.value) if str_value != self.text_input.value: - self.text_input.value = str(self.value) + self.text_input.value = str_value self.dirty = True + def call_callback(self, value): + old_value = self.value + self.value = value + if old_value != self.value: + self.callback(self.value) + def spin_callback(self, value): self.value += value - self.text_input.value = str(self.value) self.callback(self.value) self.dirty = True class FloatSpinner(Spinner): - def call_callback(self, value): + @property + def value(self): + return self._value + + @value.setter + def value(self, value): try: float_value = float(value) except ValueError: - pass - else: - if float_value != self.value: - self.value = float_value - self.callback(float_value) + return + if float_value == self.value: + return + self.value = float_value str_value = str(self.value) if str_value != self.text_input.value: - self.text_input.value = str(self.value) + self.text_input.value = str_value self.dirty = True diff --git a/ui/utils.py b/ui/utils.py deleted file mode 100644 index a0f0742..0000000 --- a/ui/utils.py +++ /dev/null @@ -1,25 +0,0 @@ -class CowDict: - def __init__(self, orig_dict): - self.orig_dict = orig_dict - self.copy = None - - @property - def dict(self): - return self.copy or self.orig_dict - - def __getitem__(self, key): - return self.dict[key] - - def _clone(self): - if self.copy is None: - self.copy = self.orig_dict.copy() - - def __setitem__(self, key, value): - self._clone() - self.copy[key] = value - - def __getattr__(self, item): - mod_methods = ("clear", "pop", "popitem", "setdefault", "update") - if self.copy is None and item in mod_methods: - self._clone() - return getattr(self.dict, item) diff --git a/vs_memory.py b/vs_memory.py index 42ba26f..8f88a10 100755 --- a/vs_memory.py +++ b/vs_memory.py @@ -4,8 +4,7 @@ from pathlib import Path from secrets import choice, randbelow from time import time -import pygame - +from launch import run, pygame from ui import Button, Child, FloatSpinner, Label, Root, Spinner @@ -263,4 +262,4 @@ class VSMemory(Root): if __name__ == "__main__": - VSMemory().run() + run() diff --git a/zenbook_conf.py b/zenbook_conf.py index 0ed472d..39415ea 100755 --- a/zenbook_conf.py +++ b/zenbook_conf.py @@ -1,16 +1,6 @@ #!/usr/bin/env python3 -import sys -from contextlib import redirect_stdout -from io import StringIO -from pathlib import Path +from launch import run -sys.path.append(str(Path(__file__).parent)) - -from zenbook_conf.zenbook_conf import ZenbookConf - -with redirect_stdout(StringIO()): - # ruff: noqa: F401 - import pygame # type: ignore - -ZenbookConf().run() +if __name__ == "__main__": + run()