Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified apps/desktop/e2e/__screenshots__/editor.spec.ts/editor-glyph-A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/desktop/e2e/__screenshots__/home.spec.ts/home-glyph-grid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 12 additions & 14 deletions apps/desktop/src/renderer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
--editor-handle-overlay-anchor: rgba(255, 255, 255, 0.75);
--editor-snap-color: #ff3b30;
--editor-segment-hover-color: #1886d7;
--editor-component-hover-outline: #1886d7;
--editor-segment-selected-color: #1886d7;
--editor-preview-color: #1886d7;
--editor-variation-outline-color: rgba(12, 146, 244, 0.45);
Expand Down Expand Up @@ -127,6 +128,11 @@
0 1px 3px -1px var(--color-shadow-key);
}

/* Unstyled text follows the theme instead of the browser's color-scheme default. */
body {
color: var(--color-primary);
}

/* Hide scrollbar while preserving scroll behavior */
@utility scrollbar-hidden {
scrollbar-width: none;
Expand All @@ -136,22 +142,14 @@
}
}

/* Keep scrollbars legible without introducing a host-defined track color. */
@utility scrollbar-themed {
scrollbar-color: var(--color-control-muted) var(--color-surface);

&::-webkit-scrollbar {
width: 10px;
}

&::-webkit-scrollbar-track {
background: var(--color-surface);
/* Overlay-style scrollbars everywhere: thumbs only show while their element is scrolling (see trackScrollActivity). */
@layer base {
* {
scrollbar-color: transparent transparent;
}

&::-webkit-scrollbar-thumb {
border: 2px solid var(--color-surface);
border-radius: 9999px;
background: var(--color-control-muted);
[data-scrolling] {
scrollbar-color: var(--color-control-muted) transparent;
}
}

Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import "./index.css";
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/app/App";
import { trackScrollActivity } from "./src/lib/themes/scrollActivity";

trackScrollActivity();

// Mount the React app
const container = document.getElementById("root");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const LeftSidebar = () => (
<ObjectsPanel />
</TabsPanel>
<TabsPanel value="variations" keepMounted className="min-h-0 flex-1 overflow-hidden">
<div className="scrollbar-themed h-full overflow-y-auto px-1 pb-2">
<div className="h-full overflow-y-auto px-1 pb-2">
<VariationPanel />
</div>
</TabsPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export const ObjectsPanel = () => {
return (
<div
ref={containerRef}
className="scrollbar-themed h-full overflow-y-auto px-1 pb-2"
className="h-full overflow-y-auto px-1 pb-2"
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}
>
<nav aria-label="Glyph objects" className="flex flex-col gap-2 pt-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const LeftSidebar = () => {
<aside
ref={scrollRef}
aria-label="Font navigation"
className="scrollbar-themed h-full w-full min-w-0 overflow-y-auto border-r border-line-subtle bg-surface"
className="h-full w-full min-w-0 overflow-y-auto border-r border-line-subtle bg-surface"
style={{ overflowAnchor: "none" }}
>
<div ref={contentRef} className="min-h-full space-y-1.5 px-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const LanguagePicker = ({
<div
role="group"
aria-label="Scripts to track"
className="scrollbar-themed max-h-80 overflow-y-auto px-2 pb-2"
className="max-h-80 overflow-y-auto px-2 pb-2"
>
{visibleScripts.length === 0 ? (
<div className="px-1 py-4 text-center text-ui text-muted">No scripts found</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const MissingGlyphsPopover = ({
<div
role="group"
aria-label={`Missing glyphs for ${languageName}`}
className="scrollbar-themed max-h-80 overflow-y-auto p-1"
className="max-h-80 overflow-y-auto p-1"
onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}
>
<div className="relative" style={{ height: (glyphs?.length ?? 0) * ROW_HEIGHT }}>
Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/src/renderer/src/lib/themes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,28 @@ describe("color themes", () => {
expect(resolveThemeSelection("gruvbox-light", "dark").id).toBe("gruvbox-light");
});

it("keeps the toolbar distinct from the canvas and its hover in every override theme", () => {
for (const theme of colorThemes.filter((theme) => theme.id !== "shift-light")) {
const properties = new Map<string, string>();
const root = {
dataset: {},
style: {
colorScheme: "",
setProperty: (name: string, value: string) => properties.set(name, value),
removeProperty: () => "",
},
} as unknown as HTMLElement;

applyResolvedTheme(theme, root);
expect(properties.get("--color-icon-button-hover"), theme.id).not.toBe(
properties.get("--color-chrome"),
);
expect(properties.get("--color-chrome"), theme.id).not.toBe(
properties.get("--color-surface-muted"),
);
}
});

it("applies theme metadata and removes overrides for Shift Light", () => {
const properties = new Map<string, string>();
const root = {
Expand Down
22 changes: 18 additions & 4 deletions apps/desktop/src/renderer/src/lib/themes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,21 +281,23 @@ function colorThemeVariables(theme: ColorTheme): Record<string, string> {
const muted = dark ? withAlpha(palette.base05, 0.6) : palette.base03;
const handleOverlay = dark ? palette.base07 : palette.base00;
const surface = dark ? palette.base01 : palette.base00;
const chrome =
palette.base02 === surface ? withAlpha(palette.base05, dark ? 0.08 : 0.12) : palette.base02;
// Light palettes put a strong selection colour in base02, so light chrome tints the canvas (base01)
// toward the text colour instead; on Shift Light's palette this reproduces its #e2e2e2 chrome.
const darkChrome = palette.base02 === surface ? withAlpha(palette.base05, 0.08) : palette.base02;
const chrome = dark ? darkChrome : mix(palette.base01, palette.base05, 0.07);

return {
"--color-background": palette.base00,
"--color-surface": surface,
"--color-surface-muted": palette.base01,
"--color-chrome": chrome,
"--color-hover": dark ? withAlpha(palette.base05, 0.16) : palette.base02,
"--color-hover": withAlpha(palette.base05, dark ? 0.16 : 0.1),
"--color-surface-hover": dark ? withAlpha(palette.base05, 0.12) : palette.base01,
"--color-surface-inverse": palette.base07,
"--color-on-surface-inverse": palette.base00,
"--color-input": dark ? withAlpha(palette.base05, 0.12) : palette.base01,
"--color-icon-button": dark ? withAlpha(palette.base05, 0.12) : palette.base01,
"--color-icon-button-hover": dark ? withAlpha(palette.base05, 0.2) : palette.base02,
"--color-icon-button-hover": withAlpha(palette.base05, dark ? 0.2 : 0.14),
"--color-line": dark ? palette.base03 : palette.base06,
"--color-line-subtle": dark ? withAlpha(palette.base05, 0.2) : palette.base01,
"--color-shadow-ambient": dark ? "rgba(0, 0, 0, 0.55)" : "rgba(50, 50, 93, 0.25)",
Expand Down Expand Up @@ -341,6 +343,7 @@ function colorThemeVariables(theme: ColorTheme): Record<string, string> {
"--editor-handle-overlay-anchor": withAlpha(handleOverlay, 0.75),
"--editor-snap-color": palette.base08,
"--editor-segment-hover-color": palette.base0D,
"--editor-component-hover-outline": palette.base0D,
"--editor-segment-selected-color": palette.base0D,
"--editor-preview-color": palette.base0D,
"--editor-variation-outline-color": withAlpha(palette.base0D, 0.45),
Expand Down Expand Up @@ -400,6 +403,17 @@ function relativeLuminance(color: string): number {
return 0.2126 * channel(1) + 0.7152 * channel(3) + 0.0722 * channel(5);
}

function mix(color: string, tint: string, amount: number): string {
const channel = (offset: number) => {
const base = Number.parseInt(color.slice(offset, offset + 2), 16);
const target = Number.parseInt(tint.slice(offset, offset + 2), 16);
return Math.round(base + (target - base) * amount)
.toString(16)
.padStart(2, "0");
};
return `#${channel(1)}${channel(3)}${channel(5)}`;
}

function withAlpha(color: string, alpha: number): string {
const red = Number.parseInt(color.slice(1, 3), 16);
const green = Number.parseInt(color.slice(3, 5), 16);
Expand Down
35 changes: 35 additions & 0 deletions apps/desktop/src/renderer/src/lib/themes/scrollActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const SCROLLING_ATTRIBUTE = "data-scrolling";
const IDLE_DELAY_MS = 800;

/**
* Marks each scrolled element with `data-scrolling` until it has been idle for a moment,
* so the global scrollbar styles in `index.css` can show the thumb only while the user is scrolling.
*
* Listens once on the document in the capture phase, because `scroll` does not bubble.
* Returns a function that removes the listener and clears pending idle timers.
*/
export function trackScrollActivity(root: Document = document): () => void {
const idleTimers = new Map<Element, number>();

const onScroll = (event: Event) => {
const element = event.target;
if (!(element instanceof Element)) return;

element.setAttribute(SCROLLING_ATTRIBUTE, "");
window.clearTimeout(idleTimers.get(element));
idleTimers.set(
element,
window.setTimeout(() => {
element.removeAttribute(SCROLLING_ATTRIBUTE);
idleTimers.delete(element);
}, IDLE_DELAY_MS),
);
};

root.addEventListener("scroll", onScroll, { capture: true, passive: true });
return () => {
root.removeEventListener("scroll", onScroll, { capture: true });
for (const timer of idleTimers.values()) window.clearTimeout(timer);
idleTimers.clear();
};
}
2 changes: 2 additions & 0 deletions packages/editor/src/lib/editor/rendering/Theme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe("editor render theme", () => {
["--editor-guides-color", "#123456"],
["--editor-variation-outline-color", "#654321"],
["--editor-read-only-lock-color", "#abcdef"],
["--editor-component-hover-outline", "#83a598"],
]);
const style = {
getPropertyValue: (name: string) => colors.get(name) ?? "",
Expand All @@ -25,6 +26,7 @@ describe("editor render theme", () => {
expect(theme.guides.color).toBe("#123456");
expect(theme.variationOutline.color).toBe("#654321");
expect(theme.readOnlyLock.color).toBe("#abcdef");
expect(theme.component.hoverOutline.stroke).toBe("#83a598");
});

it("builds GPU marker colors from the active render theme", () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/editor/src/lib/editor/rendering/Theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ export function readEditorRenderTheme(
theme.glyph.editableFill = readColor("--editor-glyph-editable-fill", theme.glyph.editableFill);
theme.glyph.stroke = readColor("--editor-glyph-stroke", theme.glyph.stroke);
theme.component.fill = readColor("--editor-component-fill", theme.component.fill);
theme.component.hoverOutline.stroke = readColor(
"--editor-component-hover-outline",
theme.component.hoverOutline.stroke,
);
theme.controlLine.color = readColor("--editor-control-line-color", theme.controlLine.color);

const handleFill = readColor("--editor-handle-fill", theme.handle.corner.idle.fill);
Expand Down
Loading