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:
@@ -278,6 +278,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
|||||||
| `src/sheet/sheet.css` (`.editor-root`, Handy), `src/Editor.tsx` (Wurzel), `browser/sheet.spec.ts` | — | geändert (10.10.2026, auf Wunsch des Nutzers): auf dem Handy (bis 639 px) nimmt das Geräte-Blatt den ganzen Editor ein, über der Leiste und den Modusknöpfen; eine Meldung bleibt darüber, Schließen gibt die Fläche zurück. Vorher stand es unter der Fläche (höchstens halb so hoch), neben einer Leiste von 137 px blieben ihm 253 von 390 px. Folge: die DHCP-Segmente stehen auf einem 390-px-Handy jetzt nebeneinander, untereinander erst unter etwa 335 px |
|
| `src/sheet/sheet.css` (`.editor-root`, Handy), `src/Editor.tsx` (Wurzel), `browser/sheet.spec.ts` | — | geändert (10.10.2026, auf Wunsch des Nutzers): auf dem Handy (bis 639 px) nimmt das Geräte-Blatt den ganzen Editor ein, über der Leiste und den Modusknöpfen; eine Meldung bleibt darüber, Schließen gibt die Fläche zurück. Vorher stand es unter der Fläche (höchstens halb so hoch), neben einer Leiste von 137 px blieben ihm 253 von 390 px. Folge: die DHCP-Segmente stehen auf einem 390-px-Handy jetzt nebeneinander, untereinander erst unter etwa 335 px |
|
||||||
| `src/simulation/PropertiesSheet.tsx` (neu), `src/simulation/DropHold.tsx` (vorher `DropControl.tsx`), `src/simulation/useSimulation.ts` (`restart`), `src/Editor.tsx`, `src/sheet/sheet.css`, `src/desktop/desktop.css`, `src/i18n/*.json` (`properties.*`; `drop.list` entfällt), `browser/viewers.spec.ts`, `browser/shared.spec.ts`, `browser/fromF.spec.ts`, `tools/live/acceptance.ts` | Java `SimulationControl` | geändert (10.10.2026, auf Wunsch des Nutzers): im Aktionsmodus ein eigener Inspector „Eigenschaften“ (Knopf über der Fläche, Blatt neben ihr wie die Geräte-Einstellungen, auf dem Handy über dem ganzen Editor) statt Hinweis und Knöpfen in der Werkzeugleiste: wer die Simulation gestartet hat, „Simulation neu starten“ (für alle, nach einer Rückfrage; die Tabellen des Datenaustauschs verlieren ihre Auswahl), „Pakete verwerfen“ und darunter die verworfenen Pakete samt Anzahl. Wer nur zusieht, sieht Starter und Liste, ohne Neustart und Verwerfen |
|
| `src/simulation/PropertiesSheet.tsx` (neu), `src/simulation/DropHold.tsx` (vorher `DropControl.tsx`), `src/simulation/useSimulation.ts` (`restart`), `src/Editor.tsx`, `src/sheet/sheet.css`, `src/desktop/desktop.css`, `src/i18n/*.json` (`properties.*`; `drop.list` entfällt), `browser/viewers.spec.ts`, `browser/shared.spec.ts`, `browser/fromF.spec.ts`, `tools/live/acceptance.ts` | Java `SimulationControl` | geändert (10.10.2026, auf Wunsch des Nutzers): im Aktionsmodus ein eigener Inspector „Eigenschaften“ (Knopf über der Fläche, Blatt neben ihr wie die Geräte-Einstellungen, auf dem Handy über dem ganzen Editor) statt Hinweis und Knöpfen in der Werkzeugleiste: wer die Simulation gestartet hat, „Simulation neu starten“ (für alle, nach einer Rückfrage; die Tabellen des Datenaustauschs verlieren ihre Auswahl), „Pakete verwerfen“ und darunter die verworfenen Pakete samt Anzahl. Wer nur zusieht, sieht Starter und Liste, ohne Neustart und Verwerfen |
|
||||||
| `src/canvas/handles.ts` (neu), `src/canvas/Canvas.tsx` (Griffe, Durchtippen, Platz von „⋯“), `src/sheet/sheet.css` (`.doc-handle`), `src/i18n/*.json` (`docu.resize`, `docu.move`), `test/handles.test.ts`, `browser/touch.spec.ts`, `browser/documentation.spec.ts` | Java `JDocuElement` (Rand von 7 zum Greifen) | erweitert (10.10.2026, auf Wunsch des Nutzers): im Dokumentationsmodus zeigt das fokussierte Element Griffe für den Finger — Trefferfläche 44 px, Punkt in Javas Fokusblau auf dem Fokusband: an den Ecken, ab drei Zielen Länge auch an den Kantenmitten; auf einem Element unter zwei Zielen nur rechts unten, außen an der Ecke, damit seine Mitte verschiebt und das Menü öffnet. Ein Text hat über seiner linken oberen Ecke einen Verschiebe-Griff (seine Mitte ist das Eingabefeld). „⋯“ steht über der rechten oberen Ecke; Griff und „⋯“ weichen am Rand der Fläche aus. Ein Tippen auf einen Griff gilt dem, was darunter liegt; langes Drücken öffnet das Menü. Gemessen: Javas Rand war 10–14 Bildschirm-Pixel breit und unsichtbar. WebKit gibt das Ende eines Tippens dem Griff statt der Fläche, die die Berührung gefangen hat; der Griff reicht seine Ereignisse deshalb weiter. Javas Rand für die Maus bleibt. Abweichend von Java: dort keine Griffe |
|
| `src/canvas/handles.ts` (neu), `src/canvas/Canvas.tsx` (Griffe, Durchtippen, Platz von „⋯“), `src/sheet/sheet.css` (`.doc-handle`), `src/i18n/*.json` (`docu.resize`, `docu.move`), `test/handles.test.ts`, `browser/touch.spec.ts`, `browser/documentation.spec.ts` | Java `JDocuElement` (Rand von 7 zum Greifen) | erweitert (10.10.2026, auf Wunsch des Nutzers): im Dokumentationsmodus zeigt das fokussierte Element Griffe für den Finger — Trefferfläche 44 px, Punkt in Javas Fokusblau auf dem Fokusband: an den Ecken, ab drei Zielen Länge auch an den Kantenmitten; auf einem Element unter zwei Zielen nur rechts unten, außen an der Ecke, damit seine Mitte verschiebt und das Menü öffnet. Ein Text hat über seiner linken oberen Ecke einen Verschiebe-Griff (seine Mitte ist das Eingabefeld). „⋯“ steht über der rechten oberen Ecke; Griff und „⋯“ weichen am Rand der Fläche aus. Ein Tippen auf einen Griff gilt dem, was darunter liegt; langes Drücken öffnet das Menü. Gemessen: Javas Rand war 10–14 Bildschirm-Pixel breit und unsichtbar. WebKit gibt das Ende eines Tippens dem Griff statt der Fläche, die die Berührung gefangen hat; der Griff reicht seine Ereignisse deshalb weiter. Javas Rand für die Maus bleibt. Abweichend von Java: dort keine Griffe |
|
||||||
|
| `src/theme/tokens.css` (Fläche, `--doc-text-border`, `--java-*`, `color-scheme`), `src/desktop/desktop.css` (109 feste Farben → `--java-*`), `src/canvas/documentationPaint.ts` (`darkRectangleFill`, `darkRectangleBorder`, `darkTextColor`), `src/canvas/Canvas.tsx`, `src/design/DocumentationText.tsx`, `src/simulation/ModeBar.tsx`, `src/sheet/sheet.css`, `src/assets/modes/*_dunkel.png` (neu, aus Javas Bildern freigestellt: `tools/dark-pictures/make.py`), `test/documentationPaint.test.ts`, `browser/theme.spec.ts` (neu) | Java `JCablePanel`, `JDocuElement`, `GUIMainMenu` (Modusbilder), Swing-Metal in den Programmfenstern | geändert (10.10.2026, auf Wunsch des Nutzers): das dunkle Schema überall — die Fläche dunkel (#262b33, vorher bewusst hell #e9ecf1) mit heller Beschriftung, helleren Kabeln und einem feinen hellen Rand um Javas Symbole (sonst versinken die schwarzen Switches); Gliederungsflächen als Tönung ihrer Farbe, Texte zur hellen Schrift hin gemischt; die Modusknöpfe mit freigestellten Bildern, der gedrückte Knopf in der Akzentfarbe; `color-scheme: dark` für Kästchen, Regler und Auswahllisten; die Programmfenster des Desktops über Java-Look-Token, hell mit Javas Werten unverändert. Hell bleiben Javas Desktop-Hintergrund und die Webseite im simulierten Browser (Inhalt). Exportiertes Bild und Bericht malen weiter Javas Farben |
|
||||||
|
|
||||||
## packages/server
|
## packages/server
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
// The editor's two schemes in Chromium: in the light one Java's colours as
|
||||||
|
// they were (a white surface, black labels, Java's grey program windows and
|
||||||
|
// mode buttons); in the dark one, as the dashboard asks for it, everything
|
||||||
|
// dark — the surface with light labels and cables, the documentation as a
|
||||||
|
// tint with light words, the mode buttons freed from Java's grey ground, the
|
||||||
|
// browser's own controls, and the program windows of the desktop — each pair
|
||||||
|
// of text and ground readable (contrast ratio as WCAG counts it).
|
||||||
|
|
||||||
|
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||||
|
import { drawing, openEditor, serveEditor, showDesktop } from "./editorPage.js";
|
||||||
|
|
||||||
|
let server: { readonly base: string; readonly close: () => Promise<void> };
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
server = await serveEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** A computed colour as red, green, blue (0–255) and alpha, whatever its notation (a mix comes as oklab): painted and read back. */
|
||||||
|
async function rgba(
|
||||||
|
page: Page,
|
||||||
|
color: string,
|
||||||
|
): Promise<[number, number, number, number]> {
|
||||||
|
return page.evaluate((value) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
const context = canvas.getContext("2d")!;
|
||||||
|
context.fillStyle = value;
|
||||||
|
context.fillRect(0, 0, 1, 1);
|
||||||
|
const [red, green, blue, alpha] = context.getImageData(0, 0, 1, 1).data;
|
||||||
|
return [red!, green!, blue!, alpha! / 255] as [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
];
|
||||||
|
}, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = (locator: Locator, property: string) =>
|
||||||
|
locator.evaluate(
|
||||||
|
(element, name) => getComputedStyle(element).getPropertyValue(name),
|
||||||
|
property,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** WCAG's relative luminance of an opaque colour. */
|
||||||
|
function luminance([red, green, blue]: readonly number[]): number {
|
||||||
|
const linear = (value: number) =>
|
||||||
|
value / 255 <= 0.03928
|
||||||
|
? value / 255 / 12.92
|
||||||
|
: ((value / 255 + 0.055) / 1.055) ** 2.4;
|
||||||
|
return (
|
||||||
|
0.2126 * linear(red!) + 0.7152 * linear(green!) + 0.0722 * linear(blue!)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function contrast(one: readonly number[], other: readonly number[]): number {
|
||||||
|
const [light, dark] = [luminance(one), luminance(other)].sort(
|
||||||
|
(a, b) => b - a,
|
||||||
|
) as [number, number];
|
||||||
|
return (light + 0.05) / (dark + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** What the scheme paints, read from the page. */
|
||||||
|
async function paint(page: Page) {
|
||||||
|
const surface = page.locator("svg.filius-canvas");
|
||||||
|
const rectangle = (await drawing(page)).docs.find(
|
||||||
|
(item) => item.kind === "rectangle",
|
||||||
|
)!;
|
||||||
|
const text = (await drawing(page)).docs.find((item) => item.kind === "text")!;
|
||||||
|
return {
|
||||||
|
scheme: await page.evaluate(
|
||||||
|
() => getComputedStyle(document.documentElement).colorScheme,
|
||||||
|
),
|
||||||
|
surface: await rgba(page, await style(surface, "background-color")),
|
||||||
|
label: await rgba(
|
||||||
|
page,
|
||||||
|
await style(page.locator("g.device text").first(), "fill"),
|
||||||
|
),
|
||||||
|
cable: await rgba(
|
||||||
|
page,
|
||||||
|
await style(page.locator("path[data-link]").first(), "stroke"),
|
||||||
|
),
|
||||||
|
rectangle: await rgba(
|
||||||
|
page,
|
||||||
|
await style(
|
||||||
|
page.locator(`g[data-doc="${rectangle.id}"] .doc-rectangle`),
|
||||||
|
"fill",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
words: await rgba(
|
||||||
|
page,
|
||||||
|
await style(page.locator(`g[data-doc="${text.id}"] .doc-text`), "color"),
|
||||||
|
),
|
||||||
|
iconFilter: await style(page.locator("g.device image").first(), "filter"),
|
||||||
|
pressedMode: await rgba(
|
||||||
|
page,
|
||||||
|
await style(
|
||||||
|
page.locator('button[name="designMode"]'),
|
||||||
|
"background-color",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
modePicture: await page
|
||||||
|
.locator('button[name="designMode"] img')
|
||||||
|
.evaluateAll((all) =>
|
||||||
|
all
|
||||||
|
.filter((each) => getComputedStyle(each).display !== "none")
|
||||||
|
.map((each) => each.getAttribute("data-scheme")),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The software installation's window on the web server's desktop: its panel, a list and its words. */
|
||||||
|
async function installation(page: Page) {
|
||||||
|
await page
|
||||||
|
.getByRole("button", { name: "Wechselt in den Aktionsmodus" })
|
||||||
|
.click();
|
||||||
|
await expect(page.locator("svg.filius-canvas")).toHaveAttribute(
|
||||||
|
"data-mode",
|
||||||
|
"simulation",
|
||||||
|
);
|
||||||
|
const web = (await drawing(page)).nodes.find(
|
||||||
|
(node) => node.name === "Webserver - 192.168.0.10",
|
||||||
|
)!.id;
|
||||||
|
await showDesktop(page, web);
|
||||||
|
await page
|
||||||
|
.locator(`section[data-desktop="${web}"] [data-program="installation"]`)
|
||||||
|
.click();
|
||||||
|
const window = page.locator(".installation");
|
||||||
|
const list = window.locator(".installation-list").first();
|
||||||
|
return {
|
||||||
|
panel: await rgba(page, await style(window, "background-color")),
|
||||||
|
field: await rgba(page, await style(list, "background-color")),
|
||||||
|
words: await rgba(page, await style(list.locator("li").first(), "color")),
|
||||||
|
taskbar: await rgba(
|
||||||
|
page,
|
||||||
|
await style(page.locator(".desktop-taskbar"), "background-color"),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("keeps Java's colours in the light scheme", async ({ page }) => {
|
||||||
|
await openEditor(page, server.base, "nat_with_mapped_port.fls");
|
||||||
|
const light = await paint(page);
|
||||||
|
expect(light.scheme).toBe("light");
|
||||||
|
expect(light.surface).toEqual([255, 255, 255, 1]);
|
||||||
|
expect(light.label).toEqual([0, 0, 0, 1]);
|
||||||
|
expect(light.cable).toEqual([64, 64, 64, 1]);
|
||||||
|
// Java's grey in the design: opaque, a fifth of the way to white; its words black.
|
||||||
|
expect(light.rectangle).toEqual([215, 215, 215, 1]);
|
||||||
|
expect(light.words).toEqual([0, 0, 0, 1]);
|
||||||
|
expect(light.modePicture).toEqual(["light"]);
|
||||||
|
expect(light.iconFilter).toBe("none");
|
||||||
|
const window = await installation(page);
|
||||||
|
expect(window).toEqual({
|
||||||
|
panel: [238, 238, 238, 1],
|
||||||
|
field: [255, 255, 255, 1],
|
||||||
|
words: [0, 0, 0, 1],
|
||||||
|
taskbar: [180, 180, 180, 1],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("goes dark everywhere when the dashboard asks, every text readable on its ground", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await openEditor(
|
||||||
|
page,
|
||||||
|
server.base,
|
||||||
|
"nat_with_mapped_port.fls",
|
||||||
|
"&theme=dark",
|
||||||
|
);
|
||||||
|
const dark = await paint(page);
|
||||||
|
expect(dark.scheme).toBe("dark");
|
||||||
|
expect(luminance(dark.surface)).toBeLessThan(0.05);
|
||||||
|
expect(contrast(dark.label, dark.surface)).toBeGreaterThanOrEqual(7);
|
||||||
|
expect(contrast(dark.cable, dark.surface)).toBeGreaterThanOrEqual(3);
|
||||||
|
// A rectangle a tint of the surface, its words light on it.
|
||||||
|
expect(dark.rectangle[3]).toBeLessThanOrEqual(0.3);
|
||||||
|
const tinted = dark.surface
|
||||||
|
.slice(0, 3)
|
||||||
|
.map(
|
||||||
|
(ground, index) =>
|
||||||
|
ground + (dark.rectangle[index]! - ground) * dark.rectangle[3],
|
||||||
|
);
|
||||||
|
expect(contrast(dark.words, tinted)).toBeGreaterThanOrEqual(4.5);
|
||||||
|
expect(dark.modePicture).toEqual(["dark"]);
|
||||||
|
// The mode that is on shown by its button, in the accent's soft colour; each icon with a faint light outline.
|
||||||
|
expect(dark.pressedMode).toEqual(
|
||||||
|
await rgba(
|
||||||
|
page,
|
||||||
|
await page.evaluate(() =>
|
||||||
|
getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
"--accent-soft",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(dark.iconFilter).toContain("drop-shadow");
|
||||||
|
const window = await installation(page);
|
||||||
|
expect(luminance(window.panel)).toBeLessThan(0.05);
|
||||||
|
expect(luminance(window.field)).toBeLessThan(0.05);
|
||||||
|
expect(luminance(window.taskbar)).toBeLessThan(0.05);
|
||||||
|
expect(contrast(window.words, window.field)).toBeGreaterThanOrEqual(7);
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 950 B |
Binary file not shown.
|
After Width: | Height: | Size: 973 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -36,7 +36,7 @@ import { bounds, cablePath, deviceBox, ICONS, iconOf, JAVA_LABEL_FONT, type Devi
|
|||||||
import { fitted, middle, shifted, spread, toScreen, toSurface, zoomedAt, zoomIn, zoomOut, type Point, type Size, type Viewport } from './viewport.js';
|
import { fitted, middle, shifted, spread, toScreen, toSurface, zoomedAt, zoomIn, zoomOut, type Point, type Size, type Viewport } from './viewport.js';
|
||||||
import { ZoomBar } from './ZoomBar.js';
|
import { ZoomBar } from './ZoomBar.js';
|
||||||
import { gripPlace, handlePlace, HANDLES, menuPlace, NO_EDGE, shownHandles, TARGET, type Edges } from './handles.js';
|
import { gripPlace, handlePlace, HANDLES, menuPlace, NO_EDGE, shownHandles, TARGET, type Edges } from './handles.js';
|
||||||
import { EDGE_BAND, edgesAt, FOCUS_BAND, FOCUS_BORDER, fontFamily, holdFrame, rectangleBorder, rectangleFill, TEXT_BORDER, textColor, type Frame } from './documentationPaint.js';
|
import { darkRectangleBorder, darkRectangleFill, darkTextColor, EDGE_BAND, edgesAt, FOCUS_BAND, FOCUS_BORDER, fontFamily, holdFrame, rectangleBorder, rectangleFill, textColor, type Frame } from './documentationPaint.js';
|
||||||
|
|
||||||
/** How far a pointer moves before a press becomes a drag. */
|
/** How far a pointer moves before a press becomes a drag. */
|
||||||
export const DRAG_THRESHOLD = 4;
|
export const DRAG_THRESHOLD = 4;
|
||||||
@@ -152,6 +152,8 @@ function Documentation({ item, documenting, focused }: { readonly item: Document
|
|||||||
fill={rectangleFill(item.color, documenting)}
|
fill={rectangleFill(item.color, documenting)}
|
||||||
stroke={focused ? 'none' : rectangleBorder(item.color)}
|
stroke={focused ? 'none' : rectangleBorder(item.color)}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
|
// The dark scheme's paint (sheet.css takes it): Java's is for a white surface.
|
||||||
|
style={{ '--doc-fill-dark': darkRectangleFill(item.color, documenting), '--doc-stroke-dark': focused ? 'none' : darkRectangleBorder(item.color) } as CSSProperties}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
@@ -166,14 +168,15 @@ function Documentation({ item, documenting, focused }: { readonly item: Document
|
|||||||
style={{
|
style={{
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
padding: 1,
|
padding: 1,
|
||||||
color: textColor(item.color),
|
// The words' colour, in the dark scheme drawn towards light (sheet.css).
|
||||||
|
...({ '--doc-ink': textColor(item.color), '--doc-ink-dark': darkTextColor(item.color) } as CSSProperties),
|
||||||
font: `${item.italic ? 'italic ' : ''}${item.bold ? 'bold ' : ''}${item.size}px ${fontFamily(item.font)}`,
|
font: `${item.italic ? 'italic ' : ''}${item.bold ? 'bold ' : ''}${item.size}px ${fontFamily(item.font)}`,
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
border: documenting && !focused ? `1px solid ${TEXT_BORDER}` : undefined,
|
border: documenting && !focused ? '1px solid var(--doc-text-border)' : undefined,
|
||||||
visibility: focused ? 'hidden' : undefined,
|
visibility: focused ? 'hidden' : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -40,6 +40,24 @@ export function textColor(color: Color): string {
|
|||||||
return `rgba(${byte(color.red)}, ${byte(color.green)}, ${byte(color.blue)}, ${color.alpha})`;
|
return `rgba(${byte(color.red)}, ${byte(color.green)}, ${byte(color.blue)}, ${color.alpha})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The editor's dark scheme (not Java's — it has none; the exported image and
|
||||||
|
* the report keep Java's paint): a rectangle's colour as a tint of the dark
|
||||||
|
* surface, stronger while documenting, its border the colour itself, faded.
|
||||||
|
*/
|
||||||
|
export function darkRectangleFill(color: Color, documenting: boolean): string {
|
||||||
|
return `rgba(${byte(color.red)}, ${byte(color.green)}, ${byte(color.blue)}, ${documenting ? 0.28 : 0.2})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function darkRectangleBorder(color: Color): string {
|
||||||
|
return `rgba(${byte(color.red)}, ${byte(color.green)}, ${byte(color.blue)}, 0.7)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A text's colour on the dark surface: drawn towards the light text colour, so that Java's black reads as light and a red as a light red. */
|
||||||
|
export function darkTextColor(color: Color): string {
|
||||||
|
return `color-mix(in oklab, ${textColor(color)} 15%, var(--canvas-label))`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Java's logical font names as the browser has them; any other a font of that name, else sans-serif. */
|
/** Java's logical font names as the browser has them; any other a font of that name, else sans-serif. */
|
||||||
export function fontFamily(name: string): string {
|
export function fontFamily(name: string): string {
|
||||||
switch (name.toLowerCase()) {
|
switch (name.toLowerCase()) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
import { replaceText, type DocumentationView } from '@mdg-filius/doc';
|
import { replaceText, type DocumentationView } from '@mdg-filius/doc';
|
||||||
import type { ScreenFrame } from '../canvas/Canvas.js';
|
import type { ScreenFrame } from '../canvas/Canvas.js';
|
||||||
import { fontFamily, textColor } from '../canvas/documentationPaint.js';
|
import { darkTextColor, fontFamily, textColor } from '../canvas/documentationPaint.js';
|
||||||
import { followText } from '../desktop/textBinding.js';
|
import { followText } from '../desktop/textBinding.js';
|
||||||
import { useTranslate } from '../i18n/i18n.js';
|
import { useTranslate } from '../i18n/i18n.js';
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ export function DocumentationText({ item, frame, text, origin, onDone, onRemove
|
|||||||
ref={area}
|
ref={area}
|
||||||
aria-label={t('docu.text')}
|
aria-label={t('docu.text')}
|
||||||
data-doc-text={item.id}
|
data-doc-text={item.id}
|
||||||
|
className="doc-text-field"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onInput={(event) => {
|
onInput={(event) => {
|
||||||
const next = event.currentTarget.value;
|
const next = event.currentTarget.value;
|
||||||
@@ -67,7 +68,8 @@ export function DocumentationText({ item, frame, text, origin, onDone, onRemove
|
|||||||
resize: 'none',
|
resize: 'none',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
color: textColor(item.color),
|
// The words' colour as the drawn text has it, in the dark scheme too (sheet.css).
|
||||||
|
...({ '--doc-ink': textColor(item.color), '--doc-ink-dark': darkTextColor(item.color) } as React.CSSProperties),
|
||||||
font: `${item.italic ? 'italic ' : ''}${item.bold ? 'bold ' : ''}${item.size}px ${fontFamily(item.font)}`,
|
font: `${item.italic ? 'italic ' : ''}${item.bold ? 'bold ' : ''}${item.size}px ${fontFamily(item.font)}`,
|
||||||
lineHeight: 'normal',
|
lineHeight: 'normal',
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
|
|||||||
@@ -167,9 +167,9 @@
|
|||||||
.desktop-network {
|
.desktop-network {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: 2px inset #c0c0c0;
|
border: 2px inset var(--java-grid);
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +187,8 @@
|
|||||||
|
|
||||||
.desktop-network dd {
|
.desktop-network dd {
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
border: 1px solid #b0b0b0;
|
border: 1px solid var(--java-line);
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
@@ -199,14 +199,14 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
flex: none;
|
flex: none;
|
||||||
background: rgb(180 180 180);
|
background: var(--java-taskbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-home,
|
.desktop-home,
|
||||||
.desktop-network-icon {
|
.desktop-network-icon {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program-titlebar {
|
.program-titlebar {
|
||||||
@@ -343,8 +343,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,8 +371,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.installation-list li {
|
.installation-list li {
|
||||||
@@ -385,11 +385,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.installation-list li[aria-selected='true'] {
|
.installation-list li[aria-selected='true'] {
|
||||||
background: rgb(184 207 229);
|
background: var(--java-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.installation-list li:focus-visible {
|
.installation-list li:focus-visible {
|
||||||
outline: 2px solid rgb(99 130 191);
|
outline: 2px solid var(--java-focus);
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,10 +404,10 @@
|
|||||||
.installation-apply {
|
.installation-apply {
|
||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f8f8f8;
|
background: var(--java-button);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -425,7 +425,7 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,9 +441,9 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,10 +452,10 @@
|
|||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f8f8f8;
|
background: var(--java-button);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@
|
|||||||
margin: 0 5px 5px;
|
margin: 0 5px 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser-page iframe {
|
.browser-page iframe {
|
||||||
@@ -496,8 +496,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 5px 10px 10px;
|
padding: 5px 10px 10px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,20 +533,20 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web-server-hosts th {
|
.web-server-hosts th {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #e4e4e4;
|
background: var(--java-head);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.web-server-hosts td {
|
.web-server-hosts td {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid #c0c0c0;
|
border: 1px solid var(--java-grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.web-server-hosts td input {
|
.web-server-hosts td input {
|
||||||
@@ -556,7 +556,7 @@
|
|||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,8 +566,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
font: 12px ui-monospace, Menlo, Consolas, monospace;
|
font: 12px ui-monospace, Menlo, Consolas, monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
@@ -588,8 +588,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,10 +612,10 @@
|
|||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f8f8f8;
|
background: var(--java-button);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -642,23 +642,23 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-client [aria-invalid='true'],
|
.mail-client [aria-invalid='true'],
|
||||||
.mail-server [aria-invalid='true'] {
|
.mail-server [aria-invalid='true'] {
|
||||||
border-color: #ff0000;
|
border-color: var(--java-invalid);
|
||||||
color: #ff0000;
|
color: var(--java-invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-tabs {
|
.mail-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
flex: none;
|
flex: none;
|
||||||
border-bottom: 1px solid #a0a0a0;
|
border-bottom: 1px solid var(--java-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-tabs button {
|
.mail-tabs button {
|
||||||
@@ -667,17 +667,17 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 6px 6px 0 0;
|
||||||
background: #e0e0e0;
|
background: var(--java-tab);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-tabs button[aria-selected='true'] {
|
.mail-tabs button[aria-selected='true'] {
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-list,
|
.mail-list,
|
||||||
@@ -685,8 +685,8 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-accounts {
|
.mail-accounts {
|
||||||
@@ -711,8 +711,8 @@
|
|||||||
.mail-list th,
|
.mail-list th,
|
||||||
.mail-accounts th {
|
.mail-accounts th {
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-bottom: 1px solid #a0a0a0;
|
border-bottom: 1px solid var(--java-border);
|
||||||
background: #e4e4e4;
|
background: var(--java-head);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -729,7 +729,7 @@
|
|||||||
|
|
||||||
.mail-list tr[aria-selected='true'] td,
|
.mail-list tr[aria-selected='true'] td,
|
||||||
.mail-accounts tr[aria-selected='true'] td {
|
.mail-accounts tr[aria-selected='true'] td {
|
||||||
background: rgb(184 207 229);
|
background: var(--java-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-preview {
|
.mail-preview {
|
||||||
@@ -737,8 +737,8 @@
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-preview pre,
|
.mail-preview pre,
|
||||||
@@ -763,8 +763,8 @@
|
|||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-form {
|
.mail-form {
|
||||||
@@ -805,7 +805,7 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0 8px 8px;
|
padding: 0 8px 8px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-dialog h4 {
|
.mail-dialog h4 {
|
||||||
@@ -821,7 +821,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
font: 16px ui-monospace, Menlo, Consolas, monospace;
|
font: 16px ui-monospace, Menlo, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,9 +833,9 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: min(90%, 380px);
|
width: min(90%, 380px);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid #808080;
|
border: 1px solid var(--java-border-strong);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
box-shadow: 0 4px 16px rgb(0 0 0 / 30%);
|
box-shadow: 0 4px 16px rgb(0 0 0 / 30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,7 +858,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-editor-bar {
|
.text-editor-bar {
|
||||||
@@ -874,17 +874,17 @@
|
|||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f8f8f8;
|
background: var(--java-button);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-editor-bar button:disabled,
|
.text-editor-bar button:disabled,
|
||||||
.file-dialog-folder button:disabled {
|
.file-dialog-folder button:disabled {
|
||||||
color: #a0a0a0;
|
color: var(--java-disabled);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,9 +893,9 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px 'Courier New', Courier, monospace;
|
font: 16px 'Courier New', Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,8 +919,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,14 +933,14 @@
|
|||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-dialog-entries button[aria-pressed='true'] {
|
.file-dialog-entries button[aria-pressed='true'] {
|
||||||
background: #b8cfe5;
|
background: var(--java-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The file explorer (GUIApplicationFileExplorerWindow): the menu's actions
|
/* The file explorer (GUIApplicationFileExplorerWindow): the menu's actions
|
||||||
@@ -954,7 +954,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-explorer-bar {
|
.file-explorer-bar {
|
||||||
@@ -968,16 +968,16 @@
|
|||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #f8f8f8;
|
background: var(--java-button);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-explorer-bar button:disabled {
|
.file-explorer-bar button:disabled {
|
||||||
color: #a0a0a0;
|
color: var(--java-disabled);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,8 +995,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,7 +1010,7 @@
|
|||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 13px Dialog, sans-serif;
|
font: 13px Dialog, sans-serif;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1019,7 +1019,7 @@
|
|||||||
.file-explorer-tree button[aria-current='true'],
|
.file-explorer-tree button[aria-current='true'],
|
||||||
.file-explorer-tree button[aria-pressed='true'],
|
.file-explorer-tree button[aria-pressed='true'],
|
||||||
.file-explorer-entries button[aria-pressed='true'] {
|
.file-explorer-entries button[aria-pressed='true'] {
|
||||||
background: #b8cfe5;
|
background: var(--java-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 639px) {
|
@media (max-width: 639px) {
|
||||||
@@ -1048,7 +1048,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-viewer-picture {
|
.image-viewer-picture {
|
||||||
@@ -1071,9 +1071,9 @@
|
|||||||
.simple-client textarea {
|
.simple-client textarea {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,13 +1083,13 @@
|
|||||||
|
|
||||||
.echo-server [aria-invalid='true'],
|
.echo-server [aria-invalid='true'],
|
||||||
.simple-client [aria-invalid='true'] {
|
.simple-client [aria-invalid='true'] {
|
||||||
border-color: #ff0000;
|
border-color: var(--java-invalid);
|
||||||
color: #ff0000;
|
color: var(--java-invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.echo-server input:read-only,
|
.echo-server input:read-only,
|
||||||
.simple-client input:read-only {
|
.simple-client input:read-only {
|
||||||
background: #eeeeee;
|
background: var(--java-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple-client-field,
|
.simple-client-field,
|
||||||
@@ -1134,15 +1134,15 @@
|
|||||||
.dns-server input:not([type='checkbox']) {
|
.dns-server input:not([type='checkbox']) {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dns-server [aria-invalid='true'] {
|
.dns-server [aria-invalid='true'] {
|
||||||
border-color: #ff0000;
|
border-color: var(--java-invalid);
|
||||||
color: #ff0000;
|
color: var(--java-invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dns-server-notice {
|
.dns-server-notice {
|
||||||
@@ -1152,19 +1152,19 @@
|
|||||||
.dns-server-table {
|
.dns-server-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dns-server-table th {
|
.dns-server-table th {
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-bottom: 1px solid #a0a0a0;
|
border-bottom: 1px solid var(--java-border);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dns-server-table td {
|
.dns-server-table td {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-bottom: 1px solid #dddddd;
|
border-bottom: 1px solid var(--java-rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dns-server-table td input {
|
.dns-server-table td input {
|
||||||
@@ -1175,7 +1175,7 @@
|
|||||||
|
|
||||||
.dns-server-table tr[aria-selected='true'] td,
|
.dns-server-table tr[aria-selected='true'] td,
|
||||||
.dns-server-table tr[aria-selected='true'] input {
|
.dns-server-table tr[aria-selected='true'] input {
|
||||||
background: #b8cfe5;
|
background: var(--java-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A computer's firewall (GUIApplicationFirewallWindow): two tabs, the boxes,
|
/* A computer's firewall (GUIApplicationFirewallWindow): two tabs, the boxes,
|
||||||
@@ -1215,9 +1215,9 @@
|
|||||||
.personal-firewall select {
|
.personal-firewall select {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1226,8 +1226,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.personal-firewall [aria-invalid='true'] {
|
.personal-firewall [aria-invalid='true'] {
|
||||||
border-color: #ff0000;
|
border-color: var(--java-invalid);
|
||||||
color: #ff0000;
|
color: var(--java-invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.personal-firewall-table td {
|
.personal-firewall-table td {
|
||||||
@@ -1269,15 +1269,15 @@
|
|||||||
.gnutella input {
|
.gnutella input {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid #a0a0a0;
|
border: 1px solid var(--java-border);
|
||||||
background: #ffffff;
|
background: var(--java-field);
|
||||||
color: #000000;
|
color: var(--java-text);
|
||||||
font: 16px Dialog, sans-serif;
|
font: 16px Dialog, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gnutella [aria-invalid='true'] {
|
.gnutella [aria-invalid='true'] {
|
||||||
border-color: #ff0000;
|
border-color: var(--java-invalid);
|
||||||
color: #ff1414;
|
color: var(--java-invalid-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gnutella-table td {
|
.gnutella-table td {
|
||||||
|
|||||||
@@ -50,6 +50,30 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Documentation on the surface: a text's words in their colour; in the dark
|
||||||
|
* scheme a rectangle as a tint and the words drawn towards light
|
||||||
|
* (documentationPaint.ts) — Java paints for a white surface. Java's icons get
|
||||||
|
* a faint light outline there, or its black switches and routers would sink
|
||||||
|
* into the dark. */
|
||||||
|
.doc-text,
|
||||||
|
.doc-text-field {
|
||||||
|
color: var(--doc-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .doc-text,
|
||||||
|
:root[data-theme='dark'] .doc-text-field {
|
||||||
|
color: var(--doc-ink-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .doc-rectangle {
|
||||||
|
fill: var(--doc-fill-dark);
|
||||||
|
stroke: var(--doc-stroke-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .filius-canvas .devices image {
|
||||||
|
filter: drop-shadow(0 0 1px rgb(255 255 255 / 55%));
|
||||||
|
}
|
||||||
|
|
||||||
/* A focused documentation item's handles (Canvas): a finger's target of
|
/* A focused documentation item's handles (Canvas): a finger's target of
|
||||||
* 44 px around a dot in Java's focus blue, over the field a text is typed
|
* 44 px around a dot in Java's focus blue, over the field a text is typed
|
||||||
* into; a text's grip a round button above its corner. */
|
* into; a text's grip a round button above its corner. */
|
||||||
@@ -117,6 +141,26 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The mode buttons' pictures (ModeBar): Java's in the light scheme; in the
|
||||||
|
* dark one freed from their grey ground, the button marking the mode that is on. */
|
||||||
|
.mode-picture {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-picture[data-scheme='dark'],
|
||||||
|
:root[data-theme='dark'] .mode-picture[data-scheme='light'] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .mode-picture[data-scheme='dark'] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] .mode-button[aria-pressed='true'] {
|
||||||
|
border-color: var(--accent) !important;
|
||||||
|
background: var(--accent-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* A word beside the mode buttons: that this person only reads. */
|
/* A word beside the mode buttons: that this person only reads. */
|
||||||
.editor-note {
|
.editor-note {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -11,8 +11,28 @@ import design from '../assets/modes/entwurfsmodus.png';
|
|||||||
import designOn from '../assets/modes/entwurfsmodus_aktiv.png';
|
import designOn from '../assets/modes/entwurfsmodus_aktiv.png';
|
||||||
import documentation from '../assets/modes/dokumodus.png';
|
import documentation from '../assets/modes/dokumodus.png';
|
||||||
import documentationOn from '../assets/modes/dokumodus_aktiv.png';
|
import documentationOn from '../assets/modes/dokumodus_aktiv.png';
|
||||||
|
import simulationDark from '../assets/modes/aktionsmodus_dunkel.png';
|
||||||
|
import simulationOnDark from '../assets/modes/aktionsmodus_aktiv_dunkel.png';
|
||||||
|
import designDark from '../assets/modes/entwurfsmodus_dunkel.png';
|
||||||
|
import designOnDark from '../assets/modes/entwurfsmodus_aktiv_dunkel.png';
|
||||||
|
import documentationDark from '../assets/modes/dokumodus_dunkel.png';
|
||||||
|
import documentationOnDark from '../assets/modes/dokumodus_aktiv_dunkel.png';
|
||||||
import { useTranslate } from '../i18n/i18n.js';
|
import { useTranslate } from '../i18n/i18n.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mode's picture: Java's (a grey button's gradient, light blue when on),
|
||||||
|
* and for the dark scheme the same freed from its ground, the button showing
|
||||||
|
* whether it is on (sheet.css: .mode-picture).
|
||||||
|
*/
|
||||||
|
function ModePicture({ light, dark }: { readonly light: string; readonly dark: string }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className="mode-picture" data-scheme="light" src={light} alt="" width={50} height={50} />
|
||||||
|
<img className="mode-picture" data-scheme="dark" src={dark} alt="" width={50} height={50} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const modeButton: CSSProperties = {
|
const modeButton: CSSProperties = {
|
||||||
width: 52,
|
width: 52,
|
||||||
height: 52,
|
height: 52,
|
||||||
@@ -46,6 +66,7 @@ export function ModeBar({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
name="documentationMode"
|
name="documentationMode"
|
||||||
|
className="mode-button"
|
||||||
style={modeButton}
|
style={modeButton}
|
||||||
aria-pressed={mode === 'documentation'}
|
aria-pressed={mode === 'documentation'}
|
||||||
aria-label={t('mode.documentation')}
|
aria-label={t('mode.documentation')}
|
||||||
@@ -53,13 +74,13 @@ export function ModeBar({
|
|||||||
disabled={!canRun && mode === 'simulation'}
|
disabled={!canRun && mode === 'simulation'}
|
||||||
onClick={() => onMode('documentation')}
|
onClick={() => onMode('documentation')}
|
||||||
>
|
>
|
||||||
<img src={mode === 'documentation' ? documentationOn : documentation} alt="" width={50} height={50} />
|
<ModePicture light={mode === 'documentation' ? documentationOn : documentation} dark={mode === 'documentation' ? documentationOnDark : documentationDark} />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" name="designMode" style={modeButton} aria-pressed={mode === 'design'} aria-label={t('mode.design')} title={t('mode.design')} disabled={!canRun && mode === 'simulation'} onClick={() => onMode('design')}>
|
<button type="button" name="designMode" className="mode-button" style={modeButton} aria-pressed={mode === 'design'} aria-label={t('mode.design')} title={t('mode.design')} disabled={!canRun && mode === 'simulation'} onClick={() => onMode('design')}>
|
||||||
<img src={mode === 'design' ? designOn : design} alt="" width={50} height={50} />
|
<ModePicture light={mode === 'design' ? designOn : design} dark={mode === 'design' ? designOnDark : designDark} />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" name="simulationMode" style={modeButton} aria-pressed={mode === 'simulation'} aria-label={t('mode.simulation')} title={t('mode.simulation')} disabled={!canRun} onClick={() => onMode('simulation')}>
|
<button type="button" name="simulationMode" className="mode-button" style={modeButton} aria-pressed={mode === 'simulation'} aria-label={t('mode.simulation')} title={t('mode.simulation')} disabled={!canRun} onClick={() => onMode('simulation')}>
|
||||||
<img src={mode === 'simulation' ? simulationOn : simulation} alt="" width={50} height={50} />
|
<ModePicture light={mode === 'simulation' ? simulationOn : simulation} dark={mode === 'simulation' ? simulationOnDark : simulationDark} />
|
||||||
</button>
|
</button>
|
||||||
{mode === 'simulation' ? (
|
{mode === 'simulation' ? (
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '0 8px', minHeight: 52, borderRadius: 8, background: 'var(--surface-raised)', border: '1px solid var(--border-default)' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '0 8px', minHeight: 52, borderRadius: 8, background: 'var(--surface-raised)', border: '1px solid var(--border-default)' }}>
|
||||||
|
|||||||
@@ -45,6 +45,28 @@
|
|||||||
--capture-on-layer: #000000;
|
--capture-on-layer: #000000;
|
||||||
--capture-chosen: #000000;
|
--capture-chosen: #000000;
|
||||||
--capture-on-chosen: #ffffff;
|
--capture-on-chosen: #ffffff;
|
||||||
|
/* A documentation text's border in the documentation mode (textPanelBorderNormalColor). */
|
||||||
|
--doc-text-border: rgb(179 179 179);
|
||||||
|
/* Java's Swing look in the desktop's program windows (Metal): a panel, a field, its border and text, a button, a
|
||||||
|
* table's head, a chosen row, the focus, a refused entry; the taskbar (GUIDesktopPanel). */
|
||||||
|
--java-panel: #eeeeee;
|
||||||
|
--java-field: #ffffff;
|
||||||
|
--java-text: #000000;
|
||||||
|
--java-border: #a0a0a0;
|
||||||
|
--java-border-strong: #808080;
|
||||||
|
--java-grid: #c0c0c0;
|
||||||
|
--java-line: #b0b0b0;
|
||||||
|
--java-rule: #dddddd;
|
||||||
|
--java-disabled: #a0a0a0;
|
||||||
|
--java-button: #f8f8f8;
|
||||||
|
--java-head: #e4e4e4;
|
||||||
|
--java-tab: #e0e0e0;
|
||||||
|
--java-selected: #b8cfe5;
|
||||||
|
--java-focus: rgb(99 130 191);
|
||||||
|
--java-invalid: #ff0000;
|
||||||
|
--java-invalid-text: #ff1414;
|
||||||
|
--java-taskbar: rgb(180 180 180);
|
||||||
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme='dark'] {
|
:root[data-theme='dark'] {
|
||||||
@@ -70,12 +92,13 @@
|
|||||||
--danger: #f2695a;
|
--danger: #f2695a;
|
||||||
--danger-soft: #3a1a17;
|
--danger-soft: #3a1a17;
|
||||||
--danger-on-soft: #ffa79b;
|
--danger-on-soft: #ffa79b;
|
||||||
/* Java's icons are drawn for a light ground: the surface stays light, a shade darker. */
|
/* The surface dark too, a step above the sheets, so that Java's black switches and routers keep their edge
|
||||||
--canvas-bg: #e9ecf1;
|
* (each icon gets a faint light outline, sheet.css); labels and cables light. */
|
||||||
--canvas-cable: rgb(64 64 64);
|
--canvas-bg: #262b33;
|
||||||
--canvas-cable-wireless: rgb(150 150 150);
|
--canvas-cable: rgb(150 158 170);
|
||||||
--canvas-cable-active: rgb(0 200 50);
|
--canvas-cable-wireless: rgb(105 113 125);
|
||||||
--canvas-label: #000000;
|
--canvas-cable-active: rgb(40 220 90);
|
||||||
|
--canvas-label: #e6e9ef;
|
||||||
--connection-plugged: rgb(0 190 0);
|
--connection-plugged: rgb(0 190 0);
|
||||||
--connection-free: rgb(210 40 40);
|
--connection-free: rgb(210 40 40);
|
||||||
--connection-marked: rgb(235 215 0);
|
--connection-marked: rgb(235 215 0);
|
||||||
@@ -86,6 +109,25 @@
|
|||||||
--capture-on-layer: #e6e9ef;
|
--capture-on-layer: #e6e9ef;
|
||||||
--capture-chosen: #e6e9ef;
|
--capture-chosen: #e6e9ef;
|
||||||
--capture-on-chosen: #14171c;
|
--capture-on-chosen: #14171c;
|
||||||
|
--doc-text-border: #4a5361;
|
||||||
|
--java-panel: #20252c;
|
||||||
|
--java-field: #15181d;
|
||||||
|
--java-text: #e6e9ef;
|
||||||
|
--java-border: #4a5361;
|
||||||
|
--java-border-strong: #5c6573;
|
||||||
|
--java-grid: #3a414c;
|
||||||
|
--java-line: #3a414c;
|
||||||
|
--java-rule: #2e343d;
|
||||||
|
--java-disabled: #6b7380;
|
||||||
|
--java-button: #2b3038;
|
||||||
|
--java-head: #2a2f37;
|
||||||
|
--java-tab: #262b33;
|
||||||
|
--java-selected: #2c4a66;
|
||||||
|
--java-focus: #6f9ad6;
|
||||||
|
--java-invalid: #f2695a;
|
||||||
|
--java-invalid-text: #ff8a7d;
|
||||||
|
--java-taskbar: #2a2f37;
|
||||||
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// How the editor paints a documentation item, against the pixels Java's
|
// How the editor paints a documentation item, against the pixels Java's
|
||||||
// JDocuElement paints (packages/doc/test/documentation.java.json, DocuRun):
|
// JDocuElement paints (packages/doc/test/documentation.java.json, DocuRun):
|
||||||
// a rectangle in the documentation mode and in the others, its border, the
|
// a rectangle in the documentation mode and in the others, its border, the
|
||||||
// text's border; the focused text's frame; where an edge is taken hold of.
|
// text's border; the focused text's frame; where an edge is taken hold of —
|
||||||
|
// and the editor's own dark paint, which Java has not.
|
||||||
|
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { edgesAt, fontFamily, holdFrame, rectangleBorder, rectangleFill, TEXT_BORDER, FOCUS_BORDER, FOCUS_BAND, textColor } from '../src/canvas/documentationPaint.js';
|
import { darkRectangleBorder, darkRectangleFill, darkTextColor, edgesAt, fontFamily, holdFrame, rectangleBorder, rectangleFill, TEXT_BORDER, FOCUS_BORDER, FOCUS_BAND, textColor } from '../src/canvas/documentationPaint.js';
|
||||||
|
|
||||||
type Rgba = [number, number, number, number];
|
type Rgba = [number, number, number, number];
|
||||||
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../doc/test/documentation.java.json'), 'utf8')) as {
|
const java = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../../doc/test/documentation.java.json'), 'utf8')) as {
|
||||||
@@ -40,6 +41,14 @@ describe("Java's paint of the documentation", () => {
|
|||||||
expect(textColor({ red: 0, green: 0, blue: 1, alpha: 0.5 })).toBe('rgba(0, 0, 255, 0.5)');
|
expect(textColor({ red: 0, green: 0, blue: 1, alpha: 0.5 })).toBe('rgba(0, 0, 255, 0.5)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('paints a rectangle in the dark scheme as a tint of its colour, stronger while documenting, and draws a text towards light', () => {
|
||||||
|
const grey = { red: 0.8, green: 0.8, blue: 0.8, alpha: 1 };
|
||||||
|
expect(darkRectangleFill(grey, true)).toBe('rgba(204, 204, 204, 0.28)');
|
||||||
|
expect(darkRectangleFill(grey, false)).toBe('rgba(204, 204, 204, 0.2)');
|
||||||
|
expect(darkRectangleBorder({ red: 1, green: 0, blue: 0, alpha: 1 })).toBe('rgba(255, 0, 0, 0.7)');
|
||||||
|
expect(darkTextColor({ red: 0, green: 0, blue: 0, alpha: 1 })).toBe('color-mix(in oklab, rgba(0, 0, 0, 1) 15%, var(--canvas-label))');
|
||||||
|
});
|
||||||
|
|
||||||
it("grows a focused text's frame as Java does, and finds the edges a drag takes", () => {
|
it("grows a focused text's frame as Java does, and finds the edges a drag takes", () => {
|
||||||
const [x, y, width, height] = java.dropped.textUnfocused.bounds as [number, number, number, number];
|
const [x, y, width, height] = java.dropped.textUnfocused.bounds as [number, number, number, number];
|
||||||
const [fx, fy, fw, fh] = java.dropped.text.bounds as [number, number, number, number];
|
const [fx, fy, fw, fh] = java.dropped.text.bounds as [number, number, number, number];
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user