feat(editor): the dark scheme everywhere — the surface, documentation, mode buttons, program windows

The surface turns dark with light labels and cables and a faint light outline
round Java's icons; documentation rectangles become a tint of their colour and
texts are drawn towards light; the mode buttons use Java's pictures freed from
their grey ground (tools/dark-pictures/make.py); native controls follow
color-scheme; the desktop's program windows paint through Java-look tokens
whose light values are Java's own. The exported image and the report keep
Java's colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 22:27:03 +00:00
co-authored by Claude Opus 5.5
parent 8c4e5049f6
commit ae10ee6c31
17 changed files with 536 additions and 127 deletions
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""The mode buttons' pictures for the editor's dark scheme (packages/editor/src/assets/modes/*_dunkel.png).
Java's pictures are 50 x 50 buttons: a frame and a vertical grey gradient (light blue and flat when the mode is
on) around the icon. On a dark button that ground would stand as a light tile, so it is freed: the outer ring of
RING pixels goes, and from there every pixel within GROUND of its row's ground colour (the gradient is row by row)
becomes transparent, flooding inwards; a pixel up to EDGE from it (the icon's edge, blended with the grey) keeps its
colour and fades with its likeness. Needs Pillow; run from the repository's root:
python3 tools/dark-pictures/make.py
"""
from collections import deque
from pathlib import Path
from PIL import Image
MODES = Path(__file__).resolve().parents[2] / 'packages/editor/src/assets/modes'
RING = 3
GROUND = 8
EDGE = 48
def freed(picture: Image.Image) -> Image.Image:
image = picture.convert('RGBA')
width, height = image.size
pixels = image.load()
ground = [pixels[RING, y] for y in range(height)]
for y in range(RING):
ground[y] = ground[RING]
for y in range(height - RING, height):
ground[y] = ground[height - RING - 1]
seen = set()
queue = deque()
for x in range(width):
for y in range(height):
if x < RING or y < RING or x >= width - RING or y >= height - RING:
pixels[x, y] = (0, 0, 0, 0)
seen.add((x, y))
queue.extend([(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)])
while queue:
x, y = queue.popleft()
if (x, y) in seen or not (0 <= x < width and 0 <= y < height):
continue
seen.add((x, y))
colour = pixels[x, y]
distance = max(abs(colour[index] - ground[y][index]) for index in range(3))
if distance <= GROUND:
pixels[x, y] = (*colour[:3], 0)
queue.extend([(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)])
elif distance <= EDGE:
pixels[x, y] = (*colour[:3], min(255, int(255 * (distance - GROUND) / (EDGE - GROUND))))
return image
if __name__ == '__main__':
for source in sorted(MODES.glob('*.png')):
if source.stem.endswith('_dunkel'):
continue
freed(Image.open(source)).save(source.with_name(f'{source.stem}_dunkel.png'))
print(source.with_name(f'{source.stem}_dunkel.png').name)