basalt-appkit 0.0.0-rc5

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runMacos.js +33 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +298 -0
  5. package/native/appkit/AppKitAnimationChoreographer.h +54 -0
  6. package/native/appkit/AppKitAnimationChoreographer.mm +125 -0
  7. package/native/appkit/AppKitDropTarget.h +34 -0
  8. package/native/appkit/AppKitDropTarget.mm +292 -0
  9. package/native/appkit/AppKitFocus.h +120 -0
  10. package/native/appkit/AppKitFocus.mm +293 -0
  11. package/native/appkit/AppKitImageLoader.h +80 -0
  12. package/native/appkit/AppKitImageLoader.mm +156 -0
  13. package/native/appkit/AppKitKeyEvents.h +26 -0
  14. package/native/appkit/AppKitKeyEvents.mm +97 -0
  15. package/native/appkit/AppKitMenuBar.mm +424 -0
  16. package/native/appkit/AppKitMountingManager.h +135 -0
  17. package/native/appkit/AppKitMountingManager.mm +946 -0
  18. package/native/appkit/AppKitRunLoopObserver.h +34 -0
  19. package/native/appkit/AppKitRunLoopObserver.mm +42 -0
  20. package/native/appkit/AppKitScrollView.h +168 -0
  21. package/native/appkit/AppKitScrollView.mm +550 -0
  22. package/native/appkit/AppKitSkiaContext.h +33 -0
  23. package/native/appkit/AppKitSkiaContext.mm +255 -0
  24. package/native/appkit/AppKitSkiaModule.h +53 -0
  25. package/native/appkit/AppKitSkiaModule.mm +81 -0
  26. package/native/appkit/AppKitSkiaPeer.h +37 -0
  27. package/native/appkit/AppKitSkiaPeer.mm +123 -0
  28. package/native/appkit/AppKitSkiaView.h +62 -0
  29. package/native/appkit/AppKitSnapshot.h +21 -0
  30. package/native/appkit/AppKitSnapshot.mm +91 -0
  31. package/native/appkit/AppKitTextInput.h +140 -0
  32. package/native/appkit/AppKitTextInput.mm +588 -0
  33. package/native/appkit/AppKitTextPeer.h +74 -0
  34. package/native/appkit/AppKitTextPeer.mm +353 -0
  35. package/native/appkit/AppKitTitleBar.h +90 -0
  36. package/native/appkit/AppKitTitleBar.mm +186 -0
  37. package/native/appkit/AppKitTouchDispatcher.h +138 -0
  38. package/native/appkit/AppKitTouchDispatcher.mm +514 -0
  39. package/native/appkit/AppKitWindowControl.mm +222 -0
  40. package/native/appkit/AppKitWindowModule.h +62 -0
  41. package/native/appkit/AppKitWindowModule.mm +194 -0
  42. package/native/appkit/ColorSchemeAppKit.mm +71 -0
  43. package/native/appkit/ComponentRegistryAppKit.mm +121 -0
  44. package/native/appkit/CoreTextLayout.h +43 -0
  45. package/native/appkit/CoreTextLayout.mm +252 -0
  46. package/native/appkit/CoreTextLayoutManager.mm +138 -0
  47. package/native/appkit/FontRegistryCoreText.mm +148 -0
  48. package/native/appkit/PlatformServicesAppKit.mm +540 -0
  49. package/native/appkit/RnAppKitView.h +417 -0
  50. package/native/appkit/RnAppKitView.mm +1605 -0
  51. package/native/appkit/RnTextLayout.h +38 -0
  52. package/native/appkit/RnTextLayout.mm +168 -0
  53. package/native/appkit/demo_layout_appkit.mm +93 -0
  54. package/native/appkit/main_appkit.mm +1860 -0
  55. package/native/appkit/mount_harness_appkit.mm +197 -0
  56. package/native/tests/test_appkit_accessibility.mm +170 -0
  57. package/native/tests/test_appkit_image.mm +237 -0
  58. package/native/tests/test_appkit_input.mm +852 -0
  59. package/native/tests/test_appkit_key_events.mm +150 -0
  60. package/native/tests/test_appkit_mounting.mm +430 -0
  61. package/native/tests/test_appkit_scrollbar.mm +132 -0
  62. package/native/tests/test_appkit_services.mm +57 -0
  63. package/native/tests/test_appkit_skia.mm +212 -0
  64. package/native/tests/test_appkit_text.mm +180 -0
  65. package/native/tests/test_appkit_textinput.mm +748 -0
  66. package/native/tests/test_appkit_titlebar.mm +107 -0
  67. package/native/tests/test_appkit_view.mm +237 -0
  68. package/package.json +26 -0
  69. package/react-native.config.js +22 -0
