react-x11 2.51.1 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -61
- package/docs/README.md +256 -0
- package/docs/accessibility.md +575 -0
- package/docs/appearance.md +317 -0
- package/docs/click-to-component.md +92 -0
- package/docs/clipboard.md +262 -0
- package/docs/components.md +1231 -0
- package/docs/dbus.md +205 -0
- package/docs/debugging.md +371 -0
- package/docs/desktop-calendar.md +247 -0
- package/docs/desktop.md +523 -0
- package/docs/devtools.md +133 -0
- package/docs/drag-and-drop.md +585 -0
- package/docs/ecosystem/animation.md +360 -0
- package/docs/ecosystem/data-fetching.md +186 -0
- package/docs/ecosystem/dev-tooling.md +393 -0
- package/docs/ecosystem/forms.md +277 -0
- package/docs/ecosystem/headless.md +194 -0
- package/docs/ecosystem/i18n.md +86 -0
- package/docs/ecosystem/icons.md +377 -0
- package/docs/ecosystem/layout.md +206 -0
- package/docs/ecosystem/routing.md +135 -0
- package/docs/ecosystem/state.md +344 -0
- package/docs/ecosystem/testing.md +474 -0
- package/docs/ecosystem/theming.md +494 -0
- package/docs/ecosystem.md +439 -0
- package/docs/elements.md +2079 -0
- package/docs/embedding.md +239 -0
- package/docs/events.md +742 -0
- package/docs/extending.md +1700 -0
- package/docs/eyedropper.md +164 -0
- package/docs/filedialog.md +222 -0
- package/docs/frame.md +436 -0
- package/docs/gl.md +173 -0
- package/docs/globalmenu.md +195 -0
- package/docs/macos.md +592 -0
- package/docs/notifications.md +150 -0
- package/docs/packaging.md +468 -0
- package/docs/permissions.md +143 -0
- package/docs/react-features.md +405 -0
- package/docs/remote.md +179 -0
- package/docs/scale.md +225 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1710 -0
- package/docs/system.md +519 -0
- package/docs/testing.md +494 -0
- package/docs/typescript.md +75 -0
- package/docs/uri-schemes.md +340 -0
- package/docs/wayland-backend.md +332 -0
- package/docs/windows-integrations.md +177 -0
- package/docs/windows.md +380 -0
- package/package.json +11 -3
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/dnd.js +2 -2
- package/src/filedialog.js +3 -3
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/keyboardstate.js +28 -0
- package/src/layouts.js +1 -1
- package/src/node.d.ts +10 -10
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/portal.js +31 -0
- package/src/refresh/index.js +2 -1
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +86 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/video.js +376 -0
- package/src/wayland/xkb.js +137 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<!-- BEGIN:react-x11-agent-rules -->
|
|
2
|
+
|
|
3
|
+
# This is react-x11, not react-dom
|
|
4
|
+
|
|
5
|
+
This app is React drawing native desktop windows with react-x11
|
|
6
|
+
{{version}}. There is no DOM, no browser and no CSS. What you know about
|
|
7
|
+
React carries over: components, hooks, state, context, Suspense, error
|
|
8
|
+
boundaries. What you know about the DOM does not. Before writing UI, read the
|
|
9
|
+
docs installed with the package. They match the version this app runs and
|
|
10
|
+
are newer than your training data.
|
|
11
|
+
|
|
12
|
+
## Where to look
|
|
13
|
+
|
|
14
|
+
**Docs**, installed with the package (paths from this file's directory):
|
|
15
|
+
`node_modules/react-x11/docs/`
|
|
16
|
+
|
|
17
|
+
| page | for |
|
|
18
|
+
| --------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
19
|
+
| `react-features.md` | **read first**: where a DOM habit breaks (refs, measuring, portals, effects) |
|
|
20
|
+
| `elements.md` | the host elements and their props |
|
|
21
|
+
| `styling.md` | the `style` prop: layout, paint, `:hover` blocks, theme tokens |
|
|
22
|
+
| `components.md` | `Button`, `Select`, `Dialog`, `MenuBar`, `Tabs`, `Table`, `SplitPane`, … |
|
|
23
|
+
| `events.md` | events, focus, keys, shortcuts |
|
|
24
|
+
| `appearance.md` | light and dark, the accent, reduced motion |
|
|
25
|
+
| `globalmenu.md` | where `MenuBar` goes on each platform |
|
|
26
|
+
| `testing.md` | `react-x11/test`: render headless, query, click, type, write a PNG |
|
|
27
|
+
| `accessibility.md` | roles, names, what a screen reader hears |
|
|
28
|
+
| `filedialog.md`, `notifications.md`, `clipboard.md` | the desktop around the app |
|
|
29
|
+
| `drag-and-drop.md`, `system.md`, `packaging.md` | more of it, and shipping the app |
|
|
30
|
+
| `ecosystem/*.md` | state, forms, routing, data fetching, i18n with libraries you know |
|
|
31
|
+
| `README.md` | the map of every page |
|
|
32
|
+
|
|
33
|
+
The same pages are online at
|
|
34
|
+
`https://sidorares.github.io/react-x11/docs/reference/<page>`.
|
|
35
|
+
|
|
36
|
+
**Types**: `node_modules/react-x11/src/index.d.ts` and
|
|
37
|
+
`node_modules/react-x11/src/types/*.d.ts` hold every element, style key, event
|
|
38
|
+
and widget with its props. Grep them for any prop you are not sure of.
|
|
39
|
+
|
|
40
|
+
**Examples** are not in the package. These are pinned to this version:
|
|
41
|
+
`https://github.com/sidorares/react-x11/tree/v{{version}}/examples`, raw files
|
|
42
|
+
at `https://raw.githubusercontent.com/sidorares/react-x11/v{{version}}/examples/<file>`.
|
|
43
|
+
`finder.jsx` and `settings.jsx` (sidebar and content), `tasks.jsx` (list and
|
|
44
|
+
input), `widgets.jsx` (every control), `dashboard.jsx`, `menu.jsx`,
|
|
45
|
+
`theming.jsx`. Read the closest one or two before writing a screen.
|
|
46
|
+
|
|
47
|
+
## Rules you will trip on
|
|
48
|
+
|
|
49
|
+
Most of these throw with a message that names the fix. Read the message
|
|
50
|
+
before guessing.
|
|
51
|
+
|
|
52
|
+
- **Host elements are lowercase and few**: `<window>`, `<popup>`, `<box>`,
|
|
53
|
+
`<text>`, `<textinput>`, `<textarea>`, `<image>`, `<svg>`, `<canvas>`,
|
|
54
|
+
`<video>`, `<glarea>`. No `<div>`, `<span>`, `<button>`, `<input>`.
|
|
55
|
+
- **Everything drawn is inside a `<window>`.** `<window>` takes its geometry as
|
|
56
|
+
props, not style: `title`, `width`, `height`, `minWidth`, `minHeight`,
|
|
57
|
+
`resizable`.
|
|
58
|
+
- **Raw strings and numbers go only inside `<text>`.** `<box>hi</box>` throws.
|
|
59
|
+
A `<text>` inside a `<text>` is a styled span.
|
|
60
|
+
- **Style lives in `style`**, an object or an array (`[s.row, on && s.on]`,
|
|
61
|
+
falsy entries skipped). `<box padding={8}>` throws. There is no
|
|
62
|
+
`className`, no CSS file, no Tailwind, no styled-components.
|
|
63
|
+
- **State blocks paint only.** `':hover'`, `':active'`, `':focus'`,
|
|
64
|
+
`':focus-visible'`, `':focus-within'` and `':disabled'` inside `style` may set
|
|
65
|
+
colours, `background`, `opacity`, `outline`, `borderRadius`. Not padding,
|
|
66
|
+
size or `border` (its width is layout).
|
|
67
|
+
- **Flexbox is React Native's direction with CSS's shrink**: `flexDirection`
|
|
68
|
+
defaults to `'column'`, and children of a column stretch to its full width.
|
|
69
|
+
A `Button` in a column becomes a slab: give it `alignSelf: 'flex-start'`, or
|
|
70
|
+
put buttons in a `flexDirection: 'row'` box. `display: 'grid'` and `gap`
|
|
71
|
+
work as in CSS.
|
|
72
|
+
- **A pane smaller than its content scrolls** only with
|
|
73
|
+
`overflow: 'scroll'` (or `'hidden'`, or `minHeight: 0`). For 1000+ rows use
|
|
74
|
+
`Table`, which is virtualized.
|
|
75
|
+
- **Colours are theme tokens**: `'$background'`, `'$surface'`, `'$text'`,
|
|
76
|
+
`'$textMuted'`, `'$border'`, `'$accent'`, `'$accentText'`, `'$accentHover'`,
|
|
77
|
+
`'$accentActive'`, `'$surfaceHover'`, `'$surfaceActive'`,
|
|
78
|
+
`'$hoverBackground'`/`'$hoverText'` (a selected row), `'$danger'`,
|
|
79
|
+
`'$success'`, `'$warning'`, `'$info'`, `'$link'`. They follow the desktop's
|
|
80
|
+
light or dark scheme and its accent. A hard-coded grey is wrong in one of
|
|
81
|
+
the two. `useTheme()` gives the resolved values, for `<canvas>` drawing.
|
|
82
|
+
- **Events**: `onClick`, `onMouseDown`/`Up`/`Move`/`Enter`/`Leave`, `onWheel`,
|
|
83
|
+
`onContextMenu`, and `onKeyDown`/`onKeyUp` on an element that is
|
|
84
|
+
`focusable`. `Button` takes **`onPress`**. Every input's `onChange` gets an
|
|
85
|
+
event: read `ev.value`. Keys: compare `ev.keysym` with `XK_RETURN`,
|
|
86
|
+
`XK_ESCAPE`, `XK_DOWN`, … from `'react-x11/keysyms'`.
|
|
87
|
+
- **Shortcuts**: `[['Control', 'S']]` is ⌘S on macOS and Ctrl+S elsewhere. Put
|
|
88
|
+
them on `MenuBar` items (`shortcut`) or use `useAccelerator(chord, fn)`.
|
|
89
|
+
Never bind Copy, Paste, Cut, Select All or Undo in a menu while a text field
|
|
90
|
+
can have focus: the fields handle those already, and both would run.
|
|
91
|
+
- **Web UI libraries do not work** (MUI, Radix, shadcn, recharts, react-dom
|
|
92
|
+
anything). Plain JavaScript libraries do: zustand, zod, date-fns, `fetch`.
|
|
93
|
+
- **No `localStorage`.** Persist with
|
|
94
|
+
`createSettings({ appId, defaults })` from `react-x11`
|
|
95
|
+
(`const [value, setValue] = settings.use('key')`) or with `node:fs`.
|
|
96
|
+
- **Paths are absolute**: `new URL('./data.json', import.meta.url)`. On macOS
|
|
97
|
+
the app's JavaScript runs on a worker thread, where `process.chdir()` throws.
|
|
98
|
+
|
|
99
|
+
## Bigger components
|
|
100
|
+
|
|
101
|
+
Charts, a data table, a tree, tabs, markdown, a code editor, a rich text
|
|
102
|
+
editor, a calendar and date picker, a colour picker, a timeline, a reorderable
|
|
103
|
+
list, flow graphs, maps, formulas and a 3D scene are in
|
|
104
|
+
`@react-x11/components`. Install it, then import from its subpaths, not the
|
|
105
|
+
package root: `@react-x11/components/charts`, `/table`, `/tree`, `/tabs`,
|
|
106
|
+
`/markdown`, `/code-editor`, `/rich-text-editor`, `/calendar`,
|
|
107
|
+
`/color-picker`, `/timeline`, `/reorder`, `/flow`, `/maps`, `/formula`,
|
|
108
|
+
`/three`. Its `README.md` and `docs/components/<name>.md` are in
|
|
109
|
+
`node_modules/@react-x11/components/`, and each subpath has its own `.d.ts`.
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
- `{{run}} start` runs the app once.
|
|
114
|
+
- `{{run}} dev` runs it with hot reload. It keeps running and is meant for a
|
|
115
|
+
person at the keyboard; do not wait for it to exit.
|
|
116
|
+
- `npx react-x11 help` lists every `REACT_X11_*` variable.
|
|
117
|
+
`REACT_X11_DEBUG_LAYOUT=1` outlines every laid-out box, for a layout that
|
|
118
|
+
does not do what you expect.
|
|
119
|
+
|
|
120
|
+
## Check your work: run it and look at it
|
|
121
|
+
|
|
122
|
+
Passing tests are not the finish line. A clipped label, a stretched button or
|
|
123
|
+
a colour that is wrong in dark mode passes every test and is obvious in a
|
|
124
|
+
picture.
|
|
125
|
+
|
|
126
|
+
1. Render the app headless and write a picture of each scheme.
|
|
127
|
+
`react-x11/test` runs a real X server inside the test process: no window
|
|
128
|
+
opens, nothing to install but `pngjs` (`npm install -D pngjs`).
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
// test/App.test.jsx — run with: node --import tsx --test test/*.test.jsx
|
|
132
|
+
import React from 'react';
|
|
133
|
+
import { mkdirSync } from 'node:fs';
|
|
134
|
+
import { afterEach, test } from 'node:test';
|
|
135
|
+
import {
|
|
136
|
+
cleanup,
|
|
137
|
+
renderX11,
|
|
138
|
+
screen,
|
|
139
|
+
toPNG,
|
|
140
|
+
userEvent,
|
|
141
|
+
} from 'react-x11/test';
|
|
142
|
+
import App from '../src/App.jsx';
|
|
143
|
+
|
|
144
|
+
afterEach(cleanup);
|
|
145
|
+
// a font file, so text measures the same every run; Linux finds one itself
|
|
146
|
+
const fonts = {
|
|
147
|
+
darwin: { 'sans-serif': '/System/Library/Fonts/Supplemental/Arial.ttf' },
|
|
148
|
+
win32: { 'sans-serif': 'C:\\Windows\\Fonts\\segoeui.ttf' },
|
|
149
|
+
}[process.platform];
|
|
150
|
+
|
|
151
|
+
for (const colorScheme of ['light', 'dark']) {
|
|
152
|
+
test(`renders in ${colorScheme}`, async () => {
|
|
153
|
+
const { ctx, window: win } = await renderX11(<App />, {
|
|
154
|
+
wrap: false, // App renders its own <window>
|
|
155
|
+
fonts,
|
|
156
|
+
colorScheme,
|
|
157
|
+
});
|
|
158
|
+
// drive it the way a person would, then assert on what shows
|
|
159
|
+
// await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
160
|
+
// screen.getByText('1 item');
|
|
161
|
+
mkdirSync('shots', { recursive: true });
|
|
162
|
+
const { width, height } = win; // the size the app's window came out
|
|
163
|
+
await toPNG(ctx, `shots/${colorScheme}.png`, { width, height });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Queries are Testing Library's: `getByText`, `getByRole(role, { name })`,
|
|
169
|
+
`getByPlaceholder`, `findBy*`, `queryBy*`, and `userEvent.click`, `type`,
|
|
170
|
+
`key`, `hover`. A `getBy*` that matches twice throws: add
|
|
171
|
+
`{ exact: true }` or narrow with `within(node)`.
|
|
172
|
+
|
|
173
|
+
2. **Open both PNGs and look at them.** Fix what looks wrong: clipped or
|
|
174
|
+
overlapping text, a cramped or empty window, rows that do not line up, a
|
|
175
|
+
colour that ignores dark mode, a control with no hover or pressed state.
|
|
176
|
+
Render again. Two or three passes is normal.
|
|
177
|
+
3. The headless render is the X11 backend: native control bezels, the macOS
|
|
178
|
+
menu bar and SF Symbols do not appear in it, and bold may come out regular.
|
|
179
|
+
Judge layout, spacing, colour and text from it. Then run `{{run}} start`
|
|
180
|
+
for a few seconds to confirm the real app starts with nothing in its
|
|
181
|
+
output.
|
|
182
|
+
|
|
183
|
+
## Design
|
|
184
|
+
|
|
185
|
+
Read [DESIGN.md](DESIGN.md) before writing UI. It says how this app should
|
|
186
|
+
look and how to get each piece out of react-x11.
|
|
187
|
+
|
|
188
|
+
<!-- END:react-x11-agent-rules -->
|
|
189
|
+
|
|
190
|
+
The block above comes from react-x11 {{version}}. Keep notes about this app
|
|
191
|
+
below it, so the block can be replaced with a newer version's.
|
|
192
|
+
|
|
193
|
+
## This app
|
|
194
|
+
|
|
195
|
+
<!-- What it is, who it is for, and the decisions an agent should keep to. -->
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Design
|
|
2
|
+
|
|
3
|
+
How this app should look and behave. People and coding agents both read it
|
|
4
|
+
before changing the UI.
|
|
5
|
+
|
|
6
|
+
The first section is this app's brief: fill it in. The rest is the house
|
|
7
|
+
style react-x11 {{version}} starts every app with: the conventions of the
|
|
8
|
+
desktops the app runs on, and how to get each one out of react-x11. Change
|
|
9
|
+
any of it that this app does differently. Then the agent follows your rules
|
|
10
|
+
and not the defaults.
|
|
11
|
+
|
|
12
|
+
## This app
|
|
13
|
+
|
|
14
|
+
- **What it is**: _one line: what someone opens it to do._
|
|
15
|
+
- **Who uses it, and how often**: _all day, or once a week?_
|
|
16
|
+
- **Platforms**: macOS, Windows and Linux, each looking like itself. _Or
|
|
17
|
+
name the one that matters most._
|
|
18
|
+
- **Layout**: _sidebar and content, three panes, content and inspector, or
|
|
19
|
+
one compact window (see "Window anatomy")._
|
|
20
|
+
- **Density**: regular. _Compact for a data-heavy tool._
|
|
21
|
+
- **Colour**: the desktop's own, with the user's accent. No brand palette.
|
|
22
|
+
_If there is a brand colour, name it and the one or two places it may
|
|
23
|
+
appear._
|
|
24
|
+
- **Voice**: short and plain. Buttons are verbs ("Add Account", not "OK").
|
|
25
|
+
|
|
26
|
+
## Look like the desktop it runs on
|
|
27
|
+
|
|
28
|
+
A desktop app is quiet, dense, aligned and driven from the keyboard. It is
|
|
29
|
+
light or dark because the desktop is. People notice in a second when an app is
|
|
30
|
+
a web page in a window.
|
|
31
|
+
|
|
32
|
+
react-x11 gives you most of this already:
|
|
33
|
+
|
|
34
|
+
- The theme tokens are the desktop's palette, its light or dark scheme and its
|
|
35
|
+
accent.
|
|
36
|
+
- The widgets wear AppKit's own bezels on macOS.
|
|
37
|
+
- `MenuBar` goes where the platform keeps menus.
|
|
38
|
+
|
|
39
|
+
Keep those defaults, and spend the effort on layout, copy and behaviour.
|
|
40
|
+
|
|
41
|
+
## Window anatomy
|
|
42
|
+
|
|
43
|
+
- **One main window** with a clear structure. Pick one of the standard
|
|
44
|
+
layouts:
|
|
45
|
+
- **Sidebar and content** (Mail, Files, Explorer): a list of places or
|
|
46
|
+
collections on the left, 200–260 px wide, then the content. This is the
|
|
47
|
+
default for anything with collections of things.
|
|
48
|
+
- **Sidebar, list and detail**: three columns, with the middle list
|
|
49
|
+
260–320 px wide.
|
|
50
|
+
- **Content and inspector**: a 240–300 px panel on the right with the
|
|
51
|
+
properties of the selection.
|
|
52
|
+
- **One compact window** for a single-purpose utility: a timer, a
|
|
53
|
+
converter. This needs no sidebar.
|
|
54
|
+
- **A toolbar** under the title bar: a 40–48 px row with the few most-used
|
|
55
|
+
actions as icon buttons, and a search field at the trailing end.
|
|
56
|
+
- **Dialogs for short tasks**: a modal dialog for a three-field "New item…"
|
|
57
|
+
form, and a popup anchored to what it edits for a small editor. Never take
|
|
58
|
+
over the whole window for a short form.
|
|
59
|
+
- **Size**: 900×600 by default for a sidebar app, 720×480 for a simple one,
|
|
60
|
+
about 360×480 for a utility. **Always set a minimum size** (about 600×400
|
|
61
|
+
for a sidebar app), so the layout never collapses.
|
|
62
|
+
|
|
63
|
+
## Menus and shortcuts
|
|
64
|
+
|
|
65
|
+
People try a shortcut before they look for a button. Write shortcuts with
|
|
66
|
+
`Control`: it is ⌘ on macOS and Ctrl elsewhere. The standard menus, in order:
|
|
67
|
+
|
|
68
|
+
- **File**: New (Control+N), Open… (Control+O), Close Window (Control+W), Save
|
|
69
|
+
(Control+S), Save As… (Control+Shift+S), Export…, Print… (Control+P)
|
|
70
|
+
- **Edit**: Undo, Redo, Cut, Copy, Paste, Delete, Select All, Find…
|
|
71
|
+
(Control+F)
|
|
72
|
+
- **View**: Show or Hide Sidebar, view modes (Control+1, Control+2, …), Zoom
|
|
73
|
+
In and Zoom Out
|
|
74
|
+
- _the app's own_: its own verbs (Item, Format, Playback, …), with shortcuts
|
|
75
|
+
for the frequent ones
|
|
76
|
+
- **Help**: _App name_ Help
|
|
77
|
+
|
|
78
|
+
And the rules around them:
|
|
79
|
+
|
|
80
|
+
- **Where the menu shows**: on macOS `MenuBar` becomes the system menu bar.
|
|
81
|
+
On Linux it goes to the desktop's panel where one hosts menus (KDE
|
|
82
|
+
Plasma), and is drawn in the window everywhere else, Windows included.
|
|
83
|
+
- **Settings and Quit**: on macOS, react-x11 adds the app menu itself, with
|
|
84
|
+
Quit (⌘Q) only. Put "Settings…" (Control+,) at the end of your first menu.
|
|
85
|
+
On Windows and Linux, put Settings… in Edit or File, and Quit (Control+Q) at
|
|
86
|
+
the end of File.
|
|
87
|
+
- An item that opens a window or asks for more ends in "…". An item that does
|
|
88
|
+
not apply is disabled, not hidden. Every frequent action is in a menu, even
|
|
89
|
+
when it also has a button.
|
|
90
|
+
- The text fields already handle Copy, Paste, Cut, Select All, Undo and Redo,
|
|
91
|
+
with a right-click menu of their own. Do not put those shortcuts on menu
|
|
92
|
+
items while a field can have focus: both would run.
|
|
93
|
+
|
|
94
|
+
## Type
|
|
95
|
+
|
|
96
|
+
- **Base size**: 13 on macOS, which is AppKit's body size, and 14 elsewhere,
|
|
97
|
+
which is react-x11's default. Set it once at the root (see the recipes
|
|
98
|
+
below). Never set `fontFamily`: the default is the system's UI face.
|
|
99
|
+
- **Scale**: base for body, controls and lists; base −2 in `'$textMuted'` for
|
|
100
|
+
secondary text and captions; base +2 and +4, semibold (600), for titles; base
|
|
101
|
+
+9 for a rare large title. Use at most three sizes on one screen.
|
|
102
|
+
Hierarchy comes from weight and the muted colour, not from size jumps.
|
|
103
|
+
- A number that changes while you watch (a timer, a price, a count) uses
|
|
104
|
+
`fontVariantNumeric: 'tabular-nums'`.
|
|
105
|
+
- A one-line label that can overflow: `textWrap: 'nowrap'` and
|
|
106
|
+
`textOverflow: 'ellipsis'`.
|
|
107
|
+
|
|
108
|
+
## Colour
|
|
109
|
+
|
|
110
|
+
- **Semantic tokens only**: `'$background'` for the window, `'$surface'` for
|
|
111
|
+
lists, fields and raised areas, `'$text'`, `'$textMuted'`, `'$border'` for
|
|
112
|
+
separators, `'$accent'`, `'$hoverBackground'` and `'$hoverText'` for a
|
|
113
|
+
selected row, and `'$danger'`, `'$success'`, `'$warning'`, `'$info'` for
|
|
114
|
+
status. A hard-coded grey is wrong in light or in dark.
|
|
115
|
+
- **The accent is the user's**, not yours. Use it for the primary button, the
|
|
116
|
+
selection, focus and toggles.
|
|
117
|
+
- **Colour highlights; it is not the ground.** Most of a window is neutral.
|
|
118
|
+
Big saturated panels are a web look.
|
|
119
|
+
- **Light and dark both look intended.** Shadows almost vanish in dark, so
|
|
120
|
+
separation comes from a slightly lighter surface instead.
|
|
121
|
+
|
|
122
|
+
## Spacing and sizes
|
|
123
|
+
|
|
124
|
+
- A 4 and 8 px grid: 20 px window margins (16 compact), 8 px between related
|
|
125
|
+
controls, 16–20 px between groups, 6–12 px of padding inside a row.
|
|
126
|
+
- Let the widgets size themselves: they follow the platform. Rows you draw
|
|
127
|
+
yourself: 28 px on macOS, 32–36 px elsewhere.
|
|
128
|
+
- Corners match the widgets'. `useTheme().radius` is their radius.
|
|
129
|
+
- Separators are 1 px lines in `'$border'`, not borders around every box.
|
|
130
|
+
- Align everything to a few vertical lines. Form labels are either
|
|
131
|
+
right-aligned against a column of fields or stacked above them in a narrow
|
|
132
|
+
pane.
|
|
133
|
+
|
|
134
|
+
## Interaction
|
|
135
|
+
|
|
136
|
+
- **Every input gets an answer on the event that started it**: a hover tint,
|
|
137
|
+
a distinct pressed state on mouse down, then the action on release.
|
|
138
|
+
- **Keyboard**: Tab moves focus, Space and Return activate, arrows move
|
|
139
|
+
through lists and grids, Return confirms a dialog, Escape cancels it,
|
|
140
|
+
Control+F focuses search, Delete removes the selection. The focus ring
|
|
141
|
+
shows on keyboard focus.
|
|
142
|
+
- **Selection**: one click selects (a filled row in `'$hoverBackground'`),
|
|
143
|
+
and a double click or Return opens.
|
|
144
|
+
- **Right click** shows a context menu with the item's verbs.
|
|
145
|
+
- **Dialog buttons** are named for what they do ("Delete 3 Items", not
|
|
146
|
+
"Yes"). On macOS and GNOME, Cancel comes first and the action is last, at
|
|
147
|
+
the trailing edge. On Windows and KDE the action comes first, then Cancel.
|
|
148
|
+
- **Destructive actions** can be undone, or are confirmed in a dialog whose
|
|
149
|
+
button says what it deletes.
|
|
150
|
+
- **Empty states**: a short centred line in `'$textMuted'` ("No Notes"), an
|
|
151
|
+
optional one-line explanation, and at most one button. Never a blank pane.
|
|
152
|
+
- **Success is shown in place**, not in a "Saved!" dialog. Dialogs are for
|
|
153
|
+
decisions.
|
|
154
|
+
|
|
155
|
+
## Icons
|
|
156
|
+
|
|
157
|
+
- Icons label places and actions in sidebars and toolbars. They do not
|
|
158
|
+
decorate paragraphs. Never use emoji as UI icons.
|
|
159
|
+
- Use the platform's own icons, which are monoline, sized like the text and
|
|
160
|
+
drawn in its colour: `<image src={{ symbol }} />` draws an SF Symbol on
|
|
161
|
+
macOS (`'folder'`) and an icon from the freedesktop theme on Linux
|
|
162
|
+
(`'folder-symbolic'`). A name the desktop lacks draws nothing, and Windows
|
|
163
|
+
has no icon theme to draw from. So an icon is never the only label, and an
|
|
164
|
+
app that needs icons everywhere ships its own.
|
|
165
|
+
- Affordances (chevrons, check, close, plus, more) come from `<Icon name>`,
|
|
166
|
+
which is the same everywhere.
|
|
167
|
+
|
|
168
|
+
## Motion and accessibility
|
|
169
|
+
|
|
170
|
+
- Motion is subtle and fast: 120–200 ms for hover, selection and disclosure.
|
|
171
|
+
Nothing bounces, and nothing animates on a timer the user did not start.
|
|
172
|
+
With reduced motion on, cut or cross-fade instead.
|
|
173
|
+
- Every control has a name a screen reader can say: an icon-only button needs
|
|
174
|
+
an `aria-label`. Text contrast is at least 4.5:1. Nothing is reachable by
|
|
175
|
+
mouse only. State is never shown by colour alone: add an icon or a word.
|
|
176
|
+
|
|
177
|
+
## The same, in react-x11
|
|
178
|
+
|
|
179
|
+
- **Window size and minimum**: `<window title="…" width={900} height={600}
|
|
180
|
+
minWidth={600} minHeight={400}>`
|
|
181
|
+
- **The app's name on macOS**: `createRoot({ cocoa: { appName: 'Notes' } })`:
|
|
182
|
+
what the menu bar and ⌘-Tab show. Without it they say "node".
|
|
183
|
+
- **Base type size**: `<ThemeProvider value={{ fontSize: process.platform ===
|
|
184
|
+
'darwin' ? 13 : 14 }}>` around the app.
|
|
185
|
+
- **Menu bar**: `<MenuBar menus={[{ label: 'File', items: [...] }]} />` inside
|
|
186
|
+
the window. Item: `{ label, onSelect, shortcut: [['Control', 'N']], enabled,
|
|
187
|
+
items }`, or `{ type: 'separator' }`. A check item: `toggleType:
|
|
188
|
+
'checkmark', toggleState: 0 or 1`.
|
|
189
|
+
- **A shortcut with no menu item**: `useAccelerator([['Control', 'K']],
|
|
190
|
+
handler)`
|
|
191
|
+
- **Sidebar**: a `<box>` with `width: 220`, `backgroundColor: '$surface'`,
|
|
192
|
+
`borderEndWidth: 1`, `borderColor: '$border'`, `padding: 8`, `gap: 2`. Rows:
|
|
193
|
+
`flexDirection: 'row'`, `alignItems: 'center'`, `gap: 8`, `paddingInline:
|
|
194
|
+
8`, `borderRadius: 6`, `':hover': { backgroundColor: '$surfaceHover' }`, and
|
|
195
|
+
when selected `backgroundColor: '$hoverBackground'` with text in
|
|
196
|
+
`'$hoverText'`. Section headers: base −2, `fontWeight: 600`, `'$textMuted'`.
|
|
197
|
+
See `examples/finder.jsx` and `settings.jsx`.
|
|
198
|
+
- **Resizable split**: `<SplitPane direction="row" defaultSize={240}
|
|
199
|
+
min={180}>` with two children.
|
|
200
|
+
- **Toolbar**: a row under the title bar: `flexDirection: 'row'`, `alignItems:
|
|
201
|
+
'center'`, `gap: 8`, `height: 44`, `paddingInline: 12`, `borderBottomWidth:
|
|
202
|
+
1`, `borderColor: '$border'`. Icon buttons in it, then `<textinput
|
|
203
|
+
placeholder="Search" />` last.
|
|
204
|
+
- **Buttons**: `<Button onPress={…}>`. `primary` is the default action, one
|
|
205
|
+
per view. `variant="outline"` for a secondary one, `variant="ghost"
|
|
206
|
+
size="small"` for toolbar and inline icon buttons. Never size a `Button`
|
|
207
|
+
yourself: `style` sizes its slot.
|
|
208
|
+
- **Other controls**: `Checkbox`, `Switch`, `RadioGroup` and `Radio`,
|
|
209
|
+
`Slider`, `Select` (`options`, `value`), `ProgressBar`, `Tooltip label="…"`,
|
|
210
|
+
`<textinput placeholder>`, `<textarea>`. Every `onChange(ev)` gives
|
|
211
|
+
`ev.value`.
|
|
212
|
+
- **Tabs**: `<Tabs items={[{ id, label, content }]} />`
|
|
213
|
+
- **Modal dialog**: `<Dialog open title="…" onClose={…}
|
|
214
|
+
actions={<>…buttons…</>}>`: a real window that keeps focus. Escape and the
|
|
215
|
+
close button call `onClose`. `autoFocus` on its first field.
|
|
216
|
+
- **Context menu and popover**: `<ContextMenu items={[…]}>` around the node it
|
|
217
|
+
belongs to. `<popup anchor={{ to: ref, placement: 'bottom' }}>` for a custom
|
|
218
|
+
popover.
|
|
219
|
+
- **Lists, tables, trees**: `Table` (`columns: [{ id, label, width, value,
|
|
220
|
+
align }]`, `rows`, `onActivate`) for sortable, virtualized data. `Tree` from
|
|
221
|
+
`@react-x11/components/tree`. Draw short lists yourself.
|
|
222
|
+
- **Hover and press on your own box**: `cursor: 'pointer'`, `':hover': {
|
|
223
|
+
backgroundColor: '$surfaceHover' }`, `':active': { backgroundColor:
|
|
224
|
+
'$surfaceActive' }`, `transition: 120`.
|
|
225
|
+
- **Focus and keys**: `focusable` (and `tabIndex`) on a custom control, and
|
|
226
|
+
the focus ring comes with it. `onKeyDown={(ev) => ev.keysym === XK_DOWN &&
|
|
227
|
+
…}` with keysyms from `react-x11/keysyms`. `role` and `aria-label` so a
|
|
228
|
+
screen reader can name it.
|
|
229
|
+
- **Light, dark, reduced motion**: tokens follow the desktop.
|
|
230
|
+
`useSystemAppearance()` gives `{ colorScheme, accent, reducedMotion, … }`,
|
|
231
|
+
and `transition: reducedMotion ? 0 : 150` respects it.
|
|
232
|
+
- **A tinted alert**: `backgroundColor: tint(theme.danger, 0.12)` with `tint`
|
|
233
|
+
from `react-x11/style` and `theme` from `useTheme()`, and `'$danger'` for
|
|
234
|
+
its text.
|
|
235
|
+
- **Files, notifications, settings**: `useFileDialog()` gives `openFile()` and
|
|
236
|
+
`saveFile()`, which resolve to paths or `null`. `useNotifier()` gives
|
|
237
|
+
`notify({ summary, body })`: check `available` first and never depend on it.
|
|
238
|
+
`createSettings({ appId, defaults })` remembers state.
|
|
239
|
+
- **Charts**: `@react-x11/components/charts`, at least 200 px tall, with
|
|
240
|
+
series in `'$accent'`, `'$info'`, `'$success'`, and a tooltip.
|
|
241
|
+
|
|
242
|
+
## What makes it look like a website (avoid)
|
|
243
|
+
|
|
244
|
+
- **A hero header**: a big title and tagline at the top of the window. The
|
|
245
|
+
window's title is in the title bar, and content starts with content.
|
|
246
|
+
- **Card soup**: every item in a rounded, shadowed, padded card on a grey
|
|
247
|
+
page. Use lists with separators and selection. Keep cards for things that
|
|
248
|
+
are visual: a gallery, a dashboard tile.
|
|
249
|
+
- **A centred landing page**: content in a narrow column with empty sides.
|
|
250
|
+
Fill the window and align to the leading edge.
|
|
251
|
+
- **Phone and web patterns**: hamburger menus, bottom tab bars, floating
|
|
252
|
+
action buttons. Use the menu bar, a sidebar, a toolbar.
|
|
253
|
+
- **Wide buttons**: the column-stretch trap, and buttons styled as links.
|
|
254
|
+
Buttons are compact and sit in a row at the trailing edge or in a toolbar.
|
|
255
|
+
- **Gradients, glows, neon, emoji icons, big shadows**, and everything
|
|
256
|
+
rounded to 16 px or more.
|
|
257
|
+
- **A palette of your own that ignores dark mode**, or replaces the user's
|
|
258
|
+
accent.
|
|
259
|
+
- **Sample data that looks fake**: "Lorem ipsum", "Item 1, Item 2". Seed 10–30
|
|
260
|
+
plausible, varied records. It is the cheapest polish there is.
|
|
261
|
+
- **Buttons that do nothing.** Cut them, or disable them and say why.
|
|
262
|
+
|
|
263
|
+
## Before calling it done
|
|
264
|
+
|
|
265
|
+
Look at a picture of every screen, in light and in dark. Is there one
|
|
266
|
+
obvious primary area? Do the leading edges line up, and are the row heights
|
|
267
|
+
consistent? Is there one accent-coloured thing per region, and is nothing
|
|
268
|
+
clipped? If it looks like part of the operating system rather than a
|
|
269
|
+
landing page, it is done.
|
package/src/cocoa/app.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// that stands in for the X socket.
|
|
6
6
|
//
|
|
7
7
|
// Two run loops. By default Node's loop is master and a timer pumps AppKit
|
|
8
|
-
// (
|
|
8
|
+
// (design/macos.md §"Input and the run loop", option 1): input waits for the
|
|
9
9
|
// pump, and AppKit's modal loops (live resize, menu tracking, a drag) stall
|
|
10
10
|
// JS timers — but not the delegate callbacks, which is why live resize
|
|
11
11
|
// still relayouts: the resize event flushes the frame synchronously on its
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// Under the launcher (`node --import react-x11/cocoa-main`, src/cocoa/
|
|
14
14
|
// main.js) the app runs on a worker instead, AppKit keeps the main thread
|
|
15
15
|
// in a real `[NSApp run]`, and this object is fed by the bridge's batches
|
|
16
|
-
// (`_routeBatch`) with no pump at all (
|
|
16
|
+
// (`_routeBatch`) with no pump at all (design/macos.md §"JS on a worker").
|
|
17
17
|
import { isMainThread } from 'node:worker_threads';
|
|
18
18
|
|
|
19
19
|
import { cssColorStraight } from 'ntk/color';
|
|
@@ -64,11 +64,11 @@ const PUMP_INTERVAL_MS = 8;
|
|
|
64
64
|
// The edge moves when the delegate returns, so the budget is how long a
|
|
65
65
|
// slow frame may hold the drag back. 50 covers what a large tree costs —
|
|
66
66
|
// the Cocoa `resize` cell measures 28ms a tick at 3,662 nodes
|
|
67
|
-
// (
|
|
67
|
+
// (design/macos.md) — with room, where the bridge's own test met every tick
|
|
68
68
|
// in 3.1ms against 3ms of layout. Past three 60Hz frames an edge that waits
|
|
69
69
|
// reads as the drag stuttering, so a tree slower than that lets the edge
|
|
70
70
|
// go first and its frame land after (Flutter's Windows embedder waits up
|
|
71
|
-
// to 100,
|
|
71
|
+
// to 100, design/windows.md §"Resize: where the threads meet"). `createRoot({ cocoa: { resizeWait } })`
|
|
72
72
|
// sets it; 0 is no handshake at all.
|
|
73
73
|
const RESIZE_WAIT_MS = 50;
|
|
74
74
|
// What `createRoot({ cocoa: { paneStretch } })` and
|
|
@@ -272,7 +272,7 @@ export class CocoaApp {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// 'surface' (the measured default) or 'layers' — the retained CALayer
|
|
275
|
-
// presenter, opt-in while
|
|
275
|
+
// presenter, opt-in while design/macos.md's measure-first gate is open.
|
|
276
276
|
this._presenterMode =
|
|
277
277
|
options.cocoa?.presenter ??
|
|
278
278
|
process.env.REACT_X11_COCOA_PRESENTER ??
|
|
@@ -391,7 +391,7 @@ export class CocoaApp {
|
|
|
391
391
|
|
|
392
392
|
// the global-menu exports, newest active: the macOS menu bar is one per
|
|
393
393
|
// app, so the most recent MenuBar owns it (per-focused-window switching
|
|
394
|
-
// is the follow-up —
|
|
394
|
+
// is the follow-up — design/macos.md §Menus)
|
|
395
395
|
this._globalMenus = [];
|
|
396
396
|
this._activeGlobalMenu = null;
|
|
397
397
|
|
package/src/cocoa/bezels.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Native control bezels — AppKit's own pixels for the core controls, cached
|
|
2
|
-
// as surfaces the ordinary 2d paint path can blit (
|
|
2
|
+
// as surfaces the ordinary 2d paint path can blit (design/macos.md §Native
|
|
3
3
|
// controls). The mechanism is offscreen NSCell/NSControl rendering, the
|
|
4
4
|
// WebKit/Gecko form-control technique: interaction, focus and keyboard stay
|
|
5
5
|
// the shared component implementation, and only the *bezel* — the pixels of
|
package/src/cocoa/fonts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The Cocoa text engine: CoreText behind the same `app.fonts` contract ntk's
|
|
2
2
|
// FontManager answers on the X11 backend. The renderer touches exactly this
|
|
3
|
-
// surface (
|
|
3
|
+
// surface (design/macos.md §"Text: the engine contract"):
|
|
4
4
|
//
|
|
5
5
|
// fonts.layout(spans, base, { maxWidth, align, lineHeight, maxLines,
|
|
6
6
|
// overflow, direction })
|
package/src/cocoa/globalmenu.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The macOS menu bar as a global-menu export (
|
|
1
|
+
// The macOS menu bar as a global-menu export (design/macos.md §Menus).
|
|
2
2
|
//
|
|
3
3
|
// The Linux global menu was built for exactly this moment: the item
|
|
4
4
|
// vocabulary is data, and the pure snapshot/IdAllocator machinery in
|
package/src/cocoa/keymap.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
//
|
|
24
24
|
// There is no input method behind this. The bridge's view swallows
|
|
25
25
|
// `keyDown:` without `interpretKeyEvents:` and is no `NSTextInputClient`
|
|
26
|
-
// (
|
|
26
|
+
// (design/macos.md, "IME"), so these fields are all the text there is. Two
|
|
27
27
|
// consequences, both handled below:
|
|
28
28
|
//
|
|
29
29
|
// - **A dead key types nothing.** ⌥E on a US layout leaves `chars` empty, and
|
package/src/cocoa/panewindow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The pane process's "window" on the Cocoa backend —
|
|
1
|
+
// The pane process's "window" on the Cocoa backend — design/frame.md's child
|
|
2
2
|
// half, with the X server replaced by shared memory.
|
|
3
3
|
//
|
|
4
4
|
// On X11 a pane renders into a real window of its own and the host embeds
|
package/src/cocoa/player.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `<video src>` on macOS (
|
|
1
|
+
// `<video src>` on macOS (design/architecture/video.md §3.2, §9): a file or URL
|
|
2
2
|
// played by AVFoundation through @windowkit/appkit's player verbs — the
|
|
3
3
|
// platform's decoder, audio, seeking and HLS — with the picture on the
|
|
4
4
|
// player's own AVPlayerLayer.
|
package/src/cocoa/presenter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The retained layer presenter — Tier L of
|
|
1
|
+
// The retained layer presenter — Tier L of design/macos.md: one CALayer per
|
|
2
2
|
// drawn node, React commits landing as property sets inside a single
|
|
3
3
|
// disabled-actions CATransaction per frame, the WindowServer compositing.
|
|
4
4
|
//
|
|
@@ -350,7 +350,7 @@ export function stylePaintsPlain(node, style = node.style ?? {}) {
|
|
|
350
350
|
/**
|
|
351
351
|
* Style property → the key path on a PropBox's own layer, for the
|
|
352
352
|
* animations the presenter takes off the frame clock and hands to the
|
|
353
|
-
* render server (
|
|
353
|
+
* render server (design/architecture/animation.md §4.2). Only what the layer
|
|
354
354
|
* expresses as a property of itself qualifies: a colour on `<text>` is a
|
|
355
355
|
* re-raster per frame, a layout property moves the siblings, and both stay
|
|
356
356
|
* on the JS loop. `scaled` values go out in points, as `_syncPropBox`
|
package/src/cocoa/promotion.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
// overlapped, hidden, clipped, faded or non-plain returns to the bitmap in
|
|
37
37
|
// the frame that finds it, the animation handed back to the clock — and a
|
|
38
38
|
// loop the clock runs is offered again once the scene would take it
|
|
39
|
-
// (`wouldTake`), where it is in its cycle.
|
|
39
|
+
// (`wouldTake`), where it is in its cycle. design/macos.md §"Layer
|
|
40
40
|
// promotion" is the account.
|
|
41
41
|
import { now } from '../nodes/animation.js';
|
|
42
42
|
import { BoxNode } from '../nodes/box.js';
|
package/src/cocoa/relaunch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Threaded mode by default (
|
|
1
|
+
// Threaded mode by default (design/macos.md §"JS on a worker"). On macOS the
|
|
2
2
|
// process main thread is AppKit's — the only thread it lets own a window —
|
|
3
3
|
// and while the app's JS runs there, every menu, drag, live resize and
|
|
4
4
|
// modal panel stops it. So the first import of react-x11 on the main thread
|
package/src/cocoa/threaded.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Threaded mode's worker half (
|
|
1
|
+
// Threaded mode's worker half (design/macos.md §"JS on a worker: a UI thread
|
|
2
2
|
// of the bridge's own"): with the launcher (src/cocoa/main.js) holding the
|
|
3
3
|
// process main thread in the bridge's `runMain()`, the app's JS runs on a
|
|
4
4
|
// `worker_threads` Worker, and this module is what that worker needs to
|
package/src/cocoa/video.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// A video's frames on a layer of their own (
|
|
1
|
+
// A video's frames on a layer of their own (design/architecture/video.md §7):
|
|
2
2
|
// what the surface presenter's sprites (src/cocoa/sprites.js) do with a part
|
|
3
3
|
// whose `contents` is a `VideoFrames` sink instead of a `paint`.
|
|
4
4
|
//
|
package/src/cocoa/window.js
CHANGED
|
@@ -39,7 +39,7 @@ const FENCE_TIMEOUT_MS = 100;
|
|
|
39
39
|
// (`_takeBack`). Three, a `CAMetalLayer`'s depth, is what an animation
|
|
40
40
|
// mostly needs; an input answered between two of its frames puts two flips
|
|
41
41
|
// inside one refresh, and four kept every write of a clicked 120Hz animation
|
|
42
|
-
// off a held buffer (
|
|
42
|
+
// off a held buffer (design/macos.md, "Measured: the swapchain and the
|
|
43
43
|
// WindowServer's hold").
|
|
44
44
|
const MAX_BUFFERS = 4;
|
|
45
45
|
// Past this many rects, what a buffer missed is copied whole: a buffer left
|
|
@@ -235,14 +235,14 @@ export class CocoaWindow {
|
|
|
235
235
|
// change.
|
|
236
236
|
this.damageRectCap = 16;
|
|
237
237
|
|
|
238
|
-
// The retained layer presenter (
|
|
238
|
+
// The retained layer presenter (design/macos.md Tier L), behind
|
|
239
239
|
// REACT_X11_COCOA_PRESENTER=layers while the surface path is the
|
|
240
240
|
// measured default. Its hooks exist only in this mode, so the feature
|
|
241
241
|
// detection in src/nodes/ keeps the surface path byte-identical; the
|
|
242
242
|
// scroll blit is shadowed off because a layer frame has no backing
|
|
243
243
|
// bitmap to blit. The last two are the animation seam: a transition or
|
|
244
244
|
// a loop the presenter takes runs in the render server and schedules no
|
|
245
|
-
// frames here (
|
|
245
|
+
// frames here (design/architecture/animation.md §4).
|
|
246
246
|
if (app._presenterMode === 'layers') {
|
|
247
247
|
this._presenter = new CocoaLayerPresenter(this);
|
|
248
248
|
this.presentFrame = (windowNode) => this._presenter.frame(windowNode);
|
|
@@ -659,7 +659,7 @@ export class CocoaWindow {
|
|
|
659
659
|
* A new size retires the chain, released on the spot (`_releaseBacking`):
|
|
660
660
|
* left to the handles' finalizers, a forty-tick drag of the two-buffer
|
|
661
661
|
* chain this used to be held 800MB until a collection happened to run —
|
|
662
|
-
* the `rss +80MB`
|
|
662
|
+
* the `rss +80MB` design/macos.md measured. The layer keeps its own
|
|
663
663
|
* reference to whichever IOSurface it is still showing, so the free is
|
|
664
664
|
* safe while that frame is on glass. A new chain starts with the one
|
|
665
665
|
* buffer its first frame paints: a live-resize tick paints its size once,
|