]> git.mar77i.info Git - zenbook_gui/commitdiff
finish layout configurator.
authormar77i <mar77i@protonmail.ch>
Sun, 21 Sep 2025 18:49:40 +0000 (20:49 +0200)
committermar77i <mar77i@protonmail.ch>
Sun, 21 Sep 2025 21:43:01 +0000 (23:43 +0200)
keyboard/clock.py
keyboard/draggable.py
keyboard/keyboard.py
keyboard/layout.py
keyboard/mousepad.py
ui/__init__.py
ui/rect.py

index 60a5ac808a59ab66752d77c87e19b7fa439a461e..a80cdbcee62b1742877d45ef8905fdd81c5398e0 100644 (file)
@@ -25,14 +25,12 @@ class ClockWidget(Child):
     def __init__(self, parent, rect):
         super().__init__(parent)
         self.rect = rect
-        min_rect_size = min(rect.size)
-        self.radius = min_rect_size * self.CLOCK_RADIUS
-        self.widths = tuple(w * min_rect_size for w in self.WIDTHS)
+        self.widths = tuple(w * min(rect.size) for w in self.WIDTHS)
         self.state = (0, 0, 0, 0, 0, 0)
 
     def update(self):
         now = datetime.now()
-        state = (
+        self.state = (
             now.strftime("%b"),
             now.day,
             now.hour,
@@ -40,16 +38,14 @@ class ClockWidget(Child):
             now.second,
             now.microsecond,
         )
-        if state != self.state:
-            self.state = state
-            self.dirty = True
+        self.dirty = True
 
     def draw(self):
         month, day, hour, minute, second, microsecond = self.state
         second = second + microsecond / 1000000
         fractional_minute = minute + second / 60
         fractional_hour = hour + fractional_minute / 60
-        self.draw_markings()
+        self.draw_markings(self.surf, self.rect)
         self.draw_date(month, "gold")
         self.draw_date(str(day), "gold", True)
         self.draw_hand_polygon(
@@ -65,36 +61,41 @@ class ClockWidget(Child):
         )
         self.draw_hand_polygon(second * tau / 60, self.HANDS[3], self.widths[3], "blue")
 
-    def draw_markings(self):
-        center = self.rect.center
-        radius = self.radius
-        markings = tuple(m * radius for m in self.MARKINGS)
-        for i in range(12):
-            angle = tau * i / 12
+    @classmethod
+    def get_symbolic_surf(cls, size):
+        surf = pygame.Surface(size, pygame.SRCALPHA)
+        surf.fill(0x7F000000)
+        rect = surf.get_rect()
+        pygame.draw.circle(
+            surf, "black", rect.center, min(surf.get_size()) * cls.CLOCK_RADIUS
+        )
+        cls.draw_markings(surf, rect)
+        return surf
+
+    @classmethod
+    def draw_markings(cls, surf, rect):
+        center = rect.center
+        radius = min(rect.size) * cls.CLOCK_RADIUS
+        for i in range(60):
+            angle = tau * i / 60
             fractions = cos(angle), -sin(angle)
             pygame.draw.line(
-                self.surf,
-                "green",
-                tuple(c + (f * markings[0]) for c, f in zip(center, fractions)),
-                tuple(c + (f * radius) for c, f in zip(center, fractions)),
+                surf,
+                ("green", "yellow")[i % 5 != 0],
+                *(
+                    tuple(c + (f * radius * value) for c, f in zip(center, fractions))
+                    for value in (cls.MARKINGS[i % 5 != 0], 1)
+                ),
             )
-            for j in range(i * 5 + 1, (i + 1) * 5):
-                angle = tau * j / 60
-                fractions = cos(angle), -sin(angle)
-                pygame.draw.line(
-                    self.surf,
-                    "yellow",
-                    tuple(c + (f * markings[1]) for c, f in zip(center, fractions)),
-                    tuple(c + (f * radius) for c, f in zip(center, fractions)),
-                )
-        pygame.draw.circle(self.surf, "green", center, radius, 1)
+        pygame.draw.circle(surf, "green", center, radius, 1)
 
     def draw_date(self, string, color, right=False):
         fs = self.font.render(string, True, color)
+        x_offset = self.MARKINGS[0] * min(self.rect.size) * self.CLOCK_RADIUS
         if right:
-            x_offset = self.MARKINGS[0] * self.radius - fs.get_width()
+            x_offset -= fs.get_width()
         else:
-            x_offset = -self.MARKINGS[0] * self.radius
+            x_offset *= -1
         self.surf.blit(
             fs,
             (
@@ -106,21 +107,22 @@ class ClockWidget(Child):
     def draw_hand_polygon(self, angle, interval, width, color):
         # fix origin and direction
         angle = tau / 4 - angle
-        center = self.rect.center
-        radius = self.radius
-        fractions = cos(angle), -sin(angle)
-        points = (
-            tuple(c + (f * interval[0] * radius) for c, f in zip(center, fractions)),
-            tuple(c + (f * interval[1] * radius) for c, f in zip(center, fractions)),
+        points = tuple(
+            tuple(
+                c + (f * value * min(self.rect.size) * self.CLOCK_RADIUS)
+                for c, f in zip(self.rect.center, (cos(angle), -sin(angle)))
+            )
+            for value in interval
         )
         offset = (cos(angle + tau / 4) * width / 2, -sin(angle + tau / 4) * width / 2)
         pygame.draw.polygon(
             self.surf,
             color,
             [
-                (points[0][0] - offset[0], points[0][1] - offset[1]),
-                (points[0][0] + offset[0], points[0][1] + offset[1]),
-                (points[1][0] + offset[0], points[1][1] + offset[1]),
-                (points[1][0] - offset[0], points[1][1] - offset[1]),
+                (
+                    points[i // 2][0] + offset[0] * sign,
+                    points[i // 2][1] + offset[1] * sign,
+                )
+                for i, sign in zip(range(4), (-1, 1, 1, -1))
             ],
         )
index 462aec5d8a8d52ee39cd21c689f31543b22ba0d9..c3d76d4263029e829d3d39d55c09a516a4ccbdda 100644 (file)
@@ -18,28 +18,29 @@ class DraggableButton(Child):
         self.drag_rel = None
 
     def draw(self):
-        if not self.pushed:
-            value_color = "lime" if self.highlight else "gray"
-            colors = ("black", value_color, value_color)
-        else:
-            colors = ("darkgray", "lightgray", "black")
-        pygame.draw.rect(self.surf, colors[0], self.rect)
-        pygame.draw.rect(self.surf, colors[1], self.rect, 8)
-        self.surf.blit(self.surface, self.rect.topleft)
-        if self.drag_pos is not None:
-            for dest in self.dests:
-                width = 6
-                if dest.collidepoint(self.drag_pos):
-                    dest = dest.inflate((128, 128))
-                    width *= 2
-                pygame.draw.rect(self.surf, "red", dest, width)
-            self.surf.blit(
-                self.surface,
-                (
-                    self.drag_pos[0] - self.drag_rel[0],
-                    self.drag_pos[1] - self.drag_rel[1],
-                ),
-            )
+        if self.drag_pos is None:
+            self.surf.blit(self.surface, self.rect.topleft)
+
+    def draw_post(self):
+        if self.drag_pos is None:
+            return
+        for dest in self.dests:
+            width = 6
+            if isinstance(dest, tuple):
+                test_cb, dest = dest
+            else:
+                test_cb = dest.collidepoint
+            if test_cb(self.drag_pos):
+                dest = dest.inflate((128, 128))
+                width *= 2
+            pygame.draw.rect(self.surf, "red", dest, width)
+        self.surf.blit(
+            self.surface,
+            (
+                self.drag_pos[0] - self.drag_rel[0],
+                self.drag_pos[1] - self.drag_rel[1],
+            ),
+        )
 
     def toggle(self, pos=None, pushed=None):
         if pos is None:
@@ -72,9 +73,13 @@ class DraggableButton(Child):
 
     def drop(self, pos):
         for i, dest in enumerate(self.dests):
-            if dest.collidepoint(pos):
+            if isinstance(dest, tuple):
+                test_cb, dest = dest
+            else:
+                test_cb = dest.collidepoint
+            if test_cb(pos):
                 self.rect, self.dests[i] = dest, self.rect
-                self.callback()
+                self.callback(i)
                 break
 
     def handle_fingerdown(self, event):
index 26e77c65ca5084a048cf7fe9a8ac4910765406cd..c7a71220475062333bf2b21fe1efdf38765efad2 100644 (file)
@@ -32,7 +32,7 @@ class Root(BaseRoot):
         self.mouse_fingers = {}
         self.display = self.keyboard._display
         self.led_mask = self.display.get_keyboard_control().led_mask
-        FPSWidget(self)
+        self.fps = FPSWidget(self)
         size = self.surf.get_size()
         self.menu_modal = MenuModal(
             self,
@@ -41,6 +41,10 @@ class Root(BaseRoot):
         self.keys_index = len(self.children)
         self.setup_widgets()
 
+    def draw(self):
+        super().draw()
+        self.fps.draw()
+
     def setup_widgets(self):
         while len(self.children) > self.keys_index:
             self.children.pop()
index 92936a1d6fefc7f0a0d20b9c4da9f0eca4a89f73..73c1bc4d2f25fd43a5095c35f8e9a9f62b345fee 100644 (file)
@@ -1,6 +1,9 @@
+from collections import OrderedDict
+from functools import partial
+
 import pygame
 
-from ui import Child, QuittableModal, Rect
+from ui import Child, LabelledRect, QuittableModal, Rect
 
 from .clock import ClockWidget
 from .draggable import DraggableButton
@@ -9,142 +12,75 @@ from .mousepad import MousePadWidget
 from .touchbutton import TouchButton
 from .key import KEYBOARD, KEYPAD_KEYS, CURSOR_KEYS
 
+PADDING = 8
 
 
-
-class LayoutWidget(Child):
-    padding = 8
-
-    @classmethod
-    def get_keyboard_keys(cls, surf, size, keys):
-        result = []
-        x_sums = [0]
-        y_spans = []
-        for key in keys:
-            if key == "\n":
-                x_sums.append(0)
-                apply_y_spans(x_sums, y_spans)
-            elif len(key) > 2:
-                width = key[2].get("x_hint", 1)
-                x_sums[-1] += width
-                y_hint = key[2].get("y_hint", 1)
-                if y_hint > 1:
-                    y_spans.append([width, y_hint - 1])
-            else:
-                x_sums[-1] += 1
-        apply_y_spans(x_sums, y_spans)
-        assert all(item[1] <= 0 for item in y_spans)
-        x, y = (0, 0)
-        x_sums_iter = iter(x_sums)
-        x_sums_len = len(x_sums)
-        key_size = get_key_size(size, x_sums_iter, x_sums_len)
-        for key in keys:
-            if key == "\n":
-                x, y = 0, y + key_size[1]
-                key_size = get_key_size(size, x_sums_iter, x_sums_len)
-                continue
-            key_width, key_height = key_size
-            if key != (None, None):
-                if len(key) > 2:
-                    key_width *= key[2].get("x_hint", 1)
-                    key_height *= key[2].get("y_hint", 1)
-                rect = pygame.Rect(
-                    (x + cls.padding / 2, y + cls.padding / 2),
-                    (key_width - cls.padding, key_height - cls.padding),
-                )
-                pygame.draw.rect(surf, "gray", rect, 1)
-                pygame.draw.ellipse(
-                    surf,
-                    "0x333333",
-                    pygame.Rect(
-                        (
-                            rect.centerx - key_size[0] / 6,
-                            rect.centery - key_size[1] / 4,
-                        ),
-                        (key_size[0] / 3, key_size[1] / 2),
-                    )
-                )
-            x += key_width
-        return result
-
-    def get_widget_row_rects(self, num_widgets):
-        if self.parent.keyboard_row == 0:
-            top = self.rect.centery
+def get_symbolic_keys(surf, size, keys):
+    result = []
+    x_sums = [0]
+    y_spans = []
+    for key in keys:
+        if key == "\n":
+            x_sums.append(0)
+            apply_y_spans(x_sums, y_spans)
+        elif len(key) > 2:
+            width = key[2].get("x_hint", 1)
+            x_sums[-1] += width
+            y_hint = key[2].get("y_hint", 1)
+            if y_hint > 1:
+                y_spans.append([width, y_hint - 1])
         else:
-            top = self.rect.top
-        size = (self.rect.width / 3, self.rect.height / 2)
-        rects = []
-        if num_widgets == 1:
-            rects.append(
-                pygame.Rect(
-                    (self.rect.left + size[0], top),
-                    size,
-                )
-            )
-        elif num_widgets == 2:
-            rects.append(
-                pygame.Rect(
-                    (self.rect.left + self.rect.width / 4 - size[0] / 2, top),
-                    size,
-                )
+            x_sums[-1] += 1
+    apply_y_spans(x_sums, y_spans)
+    assert all(item[1] <= 0 for item in y_spans)
+    x, y = (0, 0)
+    x_sums_iter = iter(x_sums)
+    x_sums_len = len(x_sums)
+    key_size = get_key_size(size, x_sums_iter, x_sums_len)
+    for key in keys:
+        if key == "\n":
+            x, y = 0, y + key_size[1]
+            key_size = get_key_size(size, x_sums_iter, x_sums_len)
+            continue
+        key_width, key_height = key_size
+        if key != (None, None):
+            if len(key) > 2:
+                key_width *= key[2].get("x_hint", 1)
+                key_height *= key[2].get("y_hint", 1)
+            rect = pygame.Rect(
+                (x + PADDING / 2, y + PADDING / 2),
+                (key_width - PADDING, key_height - PADDING),
             )
-            rects.append(
+            pygame.draw.rect(surf, "black", rect)
+            pygame.draw.rect(surf, "gray", rect, 1)
+            pygame.draw.ellipse(
+                surf,
+                "0x333333",
                 pygame.Rect(
-                    (self.rect.left + self.rect.width * 3 / 4 - size[0] / 2, top),
-                    size,
-                )
+                    (
+                        rect.centerx - key_size[0] / 6,
+                        rect.centery - key_size[1] / 4,
+                    ),
+                    (key_size[0] / 3, key_size[1] / 2),
+                ),
             )
-        elif num_widgets == 3:
-            for i in range(3):
-                rects.append(
-                    pygame.Rect(
-                        (self.rect.left + i * size[0], top),
-                        size,
-                    )
-                )
-        return rects
-
-    def get_widget_row_rect_dests(self, len_widget_row, keyset, source_rect):
-        if keyset in self.parent.widget_row:
-            keypad_dests = self.get_widget_row_rects(len_widget_row)
-            keypad_idx = self.parent.widget_row.index(KEYPAD_KEYS)
-            keypad_dests[keypad_idx], source_rect = source_rect, keypad_dests[keypad_idx]
-        elif len_widget_row < 3:
-            keypad_dests = self.get_widget_row_rects(len_widget_row + 1)
-        else:
-            keypad_dests = []
-        return source_rect, keypad_dests
+        x += key_width
+    return result
+
 
+class ScreenWidget(Child):
     def __init__(self, parent, rect):
         super().__init__(parent)
         self.rect = rect
-        keyboard_size = (rect.width, rect.height / 2)
-        self.keyboard_rects = [
-            pygame.Rect(rect.topleft, keyboard_size),
-            pygame.Rect((rect.left, rect.centery), keyboard_size),
-        ]
-        widget_size = (rect.width / 3, keyboard_size[1])
-        self.widget_rects = [
-            [
-                pygame.Rect(
-                    (rect.left + rect.width / 4 - widget_size[0] / 2, rect.top),
-                    widget_size,
-                ),
-                pygame.Rect(
-                    (rect.left + rect.width * 3 / 4 - widget_size[0] / 2, rect.top),
-                    widget_size,
-                )
-            ],
-            [
-                pygame.Rect((rect.left, rect.top), widget_size),
-                pygame.Rect((rect.left + widget_size[0], rect.top), widget_size),
-                pygame.Rect((rect.left + 2 * widget_size[1], rect.top), widget_size),
-            ],
-        ]
-        surf = pygame.Surface(keyboard_size)
-        surf.fill("black")
-        self.get_keyboard_keys(surf, keyboard_size, KEYBOARD)
-        self.keyboard = DraggableButton(
+        self.keyboard_rects = parent.keyboard_rects
+        self.keyboard = self.get_keyboard_button()
+
+    def get_keyboard_button(self):
+        keyboard_size = self.keyboard_rects[0].size
+        surf = pygame.Surface(keyboard_size, pygame.SRCALPHA)
+        surf.fill(0x7F000000)
+        get_symbolic_keys(surf, keyboard_size, KEYBOARD)
+        return DraggableButton(
             self.parent,
             self.keyboard_rects[self.parent.keyboard_row],
             surf,
@@ -157,60 +93,252 @@ class LayoutWidget(Child):
         self.keyboard.dests.clear()
         self.keyboard.dests.append(self.keyboard_rects[not self.parent.keyboard_row])
 
-    def keyboard_callback(self):
+    def keyboard_callback(self, _):
         self.parent.keyboard_row = int(
             (self.keyboard.rect.top - self.rect.top) * 2 / self.rect.height
         )
-        # move the visual widget row out of the way here
-
-    def keypad_callback(self):
-        if KEYPAD_KEYS in self.parent.widget_row:
-            self.parent.widget_row.remove(KEYPAD_KEYS)
-        if self.keypad.rect != self.keypad_rect:
-            if len(self.parent.widget_row) == 0:
-                self.parent.widget_row.append(KEYPAD_KEYS)
-                return
-            # insert KEYPAD_KEYS to widget_row at the right index
-            # rearrange the visual widget row
+        self.parent.update_draggable_buttons()
 
     def draw(self):
         pygame.draw.rect(self.surf, "gray", self.rect.inflate((-2, -2)), 1)
 
 
 class LayoutModal(QuittableModal):
-    WIDGETS = {
-        "KEYPAD_KEYS": KEYPAD_KEYS,
-        "CURSOR_KEYS": CURSOR_KEYS,
-        "MousePadWidget": MousePadWidget,
-        "ClockWidget": ClockWidget,
-    }
-
-    def __init__(self, parent, config=None):
+    WIDGETS = OrderedDict(
+        (
+            ("KEYPAD_KEYS", KEYPAD_KEYS),
+            ("CURSOR_KEYS", CURSOR_KEYS),
+            ("MousePadWidget", MousePadWidget),
+            ("ClockWidget", ClockWidget),
+        )
+    )
+    LABELS = OrderedDict(
+        (
+            (id(KEYPAD_KEYS), "Keypad keys"),
+            (id(CURSOR_KEYS), "Arrow keys"),
+            (id(MousePadWidget), "Mouse pad"),
+            (id(ClockWidget), "Clock"),
+        )
+    )
+
+    def __init__(self, parent):
         super().__init__(parent)
         size = self.surf.get_size()
         self.rect = pygame.Rect(150, 150, size[0] - 300, size[1] - 300)
         Rect(self, self.rect, "black", "gray")
+        self.keyboard_row = 0
+        self.widget_row = []
+
+        half_size = (size[0] / 2, size[1] / 2)
+        screen_rect = pygame.Rect((half_size[0] / 2, half_size[1] / 2), half_size)
         TouchButton(
             self,
-            pygame.Rect((self.rect.centerx - 128, self.rect.bottom - 128), (256, 128)),
+            pygame.Rect(
+                (
+                    self.rect.centerx - 128,
+                    (self.rect.bottom + screen_rect.bottom) / 2 - 64,
+                ),
+                (256, 128),
+            ),
             "Close",
             self.deactivate,
         )
-        self.keyboard_row = 0
-        self.widget_row = []
-        half_size = (size[0] / 2, size[1] / 2)
-        self.layout_widget = LayoutWidget(
-            self, pygame.Rect((half_size[0] / 2, half_size[1] / 2), half_size)
+        self.widget_size = (screen_rect.width / 3, screen_rect.height / 2)
+        left = (
+            self.rect.left
+            + (screen_rect.left - self.rect.left - self.widget_size[0]) / 2
+        )
+        right = (
+            screen_rect.right
+            + (self.rect.right - screen_rect.right - self.widget_size[0]) / 2
+        )
+        spacing = (self.rect.height - self.widget_size[1] * 2) / 3
+        self.home_rects = (
+            pygame.Rect((left, self.rect.top + spacing), self.widget_size),
+            pygame.Rect(
+                (left, self.rect.top + self.widget_size[1] + 2 * spacing),
+                self.widget_size,
+            ),
+            pygame.Rect((right, self.rect.top + spacing), self.widget_size),
+            pygame.Rect(
+                (right, self.rect.top + self.widget_size[1] + 2 * spacing),
+                self.widget_size,
+            ),
+        )
+        self.keyboard_rects = (
+            pygame.Rect(
+                screen_rect.topleft, (screen_rect.width, screen_rect.height / 2)
+            ),
+            pygame.Rect(
+                (screen_rect.left, screen_rect.centery),
+                (screen_rect.width, screen_rect.height / 2),
+            ),
+        )
+        self._screen_widget_rects = tuple(
+            (
+                pygame.Rect(
+                    (
+                        screen_rect.left + screen_rect.width / 12,
+                        top,
+                    ),
+                    self.widget_size,
+                ),
+                pygame.Rect(
+                    (
+                        screen_rect.left + screen_rect.width * 7 / 12,
+                        top,
+                    ),
+                    self.widget_size,
+                ),
+                pygame.Rect((screen_rect.left, top), self.widget_size),
+                pygame.Rect(
+                    (screen_rect.left + self.widget_size[0], top),
+                    self.widget_size,
+                ),
+                pygame.Rect(
+                    (screen_rect.left + 2 * self.widget_size[0], top),
+                    self.widget_size,
+                ),
+            )
+            for top in (screen_rect.centery, screen_rect.top)
         )
+        for rect, widget in zip(self.home_rects, self.WIDGETS.values()):
+            LabelledRect(self, rect, self.LABELS[id(widget)], "orange", None, "gray")
+        self.screen_widget = ScreenWidget(self, screen_rect)
+        self.draggable_buttons = self.get_draggable_buttons()
+        for draggable_button in self.draggable_buttons:
+            draggable_button.callback = partial(
+                self.draggable_callback, draggable_button
+            )
+
+    @property
+    def screen_widget_rects(self):
+        return self._screen_widget_rects[self.keyboard_row]
+
+    @staticmethod
+    def get_key_surf(size, keyset):
+        surf = pygame.Surface(size, pygame.SRCALPHA)
+        surf.fill(0x7F000000)
+        get_symbolic_keys(surf, size, keyset)
+        return surf
+
+    def get_draggable_buttons(self):
+        widget_size = self.home_rects[0].size
+        keypad_keys = DraggableButton(
+            self,
+            self.home_rects[0],
+            self.get_key_surf(widget_size, KEYPAD_KEYS),
+            [],
+            None,
+        )
+        cursor_keys = DraggableButton(
+            self,
+            self.home_rects[1],
+            self.get_key_surf(widget_size, CURSOR_KEYS),
+            [],
+            None,
+        )
+        mousepad_widget = DraggableButton(
+            self,
+            self.home_rects[2],
+            MousePadWidget.get_symbolic_surf(widget_size),
+            [],
+            None,
+        )
+        clock_widget = DraggableButton(
+            self,
+            self.home_rects[3],
+            ClockWidget.get_symbolic_surf(widget_size),
+            [],
+            None,
+        )
+        return keypad_keys, cursor_keys, mousepad_widget, clock_widget
 
     def activate(self):
         super().activate()
-        self.layout_widget.activate()
+        self.screen_widget.activate()
+        self.update_draggable_buttons()
+
+    def drops_to_home_rect(self, pos):
+        return not self.screen_widget.rect.collidepoint(pos)
+
+    def update_draggable_buttons_zero(self):
+        for i, draggable_button in enumerate(self.draggable_buttons):
+            draggable_button.dests.clear()
+            draggable_button.rect = self.home_rects[i]
+            draggable_button.dests.append(self.screen_widget_rects[3])
+
+    def update_draggable_buttons_one(self):
+        for (i, draggable_button), widget in zip(
+            enumerate(self.draggable_buttons), self.WIDGETS.values()
+        ):
+            draggable_button.dests.clear()
+            if widget in self.widget_row:
+                draggable_button.rect = self.screen_widget_rects[3]
+                draggable_button.dests.append(
+                    (self.drops_to_home_rect, self.home_rects[i])
+                )
+            else:
+                draggable_button.rect = self.home_rects[i]
+                draggable_button.dests.extend(
+                    self.screen_widget_rects[i] for i in range(2)
+                )
+
+    def update_draggable_buttons_two(self):
+        for (i, draggable_button), widget in zip(
+            enumerate(self.draggable_buttons), self.WIDGETS.values()
+        ):
+            draggable_button.dests.clear()
+            if widget in self.widget_row:
+                draggable_button.dests.append(
+                    (self.drops_to_home_rect, self.home_rects[i])
+                )
+                index = self.widget_row.index(widget)
+                draggable_button.rect = self.screen_widget_rects[index]
+                draggable_button.dests.extend(
+                    self.screen_widget_rects[i] for i in range(2) if i != index
+                )
+            else:
+                draggable_button.rect = self.home_rects[i]
+                draggable_button.dests.extend(
+                    self.screen_widget_rects[2 + i] for i in range(3)
+                )
+
+    def update_draggable_buttons_three(self):
+        for (i, draggable_button), widget in zip(
+            enumerate(self.draggable_buttons), self.WIDGETS.values()
+        ):
+            draggable_button.dests.clear()
+            if widget in self.widget_row:
+                draggable_button.dests.append(
+                    (self.drops_to_home_rect, self.home_rects[i])
+                )
+                index = self.widget_row.index(widget)
+                draggable_button.rect = self.screen_widget_rects[2 + index]
+                draggable_button.dests.extend(
+                    self.screen_widget_rects[2 + i] for i in range(3) if i != index
+                )
+            else:
+                draggable_button.rect = self.home_rects[i]
+
+    def update_draggable_buttons(self):
+        (
+            self.update_draggable_buttons_zero,
+            self.update_draggable_buttons_one,
+            self.update_draggable_buttons_two,
+            self.update_draggable_buttons_three,
+        )[len(self.widget_row)]()
 
     def deactivate(self):
         self.root.setup_widgets()
         super().deactivate()
 
+    def draw(self):
+        super().draw()
+        for child in self.children:
+            if hasattr(child, "draw_post"):
+                child.draw_post()
+
     def apply_config(self, data):
         if "keyboard_row" in data:
             self.keyboard_row = data["keyboard_row"]
@@ -220,20 +348,62 @@ class LayoutModal(QuittableModal):
                 w for w in (self.WIDGETS.get(key) for key in data["widget_row"]) if w
             )
 
-    @classmethod
-    def stringify_widgets(cls, widget_row):
-        result = [None] * len(widget_row)
-        for key, value in cls.WIDGETS.items():
-            try:
-                index = widget_row.index(value)
-            except ValueError:
-                continue
-            else:
-                result[index] = key
-        return result
-
     def get_config(self):
+        names_per_widget_ids = {id(value): key for key, value in self.WIDGETS.items()}
         return {
             "keyboard_row": self.keyboard_row,
-            "widget_row": self.stringify_widgets(self.widget_row),
+            "widget_row": [
+                names_per_widget_ids[id(widget)] for widget in self.widget_row
+            ],
         }
+
+    def draggable_callback(self, draggable_button, replaced_dest):
+        # update widget_row based on the completed drag and drop action
+        old_rect = draggable_button.dests[replaced_dest]
+        new_rect = draggable_button.rect
+        widget = tuple(self.WIDGETS.values())[
+            self.draggable_buttons.index(draggable_button)
+        ]
+        len_widget_row = len(self.widget_row)
+        if old_rect in self.home_rects:
+            screen_rects = self.screen_widget_rects
+            # we inserted the draggable button to the screen rect
+            if len_widget_row == 0:
+                self.widget_row.append(widget)
+            elif len_widget_row == 1:
+                if new_rect == screen_rects[0]:
+                    self.widget_row.insert(0, widget)
+                elif new_rect == screen_rects[1]:
+                    self.widget_row.append(widget)
+            elif len_widget_row == 2:
+                if new_rect == screen_rects[2]:
+                    self.widget_row.insert(0, widget)
+                elif new_rect == screen_rects[3]:
+                    self.widget_row.insert(1, widget)
+                elif new_rect == screen_rects[4]:
+                    self.widget_row.append(widget)
+        elif new_rect in self.home_rects:
+            self.widget_row.remove(widget)
+        elif len_widget_row == 2:
+            new_row = (self.widget_row[1], self.widget_row[0])
+            self.widget_row.clear()
+            self.widget_row.extend(new_row)
+        elif len_widget_row == 3:
+            self.swap_draggable_three({new_rect.topleft, old_rect.topleft})
+        self.update_draggable_buttons()
+
+    def swap_draggable_three(self, swapped):
+        # reorder widget_row based on the two known indices
+        screen_rects = self.screen_widget_rects
+        if swapped == {screen_rects[2].topleft, screen_rects[3].topleft}:
+            new_row = (self.widget_row[1], self.widget_row[0], self.widget_row[2])
+            self.widget_row.clear()
+            self.widget_row.extend(new_row)
+        elif swapped == {screen_rects[2].topleft, screen_rects[4].topleft}:
+            new_row = (self.widget_row[2], self.widget_row[1], self.widget_row[0])
+            self.widget_row.clear()
+            self.widget_row.extend(new_row)
+        elif swapped == {screen_rects[3].topleft, screen_rects[4].topleft}:
+            new_row = (self.widget_row[0], self.widget_row[2], self.widget_row[1])
+            self.widget_row.clear()
+            self.widget_row.extend(new_row)
index 1a7f64ec9f58255c01101ae4cf1350f502ddb8ee..22a81a51b66884e702c4c254cd43cf63e430b662 100644 (file)
@@ -20,10 +20,15 @@ SPEEDS = ((2400, 2400), (600, -600))
 
 
 class MousePadWidget(Child):
+    BUTTON_HEIGHT_DIVISOR = 6
+
     def __init__(self, parent, rect, click_cb, move_cb):
         super().__init__(parent)
         self.rect = rect
-        button_size = (rect.width / len(BUTTONS), rect.height / 6)
+        button_size = (
+            rect.width / len(BUTTONS),
+            rect.height / self.BUTTON_HEIGHT_DIVISOR,
+        )
         for i, (value, args) in enumerate(BUTTONS):
             KeyButton(
                 parent,
@@ -44,6 +49,28 @@ class MousePadWidget(Child):
             move_cb,
         )
 
+    @classmethod
+    def get_symbolic_surf(cls, size):
+        from .layout import get_symbolic_keys
+
+        surf = pygame.Surface(size, pygame.SRCALPHA)
+        surf.fill(0x7F000000)
+        button_height = size[1] / MousePadWidget.BUTTON_HEIGHT_DIVISOR
+        get_symbolic_keys(
+            surf.subsurface(
+                pygame.Rect((0, size[1] - button_height), (size[0], button_height))
+            ),
+            (size[0], button_height),
+            BUTTONS,
+        )
+        pygame.draw.rect(
+            surf, "black", pygame.Rect((0, 0), (size[0], size[1] - button_height))
+        )
+        pygame.draw.rect(
+            surf, "yellow", pygame.Rect((0, 0), (size[0], size[1] - button_height)), 1
+        )
+        return surf
+
 
 @dataclass
 class Finger:
@@ -96,6 +123,13 @@ class MousePadArea(Child):
 
     def draw(self):
         pygame.draw.rect(self.surf, "yellow", self.rect, 1)
+        for finger in self.fingers.values():
+            pygame.draw.circle(
+                self.surf,
+                "yellow",
+                MultitouchHandler.map_coords(finger.old, self.surf.get_size()),
+                16,
+            )
 
     def handle_fingerdown(self, event):
         pos = (event.x, event.y)
index cb4cbfff9f2b7bdb2e0d19a97bacb779644e4ea9..8b2ecc1b62f4092d6ac426f2bd590b26eabc401b 100644 (file)
@@ -10,7 +10,7 @@ from .label import Label
 from .message_box import MessageBox
 from .modal import Modal, QuittableModal
 from .parent import Parent
-from .rect import Rect
+from .rect import LabelledRect, Rect
 from .root import BaseRoot
 from .scroll import Scroll
 from .slider import Slider
@@ -31,6 +31,7 @@ __all__ = [
     "Icon",
     "IconButton",
     "Label",
+    "LabelledRect",
     "MessageBox",
     "Modal",
     "Parent",
index 3959c317d4c4295ad9538c17db3d2099fd56910e..2cb134aaa16bb1fb3f4c8e640b40c1363ed6c42c 100644 (file)
@@ -15,3 +15,21 @@ class Rect(Child):
             pygame.draw.rect(self.surf, self.background_color, self.rect)
         if self.border_color:
             pygame.draw.rect(self.surf, self.border_color, self.rect, 1)
+
+
+class LabelledRect(Rect):
+    def __init__(self, parent, rect, value, text_color, background_color, border_color):
+        super().__init__(parent, rect, background_color, border_color)
+        self.value = value
+        self.text_color = text_color
+
+    def draw(self):
+        super().draw()
+        fs = self.font.render(self.value, True, self.text_color)
+        self.surf.blit(
+            fs,
+            (
+                self.rect.centerx - fs.get_width() / 2,
+                self.rect.centery - fs.get_height() / 2,
+            ),
+        )