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
36 changes: 31 additions & 5 deletions shells/desktop/src/system-ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Image, View } from "@pocketjs/framework/components";
import { onFrame } from "@pocketjs/framework/lifecycle";
import { virtualNow } from "@pocketjs/framework/clock";
import { connectSvc, type CursorKind, type HostEvent } from "./svc.ts";
import { applyLang, appTitle, lw } from "./words.ts";
import { applyLang, appTitle, lang, lw } from "./words.ts";
import {
cascadePos,
clampMove,
Expand Down Expand Up @@ -77,7 +77,7 @@ import {
type WinCtl,
} from "./state.ts";
import {
ABOUT_GEO,
aboutGeo,
AboutView,
aboutHit,
FolderView,
Expand Down Expand Up @@ -121,8 +121,10 @@ import {
} from "./notepad.ts";
import { newMines, reveal, toggleFlag } from "./mines.ts";
import {
DESKTOP_ABOUT,
DESKTOP_NAME,
POCKET_APPS,
aboutBody,
appWindowTitle,
pocketAppByPackage,
type PocketAppSpec,
Expand Down Expand Up @@ -860,7 +862,10 @@ export default function App() {
kind: "about",
title: aboutLabel(),
icon: "computer",
geo: centered(ABOUT_GEO.w, ABOUT_GEO.h),
geo: (() => {
const size = aboutGeo(aboutBody(DESKTOP_ABOUT, lang()).length);
return centered(size.w, size.h);
})(),
buttons: ["close"],
resizable: false,
data,
Expand Down Expand Up @@ -1128,8 +1133,27 @@ export default function App() {
return classicStartItems();
};

/** The panel rectangle both the render and hit testing read. */
const startGeo = () => startLayout(startItems(), vp().h, metrics());
/** A launcher row's natural width: its label in the face it is drawn in
* (XP sets the pinned rows in bold), the icon slot, padding and arrow. */
const startRowW = (item: PopupItem) =>
item.sep
? 0
: metrics().startRowInset +
measure(item.label, item.bottom ? theme().fontSlot("bold") : uiSlot()) +
(item.sub ? metrics().startArrowW : 0);

/** The panel rectangle both the render and hit testing read: as wide as
* its rows, within the screen. */
const startGeo = () => {
const items = startItems();
return startLayout(
items,
vp().h,
metrics(),
(i) => startRowW(items[i]),
vp().w - metrics().startX - 2,
);
};

function startItemAt(x: number, y: number): number {
return startRowAt(startGeo(), x, y);
Expand Down Expand Up @@ -2310,6 +2334,8 @@ export default function App() {
y={startGeo().y}
w={startGeo().w}
h={startGeo().h}
leftW={startGeo().leftW}
rightW={startGeo().rightW}
items={startItems()}
hover={startHover()}
user="Pocket"
Expand Down
38 changes: 33 additions & 5 deletions shells/desktop/src/system-ui/chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,25 @@ export function PopupPanel(props: {
);
}

/** The label a launcher row shows in a row `rowW` wide: whole when the
* panel grew to fit it (wm.ts startLayout), cut with "..." past the panel's
* maximum. */
function startLabel(item: Popup["items"][number], rowW: number, theme: DesktopTheme, bold = false): string {
const m = theme.metrics;
const room = rowW - m.startRowInset - (item.sub ? m.startArrowW : 0);
const slot = theme.fontSlot(bold ? "bold" : "ui");
const measure = (text: string) => {
const ops = getOps();
return ops.measureText ? ops.measureText(text, slot) : text.length * 7;
};
return fitLabel(item.label, room, measure);
}

/** One Start row: icon slot, label, submenu arrow. Shared by both panels. */
function StartRow(props: {
item: Popup["items"][number];
hover: boolean;
rowW: number;
theme: DesktopTheme;
}) {
return (
Expand All @@ -357,7 +372,7 @@ function StartRow(props: {
? "hover"
: "normal",
)}
t={props.item.label}
t={startLabel(props.item, props.rowW, props.theme, props.item.bottom)}
/>
</View>
{props.item.sub ? (
Expand All @@ -383,6 +398,9 @@ export function StartPanel(props: {
y: number;
w: number;
h: number;
/** The two columns' widths from wm.ts startLayout. */
leftW: number;
rightW: number;
items: Popup["items"];
hover: number;
user: string;
Expand Down Expand Up @@ -421,14 +439,15 @@ export function StartPanel(props: {
/>
</View>
<View class="flex-1 flex-row">
<View class={props.theme.startColumn("left")}>
<View class={props.theme.startColumn("left")} style={{ width: props.leftW }}>
{at((it) => !it.foot && it.col !== "right" && !it.bottom).map((e) =>
e.item.sep ? (
<StartSep theme={props.theme} />
) : (
<StartRow
item={e.item}
hover={props.hover === e.i}
rowW={props.leftW}
theme={props.theme}
/>
),
Expand All @@ -441,6 +460,7 @@ export function StartPanel(props: {
<StartRow
item={e.item}
hover={props.hover === e.i}
rowW={props.leftW}
theme={props.theme}
/>
),
Expand All @@ -455,6 +475,7 @@ export function StartPanel(props: {
<StartRow
item={e.item}
hover={props.hover === e.i}
rowW={props.rightW}
theme={props.theme}
/>
),
Expand Down Expand Up @@ -540,7 +561,7 @@ export function StartMenu(props: {
? "hover"
: "normal",
)}
t={item.label}
t={startLabel(item, props.w - props.theme.metrics.startPadX * 2 - props.theme.metrics.startRailW, props.theme)}
/>
</View>
{item.sub ? (
Expand Down Expand Up @@ -581,10 +602,17 @@ export function desktopLabelText(
measure: (text: string) => number,
crowded = true,
): string {
if (selected || !crowded || measure(label) <= DESK_LABEL_MAX_W) return label;
if (selected || !crowded) return label;
return fitLabel(label, DESK_LABEL_MAX_W, measure);
}

/** A label whole when it is no wider than `maxW`, else cut with "..." until
* it fits. */
export function fitLabel(label: string, maxW: number, measure: (text: string) => number): string {
if (measure(label) <= maxW) return label;
const chars = [...label];
let n = chars.length;
while (n > 1 && measure(`${chars.slice(0, n).join("").trimEnd()}...`) > DESK_LABEL_MAX_W) n--;
while (n > 1 && measure(`${chars.slice(0, n).join("").trimEnd()}...`) > maxW) n--;
return `${chars.slice(0, n).join("").trimEnd()}...`;
}

Expand Down
10 changes: 10 additions & 0 deletions shells/desktop/src/system-ui/programs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,16 @@ export function FolderView(props: {
// Classic outer sizes; other themes reframe them around the same client
// rectangle. About leaves room for the taller antialiased faces' line boxes.
export const ABOUT_GEO = { w: 340, h: 260 } as const;
/** The body lines ABOUT_GEO's height holds, and what each further line adds:
* the tallest theme's line box and the dialog's 8px gap. */
export const ABOUT_BODY_LINES = 4;
export const ABOUT_LINE_STEP = 24;

/** The About dialog's outer size for a body of `lines` lines: a product's
* About (pocket.about.json) can run longer in one language than another. */
export function aboutGeo(lines: number): { w: number; h: number } {
return { w: ABOUT_GEO.w, h: ABOUT_GEO.h + Math.max(0, lines - ABOUT_BODY_LINES) * ABOUT_LINE_STEP };
}
export const SHUTDOWN_GEO = { w: 300, h: 176 } as const;

/** Dialog push button; armed = pressed face + 1px content nudge. `primary`
Expand Down
15 changes: 15 additions & 0 deletions shells/desktop/src/system-ui/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ export interface ChromeMetrics {
/** Panel padding around the launcher rows (mirrors startMenu's p-[…]). */
startPadX: number;
startPadY: number;
/** A launcher row's width less its label: the row's left and right
* padding, and the icon slot with its gap when the theme draws icons
* (mirrors startItem's pl-[…], pr-[…], gap-[…]). */
startRowInset: number;
/** What a submenu arrow adds to a row: the 8px arrow and its gap. */
startArrowW: number;
/** Dropdown / context / flyout popup rows and panel padding. */
popupRowH: number;
popupSepH: number;
Expand Down Expand Up @@ -593,6 +599,9 @@ export const CLASSIC_THEME: DesktopTheme = {
startLeftW: 0,
startPadX: 1,
startPadY: 1,
// pl 6 + icon 16 + gap 6 + pr 6; the arrow is gap 6 + 8.
startRowInset: 34,
startArrowW: 14,
popupRowH: 18,
popupSepH: 8,
popupPadX: 1,
Expand Down Expand Up @@ -895,6 +904,9 @@ export const XP_THEME: DesktopTheme = {
startLeftW: 172,
startPadX: 1,
startPadY: 1,
// pl 8 + icon 16 + gap 7 + pr 8; the arrow is gap 7 + 8.
startRowInset: 39,
startArrowW: 15,
popupRowH: 19,
popupSepH: 8,
popupPadX: 2,
Expand Down Expand Up @@ -1289,6 +1301,9 @@ export const AQUA_THEME: DesktopTheme = {
// a 4px breath above the first and below the last row.
startPadX: 0,
startPadY: 4,
// pl 21 + pr 16, no icons and no gap; the arrow is 8.
startRowInset: 37,
startArrowW: 8,
popupRowH: 19,
popupSepH: 9,
popupPadX: 0,
Expand Down
45 changes: 39 additions & 6 deletions shells/desktop/src/system-ui/wm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ export interface StartLayout {
h: number;
/** Column split (0 when the theme paints one column beside a rail). */
leftW: number;
/** The places column's width (0 for one column). */
rightW: number;
headerH: number;
footerH: number;
bodyY: number;
Expand All @@ -528,21 +530,51 @@ interface StartItem {
* programs column and lays `foot` items along the bottom strip, Aqua stacks
* one column under the screen bar. Paint (chrome.tsx) and hit testing
* (app.tsx) both read these rectangles, so a theme switch can never leave
* one of them behind. */
* one of them behind.
*
* The panel is as wide as its rows: `rowW(i)` is item i's natural width
* (its label, icon slot, arrow and padding), and a column the theme draws
* at `startLeftW` or `startW` grows to its widest row, up to `maxW` for the
* whole panel. A row wider than what is left past `maxW` has its label cut
* (`fitLabel` in chrome.tsx). Without `rowW` the panel keeps the theme's widths. */
export function startLayout(
items: readonly StartItem[],
vpH: number,
metrics: ChromeMetrics = DEFAULT_METRICS,
rowW: (index: number) => number = () => 0,
maxW = Number.POSITIVE_INFINITY,
): StartLayout {
const {
startRowH: row,
startSepH: sep,
startLeftW: leftW,
startPadX: padX,
startPadY: padY,
} = metrics;
const twoColumn = metrics.startHeaderH > 0;
const rightW = metrics.startW - leftW - padX * 2;
const widest = (which: (it: StartItem) => boolean) =>
items.reduce((a, it, i) => (it.sep || it.foot || !which(it) ? a : Math.max(a, Math.ceil(rowW(i)))), 0);

let leftW = metrics.startLeftW;
let rightW = metrics.startW - metrics.startLeftW - padX * 2;
let panelW = metrics.startW;
if (twoColumn) {
const leftMin = leftW;
const rightMin = rightW;
leftW = Math.max(leftMin, widest((it) => it.col !== "right"));
rightW = Math.max(rightMin, widest((it) => it.col === "right"));
// Past the maximum the places column gives back its growth first.
let over = padX * 2 + leftW + rightW - Math.max(metrics.startW, maxW);
if (over > 0) {
const back = Math.min(over, rightW - rightMin);
rightW -= back;
over -= back;
leftW -= Math.min(over, leftW - leftMin);
}
panelW = padX * 2 + leftW + rightW;
} else {
const content = widest(() => true) + padX * 2 + metrics.startRailW;
panelW = Math.max(metrics.startW, Math.min(content, maxW));
}

const height = (which: (it: StartItem) => boolean) =>
items.filter(which).reduce((a, it) => a + (it.sep ? sep : row), 0);
Expand Down Expand Up @@ -571,7 +603,7 @@ export function startLayout(
const bottomH = height((it) => !it.foot && !!it.bottom);
const bottomY = bodyY + bodyH - bottomH;
let by = bottomY;
let fx = x + metrics.startW - padX;
let fx = x + panelW - padX;
for (let i = 0; i < items.length; i++) {
const it = items[i];
const h = it.sep ? sep : row;
Expand Down Expand Up @@ -611,7 +643,7 @@ export function startLayout(
index: i,
x: x + padX + metrics.startRailW,
y: oy,
w: metrics.startW - padX * 2 - metrics.startRailW,
w: panelW - padX * 2 - metrics.startRailW,
h,
});
oy += h;
Expand All @@ -621,9 +653,10 @@ export function startLayout(
return {
x,
y,
w: metrics.startW,
w: panelW,
h,
leftW: twoColumn ? leftW : 0,
rightW: twoColumn ? rightW : 0,
headerH: metrics.startHeaderH,
footerH: metrics.startFooterH,
bodyY,
Expand Down
Loading
Loading