Design: watching screen's programme bar and subtitles panel, every look
FigCraft proof (design/watching/build-watching-osd.py): the panel docks above the bar, the bar stays while it is open, and every colour comes from the look's tokens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
The watching screen's programme bar and subtitles/sound panel, redesigned (Paul, 6 November 2026): the panel used to sit
|
||||
halfway down the right and hang over the bar, the bar hid itself while the panel was open, and the bar's white and dark
|
||||
blue ignored the theme. Here both are one family drawn only from each look's tokens (Themes.kt), the panel docked above
|
||||
the bar on the right, the bar staying while the panel is open. Drawn as SVG, converted and exported with FigCraft.
|
||||
"""
|
||||
import subprocess
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
LOOKS = {
|
||||
'Orbit Night': dict(background='#071632', surface='#12356B', raised='#194C91', accent='#FFDE59', ink='#FFFFFF', muted='#B7CBE8', deep='#0C2148', onAccent='#071632'),
|
||||
'Daybreak': dict(background='#E8F1FB', surface='#FFFFFF', raised='#D2E2F6', accent='#1E66D0', ink='#0E1E3C', muted='#4A5D7A', deep='#DCE8F6', onAccent='#FFFFFF'),
|
||||
'Nebula': dict(background='#170B2E', surface='#3A1D6B', raised='#522D8F', accent='#FF7AC6', ink='#FFFFFF', muted='#C8B4E6', deep='#22113F', onAccent='#170B2E'),
|
||||
'Retro Tube': dict(background='#1A120B', surface='#3B2A1A', raised='#574029', accent='#FFB347', ink='#FFECD2', muted='#D2B48C', deep='#241911', onAccent='#1A120B'),
|
||||
'Deep Sea': dict(background='#04212B', surface='#0B4A5C', raised='#126378', accent='#6EF0D8', ink='#F0FFFC', muted='#A0D2D2', deep='#072C38', onAccent='#04212B'),
|
||||
'High Contrast': dict(background='#000000', surface='#1A1A1A', raised='#333333', accent='#FFFF00', ink='#FFFFFF', muted='#DCDCDC', deep='#0D0D0D', onAccent='#000000'),
|
||||
}
|
||||
FONT = 'Barlow, DejaVu Sans, sans-serif'
|
||||
|
||||
|
||||
def t(x, y, s, size, fill, weight=400, anchor='start', extra=''):
|
||||
return f'<text x="{x}" y="{y}" font-family="{FONT}" font-size="{size}" font-weight="{weight}" fill="{fill}" text-anchor="{anchor}" {extra}>{escape(s)}</text>'
|
||||
|
||||
|
||||
def frame(c, panel=True):
|
||||
"""One 1920x1080 frame: a programme stand-in, the bar along the bottom, and the panel docked above it on the right."""
|
||||
o = ['<rect width="1920" height="1080" fill="#20303A"/>',
|
||||
'<defs><linearGradient id="pic" x2="1" y2="1"><stop stop-color="#3A5A6A"/><stop offset="1" stop-color="#14202A"/></linearGradient></defs>',
|
||||
'<rect width="1920" height="1080" fill="url(#pic)"/>',
|
||||
'<circle cx="700" cy="420" r="210" fill="#4E7484" opacity=".5"/><rect x="1100" y="260" width="420" height="420" rx="20" fill="#28404C" opacity=".6"/>',
|
||||
# Subtitles on the picture, kept above the bar.
|
||||
t(960, 742, 'I said we\'d be home by Christmas.', 44, '#FFFFFF', 600, 'middle', 'stroke="#000" stroke-width="3" paint-order="stroke"')]
|
||||
# The bar: one card, deep at 94%, a hairline of raised, rounded 16.
|
||||
x, y, w, h = 134, 800, 1652, 236
|
||||
o.append(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="16" fill="{c["deep"]}" fill-opacity=".94" stroke="{c["raised"]}" stroke-width="2"/>')
|
||||
o.append(t(x + 32, y + 50, '308', 34, c['accent'], 700) + t(x + 104, y + 50, 'BritX', 34, c['ink'], 700) + t(x + w - 32, y + 50, '21:30 Fri 6 Nov', 28, c['muted'], 400, 'end'))
|
||||
o.append(f'<rect x="{x + 32}" y="{y + 76}" width="92" height="40" rx="8" fill="{c["accent"]}"/>' + t(x + 78, y + 106, 'NOW', 26, c['onAccent'], 700, 'middle'))
|
||||
o.append(t(x + 144, y + 108, 'Hot Fuzz', 42, c['ink'], 700) + t(x + w - 32, y + 108, '21:00 to 23:05', 28, c['muted'], 400, 'end'))
|
||||
o.append(f'<rect x="{x + 32}" y="{y + 132}" width="{w - 64}" height="8" rx="4" fill="{c["raised"]}"/><rect x="{x + 32}" y="{y + 132}" width="{(w - 64) * .24:.0f}" height="8" rx="4" fill="{c["accent"]}"/>')
|
||||
o.append(t(x + 32, y + 178, 'NEXT 23:05 The World\'s End', 28, c['muted'], 600))
|
||||
# Hints, on the surface along the bottom of the card.
|
||||
o.append(f'<path d="M{x} {y + 196}H{x + w}V{y + h - 16}Q{x + w} {y + h} {x + w - 16} {y + h}H{x + 16}Q{x} {y + h} {x} {y + h - 16}Z" fill="{c["surface"]}"/>')
|
||||
hints = [('▲▼', 'Channel'), ('◄', 'TV guide'), ('►', 'Subtitles and sound'), ('OK', 'Hide')] if not panel else [('▲▼', 'Move'), ('OK', 'Choose'), ('◄', 'Back'), ('►', 'Close')]
|
||||
hx = x + 32
|
||||
for key, label in hints:
|
||||
kw = 22 * len(key) + 28
|
||||
o.append(f'<rect x="{hx}" y="{y + 203}" width="{kw}" height="28" rx="6" fill="{c["raised"]}"/>' + t(hx + kw / 2, y + 225, key, 20, c['ink'], 700, 'middle') + t(hx + kw + 12, y + 225, label, 24, c['ink'], 600))
|
||||
hx += kw + 30 + 14 * len(label)
|
||||
if panel:
|
||||
# The panel, docked above the bar's right end: never over it, never taller than the space above.
|
||||
px, pw, ph = x + w - 520, 520, 600
|
||||
py = y - 24 - ph
|
||||
o.append(f'<rect x="{px}" y="{py}" width="{pw}" height="{ph}" rx="16" fill="{c["deep"]}" fill-opacity=".96" stroke="{c["raised"]}" stroke-width="2"/>')
|
||||
o.append(t(px + 28, py + 52, 'SUBTITLES', 24, c['muted'], 700, extra='letter-spacing="3"'))
|
||||
rows = [('Off', False, False), ('English', True, True), ('English (SDH)', False, False), ('French', False, False)]
|
||||
ry = py + 72
|
||||
for label, selected, cursor in rows:
|
||||
if cursor: o.append(f'<rect x="{px + 16}" y="{ry}" width="{pw - 32}" height="52" rx="10" fill="{c["accent"]}"/>')
|
||||
o.append(t(px + 44, ry + 36, ('✓ ' if selected else '') + label, 30, c['onAccent'] if cursor else c['ink'], 700 if (cursor or selected) else 400))
|
||||
ry += 58
|
||||
o.append(f'<rect x="{px + 28}" y="{ry + 8}" width="{pw - 56}" height="2" fill="{c["raised"]}"/>')
|
||||
o.append(t(px + 28, ry + 56, 'SOUND', 24, c['muted'], 700, extra='letter-spacing="3"'))
|
||||
ry += 74
|
||||
for label, selected in [('English 5.1', True), ('English stereo', False), ('Commentary', False)]:
|
||||
o.append(t(px + 44, ry + 36, ('✓ ' if selected else '') + label, 30, c['ink'], 700 if selected else 400)); ry += 58
|
||||
return o
|
||||
|
||||
|
||||
def board():
|
||||
cells = []
|
||||
for i, (name, c) in enumerate(LOOKS.items()):
|
||||
gx, gy = (i % 2) * 980, (i // 2) * 600 + 140
|
||||
cells.append(f'<g transform="translate({gx + 20} {gy}) scale(.5)">' + ''.join(frame(c)) + '</g>')
|
||||
cells.append(t(gx + 20, gy + 568, name, 30, '#FFFFFF', 600))
|
||||
head = t(40, 70, 'Watching: programme bar and subtitles panel', 46, '#FFDE59', 700) + t(40, 112, 'The panel docks above the bar and the bar stays while it is open. Every colour comes from the look.', 26, '#B7CBE8')
|
||||
svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="1980" height="1960" viewBox="0 0 1980 1960"><rect width="1980" height="1960" fill="#071632"/>{head}{"".join(cells)}</svg>'
|
||||
(HERE / 'watching-osd-themes.svg').write_text(svg)
|
||||
big = f'<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="2160" viewBox="0 0 1920 2160">{"".join(frame(LOOKS["Orbit Night"], panel=False))}<g transform="translate(0 1080)">{"".join(frame(LOOKS["Orbit Night"]))}</g></svg>'
|
||||
(HERE / 'watching-osd-night.svg').write_text(big)
|
||||
for name in ('watching-osd-themes', 'watching-osd-night'):
|
||||
subprocess.run(['figcraft-cli', 'convert', str(HERE / f'{name}.svg'), str(HERE / f'{name}.figcraft')], check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(['figcraft-cli', 'export', str(HERE / f'{name}.figcraft'), '--out', str(HERE / f'{name}.png')], check=True, stdout=subprocess.DEVNULL)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
board()
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 490 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 735 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 33 KiB |
Reference in New Issue
Block a user