forked from jimrandomh/faceclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwindow-menu.ts
More file actions
104 lines (93 loc) · 3.18 KB
/
Copy pathwindow-menu.ts
File metadata and controls
104 lines (93 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { GrayImage } from "../graphics/image";
import {
noopLayerActions,
LayerStack,
type DashboardInputEvent,
type Layer,
} from "./layers";
import { MenuLayer, type MenuItem, type MenuLayout } from "./menu";
import type { WorkerAppReply } from "./shell/worker-window";
/**
* The window long-press menu. By convention every app answers a long-press by
* opening one of these: its app-specific actions followed by the default
* entries (Voice input, Close window). This is a convenience path, not the
* safety net — the shell separately opens its own escape menu when the press
* is held long enough, so an unresponsive app can always be closed.
*/
/** Viewport-relative layout; visually matches the shell's escape menu position. */
export const WINDOW_MENU_LAYOUT: MenuLayout = { x: 8, y: 8, width: 272, minHeight: 0 };
export class WindowMenuLayer extends MenuLayer {
constructor(items: MenuItem[]) {
super(null, items, WINDOW_MENU_LAYOUT);
}
}
export type WindowMenuOptions = {
/** Paint the window content the menu draws over (a fresh, mutable image). */
paintBase: () => GrayImage;
size: { width: number; height: number };
isFocused: () => boolean;
};
/**
* Menu host for worker apps, which paint pixels directly rather than through
* a persistent LayerStack. Wraps a short-lived stack so MenuLayer (and the
* ctx.stack.pop() convention in item callbacks) works unchanged.
*
* Usage: route input to handleInput while isOpen(); paint() returns the
* window content with the menu drawn over it.
*/
export class WindowMenu {
private stack: LayerStack | null = null;
constructor(private readonly options: WindowMenuOptions) {}
isOpen(): boolean {
return this.stack !== null;
}
open(items: MenuItem[]): void {
if (this.stack) return;
const base: Layer = {
paint: () => this.options.paintBase(),
handleInput: () => {},
};
const stack = new LayerStack(base, { ...noopLayerActions }, this.options.size, this.options.isFocused);
stack.push(new WindowMenuLayer(items));
this.stack = stack;
}
/** Paint the window content with the menu over it (content alone if closed). */
paint(): GrayImage {
return this.stack ? this.stack.paint() : this.options.paintBase();
}
/** Route an input event to the menu; the menu closes by popping itself. */
async handleInput(event: DashboardInputEvent): Promise<void> {
const stack = this.stack;
if (!stack) return;
await stack.handleInput(event);
if (stack.isAtBase()) {
this.stack = null;
}
}
}
/**
* The default entries every window menu ends with, in worker form: the
* actions cross to the shell as WorkerAppReply messages. In-process windows
* build the equivalent items against the shell directly (in-process-window.ts).
*/
export function defaultWindowMenuItems(
windowId: string,
post: (reply: WorkerAppReply) => void,
): MenuItem[] {
return [
{
label: "Voice input",
onSelect: (ctx) => {
ctx.stack.pop();
post({ type: "start-voice-input", windowId });
},
},
{
label: "Close window",
onSelect: (ctx) => {
ctx.stack.pop();
post({ type: "close-window-request", windowId });
},
},
];
}