Skip to content

Commit dea7720

Browse files
committed
feat: add ball example (to examples/ for now)
1 parent e00d413 commit dea7720

29 files changed

+1947
-39
lines changed

deno.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
"imports": {
4949
"@nativescript/macos-node-api": "./packages/macos/mod.ts",
50-
"@nativescript/objc-node-api": "./packages/objc/index.d.ts"
50+
"@nativescript/objc-node-api": "./packages/objc/index.d.ts",
51+
"popmotion": "npm:popmotion@^11.0.5"
5152
}
5253
}

examples/ball/AppController.js

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// @ts-check
2+
3+
import "@nativescript/macos-node-api";
4+
import { MouseCatcherView } from "./MouseCatcherView.js";
5+
// deno-lint-ignore no-unused-vars
6+
import { BallViewController } from "./BallViewController.js";
7+
import { BallWindowController } from "./BallWindowController.js";
8+
import {
9+
byConstrainingWithinBounds,
10+
getInferredRectOfHoveredDockIcon,
11+
} from "./DockUtils.js";
12+
13+
export const RADIUS = 100;
14+
15+
export class AppController extends NSObject {
16+
static {
17+
NativeClass(this);
18+
}
19+
20+
// MARK: - Init
21+
22+
init() {
23+
this.ballViewController.delegate = this;
24+
this.setupClickWindow();
25+
return super.init();
26+
}
27+
28+
setupClickWindow() {
29+
const catcher = MouseCatcherView.new();
30+
this.clickWindow.contentView = catcher;
31+
catcher.frame = {
32+
origin: { x: 0, y: 0 },
33+
size: { width: RADIUS * 2, height: RADIUS * 2 },
34+
};
35+
catcher.wantsLayer = true;
36+
// This is needed so that the window accepts mouse events
37+
catcher.layer.backgroundColor =
38+
NSColor.blackColor.colorWithAlphaComponent(0.01).CGColor;
39+
catcher.layer.cornerRadius = RADIUS;
40+
41+
const weakSelf = new WeakRef(this);
42+
catcher.onMouseDown = () => {
43+
weakSelf.deref()?.ballViewController.onMouseDown();
44+
};
45+
catcher.onMouseDrag = () => {
46+
weakSelf.deref()?.ballViewController.onMouseDrag();
47+
};
48+
catcher.onMouseUp = () => {
49+
weakSelf.deref()?.ballViewController.onMouseUp();
50+
};
51+
catcher.onScroll = ($0) => {
52+
weakSelf.deref()?.ballViewController.onScroll($0);
53+
};
54+
}
55+
56+
// MARK: - External actions
57+
dockIconClicked() {
58+
const screen = NSScreen.mainScreen;
59+
if (!screen) return;
60+
61+
if (this.ballVisible) {
62+
this.ballViewController.animatePutBack(
63+
getInferredRectOfHoveredDockIcon(screen),
64+
() => {
65+
this.ballVisible = false;
66+
},
67+
);
68+
return;
69+
}
70+
71+
const _ = this.ballViewController.view;
72+
73+
this.ballViewController.animateBallFromRect(
74+
getInferredRectOfHoveredDockIcon(screen),
75+
);
76+
this.ballVisible = true;
77+
}
78+
79+
// MARK: - State
80+
81+
_ballVisible = false;
82+
83+
/**
84+
* @param {boolean} old
85+
*/
86+
_ballVisibleDidSet(old) {
87+
if (this._ballVisible === old) {
88+
return;
89+
}
90+
91+
this.ballWindowController.window.setIsVisible(this._ballVisible);
92+
this.clickWindow.setIsVisible(this._ballVisible);
93+
94+
this.ballViewController.sceneView.isPaused = !this._ballVisible;
95+
this.showPutBackIcon = this._ballVisible;
96+
97+
if (this._ballVisible) {
98+
this.updateClickWindowPosition();
99+
}
100+
}
101+
102+
get ballVisible() {
103+
return this._ballVisible;
104+
}
105+
106+
set ballVisible(value) {
107+
const old = this._ballVisible;
108+
this._ballVisible = value;
109+
this._ballVisibleDidSet(old);
110+
}
111+
112+
// MARK - Windows
113+
/**
114+
* @type {BallWindowController}
115+
*/
116+
ballWindowController = BallWindowController.new();
117+
118+
/**
119+
* @returns {BallViewController}
120+
*/
121+
get ballViewController() {
122+
// @ts-ignore it will be BallViewController always because of BallWindowController
123+
return this.ballWindowController.window.contentViewController;
124+
}
125+
126+
/**
127+
* @type {NSWindow | null}
128+
*/
129+
_clickWindow = null;
130+
131+
get clickWindow() {
132+
if (!this._clickWindow) {
133+
const clickWindow = NSWindow.alloc()
134+
.initWithContentRectStyleMaskBackingDefer(
135+
{
136+
origin: { x: 0, y: 0 },
137+
size: { width: RADIUS, height: RADIUS * 2 },
138+
},
139+
0,
140+
NSBackingStoreType.Buffered,
141+
false,
142+
);
143+
clickWindow.isReleasedWhenClosed = false;
144+
clickWindow.level = NSScreenSaverWindowLevel; // ?
145+
clickWindow.backgroundColor = NSColor.clearColor;
146+
this._clickWindow = clickWindow;
147+
return clickWindow;
148+
}
149+
150+
return this._clickWindow;
151+
}
152+
153+
// MARK: - Dock icon
154+
155+
_showPutBackIcon = false;
156+
157+
/**
158+
* @param {boolean} _old
159+
*/
160+
_showPutBackIconDidSet(_old) {
161+
if (this._showPutBackIcon) {
162+
NSApp.dockTile.contentView = this.putBackDockView;
163+
} else {
164+
NSApp.dockTile.contentView = this.ballDockView;
165+
}
166+
NSApp.dockTile.display();
167+
}
168+
169+
get showPutBackIcon() {
170+
return this._showPutBackIcon;
171+
}
172+
173+
set showPutBackIcon(value) {
174+
const old = this._showPutBackIcon;
175+
this._showPutBackIcon = value;
176+
this._showPutBackIconDidSet(old);
177+
}
178+
179+
putBackDockView = NSImageView.imageViewWithImage(
180+
NSImage.alloc().initWithContentsOfFile(
181+
new URL("./assets/PutBack.png", import.meta.url).pathname,
182+
),
183+
);
184+
185+
ballDockView = NSImageView.imageViewWithImage(
186+
NSImage.alloc().initWithContentsOfFile(
187+
new URL("./assets/Ball.png", import.meta.url).pathname,
188+
),
189+
);
190+
191+
/**
192+
* @param {BallViewController} _vc
193+
* @param {CGRect} _pos
194+
*/
195+
ballViewControllerBallDidMoveToPosition(_vc, _pos) {
196+
this.updateClickWindowPosition();
197+
}
198+
199+
updateClickWindowPosition() {
200+
const rect = this.ballViewController.targetMouseCatcherRect;
201+
if (!this.ballVisible || !rect) return;
202+
203+
const rounding = 10;
204+
rect.origin.x = Math.round(CGRectGetMinX(rect) / rounding) * rounding;
205+
rect.origin.y = Math.round(CGRectGetMinY(rect) / rounding) * rounding;
206+
207+
// HACK: Assume scene coords are same as window coords
208+
const window = this.ballWindowController.window;
209+
if (!window) return;
210+
const screen = window.screen;
211+
if (!screen) return;
212+
213+
if (rect) {
214+
this.clickWindow.setFrameDisplay(
215+
byConstrainingWithinBounds(rect, screen.frame),
216+
false,
217+
);
218+
}
219+
}
220+
}

