Replace Python layout prototype with editable playground
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Tk renderer for the status-bar layout playground."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import font as tkfont
|
||||
from typing import Iterable
|
||||
|
||||
from statusbar_lab import CameraCutout, Container, IconContainer, StatusBarLab, rectangles_overlap
|
||||
|
||||
|
||||
TYPE_COLOURS = {
|
||||
"clock": "#9fd7ff",
|
||||
"notification": "#a7efaa",
|
||||
"status": "#ffd596",
|
||||
"chip": "#e2b0ff",
|
||||
"carrier": "#ffb4c4",
|
||||
}
|
||||
|
||||
|
||||
def render(
|
||||
lab: StatusBarLab,
|
||||
statusbar_width: float,
|
||||
statusbar_height: float,
|
||||
render_scale: float = 1.0,
|
||||
vertical_scale: float = 2.0,
|
||||
margin: int = 40,
|
||||
) -> None:
|
||||
root = tk.Tk()
|
||||
root.title("StatusBarTweak layout playground")
|
||||
width = int(statusbar_width * render_scale + margin * 2)
|
||||
height = int(statusbar_height * vertical_scale + margin * 2)
|
||||
canvas = tk.Canvas(root, width=width, height=height, bg="#242424", highlightthickness=0)
|
||||
canvas.pack()
|
||||
|
||||
def point(x: float, y: float) -> tuple[float, float]:
|
||||
return margin + x * render_scale, margin + (statusbar_height - y) * vertical_scale
|
||||
|
||||
left, bottom = point(0, 0)
|
||||
right, top = point(statusbar_width, statusbar_height)
|
||||
canvas.create_rectangle(left, top, right, bottom, fill="white", outline="#4a4a4a", width=5)
|
||||
|
||||
containers = lab.render_containers()
|
||||
for container in containers:
|
||||
_draw_container(canvas, point, container, render_scale, vertical_scale)
|
||||
|
||||
_draw_overlaps(canvas, point, containers)
|
||||
for camera in lab.cameras:
|
||||
_draw_camera(canvas, point, camera)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
def _draw_container(canvas: tk.Canvas, point, container: Container, render_scale: float, vertical_scale: float) -> None:
|
||||
if isinstance(container, IconContainer):
|
||||
_draw_icons(canvas, point, container)
|
||||
return
|
||||
|
||||
x1, y1 = point(container.left, container.bottom)
|
||||
x2, y2 = point(container.right, container.top)
|
||||
canvas.create_rectangle(x1, y2, x2, y1, fill=TYPE_COLOURS[container.kind], outline="black", width=1)
|
||||
_draw_clipped_text(canvas, container.text, x1 + 3, (y1 + y2) / 2, max(0, x2 - x1 - 6), container.height * vertical_scale)
|
||||
|
||||
|
||||
def _draw_icons(canvas: tk.Canvas, point, container: IconContainer) -> None:
|
||||
colour = TYPE_COLOURS[container.kind]
|
||||
if not container.segments():
|
||||
x, y1 = point(container.left, container.bottom)
|
||||
_, y2 = point(container.left, container.top)
|
||||
canvas.create_line(x, y1, x, y2, fill="black", width=1)
|
||||
return
|
||||
|
||||
cursor = container.left
|
||||
for segment_kind, icon_id, segment_width in container.segments():
|
||||
x1, y1 = point(cursor, container.bottom)
|
||||
x2, y2 = point(cursor + segment_width, container.top)
|
||||
if segment_kind == "gap":
|
||||
cursor += segment_width
|
||||
continue
|
||||
if segment_kind == "dot":
|
||||
canvas.create_rectangle(x1, y2, x2, y1, fill=colour, outline="black", width=1)
|
||||
radius = min(abs(x2 - x1), abs(y1 - y2)) * 0.24
|
||||
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
|
||||
canvas.create_oval(cx - radius, cy - radius, cx + radius, cy + radius, fill="black", outline="black")
|
||||
else:
|
||||
canvas.create_rectangle(x1, y2, x2, y1, fill=colour, outline="black", width=1)
|
||||
_draw_clipped_text(canvas, str(icon_id), (x1 + x2) / 2, (y1 + y2) / 2, abs(x2 - x1) - 2, abs(y1 - y2), anchor="center")
|
||||
cursor += segment_width
|
||||
|
||||
|
||||
def _draw_camera(canvas: tk.Canvas, point, camera: CameraCutout) -> None:
|
||||
x1, y1 = point(camera.left, camera.bottom)
|
||||
x2, y2 = point(camera.right, camera.top)
|
||||
canvas.create_rectangle(x1, y2, x2, y1, fill="black", outline="black")
|
||||
|
||||
|
||||
def _draw_overlaps(canvas: tk.Canvas, point, containers: Iterable[Container]) -> None:
|
||||
items = list(containers)
|
||||
for index, first in enumerate(items):
|
||||
for second in items[index + 1:]:
|
||||
if first.kind == "clock" and second.kind == "clock":
|
||||
continue
|
||||
if not rectangles_overlap(first.bounds, second.bounds):
|
||||
continue
|
||||
left = max(first.left, second.left)
|
||||
right = min(first.right, second.right)
|
||||
bottom = max(first.bottom, second.bottom)
|
||||
top = min(first.top, second.top)
|
||||
x1, y1 = point(left, bottom)
|
||||
x2, y2 = point(right, top)
|
||||
canvas.create_rectangle(x1, y2, x2, y1, fill="red", outline="red")
|
||||
radius = max(abs(x2 - x1), abs(y1 - y2)) / 2 + 12
|
||||
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
|
||||
canvas.create_oval(cx - radius, cy - radius, cx + radius, cy + radius, outline="red", width=3)
|
||||
|
||||
|
||||
def _draw_clipped_text(
|
||||
canvas: tk.Canvas,
|
||||
text: str,
|
||||
x: float,
|
||||
y: float,
|
||||
width: float,
|
||||
height: float,
|
||||
anchor: str = "w",
|
||||
) -> None:
|
||||
if width <= 0:
|
||||
return
|
||||
size = max(7, int(height * 0.42))
|
||||
text_font = tkfont.Font(family="TkDefaultFont", size=size)
|
||||
clipped = _clip_text(text_font, text, width)
|
||||
canvas.create_text(x, y, text=clipped, font=text_font, fill="black", anchor=anchor)
|
||||
|
||||
|
||||
def _clip_text(text_font: tkfont.Font, text: str, width: float) -> str:
|
||||
if text_font.measure(text) <= width:
|
||||
return text
|
||||
ellipsis = "..."
|
||||
while text and text_font.measure(text + ellipsis) > width:
|
||||
text = text[:-1]
|
||||
return text + ellipsis if text else ""
|
||||
Reference in New Issue
Block a user