@@ -0,0 +1,417 @@
1
+ // The macOS view layer: an NSView that Fabric's mounting can drive.
2
+ //
3
+ // Deliberately the same shape as the GTK one in `gtk/RnView.h` -- a view owns a
4
+ // tag and an absolute frame, does no layout of its own, and places each child
5
+ // at the rect the shadow tree already resolved. Yoga has done the layout by the
6
+ // time a mutation arrives, and a second layout system underneath it is the
7
+ // thing to avoid, not to add.
8
+ //
9
+ // Two decisions are worth stating because they are easy to get wrong and hard
10
+ // to notice afterwards.
11
+ //
12
+ // The view is **flipped**. AppKit's default origin is bottom-left; React
13
+ // Native's is top-left, and every frame Fabric produces assumes top-left. An
14
+ // unflipped view renders a correct-looking layout upside down about its own
15
+ // centre, which reads as a mysterious ordering bug rather than a coordinate
16
+ // one.
17
+ //
18
+ // The view is **layer-backed**, and props are layer properties rather than
19
+ // drawing code. Background colour, opacity, corner radius, clipping and
20
+ // transform all exist on CALayer with the semantics React Native wants, so
21
+ // mapping onto them is both less code and closer to what the platform will do
22
+ // well. The GTK side has to compose these by hand in a snapshot; here it would
23
+ // be work to avoid using them.
24
+
25
+ #pragma once
26
+
27
+ #ifdef __OBJC__
28
+ #import <Cocoa/Cocoa.h>
29
+
30
+ NS_ASSUME_NONNULL_BEGIN
31
+
32
+ @class RnAppKitView;
33
+
34
+ // Where a view sends the mouse.
35
+ //
36
+ // Set on the surface root only. A view that has no handler walks up to one that
37
+ // does, which is how every view in the tree feeds a single dispatcher without
38
+ // anything having to attach one per view -- the same arrangement the GTK side
39
+ // gets by putting its controllers on the root.
40
+ //
41
+ // Points arrive in the handler's own coordinates, which is the surface root's,
42
+ // which is what React Native calls the page.
43
+ //
44
+ // `button` is W3C's numbering rather than AppKit's selector-per-button, because
45
+ // that is what a pointer event carries and what decides whether the click
46
+ // presses anything at all. See core/PointerButtons.h.
47
+ @protocol RnAppKitInputHandler <NSObject>
48
+ - (void)rnMouseDownAt:(NSPoint)point button:(int)button;
49
+ - (void)rnMouseDraggedTo:(NSPoint)point;
50
+ - (void)rnMouseUpAt:(NSPoint)point button:(int)button;
51
+ // The pointer moving with no button down, and the pointer leaving the surface.
52
+ // Not part of React Native's touch model -- a finger that is not touching does
53
+ // not exist -- but it is what W3C pointer events call hover, and what
54
+ // `onPointerEnter` and friends are fed from. See core/HoverTracker.h.
55
+ - (void)rnMouseMovedTo:(NSPoint)point;
56
+ - (void)rnMouseExited;
57
+ @end
58
+
59
+ // Where a view sends a change of keyboard focus.
60
+ //
61
+ // Set on the surface root, like RnAppKitInputHandler, and found by walking up
62
+ // from whichever view gained or lost it. AppKit has no reliable notification
63
+ // for "the window's first responder changed" -- NSWindow's firstResponder is
64
+ // not KVO-observable -- so the views that can hold focus report it themselves.
65
+ @protocol RnAppKitFocusHandler <NSObject>
66
+ - (void)rnView:(RnAppKitView *)view didChangeFocus:(BOOL)focused;
67
+ // Enter or space on a focused view. Returns YES if it was handled, so a view
68
+ // with nothing listening passes the key on rather than swallowing it.
69
+ - (BOOL)rnActivateView:(RnAppKitView *)view;
70
+ // Tab and Shift-Tab. AppKit's own key-view loop cannot do this for a window
71
+ // built without a nib; see AppKitFocus.h.
72
+ - (BOOL)rnMoveFocusForward:(BOOL)forward;
73
+ // A key a view declared through `<KeyHandler>`. Returns YES if some view in the
74
+ // focus path claimed it, in which case the key is consumed; NO passes it on, and
75
+ // passing it on is what keeps the menu, a focused text field and a scrolling
76
+ // ancestor working. Asked through this protocol and not answered here because the
77
+ // registry is in core, which the view library does not link. See
78
+ // core/KeyEvents.h.
79
+ - (BOOL)rnView:(RnAppKitView *)view handlesKey:(NSEvent *)event;
80
+ @end
81
+
82
+ // Where a scrolling view sends the wheel.
83
+ //
84
+ // Set only on views that are ScrollViews. A view with none calls super, which
85
+ // walks the responder chain to an ancestor that has one -- so a wheel over a
86
+ // plain view inside a list scrolls the list, and a list inside a list scrolls
87
+ // the inner one first, which is what AppKit's own nesting does.
88
+ @protocol RnAppKitScrollHandler <NSObject>
89
+ // Returns YES if the scroll was consumed.
90
+ //
91
+ // Two phases, because a touchpad has two gestures in one stream. `phase` is
92
+ // the fingers: it begins when they land and ends when they lift. `momentum` is
93
+ // what the system keeps sending after they have gone, and it is the only
94
+ // reason React Native's `onMomentumScrollBegin` and `onMomentumScrollEnd` can
95
+ // be answered honestly on this platform -- macOS does the deceleration itself,
96
+ // so there is nothing here to model, only something to report.
97
+ - (BOOL)rnScrollView:(RnAppKitView *)view
98
+ by:(NSPoint)delta
99
+ precise:(BOOL)precise
100
+ phase:(NSEventPhase)phase
101
+ momentum:(NSEventPhase)momentum;
102
+ @end
103
+
104
+ // The deepest view at a point, in `root`'s coordinates, or nil for a miss.
105
+ //
106
+ // A free function because it is a pure function of the view tree and nothing
107
+ // else, which is also what makes it testable: hit testing is the part of input
108
+ // most likely to be quietly wrong, and it needs no mouse to exercise. It lives
109
+ // here rather than with the dispatcher so that testing it needs no React Native.
110
+ RnAppKitView *_Nullable RnAppKitHitTest(RnAppKitView *_Nullable root, CGFloat x, CGFloat y);
111
+
112
+ @interface RnAppKitView : NSView
113
+
114
+ // Fabric's tag for this view. Set once, at creation.
115
+ @property(nonatomic, readonly) NSInteger rnTag;
116
+
117
+ + (instancetype)viewWithTag:(NSInteger)tag;
118
+
119
+ // The frame the shadow tree resolved, in the parent's coordinates, top-left
120
+ // origin. Applied directly: nothing here recomputes it.
121
+ - (void)setRnFrameX:(CGFloat)x y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height;
122
+
123
+ // Components are premultiplied-free 0..1, as React Native's colour components
124
+ // arrive. Passing hasColor:NO means "no background", which is not the same as
125
+ // transparent black: a view with no background does not paint at all.
126
+ - (void)setRnBackgroundColorRed:(CGFloat)red
127
+ green:(CGFloat)green
128
+ blue:(CGFloat)blue
129
+ alpha:(CGFloat)alpha
130
+ hasColor:(BOOL)hasColor;
131
+
132
+ - (void)setRnOpacity:(CGFloat)opacity;
133
+
134
+ // React Native's transform, as sixteen floats in CSS `matrix3d` order -- which
135
+ // is CATransform3D's field order too, so the two are the same sixteen numbers
136
+ // in the same places. Null clears it.
137
+ //
138
+ // The anchor is the view's centre, which is what React Native means by an
139
+ // untouched `transformOrigin` and what a layer-backed NSView already uses.
140
+ - (void)setRnTransform:(nullable const float *)matrix;
141
+
142
+ // `backfaceVisibility: 'hidden'`. A view whose transform has turned it away
143
+ // from the viewer is not drawn at all -- nor are its children, which is what
144
+ // CALayer's own `doubleSided` does and what a card's back should do.
145
+ //
146
+ // Whether it has turned away is `basalt::facesAway`; see core/Backface.h for
147
+ // why the rule is shared rather than left to each toolkit.
148
+ - (void)setRnHidesBackFace:(BOOL)hides;
149
+
150
+ // `display: 'none'`. Separate from the back face above because the two are
151
+ // independent reasons to hide the same view, and Fabric applies them through
152
+ // different calls -- props, then layout metrics. Setting `hidden` directly
153
+ // from either one made the later call answer for both, which is why a view
154
+ // turned away from the viewer came back the moment its layout was applied.
155
+ - (void)setRnHidden:(BOOL)hidden;
156
+
157
+ // How an image fills its frame. Mirrors React Native's ImageResizeMode, minus
158
+ // Repeat, which needs a tiled draw rather than one image draw.
159
+ // Accessible states. Each is a tri-state: unset leaves AppKit's default alone,
160
+ // which is not the same as setting it false.
161
+ typedef NS_ENUM(NSInteger, RnAppKitAccessibleFlag) {
162
+ RnAppKitAccessibleUnset,
163
+ RnAppKitAccessibleFalse,
164
+ RnAppKitAccessibleTrue,
165
+ };
166
+
167
+ // `pointerEvents`, which decides what a press can land on rather than what is
168
+ // drawn. CSS's four values, and React Native's: a view is a target, or it is
169
+ // not, or its children are and it is not, or it is and they are not.
170
+ //
171
+ // Here rather than in the mounting manager because hit testing is a pure
172
+ // function of the view tree -- `RnAppKitHitTest` takes no React Native types
173
+ // and has nowhere to look a prop up.
174
+ typedef NS_ENUM(NSInteger, RnAppKitPointerEvents) {
175
+ RnAppKitPointerEventsAuto,
176
+ RnAppKitPointerEventsNone,
177
+ RnAppKitPointerEventsBoxNone,
178
+ RnAppKitPointerEventsBoxOnly,
179
+ };
180
+
181
+ typedef NS_ENUM(NSInteger, RnAppKitImageFit) {
182
+ RnAppKitImageFitCover,
183
+ RnAppKitImageFitContain,
184
+ RnAppKitImageFitStretch,
185
+ RnAppKitImageFitCenter,
186
+ // Tiled from the top left at the image's own size, which is what CSS
187
+ // `repeat` and React Native's `resizeMode: 'repeat'` mean.
188
+ RnAppKitImageFitRepeat,
189
+ };
190
+
191
+ // The decoded pixels of an <Image>. Pass nil to clear.
192
+ //
193
+ // A CGImage rather than a React Native type, so the view layer stays free of
194
+ // React Native headers -- the same arrangement the text layout has.
195
+ // AppKitImageLoader produces the image and AppKitMountingManager chooses the
196
+ // fit.
197
+ - (void)setRnImage:(nullable CGImageRef)image fit:(RnAppKitImageFit)fit;
198
+
199
+ // `tintColor`: recolours the image, keeping its alpha, so one silhouette asset
200
+ // can be drawn in any colour. Pass nil to clear.
201
+ //
202
+ // Separate from the image because the tint arrives from the props and the image
203
+ // from a loader, and either can land first.
204
+ - (void)setRnImageTint:(nullable NSColor *)tint;
205
+
206
+ // The paragraph this view draws, or nil for a view that draws none.
207
+ //
208
+ // A view either paints a layer or draws text; nothing here does both, because
209
+ // React Native's <Text> is a Paragraph node with no children of its own. Held
210
+ // as an opaque object so this header stays free of Core Text -- see
211
+ // RnTextLayout.h for the object, and CoreTextLayout.h for what builds one --
212
+ // both the mounting manager and the measurement seam go through that, so the
213
+ // size a view draws at is the size Yoga was told.
214
+ - (void)setRnTextLayout:(nullable id)layout;
215
+ - (void)setRnClipsChildren:(BOOL)clips;
216
+
217
+ // `zIndex`. Reorders painting and hit testing, and never the child list --
218
+ // Fabric's Insert and Remove carry an index into that list, so it has to stay
219
+ // in mutation order. Equal values keep document order, which is what CSS and
220
+ // React Native both promise.
221
+ - (void)setRnZIndex:(NSInteger)zIndex;
222
+ @property(nonatomic, readonly) NSInteger rnZIndex;
223
+
224
+ // `pointerEvents`. Read by RnAppKitHitTest and by nothing else -- it changes
225
+ // what a press finds and never what is drawn.
226
+ @property(nonatomic) RnAppKitPointerEvents rnPointerEvents;
227
+
228
+ // Whether this view takes keyboard focus, and therefore whether Tab stops on
229
+ // it.
230
+ //
231
+ // React Native has a `focusable` prop and it does not reach this platform:
232
+ // ReactCommon parses it only into Android's and tvOS's HostPlatformViewProps,
233
+ // and the C++ host's is a bare alias of BaseViewProps. What does reach here is
234
+ // `accessible`, which is what <Pressable> sets on everything it renders. See
235
+ // AppKitFocus.h.
236
+ //
237
+ // AppKit owns the chain: a view that accepts first responder status joins the
238
+ // window's key-view loop, so Tab and Shift+Tab work without this project
239
+ // deciding what "next" means.
240
+ @property(nonatomic) BOOL rnFocusable;
241
+
242
+ // This view's children in the order they paint: back to front, stably sorted
243
+ // by zIndex. The same list hit testing walks in reverse, so what is on top is
244
+ // what is hit -- the Win32 host pairs them the same way.
245
+ - (NSArray<RnAppKitView *> *)rnChildrenInPaintOrder;
246
+
247
+ // This view's placement in its parent: the frame's translation with the
248
+ // transform composed in, anchored at the centre.
249
+ //
250
+ // Public for one reason, which is the same reason Win32's `localToParent` is:
251
+ // hit testing inverts this, so deriving it anywhere else would let the two end
252
+ // up with different ideas of where a view is. A transform that is not affine --
253
+ // perspective -- has no 2D inverse, and this answers with the translation
254
+ // alone, which is what hit testing did for every transform before this existed.
255
+ - (CGAffineTransform)rnLocalToParent;
256
+
257
+ // A point in `root`'s coordinates, expressed in this view's own.
258
+ //
259
+ // Walks up composing `rnLocalToParent` and inverts the chain, which is the
260
+ // same matrix the hit test inverts one level at a time on the way down -- so a
261
+ // press and the coordinates it reports cannot disagree about where a view is.
262
+ //
263
+ // Not `-[NSView convertPoint:fromView:]`, which ignores the layer transform
264
+ // and so answers for a rotated view as though it were not rotated.
265
+ //
266
+ // NO when the chain has no inverse, which `scale: 0` produces and which has no
267
+ // sensible point in it; `out` is untouched then.
268
+ - (BOOL)rnPageToLocal:(NSPoint)page
269
+ fromRoot:(nullable RnAppKitView *)root
270
+ into:(NSPoint *)out;
271
+ - (void)setRnCornerRadius:(CGFloat)radius;
272
+
273
+ // Per-corner radii, as four (horizontal, vertical) pairs -- eight floats -- in
274
+ // the order top-left, top-right, bottom-right, bottom-left. React Native's
275
+ // radii are elliptical and CALayer's single `cornerRadius` cannot express
276
+ // that, so anything a plain cornerRadius cannot say is applied as a mask layer
277
+ // instead. Null clears them. `setRnCornerRadius:` is the uniform-circular
278
+ // shorthand and goes through here.
279
+ - (void)setRnBorderRadii:(nullable const CGFloat *)radii;
280
+
281
+ // Per-edge border widths and colours, in the order top, right, bottom, left --
282
+ // the order CSS names them, and the order the GTK and Win32 sides store them
283
+ // in. `widths` is four floats; `colors` is sixteen, four RGBA quadruples in
284
+ // the same edge order. Either being null clears the border.
285
+ - (void)setRnBorderWidths:(nullable const CGFloat *)widths
286
+ colors:(nullable const CGFloat *)colors;
287
+
288
+ // Fabric's Insert and Remove carry an index into the parent's child list, so
289
+ // that list has to stay in mutation order.
290
+ - (void)insertRnChild:(RnAppKitView *)child atIndex:(NSInteger)index;
291
+ - (void)removeRnChild:(RnAppKitView *)child;
292
+
293
+ // The same one-line-per-view format the GTK side produces, so the two can be
294
+ // compared and eventually asserted on by the same tests.
295
+ // --- React DevTools' overlay --------------------------------------------------
296
+ //
297
+ // The rectangles a `DebuggingOverlay` draws: an inspected element's blue box,
298
+ // or an outline around everything that just re-rendered. Set from
299
+ // AppKitMountingManager, which parses them; see core/DebuggingOverlay.h.
300
+ //
301
+ // Eight numbers per rectangle -- x, y, width, height, then r, g, b, a -- and a
302
+ // flag per rectangle for whether to fill. Plain arrays rather than the core
303
+ // struct because this file has no React Native in it.
304
+ - (void)setRnHighlights:(nullable const float *)rectangles
305
+ filled:(nullable const bool *)filled
306
+ count:(NSInteger)count;
307
+
308
+ // Accessibility, as a screen reader sees it.
309
+ //
310
+ // The role arrives as React Native's own string -- "button", "image", "text" --
311
+ // rather than as an NSAccessibilityRole, and the view maps it. Two reasons.
312
+ // The mapping is a platform decision and belongs beside the platform; and the
313
+ // string is what `describeTree` reports, so the two hosts' trees can be
314
+ // compared without one of them speaking AppKit's vocabulary and the other
315
+ // speaking GTK's.
316
+ //
317
+ // Pass nil or an empty string for no role, which is what a plain <View> is.
318
+ - (void)setRnAccessibleRole:(nullable NSString *)role;
319
+
320
+ // `label` is the accessible name and `hint` the description; either may be nil
321
+ // or empty to leave it unset.
322
+ - (void)setRnAccessibleLabel:(nullable NSString *)label hint:(nullable NSString *)hint;
323
+
324
+ // Accessible states. Each is a tri-state: unset leaves AppKit's default alone,
325
+ // which is not the same as setting it false.
326
+
327
+ - (void)setRnAccessibleStateDisabled:(RnAppKitAccessibleFlag)disabled
328
+ checked:(RnAppKitAccessibleFlag)checked
329
+ selected:(RnAppKitAccessibleFlag)selected
330
+ expanded:(RnAppKitAccessibleFlag)expanded
331
+ busy:(RnAppKitAccessibleFlag)busy;
332
+
333
+ // Hidden from assistive technology, for accessible={false} and
334
+ // accessibilityElementsHidden.
335
+ - (void)setRnAccessibleHidden:(BOOL)hidden;
336
+
337
+ // The app's `nativeID`, which this platform uses to mark a view for a native
338
+ // behaviour it has no React Native prop for -- today, a drop target. See
339
+ // core/DragAndDrop.h, and core/TitleBarRegions.h for the older use of the
340
+ // same idea. Stored and never drawn.
341
+ @property(nonatomic, copy, nullable) NSString *rnNativeId;
342
+
343
+ // Set on the surface root. See RnAppKitInputHandler.
344
+ @property(nonatomic, weak, nullable) id<RnAppKitInputHandler> rnInputHandler;
345
+
346
+ // Set on the surface root. See RnAppKitFocusHandler.
347
+ @property(nonatomic, weak, nullable) id<RnAppKitFocusHandler> rnFocusHandler;
348
+
349
+ // The text field this view hosts, for <TextInput>.
350
+ //
351
+ // Set by AppKitTextInputManager, which owns it and positions it; the view only
352
+ // reads it, to report the field's contents in `describeTree`. Held weakly so a
353
+ // deleted field is not kept alive by the view that was showing it.
354
+ //
355
+ // A real NSTextField rather than a caret drawn on a paragraph: that brings
356
+ // input methods, selection, the clipboard and every key binding a Mac user
357
+ // expects, none of which is worth reimplementing and all of which is easy to
358
+ // get subtly wrong. The GTK side embeds a real GtkText for the same reason.
359
+ // The <TextInput> peer: an NSTextField for a single-line field and an
360
+ // NSTextView for a multiline one. See AppKitTextPeer.h.
361
+ @property(nonatomic, weak, nullable) NSView *rnEditable;
362
+
363
+ // --- Controls ----------------------------------------------------------------
364
+ //
365
+ // The three components that are a toolkit control rather than a box:
366
+ // <ActivityIndicator> and <RefreshControl> are an NSProgressIndicator,
367
+ // <Switch> is an NSSwitch. Held the same way the text peer above is -- weakly,
368
+ // because the subview relationship is what owns it -- and for the same reason:
369
+ // AppKit's own control brings the theme, the animation and the accessibility
370
+ // with it.
371
+ typedef NS_ENUM(NSInteger, RnAppKitControlKind) {
372
+ RnAppKitControlNone = 0,
373
+ RnAppKitControlSpinner,
374
+ RnAppKitControlSwitch,
375
+ };
376
+
377
+ @property(nonatomic, weak, nullable) NSView *rnControl;
378
+ @property(nonatomic) RnAppKitControlKind rnControlKind;
379
+
380
+ // What `describeTree` prints for this control, written by
381
+ // core/DesktopControls.h so that three hosts cannot describe the same switch
382
+ // differently.
383
+ @property(nonatomic, copy, nullable) NSString *rnControlDescription;
384
+
385
+ // Set on ScrollViews only. See RnAppKitScrollHandler.
386
+ @property(nonatomic, weak, nullable) id<RnAppKitScrollHandler> rnScrollHandler;
387
+
388
+ // Shifts this view's children by (-x, -y), which is how a ScrollView scrolls.
389
+ //
390
+ // Through `bounds.origin` rather than by moving every child: AppKit then shifts
391
+ // drawing, hit testing and the clip together, and the frames the mounting
392
+ // manager wrote stay exactly the ones Yoga produced. The GTK side does the same
393
+ // thing by shifting children in its layout manager, for the same reason --
394
+ // React Native decides sizes, and the platform only places.
395
+ - (void)setRnScrollOffsetX:(CGFloat)x y:(CGFloat)y;
396
+ - (NSPoint)rnScrollOffset;
397
+
398
+ // The overlay scrollbars, as core/ScrollIndicator.h decides them: an offset
399
+ // along the track and a length, per axis, with a length of zero meaning "do not
400
+ // draw one".
401
+ //
402
+ // Drawn by a subview kept above every React child rather than in `drawRect:`,
403
+ // because AppKit paints subviews over their superview and a ScrollView's whole
404
+ // job is to have one covering it. That view is not an RnAppKitView, so it stays
405
+ // out of the paint order, out of hit testing and out of `describeTree`'s
406
+ // children -- the numbers are reported on the ScrollView's own line instead.
407
+ - (void)setRnScrollIndicatorVerticalOffset:(CGFloat)verticalOffset
408
+ verticalLength:(CGFloat)verticalLength
409
+ horizontalOffset:(CGFloat)horizontalOffset
410
+ horizontalLength:(CGFloat)horizontalLength;
411
+
412
+ - (NSString *)describeTree;
413
+
414
+ @end
415
+
416
+ NS_ASSUME_NONNULL_END
417
+ #endif