+from functools import partial
from pathlib import Path
import pygame
from ui import Root
from .book_manager import BookManager
+from .color_menu import ColorMenu
from .draw_image import DrawImage
+from .line_menu import LineMenu
from .menu import Menu
+from .page_menu import PageMenu
# - color menu; set background (default for new pages) and foreground color
-# - stub with a 6 hex-digit textinputs and a little rectangle
+# - stub with a 6 hex-digit textinput and a little rectangle to show the color
# - page menu; browse through next and prev pages using stylus
+# - line menu: choose line width and stroke method
# - info layer (F3)
-# - list color and page menus in the esc menu but also using keyboard shortcuts c/p
+# - list color and page menus in the main menu but also using keyboard shortcuts c/p/l
class BookPaint(Root):
pygame.display.set_mode((0, 0), pygame.FULLSCREEN),
pygame.font.Font(None, size=96),
)
- self.menu = Menu(self)
+ self.color_menu = ColorMenu(self)
self.draw_image = DrawImage(self, "white")
- book_path = Path("/dev/shm/book")
+ self.line_menu = LineMenu(self)
+ self.page_menu = PageMenu(self)
+ self.menu = Menu(self)
+ book_path = Path("book")
if not book_path.exists():
book_path.mkdir(0o755)
self.book_manager = BookManager(book_path)
- def key_escape(self):
- self.menu.activate()
+ def show_menu(self, menu_attr):
+ getattr(self, menu_attr).activate()
def save_file(self):
self.book_manager.save_file(self.draw_image.surface)
self.set_image(self.book_manager.prev_file())
KEY_METHODS = {
- frozenset(): {pygame.K_ESCAPE: key_escape},
+ frozenset(): {
+ pygame.K_ESCAPE: partial(show_menu, menu_attr="menu"),
+ pygame.K_c: partial(show_menu, menu_attr="color_menu"),
+ pygame.K_l: partial(show_menu, menu_attr="line_menu"),
+ pygame.K_p: partial(show_menu, menu_attr="page_menu"),
+ },
frozenset({pygame.KMOD_CTRL}): {
pygame.K_s: save_file,
pygame.K_PAGEDOWN: next_file,
--- /dev/null
+import pygame
+
+from ui import Button, Label, Modal, TextInput
+
+
+class ColorMenu(Modal):
+ def __init__(self, parent):
+ super().__init__(parent)
+ size = self.surf.get_size()
+ self.size_third = tuple(a // 3 for a in size)
+ button_height = 128
+ y = self.size_third[1]
+ Label(
+ self,
+ pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+ "Color Menu",
+ Label.HAlign.CENTER,
+ )
+ y += button_height + 16
+ self.input = TextInput(
+ self,
+ pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+ self.set_color,
+ "",
+ )
+ y += (button_height + 16) * 2
+ Button(
+ self,
+ pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+ "Close",
+ self.deactivate
+ )
+
+ def activate(self):
+ super().activate()
+ self.input.value = f"{int(self.parent.draw_image.color) >> 8:06x}"
+
+ def set_color(self, value):
+ 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
+
+ def draw_modal(self):
+ super().draw_modal()
+ rect = pygame.Rect(self.size_third, self.size_third)
+ 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),
+ ),
+ )
+
+ KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
from vectors import StrokeRoundLine, StrokeSquareLine
-class StrokeMethod(Enum):
- ROUND = auto()
- SQUARE = auto()
- PYGAME = auto()
-
-
class DrawImage(Child):
- FINGER_TIMEOUT = 30
+ class StrokeMethod(Enum):
+ ROUND = auto()
+ SQUARE = auto()
+ PYGAME = auto()
+
+ DEFAULT_STROKE_METHOD = StrokeMethod.ROUND
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.surface = pygame.Surface(self.surf.get_size(), 0, 24)
- self.clear()
+ self.image = pygame.Surface(self.surf.get_size(), 0, 24)
+ self.stroke_method = self.DEFAULT_STROKE_METHOD
self.image_dirty = False
- self.stroke_method = next(iter(StrokeMethod))
self.prev_pos = None
+ self.clear()
def clear(self):
- self.surface.fill("black")
+ self.image.fill("black")
self.image_dirty = False
self.dirty = True
+ self.prev_pos = None
- def dot(self, pos):
- pygame.draw.circle(self.surf, self.color, pos, self.line_width // 2)
+ def dot(self, surf, pos):
+ if self.stroke_method == self.StrokeMethod.SQUARE:
+ hw = self.line_width // 2
+ pygame.draw.rect(
+ surf,
+ self.color,
+ pygame.Rect(
+ (pos[0] - hw, pos[1] - hw), (self.line_width, self.line_width)
+ )
+ )
+ elif self.line_width // 2 == 0:
+ surf.set_at(pos, self.color)
+ else:
+ pygame.draw.circle(surf, self.color, pos, self.line_width // 2)
def draw(self):
- self.surf.blit(self.surface, (0, 0))
- self.dot(pygame.mouse.get_pos())
+ self.surf.blit(self.image, (0, 0))
+ self.dot(self.surf, pygame.mouse.get_pos())
def handle_mousebuttondown(self, ev):
if ev.button == 1:
self.dirty = True
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
+ if self.prev_pos is None:
+ return
+ self.image_dirty = True
+ if ev.pos == self.prev_pos:
+ self.dot(self.image, ev.pos)
+ return
+ if self.stroke_method == self.StrokeMethod.ROUND:
+ StrokeRoundLine(
+ self.prev_pos, ev.pos, self.line_width
+ ).draw(self.image, self.color)
+ elif self.stroke_method == self.StrokeMethod.SQUARE:
+ StrokeSquareLine(
+ self.prev_pos, ev.pos, self.line_width
+ ).draw(self.image, self.color)
+ elif self.stroke_method == self.StrokeMethod.PYGAME:
+ pygame.draw.line(
+ self.image, self.color, self.prev_pos, ev.pos, self.line_width
+ )
+ self.prev_pos = ev.pos
def handle_mousebuttonup(self, ev):
- if ev.button == 1 and self.prev_pos is not None:
+ if ev.button == 1:
self.handle_mousemotion(ev)
self.prev_pos = None
def set_image(self, surf):
- self.surface.blit(surf, (0, 0))
+ self.image.blit(surf, (0, 0))
self.dirty = True
--- /dev/null
+import pygame
+
+from ui import DropDown, Label, Modal, Slider, TextInput
+
+from .draw_image import DrawImage
+
+
+class LineMenu(Modal):
+ def __init__(self, parent):
+ super().__init__(parent)
+ size = self.surf.get_size()
+ self.size_third = tuple(a // 3 for a in size)
+ button_height = 128
+ x = self.size_third[0]
+ y = self.size_third[1]
+ Label(
+ self,
+ pygame.Rect((x, y), (x, button_height)),
+ "Line Menu",
+ Label.HAlign.CENTER,
+ )
+ y += button_height + 16
+ self.slider = Slider(
+ self,
+ pygame.Rect((x, y), (x, button_height)),
+ Slider.Direction.HORIZONTAL,
+ 0,
+ button_height,
+ self.set_line_width,
+ )
+ y += button_height + 16
+ self.input = TextInput(
+ self,
+ pygame.Rect((x, y), (x, button_height)),
+ self.set_line_width,
+ "",
+ )
+ y += button_height + 16
+ self.stroke_methods = list(DrawImage.StrokeMethod)
+ self.stroke_method = DropDown(
+ self,
+ pygame.Rect((self.size_third[0], y), (x, 128)),
+ self.parent.draw_image.stroke_method.name.title(),
+ [m.name.title() for m in self.stroke_methods],
+ self.set_stroke_method,
+ )
+
+ def activate(self):
+ super().activate()
+ self.update_value(self.parent.draw_image.line_width)
+
+ def set_line_width(self, value):
+ if isinstance(value, str):
+ value = int(value)
+ if value <= 0:
+ value = 1
+ self.parent.draw_image.line_width = value
+ self.update_value(value)
+
+ def set_stroke_method(self, value):
+ value = self.stroke_methods[value]
+ self.parent.draw_image.stroke_method = value
+ self.stroke_method.value = value.name.title()
+
+ def update_value(self, value):
+ self.slider.value = value
+ self.input.value = str(value)
+ self.dirty = True
+
+ def draw_modal(self):
+ super().draw_modal()
+ rect = pygame.Rect(self.size_third, self.size_third)
+ rect.inflate_ip((32, 32))
+ pygame.draw.rect(self.surf, "black", rect)
+ pygame.draw.rect(self.surf, "gray", rect, 1)
+
+
+ KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
+from functools import partial
+
import pygame
-from ui import (
- Button,
- Modal,
-)
+from ui import Button, Modal
class Menu(Modal):
def __init__(self, parent):
+ from .bookpaint import BookPaint
+
+ self.parent: BookPaint
super().__init__(parent)
- size = self.root.surf.get_size()
- width_third = size[0] // 3
+ self.suspended = False
+ size = self.surf.get_size()
+ self.size_third = tuple(a // 3 for a in size)
+ height_two_thirds = size[1] * 2 // 3
+ self.button_height = 128
Button(
self,
- pygame.Rect((width_third, size[1] * 2 // 3), (width_third, 128)),
+ pygame.Rect(
+ (self.size_third[0], height_two_thirds),
+ (self.size_third[0], self.button_height),
+ ),
"Quit",
self.root.handle_quit,
)
+ y = size[1] // 3
+ Button(
+ self,
+ pygame.Rect(
+ (self.size_third[0], y), (self.size_third[0], self.button_height)
+ ),
+ "Color",
+ partial(self.parent.show_menu, menu_attr="color_menu"),
+ )
+ y += self.button_height + 16
+ Button(
+ self,
+ pygame.Rect(
+ (self.size_third[0], y), (self.size_third[0], self.button_height)
+ ),
+ "Line",
+ partial(self.parent.show_menu, menu_attr="line_menu"),
+ )
+ y += self.button_height + 16
+ Button(
+ self,
+ pygame.Rect(
+ (self.size_third[0], y), (self.size_third[0], self.button_height)
+ ),
+ "Page",
+ partial(self.parent.show_menu, menu_attr="page_menu"),
+ )
+ assert y + self.button_height < height_two_thirds
+
+ def draw(self):
+ if self.root.focus_stack[-1] is self:
+ super().draw()
+
+ def draw_modal(self):
+ super().draw_modal()
+ rect = pygame.Rect(
+ self.size_third,
+ (self.size_third[0], self.size_third[1] + self.button_height),
+ )
+ rect.inflate_ip((32, 32))
+ pygame.draw.rect(self.surf, "black", rect)
+ pygame.draw.rect(self.surf, "gray", rect, 1)
KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
--- /dev/null
+import pygame
+
+from ui import Label, Modal
+
+
+class PageMenu(Modal):
+ def __init__(self, parent):
+ super().__init__(parent)
+ Label(self, pygame.Rect((100, 100), (200, 128)), "Page Menu")
+
+ def draw_modal(self):
+ pass
+
+ KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
+++ /dev/null
-from .bar_layout import BarLayout
-from .grid_layout import GridLayout
-
-__all__ = ["BarLayout", "GridLayout"]
+++ /dev/null
-import pygame
-
-
-class BarLayout:
- def __init__(self, rect, pad):
- self.rect = rect
- self.pad = pad
- self.rects = []
-
- def get_rect(self):
- rect = pygame.Rect((0, self.rect.top), (0, self.rect.height))
- self.rects.append(rect)
- len_rects = len(self.rects)
- left = self.rect.left + self.pad // 2
- width = (self.rect.width - len_rects * self.pad) // len_rects
- for i, r in enumerate(self.rects):
- r.left = left + i * (self.pad + width)
- r.width = width
- return rect
+++ /dev/null
-import pygame
-
-
-class GridLayout:
- def __init__(self, rect, size):
- self.rect = rect
- self.size = size
-
- def get_rect(self, pos, span=(1, 1)):
- if pos[0] < 0 or pos[0] >= self.size[0] or pos[1] < 0 or pos[1] >= self.size[1]:
- raise ValueError(f"coordinate not on grid: {pos}")
- if (
- span[0] < 1
- or pos[0] + span[0] > self.size[0]
- or span[1] < 1
- or pos[0] + span[0] > self.size[0]
- ):
- raise ValueError(f"span exceeds the grid: {span}")
- size = (self.rect.width / self.size[0], self.rect.height / self.size[1])
- return pygame.Rect(
- (
- int(self.rect.left + pos[0] * size[0]),
- int(self.rect.top + pos[1] * size[1]),
- ),
- (size[0] * span[0], size[1] * span[1]),
- )
+from enum import Enum, auto
import pygame
from .child import Child
super().__init__(parent)
self.rect = rect
self.extent = (
- self._get_extent_base() - 1 if handle_size is None else handle_size
+ self._get_extent_base() - (1 if handle_size is None else handle_size)
)
self.value = value
self.handle_size = handle_size
class Slider:
- HORIZONTAL = 0
- VERTICAL = 1
+ class Direction(Enum):
+ HORIZONTAL = auto()
+ VERTICAL = auto()
def __new__(cls, parent, rect, direction, value=0, handle_size=None, callback=None):
- if direction == cls.HORIZONTAL:
+ if direction == cls.Direction.HORIZONTAL:
klass = HorizontalSlider
- else: # direction == cls.VERTICAL
+ else: # direction == cls.Direction.VERTICAL
klass = VerticalSlider
return klass(parent, rect, value, handle_size, callback)