examples/ball/AppDelegate.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// @ts-check
2+
3+
import "@nativescript/macos-node-api";
4+
import { AppController } from "./AppController.js";
5+
6+
export class AppDelegate extends NSObject {
7+
static ObjCProtocols = [NSApplicationDelegate];
8+
9+
static {
10+
NativeClass(this);
11+
}
12+
13+
appController = AppController.new();
14+
15+
/**
16+
* @param {NSNotification} _notification
17+
*/
18+
applicationWillFinishLaunching(_notification) {
19+
NSApp.applicationIconImage = NSImage.alloc().initWithContentsOfFile(
20+
new URL("./assets/Ball.png", import.meta.url).pathname,
21+
);
22+
}
23+
24+
/**
25+
* @param {NSApplication} _app
26+
* @returns {boolean}
27+
*/
28+
applicationSupportsSecureRestorableState(_app) {
29+
return true;
30+
}
31+
32+
// When dock icon is pressed, animate ball from dock pos
33+
/**
34+
* @param {NSApplication} _sender
35+
* @param {boolean} _flag
36+
* @returns {boolean}
37+
*/
38+
applicationShouldHandleReopenHasVisibleWindows(_sender, _flag) {
39+
this.appController.dockIconClicked();
40+
return true;
41+
}
42+
}

0 commit comments

Comments
 (0)