@octane-xplat/macos-renderer 0.11.0 → 0.11.1
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 +152 -8
- package/package.json +4 -3
- package/src/appearance.ts +56 -0
- package/src/appkit.d.ts +303 -30
- package/src/image.ts +92 -0
- package/src/index.ts +1112 -390
- package/src/intrinsics.d.ts +8 -2
- package/src/{layer.mjs → layer.ts} +74 -22
- package/src/presentation.d.mts +29 -0
- package/src/{presentation.mjs → presentation.ts} +102 -17
package/src/appkit.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Ambient host globals injected by @nativescript/macos-node-api and the
|
|
2
2
|
// embedding Node process. The prerelease ships no generated AppKit metadata
|
|
3
|
-
// types, so
|
|
4
|
-
//
|
|
3
|
+
// types, so the declarations below cover the native surface the renderer
|
|
4
|
+
// calls for event and control wiring (target/action, gesture recognizers,
|
|
5
|
+
// tracking areas, notification observers, menus, pickers, sheets). Members
|
|
6
|
+
// outside that surface still resolve to `any` through the index signatures.
|
|
5
7
|
|
|
6
8
|
type NSClass<T> = {
|
|
7
9
|
new (...args: any[]): T
|
|
@@ -9,13 +11,32 @@ type NSClass<T> = {
|
|
|
9
11
|
[key: string]: any
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
+
// Core Graphics geometry, bridged as plain objects.
|
|
15
|
+
interface NSPoint {
|
|
16
|
+
x: number
|
|
17
|
+
y: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface NSSize {
|
|
21
|
+
width: number
|
|
22
|
+
height: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface NSRect {
|
|
26
|
+
origin: NSPoint
|
|
27
|
+
size: NSSize
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Objective-C classes. Instances expose bridged members beyond the declared
|
|
31
|
+
// surface as `any`; the type name is real so parameters and node structs can
|
|
32
|
+
// reference them.
|
|
14
33
|
declare class NSObject {
|
|
15
34
|
[key: string]: any
|
|
16
35
|
static alloc(): any
|
|
17
36
|
static new(): any
|
|
18
37
|
static extend(methods: object, options?: object): any
|
|
38
|
+
init(): this
|
|
39
|
+
isEqual?(other: any): boolean
|
|
19
40
|
}
|
|
20
41
|
|
|
21
42
|
interface NSAttributedString extends NSObject {}
|
|
@@ -31,16 +52,52 @@ interface NSURLRequest extends NSObject {}
|
|
|
31
52
|
declare const NSURLRequest: NSClass<NSURLRequest>
|
|
32
53
|
|
|
33
54
|
// WebKit is dlopen'd at runtime by the webview leaf.
|
|
34
|
-
interface WKWebView extends NSView {
|
|
55
|
+
interface WKWebView extends NSView {
|
|
56
|
+
navigationDelegate: any
|
|
57
|
+
evaluateJavaScriptCompletionHandler(script: string, handler: any): void
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
declare const WKWebView: NSClass<WKWebView>
|
|
36
61
|
|
|
37
62
|
interface WKWebViewConfiguration extends NSObject {}
|
|
38
63
|
declare const WKWebViewConfiguration: NSClass<WKWebViewConfiguration>
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
// NSControl carries the target/action channel shared by buttons, sliders,
|
|
66
|
+
// text fields, and date pickers.
|
|
67
|
+
interface NSControl extends NSView {
|
|
68
|
+
target: any
|
|
69
|
+
action: string | null
|
|
70
|
+
enabled: boolean
|
|
71
|
+
doubleValue: number
|
|
72
|
+
font: any
|
|
73
|
+
textColor: any
|
|
74
|
+
cell: any
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface NSButton extends NSControl {
|
|
78
|
+
bezelStyle: number
|
|
79
|
+
state: number
|
|
80
|
+
setButtonType(type: number): void
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface NSButtonClass extends NSClass<NSButton> {
|
|
84
|
+
buttonWithTitleTargetAction(title: string, target: any, action: string): NSButton
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare const NSButton: NSButtonClass
|
|
88
|
+
|
|
89
|
+
interface NSGestureRecognizer extends NSObject {
|
|
90
|
+
initWithTargetAction(target: any, action: string): this
|
|
91
|
+
readonly view: NSView
|
|
92
|
+
state: number
|
|
93
|
+
enabled: boolean
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface NSClickGestureRecognizer extends NSGestureRecognizer {
|
|
97
|
+
numberOfClicksRequired: number
|
|
98
|
+
buttonMask: number
|
|
99
|
+
}
|
|
42
100
|
|
|
43
|
-
interface NSClickGestureRecognizer extends NSObject {}
|
|
44
101
|
declare const NSClickGestureRecognizer: NSClass<NSClickGestureRecognizer>
|
|
45
102
|
|
|
46
103
|
interface NSColor extends NSObject {}
|
|
@@ -52,17 +109,51 @@ declare const NSColorSpace: NSClass<NSColorSpace>
|
|
|
52
109
|
interface NSData extends NSObject {}
|
|
53
110
|
declare const NSData: NSClass<NSData>
|
|
54
111
|
|
|
55
|
-
interface NSDate extends NSObject {
|
|
56
|
-
|
|
112
|
+
interface NSDate extends NSObject {
|
|
113
|
+
readonly timeIntervalSince1970: number
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface NSDateClass extends NSClass<NSDate> {
|
|
117
|
+
dateWithTimeIntervalSince1970(seconds: number): NSDate
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
declare const NSDate: NSDateClass
|
|
121
|
+
|
|
122
|
+
interface NSDatePicker extends NSControl {
|
|
123
|
+
datePickerElements: number
|
|
124
|
+
datePickerStyle: number
|
|
125
|
+
dateValue: NSDate | null
|
|
126
|
+
minDate: NSDate | null
|
|
127
|
+
maxDate: NSDate | null
|
|
128
|
+
}
|
|
57
129
|
|
|
58
|
-
interface NSDatePicker extends NSView {}
|
|
59
130
|
declare const NSDatePicker: NSClass<NSDatePicker>
|
|
60
131
|
|
|
61
132
|
interface NSEdgeInsets extends NSObject {}
|
|
62
133
|
declare const NSEdgeInsets: NSClass<NSEdgeInsets>
|
|
63
134
|
|
|
64
|
-
interface NSEvent extends NSObject {
|
|
65
|
-
|
|
135
|
+
interface NSEvent extends NSObject {
|
|
136
|
+
readonly type: number
|
|
137
|
+
readonly keyCode: number
|
|
138
|
+
readonly modifierFlags: number
|
|
139
|
+
readonly window: NSWindow | null
|
|
140
|
+
readonly locationInWindow: NSPoint
|
|
141
|
+
readonly trackingArea: NSTrackingArea
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface NSEventClass extends NSClass<NSEvent> {
|
|
145
|
+
addLocalMonitorForEventsMatchingMaskHandler(
|
|
146
|
+
mask: number,
|
|
147
|
+
handler: (event: NSEvent) => NSEvent | null,
|
|
148
|
+
): any
|
|
149
|
+
addGlobalMonitorForEventsMatchingMaskHandler(
|
|
150
|
+
mask: number,
|
|
151
|
+
handler: (event: NSEvent) => NSEvent | null,
|
|
152
|
+
): any
|
|
153
|
+
removeMonitor(monitor: any): void
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declare const NSEvent: NSEventClass
|
|
66
157
|
|
|
67
158
|
interface NSFont extends NSObject {}
|
|
68
159
|
declare const NSFont: NSClass<NSFont>
|
|
@@ -76,63 +167,241 @@ declare const NSFontManager: NSClass<NSFontManager>
|
|
|
76
167
|
interface NSImage extends NSObject {}
|
|
77
168
|
declare const NSImage: NSClass<NSImage>
|
|
78
169
|
|
|
79
|
-
interface NSImageView extends
|
|
170
|
+
interface NSImageView extends NSControl {
|
|
171
|
+
image: NSImage | null
|
|
172
|
+
imageScaling: number
|
|
173
|
+
}
|
|
174
|
+
|
|
80
175
|
declare const NSImageView: NSClass<NSImageView>
|
|
81
176
|
|
|
82
|
-
interface NSMenu extends NSObject {
|
|
177
|
+
interface NSMenu extends NSObject {
|
|
178
|
+
autoenablesItems: boolean
|
|
179
|
+
addItem(item: NSMenuItem): void
|
|
180
|
+
}
|
|
181
|
+
|
|
83
182
|
declare const NSMenu: NSClass<NSMenu>
|
|
84
183
|
|
|
85
|
-
interface NSMenuItem extends NSObject {
|
|
86
|
-
|
|
184
|
+
interface NSMenuItem extends NSObject {
|
|
185
|
+
initWithTitleActionKeyEquivalent(
|
|
186
|
+
title: string,
|
|
187
|
+
action: string | null,
|
|
188
|
+
keyEquivalent: string,
|
|
189
|
+
): this
|
|
190
|
+
target: any
|
|
191
|
+
action: string | null
|
|
192
|
+
enabled: boolean
|
|
193
|
+
tag?: number
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface NSMenuItemClass extends NSClass<NSMenuItem> {
|
|
197
|
+
separatorItem(): NSMenuItem
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
declare const NSMenuItem: NSMenuItemClass
|
|
201
|
+
|
|
202
|
+
interface NSApplication extends NSObject {
|
|
203
|
+
readonly keyWindow: NSWindow | null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface NSApplicationClass extends NSClass<NSApplication> {
|
|
207
|
+
readonly sharedApplication: NSApplication
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
declare const NSApplication: NSApplicationClass
|
|
211
|
+
|
|
212
|
+
interface NSAppearance extends NSObject {}
|
|
213
|
+
interface NSAppearanceClass extends NSClass<NSAppearance> {
|
|
214
|
+
appearanceNamed(name: string): NSAppearance
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
declare const NSAppearance: NSAppearanceClass
|
|
87
218
|
|
|
88
219
|
interface NSMutableParagraphStyle extends NSObject {}
|
|
89
220
|
declare const NSMutableParagraphStyle: NSClass<NSMutableParagraphStyle>
|
|
90
221
|
|
|
91
|
-
interface NSNotification extends NSObject {
|
|
222
|
+
interface NSNotification extends NSObject {
|
|
223
|
+
readonly object: any
|
|
224
|
+
}
|
|
225
|
+
|
|
92
226
|
declare const NSNotification: NSClass<NSNotification>
|
|
93
227
|
|
|
94
|
-
interface NSNotificationCenter extends NSObject {
|
|
95
|
-
|
|
228
|
+
interface NSNotificationCenter extends NSObject {
|
|
229
|
+
addObserverForNameObjectQueueUsingBlock(
|
|
230
|
+
name: string,
|
|
231
|
+
object: any,
|
|
232
|
+
queue: any,
|
|
233
|
+
block: () => void,
|
|
234
|
+
): any
|
|
235
|
+
removeObserver(observer: any): void
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface NSNotificationCenterClass extends NSClass<NSNotificationCenter> {
|
|
239
|
+
readonly defaultCenter: NSNotificationCenter
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
declare const NSNotificationCenter: NSNotificationCenterClass
|
|
96
243
|
|
|
97
244
|
interface NSNumber extends NSObject {}
|
|
98
245
|
declare const NSNumber: NSClass<NSNumber>
|
|
99
246
|
|
|
100
|
-
interface NSPanGestureRecognizer extends
|
|
247
|
+
interface NSPanGestureRecognizer extends NSGestureRecognizer {
|
|
248
|
+
translationInView(view: NSView | null): NSPoint
|
|
249
|
+
velocityInView(view: NSView | null): NSPoint
|
|
250
|
+
}
|
|
251
|
+
|
|
101
252
|
declare const NSPanGestureRecognizer: NSClass<NSPanGestureRecognizer>
|
|
102
253
|
|
|
103
|
-
interface NSPopover extends NSObject {
|
|
254
|
+
interface NSPopover extends NSObject {
|
|
255
|
+
contentViewController: NSViewController
|
|
256
|
+
contentSize: NSSize
|
|
257
|
+
behavior: number
|
|
258
|
+
animates: boolean
|
|
259
|
+
showRelativeToRectOfViewPreferredEdge(rect: NSRect, view: NSView, preferredEdge: number): void
|
|
260
|
+
close(): void
|
|
261
|
+
}
|
|
262
|
+
|
|
104
263
|
declare const NSPopover: NSClass<NSPopover>
|
|
105
264
|
|
|
106
|
-
interface NSScrollView extends NSView {
|
|
265
|
+
interface NSScrollView extends NSView {
|
|
266
|
+
documentView: NSView | null
|
|
267
|
+
contentView: any
|
|
268
|
+
borderType: number
|
|
269
|
+
drawsBackground: boolean
|
|
270
|
+
hasVerticalScroller: boolean
|
|
271
|
+
hasHorizontalScroller: boolean
|
|
272
|
+
reflectScrolledClipView(clipView: any): void
|
|
273
|
+
}
|
|
274
|
+
|
|
107
275
|
declare const NSScrollView: NSClass<NSScrollView>
|
|
108
276
|
|
|
109
|
-
interface NSSlider extends
|
|
277
|
+
interface NSSlider extends NSControl {
|
|
278
|
+
minValue: number
|
|
279
|
+
maxValue: number
|
|
280
|
+
}
|
|
281
|
+
|
|
110
282
|
declare const NSSlider: NSClass<NSSlider>
|
|
111
283
|
|
|
112
284
|
interface NSStackView extends NSView {}
|
|
113
285
|
declare const NSStackView: NSClass<NSStackView>
|
|
114
286
|
|
|
115
|
-
interface NSTextField extends
|
|
287
|
+
interface NSTextField extends NSControl {
|
|
288
|
+
stringValue: string
|
|
289
|
+
placeholderString?: string
|
|
290
|
+
placeholderAttributedString: any
|
|
291
|
+
bezeled: boolean
|
|
292
|
+
drawsBackground: boolean
|
|
293
|
+
editable: boolean
|
|
294
|
+
selectable: boolean
|
|
295
|
+
sendsActionOnEndEditing: boolean
|
|
296
|
+
alignment: number
|
|
297
|
+
}
|
|
298
|
+
|
|
116
299
|
declare const NSTextField: NSClass<NSTextField>
|
|
117
300
|
|
|
118
|
-
interface
|
|
301
|
+
interface NSSecureTextField extends NSTextField {}
|
|
302
|
+
declare const NSSecureTextField: NSClass<NSSecureTextField>
|
|
303
|
+
|
|
304
|
+
interface NSTextView extends NSView {
|
|
305
|
+
string: string
|
|
306
|
+
editable: boolean
|
|
307
|
+
selectable: boolean
|
|
308
|
+
drawsBackground: boolean
|
|
309
|
+
delegate: any
|
|
310
|
+
font: any
|
|
311
|
+
textColor: any
|
|
312
|
+
textContainer: any
|
|
313
|
+
textContainerInset: NSSize
|
|
314
|
+
}
|
|
315
|
+
|
|
119
316
|
declare const NSTextView: NSClass<NSTextView>
|
|
120
317
|
|
|
121
|
-
interface NSTrackingArea extends NSObject {
|
|
318
|
+
interface NSTrackingArea extends NSObject {
|
|
319
|
+
initWithRectOptionsOwnerUserInfo(rect: NSRect, options: number, owner: any, userInfo: any): this
|
|
320
|
+
}
|
|
321
|
+
|
|
122
322
|
declare const NSTrackingArea: NSClass<NSTrackingArea>
|
|
123
323
|
|
|
124
324
|
interface NSURL extends NSObject {}
|
|
125
325
|
declare const NSURL: NSClass<NSURL>
|
|
126
326
|
|
|
127
|
-
interface NSView extends NSObject {
|
|
327
|
+
interface NSView extends NSObject {
|
|
328
|
+
initWithFrame(frame: NSRect): this
|
|
329
|
+
addSubview(child: NSView): void
|
|
330
|
+
removeFromSuperview(): void
|
|
331
|
+
addGestureRecognizer(recognizer: NSGestureRecognizer): void
|
|
332
|
+
removeGestureRecognizer(recognizer: NSGestureRecognizer): void
|
|
333
|
+
addTrackingArea(area: NSTrackingArea): void
|
|
334
|
+
removeTrackingArea(area: NSTrackingArea): void
|
|
335
|
+
hitTest(point: NSPoint): NSView | null
|
|
336
|
+
acceptsFirstResponder(): boolean
|
|
337
|
+
keyDown(event: NSEvent): void
|
|
338
|
+
layout(): void
|
|
339
|
+
layoutSubtreeIfNeeded(): void
|
|
340
|
+
convertRectToView(rect: NSRect, view: NSView | null): NSRect
|
|
341
|
+
isFlipped(): boolean
|
|
342
|
+
frame: NSRect
|
|
343
|
+
bounds: NSRect
|
|
344
|
+
readonly fittingSize: NSSize
|
|
345
|
+
readonly superview: NSView | null
|
|
346
|
+
readonly window: NSWindow | null
|
|
347
|
+
hidden: boolean
|
|
348
|
+
tag?: number
|
|
349
|
+
menu: NSMenu | null
|
|
350
|
+
alphaValue: number
|
|
351
|
+
autoresizingMask?: number
|
|
352
|
+
translatesAutoresizingMaskIntoConstraints: boolean
|
|
353
|
+
wantsLayer: boolean
|
|
354
|
+
postsBoundsChangedNotifications: boolean
|
|
355
|
+
postsFrameChangedNotifications: boolean
|
|
356
|
+
}
|
|
357
|
+
|
|
128
358
|
declare const NSView: NSClass<NSView>
|
|
129
359
|
|
|
130
|
-
interface NSViewController extends NSObject {
|
|
360
|
+
interface NSViewController extends NSObject {
|
|
361
|
+
view: NSView
|
|
362
|
+
preferredContentSize: NSSize
|
|
363
|
+
}
|
|
364
|
+
|
|
131
365
|
declare const NSViewController: NSClass<NSViewController>
|
|
132
366
|
|
|
133
|
-
interface NSWindow extends NSObject {
|
|
367
|
+
interface NSWindow extends NSObject {
|
|
368
|
+
initWithContentRectStyleMaskBackingDefer(
|
|
369
|
+
contentRect: NSRect,
|
|
370
|
+
styleMask: number,
|
|
371
|
+
backing: number,
|
|
372
|
+
defer: boolean,
|
|
373
|
+
): this
|
|
374
|
+
contentView: NSView
|
|
375
|
+
contentViewController: NSViewController
|
|
376
|
+
frame: NSRect
|
|
377
|
+
firstResponder: any
|
|
378
|
+
delegate: any
|
|
379
|
+
effectiveAppearance: any
|
|
380
|
+
releasedWhenClosed: boolean
|
|
381
|
+
makeFirstResponder(responder: any): boolean
|
|
382
|
+
beginSheetCompletionHandler(sheet: NSWindow, handler: () => void): void
|
|
383
|
+
endSheet(sheet: NSWindow): void
|
|
384
|
+
orderOut(sender: any): void
|
|
385
|
+
center(): void
|
|
386
|
+
makeKeyAndOrderFront(sender: any): void
|
|
387
|
+
}
|
|
388
|
+
|
|
134
389
|
declare const NSWindow: NSClass<NSWindow>
|
|
135
390
|
|
|
391
|
+
// QuartzCore, referenced lazily by the presentation bridge for sheet
|
|
392
|
+
// transition fades.
|
|
393
|
+
interface CABasicAnimation extends NSObject {
|
|
394
|
+
fromValue: any
|
|
395
|
+
toValue: any
|
|
396
|
+
duration: number
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
interface CABasicAnimationClass extends NSClass<CABasicAnimation> {
|
|
400
|
+
animationWithKeyPath(keyPath: string): CABasicAnimation
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
declare const CABasicAnimation: CABasicAnimationClass
|
|
404
|
+
|
|
136
405
|
// Enum containers. Members resolve to `number`, so bitwise masks compose.
|
|
137
406
|
declare const NSBackingStoreType: { [key: string]: number }
|
|
138
407
|
declare const NSBezelStyle: { [key: string]: number }
|
|
@@ -145,6 +414,7 @@ declare const NSPopoverBehavior: { [key: string]: number }
|
|
|
145
414
|
declare const NSRectEdge: { [key: string]: number }
|
|
146
415
|
declare const NSStackViewDistribution: { [key: string]: number }
|
|
147
416
|
declare const NSStackViewGravity: { [key: string]: number }
|
|
417
|
+
declare const NSLineBreakMode: { [key: string]: number }
|
|
148
418
|
declare const NSTextAlignment: { [key: string]: number }
|
|
149
419
|
declare const NSTrackingAreaOptions: { [key: string]: number }
|
|
150
420
|
declare const NSUserInterfaceLayoutOrientation: { [key: string]: number }
|
|
@@ -176,9 +446,12 @@ declare function dlopen(path: string, flags: number): unknown
|
|
|
176
446
|
declare var __xplatAppKit: {
|
|
177
447
|
observeHover?: (view: NSView, handlers: any) => void
|
|
178
448
|
showAnchoredPopup?: (options: any) => void
|
|
449
|
+
showLayer?: (options: any) => any
|
|
179
450
|
attachContextMenu?: (view: NSView, options: any, onSelect: any) => void
|
|
180
451
|
attachDatePicker?: (view: NSView, options: any, onChange: any) => void
|
|
181
452
|
presentSheet?: (view: NSView, options: any) => void
|
|
453
|
+
presentSurface?: (options: any) => any
|
|
454
|
+
setSurfaceAppearance?: (view: NSView, dark: boolean) => void
|
|
182
455
|
[key: string]: any
|
|
183
456
|
}
|
|
184
457
|
|
package/src/image.ts
CHANGED
|
@@ -21,3 +21,95 @@ export function loadImage(source: unknown): NSImage | null {
|
|
|
21
21
|
|
|
22
22
|
return null
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
/** Per-view load state. The generation invalidates in-flight decodes when the
|
|
26
|
+
* source changes or `disposeImage` runs — the same guard the webview leaf
|
|
27
|
+
* applies to async measurements. */
|
|
28
|
+
interface ImageRequest {
|
|
29
|
+
source: string
|
|
30
|
+
generation: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const requests = new WeakMap<object, ImageRequest>()
|
|
34
|
+
|
|
35
|
+
/** Decoded images shared across views; bounded so galleries cannot retain
|
|
36
|
+
* every source they have ever shown. Failures are not cached — a file that
|
|
37
|
+
* appears later or a retry must still decode. */
|
|
38
|
+
const cache = new Map<string, NSImage>()
|
|
39
|
+
const pending = new Map<string, ((image: NSImage | null) => void)[]>()
|
|
40
|
+
const IMAGE_CACHE_LIMIT = 64
|
|
41
|
+
|
|
42
|
+
function decodeImage(source: string) {
|
|
43
|
+
const callbacks = pending.get(source) ?? []
|
|
44
|
+
pending.delete(source)
|
|
45
|
+
|
|
46
|
+
let image: NSImage | null = null
|
|
47
|
+
try {
|
|
48
|
+
image = loadImage(source)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('[macos-image] decode failed', error)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (image) {
|
|
54
|
+
cache.delete(source)
|
|
55
|
+
cache.set(source, image)
|
|
56
|
+
while (cache.size > IMAGE_CACHE_LIMIT) {
|
|
57
|
+
cache.delete(cache.keys().next().value!)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const callback of callbacks) {
|
|
62
|
+
callback(image)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Points `view` at `source`. Cached decodes assign immediately; anything else
|
|
67
|
+
* decodes on a later run-loop turn so command batches never block on image
|
|
68
|
+
* I/O. The previous image stays visible until its replacement resolves. */
|
|
69
|
+
export function updateImage(view: NSImageView, source: unknown) {
|
|
70
|
+
const src = String(source ?? '')
|
|
71
|
+
let request = requests.get(view)
|
|
72
|
+
if (!request) {
|
|
73
|
+
request = { source: '', generation: 0 }
|
|
74
|
+
requests.set(view, request)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (request.source === src) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
request.source = src
|
|
82
|
+
const generation = ++request.generation
|
|
83
|
+
const deliver = (image: NSImage | null) => {
|
|
84
|
+
if (requests.get(view) !== request || request.generation !== generation) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
view.image = image
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!src) {
|
|
92
|
+
deliver(null)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cached = cache.get(src)
|
|
97
|
+
if (cached) {
|
|
98
|
+
deliver(cached)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let waiters = pending.get(src)
|
|
103
|
+
if (!waiters) {
|
|
104
|
+
waiters = []
|
|
105
|
+
pending.set(src, waiters)
|
|
106
|
+
setTimeout(() => decodeImage(src), 0)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
waiters.push(deliver)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Invalidates any in-flight decode for `view`; call when its node is destroyed. */
|
|
113
|
+
export function disposeImage(view: NSImageView) {
|
|
114
|
+
requests.delete(view)
|
|
115
|
+
}
|