tau = 2 * pi
+class UIParent:
+ BACKGROUND_COLOR = None
+
+ def __init__(self, surf, font):
+ self.surf = surf
+ self.font = font
+ self.running = True
+ self.dirty = False
+ self.children = []
+ self.clock = pygame.time.Clock()
+
+ def handle_event(self, ev):
+ if ev.type == pygame.QUIT:
+ self.running = False
+ return False
+ elif ev.type == pygame.WINDOWEXPOSED:
+ self.dirty = True
+ elif ev.type == pygame.KEYDOWN:
+ if ev.key == pygame.K_ESCAPE:
+ self.running = False
+ return False
+ method_name = f"handle_{pygame.event.event_name(ev.type).lower()}"
+ for child in self.children:
+ if not hasattr(child, method_name):
+ continue
+ getattr(child, method_name)(ev)
+ return True
+
+ def update(self):
+ for child in self.children:
+ child.update()
+
+ def draw(self):
+ background_color = getattr(self, "BACKGROUND_COLOR", None)
+ if background_color is not None:
+ self.surf.fill(background_color)
+ for child in self.children:
+ child.draw()
+
+ def run(self):
+ while True:
+ for ev in pygame.event.get():
+ if not self.handle_event(ev):
+ break
+ if not self.running:
+ break
+ self.update()
+ if self.dirty:
+ self.draw()
+ pygame.display.update()
+ self.dirty = False
+ self.clock.tick(60)
+
+
+class UIChild:
+ def __init__(self, parent):
+ self.parent = parent
+
+ def draw(self):
+ pass
+
+ def update(self):
+ pass
+
+
def ease_in_out_elastic(mag):
p = 1 - mag
s = p / tau * asin(1)
return inner
-class Switch:
+class Switch(UIChild):
MOVE_FOR_SEC = 1
EASE = staticmethod(ease_in_out_elastic((5 ** .5 - 1) / 2))
def __init__(self, parent, rect, update_callback, setting=False):
- self.parent = parent
+ super().__init__(parent)
self.rect = rect
self.update_callback = update_callback
if setting is not None and not isinstance(setting, bool):
if self.setting is None:
current = 0.5
else:
- current = min(max(self.setting, 0), 1)
+ current = min(max(int(self.setting), 0), 1)
else:
current = (t - self.moving_since) / self.MOVE_FOR_SEC
if not self.setting:
self.moving_since = time() - offset
-class Button:
+class Button(UIChild):
def __init__(self, parent, rect, label, callback):
- self.parent = parent
+ super().__init__(parent)
self.rect = rect
self.label = label
self.pushed = False
fs, (center[0] - fs_size[0] // 2, center[1] - fs_size[1] // 2)
)
- def update(self):
- pass
-
def handle_mousebuttondown(self, ev):
if ev.button == 1 and self.rect.collidepoint(ev.pos):
self.pushed = True
self.parent.dirty = True
-class IconButton:
- def __init__(self, parent, name, shape, shape_size, rect):
- self.parent = parent
- self.name = name
+class IconButton(Button):
+ def __init__(self, parent, shape, shape_size, rect, callback, is_active):
+ super().__init__(parent, rect, None, callback)
self.shape = shape
self.shape_size = shape_size
- self.rect = rect
+ self.is_active = is_active
def draw(self):
- color = "lime " if self.parent.xrandr_conf.is_active(self.name) else "gray"
- pygame.draw.rect(self.parent.surf, color, self.rect, 8)
units = (16, 16)
full_size = (units[0] * self.shape_size[0], units[1] * self.shape_size[1])
center = self.rect.center
+ if self.pushed:
+ pygame.draw.rect(self.parent.surf, "honeydew4", self.rect)
self.shape.draw(
self.parent.surf,
(center[0] - full_size[0] // 2, center[1] - full_size[1] // 2),
units,
- "white",
+ "black" if self.pushed else "white",
)
+ color = "lime" if self.is_active() else ("red" if self.pushed else "gray")
+ pygame.draw.rect(self.parent.surf, color, self.rect, 8)
- def update(self):
- pass
- def handle_mousebuttondown(self, ev):
- if not self.rect.collidepoint(ev.pos):
- return
- self.parent.xrandr_conf.update(self.name)
- if self.name != "single":
- self.parent.bluetooth_conf.update(True)
- self.parent.bt_switch.update(True)
- settings_per_device_type = {
- "touchpad": self.parent.touch_switch.setting,
- "stylus": self.parent.stylus_switch.setting,
- }
- for device_type, setting in settings_per_device_type.items():
- if setting is not None:
- self.parent.xinput_conf.update_by_type(device_type, setting)
- self.parent.dirty = True
-
-
-class Icon:
+class Icon(UIChild):
def __init__(self, parent, shape, pos):
- self.parent = parent
+ super().__init__(parent)
self.shape = shape
self.pos = pos
- def update(self):
- pass
-
def draw(self):
self.shape.draw(self.parent.surf, self.pos, (8, 8), "gray")
import pygame
from bluetooth import BluetoothConf
-from ui import Button, Icon, IconButton, Switch
+from ui import Button, Icon, IconButton, Switch, UIParent
from vectors import (
bluetooth,
laptop_double,
from xrandr import XrandrConf
-class ZenbookConf:
+class FPSMixin:
+ FPS_COLOR = "yellow"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.current_fps = None
+
+ def update(self):
+ super().update()
+ new_fps = int(self.clock.get_fps())
+ if self.current_fps != new_fps:
+ self.current_fps = new_fps
+ self.dirty = True
+
+ def draw(self):
+ super().draw()
+ surf_size = self.surf.get_size()
+ fs = self.font.render(f"{int(self.current_fps)} FPS", True, self.FPS_COLOR)
+ fs_size = fs.get_size()
+ self.surf.blit(
+ fs, (surf_size[0] - fs_size[0] - 7, surf_size[1] - fs_size[1] - 7)
+ )
+
+
+class ZenbookConf(FPSMixin, UIParent):
+ BACKGROUND_COLOR = 0x333333
+
def __init__(self):
pygame.init()
- self.surf = pygame.display.set_mode((1536, 1200))
- self.font = pygame.font.Font(None, size=96)
- self.clock = pygame.time.Clock()
- self.running = True
- self.dirty = False
+ super().__init__(
+ pygame.display.set_mode((1536, 1200)), pygame.font.Font(None, size=96),
+ )
self.xrandr_conf = XrandrConf()
self.xinput_conf = XinputConf(self.xrandr_conf)
self.bluetooth_conf = BluetoothConf()
- self.current_fps = -1
bt_switch = Switch(
self,
pygame.Rect((128, 552), (256, 128)),
partial(self.xinput_conf.update_by_type, "stylus"),
self.xinput_conf.conf_by_type("stylus"),
)
- self.children = (
- IconButton(
- self,
- "single",
- laptop_single,
- (22, 22),
- pygame.Rect((68, 68), (416, 416)),
- ),
- IconButton(
- self,
- "double",
- laptop_double,
- (22, 22),
- pygame.Rect((568, 68), (416, 416)),
- ),
- IconButton(
- self,
- "vertical",
- laptop_vertical,
- (22, 22),
- pygame.Rect((1068, 68), (416, 416)),
- ),
- bt_switch,
- Icon(
- self,
- bluetooth,
- (bt_switch.rect.right + 68, bt_switch.rect.centery - 88),
- ),
- touch_switch,
- Icon(
- self,
- touchscreen,
- (touch_switch.rect.right + 68, touch_switch.rect.centery - 88),
- ),
- stylus_switch,
- Icon(
- self,
- stylus,
- (stylus_switch.rect.right + 68, stylus_switch.rect.centery - 88),
- ),
- Button(
- self,
- pygame.Rect((784, 748), (384, 128)),
- "Re-apply",
- partial(self.xinput_conf.reapply_by_type, "touchpad")
- ),
- Button(
- self,
- pygame.Rect((784, 944), (384, 128)),
- "Re-apply",
- partial(self.xinput_conf.reapply_by_type, "stylus")
- ),
+ self.children.extend(
+ (
+ IconButton(
+ self,
+ laptop_single,
+ (22, 22),
+ pygame.Rect((68, 68), (416, 416)),
+ partial(
+ self.laptop_cb, "single", bt_switch, touch_switch, stylus_switch
+ ),
+ partial(self.laptop_is_active, "single"),
+ ),
+ IconButton(
+ self,
+ laptop_double,
+ (22, 22),
+ pygame.Rect((568, 68), (416, 416)),
+ partial(
+ self.laptop_cb, "double", bt_switch, touch_switch, stylus_switch
+ ),
+ partial(self.laptop_is_active, "double"),
+ ),
+ IconButton(
+ self,
+ laptop_vertical,
+ (22, 22),
+ pygame.Rect((1068, 68), (416, 416)),
+ partial(
+ self.laptop_cb,
+ "vertical",
+ bt_switch,
+ touch_switch,
+ stylus_switch,
+ ),
+ partial(self.laptop_is_active, "vertical"),
+ ),
+ bt_switch,
+ Icon(
+ self,
+ bluetooth,
+ (bt_switch.rect.right + 68, bt_switch.rect.centery - 88),
+ ),
+ touch_switch,
+ Icon(
+ self,
+ touchscreen,
+ (touch_switch.rect.right + 68, touch_switch.rect.centery - 88),
+ ),
+ stylus_switch,
+ Icon(
+ self,
+ stylus,
+ (stylus_switch.rect.right + 68, stylus_switch.rect.centery - 88),
+ ),
+ Button(
+ self,
+ pygame.Rect((784, 748), (384, 128)),
+ "Re-apply",
+ partial(self.xinput_conf.reapply_by_type, "touchpad")
+ ),
+ Button(
+ self,
+ pygame.Rect((784, 944), (384, 128)),
+ "Re-apply",
+ partial(self.xinput_conf.reapply_by_type, "stylus")
+ ),
+ )
)
- def handle_event(self, ev):
- if ev.type == pygame.QUIT:
- self.running = False
- return False
- elif ev.type == pygame.WINDOWEXPOSED:
- self.dirty = True
- elif ev.type == pygame.KEYDOWN:
- if ev.key == pygame.K_ESCAPE:
- self.running = False
- return False
- method_name = f"handle_{pygame.event.event_name(ev.type).lower()}"
- for child in self.children:
- method = getattr(child, method_name, None)
- if method is None:
- continue
- method(ev)
- return True
-
- def update(self):
- for child in self.children:
- child.update()
- if self.clock.get_fps() != self.current_fps:
- self.current_fps = int(self.clock.get_fps())
- self.dirty = True
-
- def draw(self):
- self.surf.fill(0x333333)
- for child in self.children:
- child.draw()
+ def laptop_cb(self, name, bt_switch, touch_switch, stylus_switch):
+ self.xrandr_conf.update(name)
+ if name != "single":
+ self.bluetooth_conf.update(True)
+ bt_switch.update(True)
+ settings_per_device_type = {
+ "touchpad": touch_switch.setting,
+ "stylus": stylus_switch.setting,
+ }
+ for device_type, setting in settings_per_device_type.items():
+ if setting is not None:
+ self.xinput_conf.update_by_type(device_type, setting)
+ self.dirty = True
- def run(self):
- while True:
- for ev in pygame.event.get():
- if not self.handle_event(ev):
- break
- if not self.running:
- break
- self.update()
- if self.dirty:
- self.draw()
- pygame.display.update()
- self.dirty = False
- self.clock.tick(60)
+ def laptop_is_active(self, name):
+ return self.xrandr_conf.is_active(name)
if __name__ == "__main__":