import pygame
from ui import Root
+from zenbook_conf.xrandr import XrandrConf
from .bookpaint_menu import BookPaintMenu
from .draw_image import DrawImage
def __init__(self):
pygame.init()
+ self.current_display = 0
+ self.display_flags = pygame.FULLSCREEN
super().__init__(
- pygame.display.set_mode((0, 0), pygame.FULLSCREEN, display=0),
+ pygame.display.set_mode(
+ (0, 0), self.display_flags, display=self.current_display
+ ),
pygame.font.Font(None, size=96),
)
- self.background_color = "blue"
+ self.background_color = self.BACKGROUND_COLOR
+ self.foreground_color = "white"
+ self.menu = BookPaintMenu(self)
self.draw_image = DrawImage(self)
self.draw_image.clear(self.background_color)
- self.menu = BookPaintMenu(self)
+ self.xrandr_conf = XrandrConf(True)
def key_escape(self):
self.menu.activate()
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
import pygame
-from ui import Button, Modal
+from layout import BarLayout, GridLayout
+from ui import (
+ Button,
+ CenterLabel,
+ ColorButton,
+ DropDown,
+ Modal,
+ Rect,
+ RepeatButton,
+ RightLabel,
+ Slider,
+ TextInput,
+)
class BookPaintMenu(Modal):
super().__init__(parent)
Button(
self,
- pygame.Rect((200, 200), (256, 128)),
+ 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,
+ "book/",
+ )
+ ColorButton(
+ self,
+ grid_layout.get_rect((1, 1)).inflate((pad, pad)),
+ self.root.background_color,
+ lambda: None,
+ )
+ Button(
+ self,
+ grid_layout.get_rect((2, 1)).inflate((pad, pad)),
+ "Fill Page",
+ lambda: None,
+ )
+ ColorButton(
+ self,
+ grid_layout.get_rect((1, 2)).inflate((pad, pad)),
+ self.root.foreground_color,
+ lambda: None,
+ )
+ Slider(
+ self,
+ grid_layout.get_rect((1, 3)).inflate((pad, pad)),
+ Slider.HORIZONTAL,
+ handle_size=96,
+ callback=lambda _: None,
+ ),
+ DropDown(
+ self,
+ grid_layout.get_rect((1, 4)).inflate((pad, pad)),
+ "<choose>",
+ ["a", "b", "c"],
+ lambda _: None,
+ )
+ DropDown(
+ self,
+ grid_layout.get_rect((1, 5)).inflate((pad, pad)),
+ "<choose>",
+ ["a", "b", "c"],
+ lambda _: None,
+ )
+ 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,
+ ),
def key_escape(self):
self.deactivate()
- self.dirty = True
KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}}
+
+ def set_book_path(self, value):
+ print("book path", value)
+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]),
+ )
+++ /dev/null
-import pygame
-
-
-class MenuLayout:
- def __init__(self, size, num_columns, center_at_y, spacer_y):
- self.size = size
- self.num_columns = num_columns
- self.columns = [[] for _ in range(num_columns)]
- self.center_at_y = center_at_y
- self.spacer_y = spacer_y
-
- def get_center_x(self, column):
- return self.size[0] * (column + 1) // (self.num_columns + 1)
-
- def get_rect(self, column, rect_size):
- rect = pygame.Rect(
- (self.get_center_x(column) - rect_size[0] // 2, 0), rect_size
- )
- self.columns[column].append(rect)
- return rect
-
- def __call__(self):
- for column in self.columns:
- total_height = (len(column) - 1) * self.spacer_y + sum(
- r.height for r in column
- )
- y = 0
- for rect in column:
- rect.top = self.center_at_y - total_height // 2 + y
- y += rect.height + self.spacer_y
-
-
-class BarLayout:
- def __init__(self, size, center_at_y, spacer_x):
- self.size = size
- self.center_at_y = center_at_y
- self.spacer_x = spacer_x
- self.row = []
-
- def get_rect(self, rect_size):
- rect = pygame.Rect((0, self.center_at_y - rect_size[1] // 2), rect_size)
- self.row.append(rect)
- return rect
-
- def __call__(self):
- total_width = (len(self.row) - 1) * self.spacer_x + sum(
- r.width for r in self.row
- )
- x = (self.size[0] - total_width) // 2
- for rect in self.row:
- rect.left = x
- x += rect.width + self.spacer_x
from .button import Button
from .child import Child
+from .color_button import ColorButton
from .drop_down import DropDown
from .event_method_dispatcher import EventMethodDispatcher
from .fps_widget import FPSWidget
from .icon import Icon
from .icon_button import IconButton
-from .label import Label
+from .label import CenterLabel, Label, LeftLabel, RightLabel
from .message_box import MessageBox
from .modal import Modal
from .parent import Parent
__all__ = [
"Button",
+ "CenterLabel",
"Child",
+ "ColorButton",
"DropDown",
"EventMethodDispatcher",
"FPSWidget",
"Icon",
"IconButton",
"Label",
+ "LeftLabel",
"MessageBox",
"Modal",
"Parent",
"Rect",
"RepeatButton",
+ "RightLabel",
"Root",
"Scroll",
"Slider",
--- /dev/null
+import pygame
+
+from .button import Button
+
+
+class ColorButton(Button):
+ def __init__(self, parent, rect, color, callback, highlight=False):
+ super().__init__(parent, rect, "", callback, highlight)
+ self.color = color
+
+ def draw(self):
+ if not self.pushed:
+ colors = (self.color, "lime" if self.highlight else "gray")
+ else:
+ colors = ("darkgray", "lightgray")
+ pygame.draw.rect(self.surf, colors[0], self.rect)
+ pygame.draw.rect(self.surf, colors[1], self.rect, 8)
rect.bottomleft, (rect.width, rect.height * len(entries))
)
+ KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
+
def choose(self, i=None):
self.deactivate()
self.callback(i)
from .child import Child
-class Label(Child):
+class CenterLabel(Child):
def __init__(self, parent, rect, value):
super().__init__(parent)
self.rect = rect
self.value = value
+ 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.rect.left + 16, self.rect.centery - fs.get_height() // 2)
+ self.surf.blit(fs, self.get_blit_pos())
+
+
+class LeftLabel(CenterLabel):
+ def get_blit_pos(self):
+ return (
+ self.rect.left + 16,
+ self.rect.centery - self.font.size(self.value)[1] // 2,
)
+
+
+class RightLabel(CenterLabel):
+ def get_blit_pos(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
class Parent(EventMethodDispatcher):
running: bool
stop_event: bool
+ parent: "root.Root"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def draw(self):
super().draw()
for child in self.children:
- if child.enabled:
+ if child.enabled and child not in self.root.focus_stack:
child.draw()
def draw(self):
if hasattr(self, "BACKGROUND_COLOR"):
self.surf.fill(self.BACKGROUND_COLOR)
- super().draw()
+ for parent in self.focus_stack:
+ if parent is self:
+ super().draw()
+ else:
+ parent.draw()
def run(self):
while True:
WIDTH_HEIGHT_PATTERN = re.compile(r"^\s{8}(h: width|v: height)\s+(\d+) ")
OUTPUTS = ("eDP-1", "eDP-2")
- def __init__(self):
+ def __init__(self, simple=False):
+ self.simple = simple
self.current_conf = self.get_conf()
@staticmethod
"outputs": [],
}
- @classmethod
- def parse_output_line(cls, line):
- name, connected, rest = cls.strip_each(line.split(" ", 2))
+ def parse_output_line(self, line):
+ name, connected, rest = self.strip_each(line.split(" ", 2))
output = {
"name": name,
- "connected": cls.CONNECTED_MAPPING.get(
+ "connected": self.CONNECTED_MAPPING.get(
connected, "unknown connection"
),
}
if output["connected"] is False:
return output
- resolution = cls.RESOLUTION_PATTERN.search(rest)
+ resolution = self.RESOLUTION_PATTERN.search(rest)
output["active"] = bool(resolution)
- if resolution is None:
+ if self.simple or resolution is None:
return output
- parsed = cls.int_tuple(resolution.group(i) for i in range(1, 5))
+ parsed = self.int_tuple(resolution.group(i) for i in range(1, 5))
direction_reflection = (
- cls.DIRECTIONS_REFLECTIONS_PATTERN.search(rest)
+ self.DIRECTIONS_REFLECTIONS_PATTERN.search(rest)
)
end = direction_reflection.end()
output.update(
"reflection": direction_reflection.group(2) or "none",
"available_rotations_reflections": [
m.group(0)
- for m in cls.AVAILABLE_DIRECTIONS_REFLECTIONS_PATTERN.finditer(
+ for m in self.AVAILABLE_DIRECTIONS_REFLECTIONS_PATTERN.finditer(
rest[end:rest.find(")", end)]
)
],
"modes": []
}
)
- mm_size = cls.MM_SIZE_PATTERN.search(rest)
+ mm_size = self.MM_SIZE_PATTERN.search(rest)
if mm_size:
output["mm_size"] = (
int(mm_size.group(1)), int(mm_size.group(2))
"mode_flags": cls.MODE_FLAGS_PATTERN.findall(line),
}
- @classmethod
- def get_conf(cls):
+ def get_conf(self):
screens = []
current_screen = None
- output = subprocess.check_output(["xrandr", "--verbose"], text=True)
- lines = output.split(os.linesep)
+ lines = subprocess.check_output(
+ ["xrandr", "--verbose"], text=True
+ ).split(os.linesep)
for i, line in enumerate(lines):
if not line:
continue
elif line.startswith("Screen "):
current_screen = len(screens)
- screens.append(cls.parse_screen_line(line))
+ screens.append(self.parse_screen_line(line))
elif not line.startswith("\t") and not line.startswith(" "):
- screens[current_screen]["outputs"].append(cls.parse_output_line(line))
- elif line.startswith(" "):
+ screens[current_screen]["outputs"].append(self.parse_output_line(line))
+ elif line.startswith(" ") and not self.simple:
output = screens[current_screen]["outputs"][-1]
if line[3] == " " or "modes" not in output:
continue
- output["modes"].append(cls.parse_mode(*lines[i:i + 3]))
+ output["modes"].append(self.parse_mode(*lines[i:i + 3]))
return screens
@classmethod
args.extend((f"--{key.replace("_", "-")}", value))
return subprocess.run(["xrandr", "--output", cls.OUTPUTS[output], *args])
+ def renew(self):
+ self.current_conf = self.get_conf()
+
def update(self, mode):
if mode not in ("single", "double", "vertical"):
raise ValueError(f"unknown mode: {mode}")
if mode == "vertical":
self.call(0, "auto", rotate="left")
self.call(1, "auto", rotate="left", left_of=self.OUTPUTS[0])
- self.current_conf = self.get_conf()
+ self.renew()
def get_relevant_outputs(self, screen_id=0):
screen = next(s for s in self.current_conf if s["screen"] == screen_id)
pygame.display.iconify()
def next_display(self):
- current_display = (self.current_display + 1) % self.xrandr_conf.count_active()
+ 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
- self.surf = pygame.display.set_mode(
- self.surf.get_size(), display=self.current_display
- )
- self.dirty = True
+ reset = True
+ if not reset:
+ return
+ self.surf = pygame.display.set_mode(
+ self.surf.get_size(), display=self.current_display
+ )
+ self.dirty = True