Skip to content

Commit 7049bb8

Browse files
committed
io / graphic window
1 parent 8acce2c commit 7049bb8

1 file changed

Lines changed: 386 additions & 0 deletions

File tree

runtime/skunk_window_runtime.m

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
#import <Cocoa/Cocoa.h>
2+
#include <ctype.h>
3+
#include <mach/mach_time.h>
4+
#include <math.h>
5+
#include <stdbool.h>
6+
#include <stdint.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
10+
@class SkunkCanvasView;
11+
@class SkunkWindowDelegate;
12+
13+
typedef struct SkunkWindow {
14+
int32_t width;
15+
int32_t height;
16+
uint32_t *pixels;
17+
bool open;
18+
bool headless;
19+
double last_present_time;
20+
double delta_time;
21+
uint8_t key_down[256];
22+
NSWindow *ns_window;
23+
SkunkCanvasView *ns_view;
24+
SkunkWindowDelegate *delegate;
25+
} SkunkWindow;
26+
27+
static double skunk_now_seconds(void) {
28+
static mach_timebase_info_data_t timebase = {0, 0};
29+
if (timebase.denom == 0) {
30+
mach_timebase_info(&timebase);
31+
}
32+
uint64_t ticks = mach_absolute_time();
33+
double nanos = (double)ticks * (double)timebase.numer / (double)timebase.denom;
34+
return nanos / 1000000000.0;
35+
}
36+
37+
static bool skunk_headless_enabled(void) {
38+
const char *value = getenv("SKUNK_WINDOW_HEADLESS");
39+
return value != NULL && value[0] != '\0' && strcmp(value, "0") != 0;
40+
}
41+
42+
static void skunk_window_step_clock(SkunkWindow *window) {
43+
if (window == NULL) {
44+
return;
45+
}
46+
if (window->headless) {
47+
window->delta_time = 1.0 / 60.0;
48+
window->last_present_time = skunk_now_seconds();
49+
return;
50+
}
51+
double now = skunk_now_seconds();
52+
if (window->last_present_time <= 0.0) {
53+
window->delta_time = 1.0 / 60.0;
54+
} else {
55+
window->delta_time = now - window->last_present_time;
56+
if (window->delta_time <= 0.0) {
57+
window->delta_time = 1.0 / 60.0;
58+
}
59+
}
60+
window->last_present_time = now;
61+
}
62+
63+
static void skunk_update_key_state(SkunkWindow *window, NSEvent *event, bool is_down) {
64+
if (window == NULL || event == nil) {
65+
return;
66+
}
67+
NSString *characters = [event charactersIgnoringModifiers];
68+
if (characters == nil || [characters length] == 0) {
69+
return;
70+
}
71+
unichar ch = [characters characterAtIndex:0];
72+
if (ch < 256) {
73+
int lowered = tolower((int)ch);
74+
if (lowered >= 0 && lowered < 256) {
75+
window->key_down[lowered] = is_down ? 1 : 0;
76+
}
77+
}
78+
}
79+
80+
@interface SkunkWindowDelegate : NSObject <NSWindowDelegate> {
81+
@public
82+
SkunkWindow *skunkWindow;
83+
}
84+
@end
85+
86+
@implementation SkunkWindowDelegate
87+
- (BOOL)windowShouldClose:(id)sender {
88+
(void)sender;
89+
if (skunkWindow != NULL) {
90+
skunkWindow->open = false;
91+
}
92+
return YES;
93+
}
94+
@end
95+
96+
@interface SkunkCanvasView : NSView {
97+
@public
98+
SkunkWindow *skunkWindow;
99+
}
100+
@end
101+
102+
@implementation SkunkCanvasView
103+
- (BOOL)isFlipped {
104+
return YES;
105+
}
106+
107+
- (BOOL)acceptsFirstResponder {
108+
return YES;
109+
}
110+
111+
- (BOOL)canBecomeKeyView {
112+
return YES;
113+
}
114+
115+
- (void)keyDown:(NSEvent *)event {
116+
skunk_update_key_state(skunkWindow, event, true);
117+
}
118+
119+
- (void)keyUp:(NSEvent *)event {
120+
skunk_update_key_state(skunkWindow, event, false);
121+
}
122+
123+
- (void)drawRect:(NSRect)dirtyRect {
124+
(void)dirtyRect;
125+
if (skunkWindow == NULL || skunkWindow->pixels == NULL) {
126+
return;
127+
}
128+
129+
CGContextRef context = [[NSGraphicsContext currentContext] CGContext];
130+
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
131+
132+
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
133+
CGDataProviderRef provider = CGDataProviderCreateWithData(
134+
NULL,
135+
skunkWindow->pixels,
136+
(size_t)skunkWindow->width * (size_t)skunkWindow->height * sizeof(uint32_t),
137+
NULL
138+
);
139+
CGImageRef image = CGImageCreate(
140+
(size_t)skunkWindow->width,
141+
(size_t)skunkWindow->height,
142+
8,
143+
32,
144+
(size_t)skunkWindow->width * sizeof(uint32_t),
145+
color_space,
146+
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little,
147+
provider,
148+
NULL,
149+
false,
150+
kCGRenderingIntentDefault
151+
);
152+
153+
CGRect bounds = CGRectMake(0, 0, skunkWindow->width, skunkWindow->height);
154+
CGContextDrawImage(context, bounds, image);
155+
156+
CGImageRelease(image);
157+
CGDataProviderRelease(provider);
158+
CGColorSpaceRelease(color_space);
159+
}
160+
@end
161+
162+
static void skunk_window_pump_events(SkunkWindow *window) {
163+
if (window == NULL || window->headless) {
164+
return;
165+
}
166+
167+
@autoreleasepool {
168+
for (;;) {
169+
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
170+
untilDate:[NSDate distantPast]
171+
inMode:NSDefaultRunLoopMode
172+
dequeue:YES];
173+
if (event == nil) {
174+
break;
175+
}
176+
[NSApp sendEvent:event];
177+
}
178+
[NSApp updateWindows];
179+
}
180+
}
181+
182+
void *skunk_window_create(int32_t width, int32_t height, const char *title) {
183+
SkunkWindow *window = (SkunkWindow *)calloc(1, sizeof(SkunkWindow));
184+
if (window == NULL) {
185+
return NULL;
186+
}
187+
188+
window->width = width > 0 ? width : 1;
189+
window->height = height > 0 ? height : 1;
190+
window->pixels = (uint32_t *)calloc(
191+
(size_t)window->width * (size_t)window->height,
192+
sizeof(uint32_t)
193+
);
194+
window->open = true;
195+
window->headless = skunk_headless_enabled();
196+
window->delta_time = 1.0 / 60.0;
197+
window->last_present_time = skunk_now_seconds();
198+
199+
if (window->headless) {
200+
return window;
201+
}
202+
203+
@autoreleasepool {
204+
[NSApplication sharedApplication];
205+
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
206+
[NSApp finishLaunching];
207+
208+
NSRect frame = NSMakeRect(0, 0, window->width, window->height);
209+
NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
210+
211+
window->ns_window = [[NSWindow alloc] initWithContentRect:frame
212+
styleMask:style
213+
backing:NSBackingStoreBuffered
214+
defer:NO];
215+
window->ns_view = [[SkunkCanvasView alloc] initWithFrame:frame];
216+
window->ns_view->skunkWindow = window;
217+
window->delegate = [[SkunkWindowDelegate alloc] init];
218+
window->delegate->skunkWindow = window;
219+
220+
NSString *ns_title = title != NULL ? [NSString stringWithUTF8String:title] : @"Skunk";
221+
[window->ns_window setTitle:ns_title];
222+
[window->ns_window setDelegate:window->delegate];
223+
[window->ns_window setReleasedWhenClosed:NO];
224+
[window->ns_window setContentView:window->ns_view];
225+
[window->ns_window center];
226+
[window->ns_window makeKeyAndOrderFront:nil];
227+
[window->ns_window makeFirstResponder:window->ns_view];
228+
[NSApp activateIgnoringOtherApps:YES];
229+
}
230+
231+
skunk_window_pump_events(window);
232+
return window;
233+
}
234+
235+
bool skunk_window_is_open(void *window_ptr) {
236+
SkunkWindow *window = (SkunkWindow *)window_ptr;
237+
if (window == NULL) {
238+
return false;
239+
}
240+
skunk_window_pump_events(window);
241+
return window->open;
242+
}
243+
244+
void skunk_window_poll(void *window_ptr) {
245+
SkunkWindow *window = (SkunkWindow *)window_ptr;
246+
if (window == NULL) {
247+
return;
248+
}
249+
skunk_window_pump_events(window);
250+
}
251+
252+
void skunk_window_clear(void *window_ptr, int32_t color) {
253+
SkunkWindow *window = (SkunkWindow *)window_ptr;
254+
if (window == NULL || window->pixels == NULL) {
255+
return;
256+
}
257+
uint32_t packed = (uint32_t)color;
258+
size_t count = (size_t)window->width * (size_t)window->height;
259+
for (size_t i = 0; i < count; ++i) {
260+
window->pixels[i] = packed;
261+
}
262+
}
263+
264+
void skunk_window_draw_rect(
265+
void *window_ptr,
266+
double x,
267+
double y,
268+
double width,
269+
double height,
270+
int32_t color
271+
) {
272+
SkunkWindow *window = (SkunkWindow *)window_ptr;
273+
if (window == NULL || window->pixels == NULL) {
274+
return;
275+
}
276+
277+
int x0 = (int)llround(x);
278+
int y0 = (int)llround(y);
279+
int x1 = x0 + (int)llround(width);
280+
int y1 = y0 + (int)llround(height);
281+
282+
if (x0 < 0) {
283+
x0 = 0;
284+
}
285+
if (y0 < 0) {
286+
y0 = 0;
287+
}
288+
if (x1 > window->width) {
289+
x1 = window->width;
290+
}
291+
if (y1 > window->height) {
292+
y1 = window->height;
293+
}
294+
if (x0 >= x1 || y0 >= y1) {
295+
return;
296+
}
297+
298+
uint32_t packed = (uint32_t)color;
299+
for (int row = y0; row < y1; ++row) {
300+
size_t offset = (size_t)row * (size_t)window->width;
301+
for (int col = x0; col < x1; ++col) {
302+
window->pixels[offset + (size_t)col] = packed;
303+
}
304+
}
305+
}
306+
307+
void skunk_window_present(void *window_ptr) {
308+
SkunkWindow *window = (SkunkWindow *)window_ptr;
309+
if (window == NULL) {
310+
return;
311+
}
312+
313+
if (!window->headless && window->ns_view != nil) {
314+
@autoreleasepool {
315+
[window->ns_view setNeedsDisplay:YES];
316+
[window->ns_view displayIfNeeded];
317+
}
318+
}
319+
320+
skunk_window_pump_events(window);
321+
skunk_window_step_clock(window);
322+
}
323+
324+
double skunk_window_delta_time(void *window_ptr) {
325+
SkunkWindow *window = (SkunkWindow *)window_ptr;
326+
if (window == NULL) {
327+
return 0.0;
328+
}
329+
return window->delta_time;
330+
}
331+
332+
void skunk_window_close(void *window_ptr) {
333+
SkunkWindow *window = (SkunkWindow *)window_ptr;
334+
if (window == NULL) {
335+
return;
336+
}
337+
window->open = false;
338+
if (!window->headless && window->ns_window != nil) {
339+
@autoreleasepool {
340+
[window->ns_window orderOut:nil];
341+
[window->ns_window close];
342+
}
343+
}
344+
}
345+
346+
void skunk_window_deinit(void *window_ptr) {
347+
SkunkWindow *window = (SkunkWindow *)window_ptr;
348+
if (window == NULL) {
349+
return;
350+
}
351+
352+
if (!window->headless) {
353+
@autoreleasepool {
354+
if (window->ns_window != nil) {
355+
[window->ns_window setDelegate:nil];
356+
}
357+
if (window->delegate != nil) {
358+
[window->delegate release];
359+
window->delegate = nil;
360+
}
361+
if (window->ns_view != nil) {
362+
[window->ns_view release];
363+
window->ns_view = nil;
364+
}
365+
if (window->ns_window != nil) {
366+
[window->ns_window release];
367+
window->ns_window = nil;
368+
}
369+
}
370+
}
371+
372+
free(window->pixels);
373+
free(window);
374+
}
375+
376+
bool skunk_keyboard_is_down(void *window_ptr, uint16_t key) {
377+
SkunkWindow *window = (SkunkWindow *)window_ptr;
378+
if (window == NULL || key >= 256) {
379+
return false;
380+
}
381+
int lowered = tolower((int)key);
382+
if (lowered < 0 || lowered >= 256) {
383+
return false;
384+
}
385+
return window->key_down[lowered] != 0;
386+
}

0 commit comments

Comments
 (0)