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,
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(
)
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,
(
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))
],
)
+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
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,
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"]
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)