v / vlib / gg / gg_darwin.m
252 lines · 230 sloc · 8.19 KB · 013cf65a16630b5c4f28942ea9e759777814f367
Raw
1#include <Cocoa/Cocoa.h>
2
3NSColor* nscolor(gg__Color c) {
4 float red = (float)c.r / 255.0f;
5 float green = (float)c.g / 255.0f;
6 float blue = (float)c.b / 255.0f;
7 return [NSColor colorWithDeviceRed:red green:green blue:blue alpha:1.0f];
8}
9
10NSString* nsstring(string s) {
11 return [[NSString alloc] initWithBytesNoCopy:s.str
12 length:s.len
13 encoding:NSUTF8StringEncoding
14 freeWhenDone:false];
15}
16
17gg__Size gg_get_screen_size() {
18 NSScreen *currentScreen = nil;
19 NSWindow *mainWindow = [NSApp mainWindow];
20 // 1. Try screen containing the main window
21 if (mainWindow) {
22 currentScreen = [mainWindow screen];
23 }
24 // 2. If no main window, try the key window (might be different, e.g., a panel)
25 if (!currentScreen) {
26 NSWindow *keyWindow = [NSApp keyWindow];
27 if (keyWindow) {
28 currentScreen = [keyWindow screen];
29 }
30 }
31 // 3. If no relevant window, find the screen containing the mouse cursor
32 if (!currentScreen) {
33 // Get mouse location in global screen coordinates (bottom-left origin)
34 NSPoint mouseLocation = [NSEvent mouseLocation];
35 NSArray<NSScreen *> *screens = [NSScreen screens];
36 for (NSScreen *screen in screens) {
37 // Check if the mouse location is within the screen's frame
38 // Note: Both mouseLocation and screen.frame use bottom-left origin coordinates
39 if (NSMouseInRect(mouseLocation, [screen frame], NO)) {
40 currentScreen = screen;
41 break; // Found the screen with the mouse
42 }
43 }
44 }
45 // 4. As a last resort, fall back to the main screen
46 if (!currentScreen) {
47 NSLog(@"Warning: Could not determine current screen based on window or mouse. Falling back to mainScreen.");
48 currentScreen = [NSScreen mainScreen];
49 }
50 // Now get the size of the determined screen
51 NSDictionary *description = [currentScreen deviceDescription];
52 // Use NSDeviceSize to get pixel dimensions
53 NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
54 // Create the V gg.Size object
55 gg__Size res;
56 res.width = displayPixelSize.width;
57 res.height = displayPixelSize.height;
58 return res;
59}
60
61void gg_macos_resize_window(void *window_ptr, int width, int height) {
62 if (window_ptr == nil || width <= 0 || height <= 0) {
63 return;
64 }
65 NSWindow *window = (__bridge NSWindow *)window_ptr;
66 [window setContentSize:NSMakeSize((CGFloat)width, (CGFloat)height)];
67}
68
69void gg_macos_set_window_resizable(void *window_ptr, bool resizable) {
70 if (window_ptr == nil) {
71 return;
72 }
73 NSWindow *window = (__bridge NSWindow *)window_ptr;
74 NSWindowStyleMask style = [window styleMask];
75 if (resizable) {
76 style |= NSWindowStyleMaskResizable;
77 } else {
78 style &= ~NSWindowStyleMaskResizable;
79 }
80 [window setStyleMask:style];
81}
82
83// When non-zero, every string is drawn in a monospace font regardless of the
84// per-call cfg.mono flag. Lets an app switch the whole UI to a fixed-width font
85// so text widths can be computed as char_count * advance (no CoreText layout).
86// Default 0 => behaviour unchanged for every other gg app.
87int g_gg_force_mono = 0;
88void gg_set_force_mono(int on) { g_gg_force_mono = on; }
89
90// Cache of the (font + color) attribute dictionaries passed to -drawAtPoint:.
91// darwin_draw_string is called once per text run, every frame; building a fresh
92// NSFont (font lookup), NSColor, and NSDictionary on each call was pure ObjC
93// churn (CPU + autorelease-pool pressure). The set of distinct (size, style,
94// color) combinations a UI actually uses is tiny (a few sizes × a theme palette),
95// so we memoize the finished attribute dict. Keyed on everything that changes the
96// dict — color rgb (nscolor ignores alpha), size, bold, mono, and the global
97// force-mono flag. The dictionary retains each cached attr dict (ARC), so they
98// persist for the process; bounded by the number of distinct combinations.
99static NSMutableDictionary<NSNumber*, NSDictionary*>* g_text_attr_cache = nil;
100
101static NSDictionary* darwin_text_attrs(gg__TextCfg cfg) {
102 uint64_t key = ((uint64_t)cfg.color.r)
103 | ((uint64_t)cfg.color.g << 8)
104 | ((uint64_t)cfg.color.b << 16)
105 | (((uint64_t)cfg.size & 0xFFFF) << 24)
106 | ((uint64_t)(cfg.bold ? 1 : 0) << 40)
107 | ((uint64_t)(cfg.mono ? 1 : 0) << 41)
108 | ((uint64_t)(g_gg_force_mono ? 1 : 0) << 42);
109 if (g_text_attr_cache == nil) {
110 g_text_attr_cache = [[NSMutableDictionary alloc] init];
111 }
112 NSNumber* k = @(key);
113 NSDictionary* cached = [g_text_attr_cache objectForKey:k];
114 if (cached != nil) {
115 return cached;
116 }
117
118 NSFont* font = [NSFont userFontOfSize:cfg.size];
119 // # NSFont* font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size];
120 if (g_gg_force_mono) {
121 // Global monospace mode: requested size minus 1, kept in sync with the
122 // app-side width metric (see fuse_text_width / mono_char_advance).
123 font = [NSFont fontWithName:@"Menlo" size:cfg.size - 1];
124 } else if (cfg.mono) {
125 // # font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size];
126 font = [NSFont fontWithName:@"Menlo" size:cfg.size - 5];
127 }
128 if (cfg.bold) {
129 font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
130 }
131
132 NSDictionary* attr = @{
133 NSForegroundColorAttributeName : nscolor(cfg.color),
134 // NSParagraphStyleAttributeName: paragraphStyle,
135 NSFontAttributeName : font,
136 };
137 [g_text_attr_cache setObject:attr forKey:k];
138 return attr;
139}
140
141void darwin_draw_string(int x, int y, string s, gg__TextCfg cfg) {
142 NSDictionary* attr = darwin_text_attrs(cfg);
143 [nsstring(s) drawAtPoint:NSMakePoint(x, y - 15) withAttributes:attr];
144}
145
146int darwin_text_width(string s) {
147 // println('text_width "${s}" len=${s.len}')
148 NSString* n = @"";
149 if (s.len == 1) {
150 // println('len=1')
151 n = [NSString stringWithFormat:@"%c", s.str[0]];
152 } else {
153 n = nsstring(s);
154 }
155 /*
156 # if (!defaultFont){
157 # defaultFont = [NSFont userFontOfSize: ui__DEFAULT_FONT_SIZE];
158 # }
159 # NSDictionary *attrs = @{
160 # NSFontAttributeName: defaultFont,
161 # };
162 */
163 NSSize size = [n sizeWithAttributes:nil];
164 // # printf("!!!%f\n", ceil(size.width));
165 return (int)(ceil(size.width));
166}
167
168void darwin_draw_rect(float x, float y, float width, float height, gg__Color c) {
169 NSColor* color = nscolor(c);
170 NSRect rect = NSMakeRect(x, y, width, height);
171 [color setFill];
172 NSRectFill(rect);
173}
174
175static void mark_view_tree_needs_display(NSView *view) {
176 if (view == nil) {
177 return;
178 }
179 [view setNeedsDisplay:YES];
180 for (NSView *subview in [view subviews]) {
181 mark_view_tree_needs_display(subview);
182 }
183}
184
185void darwin_window_refresh() {
186 dispatch_async(dispatch_get_main_queue(), ^{
187 NSWindow *window = [NSApp mainWindow];
188 if (window == nil) {
189 window = [NSApp keyWindow];
190 }
191 if (window == nil) {
192 return;
193 }
194 NSView *contentView = [window contentView];
195 if (contentView == nil) {
196 return;
197 }
198 mark_view_tree_needs_display(contentView);
199 [window displayIfNeeded];
200 });
201
202 // puts("refresh");
203 //[[NSApp mainWindow].contentView drawRect:NSMakeRect(0,0,2000,2000)];
204 //[[NSGraphicsContext currentContext] flushGraphics];
205}
206
207gg__Image darwin_create_image(string path_) {
208 // file = file.trim_space()
209 NSString* path = nsstring(path_);
210 NSImage* img = [[NSImage alloc] initWithContentsOfFile:path];
211 if (img == 0) {
212 }
213 NSSize size = [img size];
214 gg__Image res;
215 res.width = size.width;
216 res.height = size.height;
217 res.path = path_;
218 res.ok = true;
219 // printf("inited img width=%d\n", res.width) ;
220 // need __bridge_retained so that the pointer is not freed by ARC
221 res.data = (__bridge_retained voidptr)(img);
222 return res;
223}
224
225void darwin_draw_image(float x, float y, float w, float h, gg__Image* img) {
226 NSImage* i = (__bridge NSImage*)(img->data);
227 [i drawInRect:NSMakeRect(x, y, w, h)];
228}
229
230void darwin_draw_circle(float x, float y, float d, gg__Color color) {
231 NSColor* c = nscolor(color);
232 NSRect rect = NSMakeRect(x, y, d * 2, d * 2);
233 NSBezierPath* circlePath = [NSBezierPath bezierPath];
234 [circlePath appendBezierPathWithOvalInRect:rect];
235 [c setFill];
236 // [circlePath stroke];
237 [circlePath fill];
238 // NSRectFill(rect);
239}
240
241void darwin_draw_circle_empty(float x, float y, float d, gg__Color color) {
242 NSColor* outlineColor = nscolor(color);
243 CGFloat outlineWidth = 1.0; //2.0;
244
245 NSRect rect = NSMakeRect(x, y, d * 2, d * 2);
246 NSBezierPath* circlePath = [NSBezierPath bezierPath];
247 [circlePath appendBezierPathWithOvalInRect:rect];
248
249 [outlineColor setStroke];
250 [circlePath setLineWidth:outlineWidth];
251 [circlePath stroke];
252}
253