react-x11 2.4.0 → 2.5.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/package.json +1 -1
- package/src/cocoa/app.js +17 -2
- package/src/cocoa/context2d.js +21 -3
- package/src/cocoa/surface.js +229 -0
- package/src/ntk.d.ts +63 -4
- package/src/ntk.js +30 -0
package/package.json
CHANGED
package/src/cocoa/app.js
CHANGED
|
@@ -24,13 +24,14 @@ import { CocoaGlobalMenuExport } from './globalmenu.js';
|
|
|
24
24
|
import { CocoaPaneHost } from './panehost.js';
|
|
25
25
|
import { CocoaPaneWindow } from './panewindow.js';
|
|
26
26
|
import { CocoaFontManager } from './fonts.js';
|
|
27
|
+
import { CocoaSurface } from './surface.js';
|
|
27
28
|
import { CocoaWindow } from './window.js';
|
|
28
29
|
import { decodeKey, modifierMask } from './keymap.js';
|
|
29
30
|
import { loadNative } from './native.js';
|
|
30
31
|
|
|
31
32
|
const RAF_INTERVAL_MS = 16;
|
|
32
33
|
|
|
33
|
-
class CocoaApp {
|
|
34
|
+
export class CocoaApp {
|
|
34
35
|
constructor(native, options = {}) {
|
|
35
36
|
this._native = native;
|
|
36
37
|
this.options = options;
|
|
@@ -53,7 +54,10 @@ class CocoaApp {
|
|
|
53
54
|
process.env.REACT_X11_COCOA_PRESENTER ??
|
|
54
55
|
'surface';
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
// the app's own bridge, so an app over a fake one (the tests) needs no
|
|
58
|
+
// real bridge on the machine — the manager's default loads it only when
|
|
59
|
+
// it is built standalone
|
|
60
|
+
this.fonts = new CocoaFontManager(native);
|
|
57
61
|
|
|
58
62
|
// AppKit-rendered control bezels. Its *presence* is the capability:
|
|
59
63
|
// `useSupports('nativeControls')` and the widget set's `controls:
|
|
@@ -189,6 +193,17 @@ class CocoaApp {
|
|
|
189
193
|
return new CocoaPaneHost(this, wnd);
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
/**
|
|
197
|
+
* The offscreen-surface seam `react-x11/ntk`'s `Surface` dispatches on:
|
|
198
|
+
* ntk's `Surface` contract over a CG bitmap (src/cocoa/surface.js). Its
|
|
199
|
+
* presence is what makes `new Surface(app, { width, height })` answer a
|
|
200
|
+
* surface here rather than ntk's pixmap, which needs an X connection — a
|
|
201
|
+
* backend without the method gets ntk's, so an X app is never asked.
|
|
202
|
+
*/
|
|
203
|
+
createSurface(options) {
|
|
204
|
+
return new CocoaSurface(this, options);
|
|
205
|
+
}
|
|
206
|
+
|
|
192
207
|
/**
|
|
193
208
|
* The `useGlobalMenu` transport seam: same owner shape as the D-Bus
|
|
194
209
|
* GlobalMenuExport (start/stop/update), pointed at the macOS menu bar.
|
package/src/cocoa/context2d.js
CHANGED
|
@@ -279,16 +279,34 @@ export class CocoaContext2D {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
save() {
|
|
282
|
+
// Sync before pushing: a fresh surface empties the stack on its way in,
|
|
283
|
+
// and a state pushed ahead of that sync was lost to it — the first
|
|
284
|
+
// save/restore pair on a new context restored nothing.
|
|
285
|
+
const surface = this._s();
|
|
282
286
|
this._stack.push({ ...this._state, dash: [...this._state.dash] });
|
|
283
|
-
this._native.ctxSave(
|
|
287
|
+
this._native.ctxSave(surface);
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
restore() {
|
|
291
|
+
// Canvas's rule: a restore with nothing saved does nothing. It is also
|
|
292
|
+
// what keeps a surface's base state safe under an unbalanced painter —
|
|
293
|
+
// the native stack below the JS one is the surface's own — and what a
|
|
294
|
+
// replaced backing surface wants, since it has nothing saved either.
|
|
295
|
+
const surface = this._s();
|
|
287
296
|
const prev = this._stack.pop();
|
|
288
|
-
if (prev)
|
|
289
|
-
this.
|
|
297
|
+
if (!prev) return;
|
|
298
|
+
this._state = prev;
|
|
299
|
+
this._native.ctxRestore(surface);
|
|
290
300
|
}
|
|
291
301
|
|
|
302
|
+
/**
|
|
303
|
+
* ntk's contract has a caller who took a context owing it a `destroy()`
|
|
304
|
+
* — there it is a GC and a Picture. Here a context is JS state over the
|
|
305
|
+
* surface's own graphics state, so there is nothing to free; the call is
|
|
306
|
+
* honoured so a caller written against ntk needs no branch.
|
|
307
|
+
*/
|
|
308
|
+
destroy() {}
|
|
309
|
+
|
|
292
310
|
_concat(a2, b2, c2, d2, e2, f2) {
|
|
293
311
|
const [a, b, c, d, e, f] = this._state.ctm;
|
|
294
312
|
this._state.ctm = [
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// An offscreen drawing surface on the Cocoa backend — ntk's `Surface`
|
|
2
|
+
// contract (draw once, composite many, and shift a retained band in place)
|
|
3
|
+
// over one @windowkit/appkit CG bitmap. `react-x11/ntk`'s `Surface` hands
|
|
4
|
+
// out one of these when the app it is given is a Cocoa app (the app's
|
|
5
|
+
// `createSurface` seam, src/cocoa/app.js), so a component allocates its
|
|
6
|
+
// buffer the same way on both backends and names neither:
|
|
7
|
+
//
|
|
8
|
+
// const surface = new Surface(app, { width, height }); // device pixels
|
|
9
|
+
// const ctx = surface.getContext('2d'); // a CocoaContext2D
|
|
10
|
+
// ctx.fillRect(0, 0, width, height);
|
|
11
|
+
// surface.copyWithin({ x: 0, y: 0, width, height }, 0, -rowHeight);
|
|
12
|
+
// windowCtx.drawImage(surface, x, y); // one composite
|
|
13
|
+
//
|
|
14
|
+
// What is the same as ntk's: the constructor, `width`/`height`/`format`/
|
|
15
|
+
// `depth`/`bytes`, `getContext`, `render`, `clear`, `copyWithin` down to its
|
|
16
|
+
// clamping (ntk#252 — integer deltas, the band that survives, false when
|
|
17
|
+
// nothing does), `destroy`/`Symbol.dispose`, and `drawImage` taking the
|
|
18
|
+
// surface as a source. What differs is stated here, because this is where
|
|
19
|
+
// it lives:
|
|
20
|
+
//
|
|
21
|
+
// - **One graphics state per surface.** CoreGraphics keeps the CTM, the
|
|
22
|
+
// clip and the path in the bitmap context itself, where an X connection
|
|
23
|
+
// keeps them per Picture/GC. So `getContext('2d')` answers the same
|
|
24
|
+
// context every time — a JS object over that one state, nothing to free,
|
|
25
|
+
// and `destroy()` on it is a no-op — and `render()` brackets its callback
|
|
26
|
+
// in save/restore from the identity transform, so a one-shot draw leaves
|
|
27
|
+
// no residue for the next painter, which is what ntk gets from building a
|
|
28
|
+
// fresh context per call.
|
|
29
|
+
// - **`format: 'a8'` is not here yet.** Coverage surfaces are what the paint
|
|
30
|
+
// cache's masks and the shadows use, and both stay on their X path; every
|
|
31
|
+
// consumer that allocates a surface of its own asks for argb32. Asking
|
|
32
|
+
// for a8 throws rather than answering a colour surface that would
|
|
33
|
+
// composite differently.
|
|
34
|
+
// - **No Picture.** `picture()` is X's compositing handle; here a surface
|
|
35
|
+
// composites through `ctx.drawImage`, and asking for the picture says so.
|
|
36
|
+
// - **Freed on collection.** The bridge frees the bitmap from the handle's
|
|
37
|
+
// finalizer; `destroy()` drops the handle and refuses further use, so the
|
|
38
|
+
// memory goes with the next GC rather than on the call.
|
|
39
|
+
//
|
|
40
|
+
// Units are device pixels, like the window's backing store: a caller sizes
|
|
41
|
+
// one from `contentBox()` numbers, which are device pixels already
|
|
42
|
+
// (docs/scale.md). The bridge is told the app's scale so the bitmap carries
|
|
43
|
+
// it — inert for a `drawImage` source, right for a layer's contents.
|
|
44
|
+
import { CocoaContext2D } from './context2d.js';
|
|
45
|
+
|
|
46
|
+
export class CocoaSurface {
|
|
47
|
+
constructor(app, { width, height, format = 'argb32' } = {}) {
|
|
48
|
+
if (
|
|
49
|
+
!Number.isInteger(width) ||
|
|
50
|
+
!Number.isInteger(height) ||
|
|
51
|
+
width <= 0 ||
|
|
52
|
+
height <= 0
|
|
53
|
+
) {
|
|
54
|
+
throw new Error('Surface: width and height must be positive integers');
|
|
55
|
+
}
|
|
56
|
+
if (format === 'a8') {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Surface: format 'a8' (a coverage surface) is not on the cocoa " +
|
|
59
|
+
'backend yet — allocate argb32, which every backend has, and ' +
|
|
60
|
+
'tint through fillStyle/globalAlpha; track docs/macos.md ' +
|
|
61
|
+
'"Custom drawing on a layer tree".',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (format !== 'argb32') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Surface: unknown format ${JSON.stringify(format)} (argb32 or a8)`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
this.app = app;
|
|
70
|
+
this.width = width;
|
|
71
|
+
this.height = height;
|
|
72
|
+
this.format = format;
|
|
73
|
+
this.depth = 32;
|
|
74
|
+
this._native = app._native;
|
|
75
|
+
this._fonts = app.fonts ?? null;
|
|
76
|
+
this._ctx = null;
|
|
77
|
+
this._destroyed = false;
|
|
78
|
+
this._surfaceHandle = this._native.createSurface(
|
|
79
|
+
width,
|
|
80
|
+
height,
|
|
81
|
+
app.scale ?? 1,
|
|
82
|
+
);
|
|
83
|
+
// a fresh bitmap's contents are the allocator's; a surface that is only
|
|
84
|
+
// partly drawn must composite nothing where nothing was drawn
|
|
85
|
+
this.clear();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** bytes of backing storage — what a cache budgets against */
|
|
89
|
+
get bytes() {
|
|
90
|
+
return this.width * this.height * 4;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** X's compositing handle, which this backend does not have. */
|
|
94
|
+
picture() {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'Surface: a surface on the cocoa backend has no XRender Picture — ' +
|
|
97
|
+
'composite it with ctx.drawImage(surface, x, y), which takes a ' +
|
|
98
|
+
'surface directly on both backends.',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_handle() {
|
|
103
|
+
if (this._destroyed) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'Surface: destroyed — a context on a destroyed surface cannot ' +
|
|
106
|
+
'draw; allocate a new Surface and draw into that.',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return this._surfaceHandle;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_context() {
|
|
113
|
+
if (!this._ctx) {
|
|
114
|
+
this._ctx = new CocoaContext2D(
|
|
115
|
+
this._native,
|
|
116
|
+
() => this._handle(),
|
|
117
|
+
() => 1,
|
|
118
|
+
);
|
|
119
|
+
this._ctx._fonts = this._fonts;
|
|
120
|
+
}
|
|
121
|
+
return this._ctx;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The 2d context on the bitmap — the same one every time, since the
|
|
126
|
+
* bitmap has one graphics state (see the header). ntk's contract has the
|
|
127
|
+
* caller owning it and owing it a `destroy()`; that call is honoured as a
|
|
128
|
+
* no-op, so a caller written against ntk needs no branch.
|
|
129
|
+
*/
|
|
130
|
+
getContext(name = '2d') {
|
|
131
|
+
this._handle();
|
|
132
|
+
if (name !== '2d') {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Surface: getContext(${JSON.stringify(name)}) — a surface on the ` +
|
|
135
|
+
"cocoa backend has a '2d' context and nothing else.",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return this._context();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Draw into the surface through a context that starts clean — identity
|
|
143
|
+
* transform, the fill and line state as they were — and leaves the
|
|
144
|
+
* surface's state as it found it: the save/restore bracket stands in for
|
|
145
|
+
* the per-call context ntk builds and destroys.
|
|
146
|
+
*/
|
|
147
|
+
render(fn) {
|
|
148
|
+
const ctx = this.getContext('2d');
|
|
149
|
+
ctx.save();
|
|
150
|
+
try {
|
|
151
|
+
ctx.resetTransform();
|
|
152
|
+
fn(ctx);
|
|
153
|
+
} finally {
|
|
154
|
+
ctx.restore();
|
|
155
|
+
}
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Reset every pixel to fully transparent, whatever transform a live
|
|
160
|
+
* context holds — the clear is issued from the identity. */
|
|
161
|
+
clear() {
|
|
162
|
+
if (this._destroyed) return this;
|
|
163
|
+
const ctx = this._context();
|
|
164
|
+
ctx.save();
|
|
165
|
+
try {
|
|
166
|
+
ctx.resetTransform();
|
|
167
|
+
ctx.clearRect(0, 0, this.width, this.height);
|
|
168
|
+
} finally {
|
|
169
|
+
ctx.restore();
|
|
170
|
+
}
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Scroll the pixels of `src` (surface coordinates, `{x, y, width,
|
|
176
|
+
* height}`) by (dx, dy) in place: one in-place copy of the band that
|
|
177
|
+
* survives the shift (the bridge's `scrollSurface`, a memmove per row), in
|
|
178
|
+
* place of redrawing everything that merely moved. True when the copy was
|
|
179
|
+
* issued; false means "nothing survives the shift here" and the caller
|
|
180
|
+
* repaints `src` exactly as it would have without this method.
|
|
181
|
+
*
|
|
182
|
+
* ntk#252's contract, clamp for clamp: refused when the delta is
|
|
183
|
+
* fractional (a sub-pixel shift changes every pixel), when it is zero,
|
|
184
|
+
* when nothing of `src` survives after clamping to the surface, or on a
|
|
185
|
+
* destroyed surface. The band is `clamped src ∩ (clamped src + delta)`,
|
|
186
|
+
* so nothing outside `src` is written; the overlap is safe because the
|
|
187
|
+
* copy walks rows in the direction that reads before it overwrites.
|
|
188
|
+
*/
|
|
189
|
+
copyWithin(src, dx, dy) {
|
|
190
|
+
if (this._destroyed) return false;
|
|
191
|
+
if (!Number.isInteger(dx) || !Number.isInteger(dy)) return false;
|
|
192
|
+
if (dx === 0 && dy === 0) return false;
|
|
193
|
+
const x0 = Math.max(0, Math.floor(src.x));
|
|
194
|
+
const y0 = Math.max(0, Math.floor(src.y));
|
|
195
|
+
const x1 = Math.min(this.width, Math.ceil(src.x + src.width));
|
|
196
|
+
const y1 = Math.min(this.height, Math.ceil(src.y + src.height));
|
|
197
|
+
const dstX0 = Math.max(x0, x0 + dx);
|
|
198
|
+
const dstY0 = Math.max(y0, y0 + dy);
|
|
199
|
+
const dstX1 = Math.min(x1, x1 + dx);
|
|
200
|
+
const dstY1 = Math.min(y1, y1 + dy);
|
|
201
|
+
// written as the positive test so a NaN edge (a rect with no numbers
|
|
202
|
+
// in it) is a refusal too, never a native call with garbage
|
|
203
|
+
if (!(dstX1 > dstX0 && dstY1 > dstY0)) return false;
|
|
204
|
+
return Boolean(
|
|
205
|
+
this._native.scrollSurface(
|
|
206
|
+
this._surfaceHandle,
|
|
207
|
+
x0,
|
|
208
|
+
y0,
|
|
209
|
+
x1 - x0,
|
|
210
|
+
y1 - y0,
|
|
211
|
+
dx,
|
|
212
|
+
dy,
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
destroy() {
|
|
218
|
+
if (this._destroyed) return;
|
|
219
|
+
this._destroyed = true;
|
|
220
|
+
this._surfaceHandle = null;
|
|
221
|
+
this._ctx = null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
[Symbol.dispose]() {
|
|
225
|
+
this.destroy();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default CocoaSurface;
|
package/src/ntk.d.ts
CHANGED
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
* than a hand-written mirror that would drift out of date silently. The
|
|
17
17
|
* named exports are the ones an extension actually reaches for; anything
|
|
18
18
|
* else ntk has is still there at runtime.
|
|
19
|
+
*
|
|
20
|
+
* `Surface` is the exception, typed in full: it is react-x11's own class,
|
|
21
|
+
* answering ntk's pixmap on an X connection and a CG bitmap on the cocoa
|
|
22
|
+
* backend, so its shape is this package's to declare.
|
|
19
23
|
*/
|
|
24
|
+
import type { Context2D } from './node.js';
|
|
25
|
+
|
|
20
26
|
export const createClient: (
|
|
21
27
|
options?: Record<string, unknown>,
|
|
22
28
|
) => Promise<unknown>;
|
|
@@ -26,12 +32,65 @@ export const Clipboard: new (...args: unknown[]) => unknown;
|
|
|
26
32
|
export const Path2D: new (...args: unknown[]) => unknown;
|
|
27
33
|
export const Image: new (...args: unknown[]) => unknown;
|
|
28
34
|
export const Pixmap: new (...args: unknown[]) => unknown;
|
|
35
|
+
|
|
36
|
+
/** What `new Surface(app, options)` takes: a size in device pixels. */
|
|
37
|
+
export interface SurfaceOptions {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
/**
|
|
41
|
+
* `'argb32'` (the default) on every backend. `'a8'`, a coverage surface
|
|
42
|
+
* that composites as a mask for the fill style, is X11-only today and
|
|
43
|
+
* throws on the cocoa backend.
|
|
44
|
+
*/
|
|
45
|
+
format?: 'argb32' | 'a8';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A rectangle in surface coordinates — what `copyWithin` shifts. */
|
|
49
|
+
export interface SurfaceRect {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
/**
|
|
30
|
-
*
|
|
31
|
-
* buffer, `copyWithin(src, dx, dy)` shifts the
|
|
32
|
-
*
|
|
57
|
+
* An offscreen surface: draw once, composite many — and, for an element
|
|
58
|
+
* that scrolls a retained buffer, `copyWithin(src, dx, dy)` shifts the
|
|
59
|
+
* surviving band in place. On an X connection it is ntk's pixmap and
|
|
60
|
+
* Picture; on the cocoa backend a CG bitmap; the same object shape either
|
|
61
|
+
* way, and `ctx.drawImage(surface, …)` takes it as a source on both. See
|
|
62
|
+
* [extending.md](../docs/extending.md) "Scrolling the pixels, not just the
|
|
63
|
+
* offset".
|
|
33
64
|
*/
|
|
34
|
-
export
|
|
65
|
+
export interface Surface {
|
|
66
|
+
readonly app: unknown;
|
|
67
|
+
readonly width: number;
|
|
68
|
+
readonly height: number;
|
|
69
|
+
readonly format: 'argb32' | 'a8';
|
|
70
|
+
readonly depth: 8 | 32;
|
|
71
|
+
/** Bytes of backing storage — what a cache budgets against. */
|
|
72
|
+
readonly bytes: number;
|
|
73
|
+
/**
|
|
74
|
+
* A 2d context on the surface. The caller owns it and owes it a
|
|
75
|
+
* `destroy()` — real on X11 (a GC and a Picture), a no-op on cocoa, where
|
|
76
|
+
* a surface has one context for its whole life.
|
|
77
|
+
*/
|
|
78
|
+
getContext(name: '2d', ...args: unknown[]): Context2D;
|
|
79
|
+
/** Draw through a context that exists for the call. */
|
|
80
|
+
render(fn: (ctx: Context2D) => void): this;
|
|
81
|
+
/** Reset every pixel to fully transparent. */
|
|
82
|
+
clear(): this;
|
|
83
|
+
/**
|
|
84
|
+
* Shift `src` by a whole-pixel delta in place; true when a band survived
|
|
85
|
+
* the shift and was copied, false when the caller should repaint `src`.
|
|
86
|
+
*/
|
|
87
|
+
copyWithin(src: SurfaceRect, dx: number, dy: number): boolean;
|
|
88
|
+
/** X11 only — the server-side Picture, for `<image picture>`. Throws on the cocoa backend. */
|
|
89
|
+
picture(app?: unknown): unknown;
|
|
90
|
+
destroy(): void;
|
|
91
|
+
[Symbol.dispose](): void;
|
|
92
|
+
}
|
|
93
|
+
export const Surface: new (app: unknown, options: SurfaceOptions) => Surface;
|
|
35
94
|
/** `code` values on a failed GL setup — see `<glarea onError>`. */
|
|
36
95
|
export const GLXError: {
|
|
37
96
|
NO_EXTENSION: 'GLX_NO_EXTENSION';
|
package/src/ntk.js
CHANGED
|
@@ -21,5 +21,35 @@
|
|
|
21
21
|
// for one — they were reachable but never declared — wants
|
|
22
22
|
// `@react-x11/components` (`<Markdown>`, `<Formula>`). `SvgView` is still
|
|
23
23
|
// here; a drawing is not a document.
|
|
24
|
+
//
|
|
25
|
+
// One name is not a plain re-export. `Surface` below asks the app it is
|
|
26
|
+
// handed for the implementation, because ntk's own is a pixmap and a
|
|
27
|
+
// Picture — an X connection's — and a component allocates its buffer
|
|
28
|
+
// without knowing which backend it was mounted on. This subpath is where a
|
|
29
|
+
// drawing-adjacent name gets its backend-neutral answer; the X-only names
|
|
30
|
+
// (`createClient`, `Pixmap`, `Picture`, `XEmbedSocket`) stay X-only.
|
|
31
|
+
import { Surface as NtkSurface } from 'ntk';
|
|
32
|
+
|
|
24
33
|
export * from 'ntk';
|
|
25
34
|
export { default } from 'ntk';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ntk's offscreen `Surface`, on whichever backend `app` is.
|
|
38
|
+
*
|
|
39
|
+
* An app that makes its own surfaces answers `createSurface(options)` —
|
|
40
|
+
* the Cocoa app does, over a CG bitmap (src/cocoa/surface.js) — and an ntk
|
|
41
|
+
* connection has no such method and gets ntk's pixmap. The result is
|
|
42
|
+
* whichever implementation answered, not an instance of this class: the
|
|
43
|
+
* contract is the shape — `width`/`height`, `getContext('2d')`, `render`,
|
|
44
|
+
* `clear`, `copyWithin`, `destroy`, and `ctx.drawImage(surface, …)` —
|
|
45
|
+
* (docs/extending.md "Scrolling the pixels, not just the offset"), and
|
|
46
|
+
* nothing needs `instanceof`.
|
|
47
|
+
*/
|
|
48
|
+
export class Surface {
|
|
49
|
+
constructor(app, options) {
|
|
50
|
+
if (typeof app?.createSurface === 'function') {
|
|
51
|
+
return app.createSurface(options);
|
|
52
|
+
}
|
|
53
|
+
return new NtkSurface(app, options);
|
|
54
|
+
}
|
|
55
|
+
}
|