Files

253 lines
8.2 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <Cocoa/Cocoa.h>
NSColor* nscolor(gg__Color c) {
float red = (float)c.r / 255.0f;
float green = (float)c.g / 255.0f;
float blue = (float)c.b / 255.0f;
return [NSColor colorWithDeviceRed:red green:green blue:blue alpha:1.0f];
}
NSString* nsstring(string s) {
return [[NSString alloc] initWithBytesNoCopy:s.str
length:s.len
encoding:NSUTF8StringEncoding
freeWhenDone:false];
}
gg__Size gg_get_screen_size() {
NSScreen *currentScreen = nil;
NSWindow *mainWindow = [NSApp mainWindow];
// 1. Try screen containing the main window
if (mainWindow) {
currentScreen = [mainWindow screen];
}
// 2. If no main window, try the key window (might be different, e.g., a panel)
if (!currentScreen) {
NSWindow *keyWindow = [NSApp keyWindow];
if (keyWindow) {
currentScreen = [keyWindow screen];
}
}
// 3. If no relevant window, find the screen containing the mouse cursor
if (!currentScreen) {
// Get mouse location in global screen coordinates (bottom-left origin)
NSPoint mouseLocation = [NSEvent mouseLocation];
NSArray<NSScreen *> *screens = [NSScreen screens];
for (NSScreen *screen in screens) {
// Check if the mouse location is within the screen's frame
// Note: Both mouseLocation and screen.frame use bottom-left origin coordinates
if (NSMouseInRect(mouseLocation, [screen frame], NO)) {
currentScreen = screen;
break; // Found the screen with the mouse
}
}
}
// 4. As a last resort, fall back to the main screen
if (!currentScreen) {
NSLog(@"Warning: Could not determine current screen based on window or mouse. Falling back to mainScreen.");
currentScreen = [NSScreen mainScreen];
}
// Now get the size of the determined screen
NSDictionary *description = [currentScreen deviceDescription];
// Use NSDeviceSize to get pixel dimensions
NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
// Create the V gg.Size object
gg__Size res;
res.width = displayPixelSize.width;
res.height = displayPixelSize.height;
return res;
}
void gg_macos_resize_window(void *window_ptr, int width, int height) {
if (window_ptr == nil || width <= 0 || height <= 0) {
return;
}
NSWindow *window = (__bridge NSWindow *)window_ptr;
[window setContentSize:NSMakeSize((CGFloat)width, (CGFloat)height)];
}
void gg_macos_set_window_resizable(void *window_ptr, bool resizable) {
if (window_ptr == nil) {
return;
}
NSWindow *window = (__bridge NSWindow *)window_ptr;
NSWindowStyleMask style = [window styleMask];
if (resizable) {
style |= NSWindowStyleMaskResizable;
} else {
style &= ~NSWindowStyleMaskResizable;
}
[window setStyleMask:style];
}
// When non-zero, every string is drawn in a monospace font regardless of the
// per-call cfg.mono flag. Lets an app switch the whole UI to a fixed-width font
// so text widths can be computed as char_count * advance (no CoreText layout).
// Default 0 => behaviour unchanged for every other gg app.
int g_gg_force_mono = 0;
void gg_set_force_mono(int on) { g_gg_force_mono = on; }
// Cache of the (font + color) attribute dictionaries passed to -drawAtPoint:.
// darwin_draw_string is called once per text run, every frame; building a fresh
// NSFont (font lookup), NSColor, and NSDictionary on each call was pure ObjC
// churn (CPU + autorelease-pool pressure). The set of distinct (size, style,
// color) combinations a UI actually uses is tiny (a few sizes × a theme palette),
// so we memoize the finished attribute dict. Keyed on everything that changes the
// dict — color rgb (nscolor ignores alpha), size, bold, mono, and the global
// force-mono flag. The dictionary retains each cached attr dict (ARC), so they
// persist for the process; bounded by the number of distinct combinations.
static NSMutableDictionary<NSNumber*, NSDictionary*>* g_text_attr_cache = nil;
static NSDictionary* darwin_text_attrs(gg__TextCfg cfg) {
uint64_t key = ((uint64_t)cfg.color.r)
| ((uint64_t)cfg.color.g << 8)
| ((uint64_t)cfg.color.b << 16)
| (((uint64_t)cfg.size & 0xFFFF) << 24)
| ((uint64_t)(cfg.bold ? 1 : 0) << 40)
| ((uint64_t)(cfg.mono ? 1 : 0) << 41)
| ((uint64_t)(g_gg_force_mono ? 1 : 0) << 42);
if (g_text_attr_cache == nil) {
g_text_attr_cache = [[NSMutableDictionary alloc] init];
}
NSNumber* k = @(key);
NSDictionary* cached = [g_text_attr_cache objectForKey:k];
if (cached != nil) {
return cached;
}
NSFont* font = [NSFont userFontOfSize:cfg.size];
// # NSFont* font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size];
if (g_gg_force_mono) {
// Global monospace mode: requested size minus 1, kept in sync with the
// app-side width metric (see fuse_text_width / mono_char_advance).
font = [NSFont fontWithName:@"Menlo" size:cfg.size - 1];
} else if (cfg.mono) {
// # font = [NSFont fontWithName:@"Roboto Mono" size:cfg.size];
font = [NSFont fontWithName:@"Menlo" size:cfg.size - 5];
}
if (cfg.bold) {
font = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
}
NSDictionary* attr = @{
NSForegroundColorAttributeName : nscolor(cfg.color),
// NSParagraphStyleAttributeName: paragraphStyle,
NSFontAttributeName : font,
};
[g_text_attr_cache setObject:attr forKey:k];
return attr;
}
void darwin_draw_string(int x, int y, string s, gg__TextCfg cfg) {
NSDictionary* attr = darwin_text_attrs(cfg);
[nsstring(s) drawAtPoint:NSMakePoint(x, y - 15) withAttributes:attr];
}
int darwin_text_width(string s) {
// println('text_width "${s}" len=${s.len}')
NSString* n = @"";
if (s.len == 1) {
// println('len=1')
n = [NSString stringWithFormat:@"%c", s.str[0]];
} else {
n = nsstring(s);
}
/*
# if (!defaultFont){
# defaultFont = [NSFont userFontOfSize: ui__DEFAULT_FONT_SIZE];
# }
# NSDictionary *attrs = @{
# NSFontAttributeName: defaultFont,
# };
*/
NSSize size = [n sizeWithAttributes:nil];
// # printf("!!!%f\n", ceil(size.width));
return (int)(ceil(size.width));
}
void darwin_draw_rect(float x, float y, float width, float height, gg__Color c) {
NSColor* color = nscolor(c);
NSRect rect = NSMakeRect(x, y, width, height);
[color setFill];
NSRectFill(rect);
}
static void mark_view_tree_needs_display(NSView *view) {
if (view == nil) {
return;
}
[view setNeedsDisplay:YES];
for (NSView *subview in [view subviews]) {
mark_view_tree_needs_display(subview);
}
}
void darwin_window_refresh() {
dispatch_async(dispatch_get_main_queue(), ^{
NSWindow *window = [NSApp mainWindow];
if (window == nil) {
window = [NSApp keyWindow];
}
if (window == nil) {
return;
}
NSView *contentView = [window contentView];
if (contentView == nil) {
return;
}
mark_view_tree_needs_display(contentView);
[window displayIfNeeded];
});
// puts("refresh");
//[[NSApp mainWindow].contentView drawRect:NSMakeRect(0,0,2000,2000)];
//[[NSGraphicsContext currentContext] flushGraphics];
}
gg__Image darwin_create_image(string path_) {
// file = file.trim_space()
NSString* path = nsstring(path_);
NSImage* img = [[NSImage alloc] initWithContentsOfFile:path];
if (img == 0) {
}
NSSize size = [img size];
gg__Image res;
res.width = size.width;
res.height = size.height;
res.path = path_;
res.ok = true;
// printf("inited img width=%d\n", res.width) ;
// need __bridge_retained so that the pointer is not freed by ARC
res.data = (__bridge_retained voidptr)(img);
return res;
}
void darwin_draw_image(float x, float y, float w, float h, gg__Image* img) {
NSImage* i = (__bridge NSImage*)(img->data);
[i drawInRect:NSMakeRect(x, y, w, h)];
}
void darwin_draw_circle(float x, float y, float d, gg__Color color) {
NSColor* c = nscolor(color);
NSRect rect = NSMakeRect(x, y, d * 2, d * 2);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect:rect];
[c setFill];
// [circlePath stroke];
[circlePath fill];
// NSRectFill(rect);
}
void darwin_draw_circle_empty(float x, float y, float d, gg__Color color) {
NSColor* outlineColor = nscolor(color);
CGFloat outlineWidth = 1.0; //2.0;
NSRect rect = NSMakeRect(x, y, d * 2, d * 2);
NSBezierPath* circlePath = [NSBezierPath bezierPath];
[circlePath appendBezierPathWithOvalInRect:rect];
[outlineColor setStroke];
[circlePath setLineWidth:outlineWidth];
[circlePath stroke];
}