From 7a0538b2ea7606d16b026926d18f7ec3e85e46b8 Mon Sep 17 00:00:00 2001 From: Paolo Marrone Date: Mon, 24 Mar 2025 16:50:23 +0100 Subject: [PATCH] cocoa! --- test.c | 2 +- vinci-cocoa.m | 432 ++++++++++++++++++++++++++++++++++++++++++++++++++ vinci.h | 4 +- 3 files changed, 435 insertions(+), 3 deletions(-) create mode 100644 vinci-cocoa.m diff --git a/test.c b/test.c index e9706f2..5f55534 100644 --- a/test.c +++ b/test.c @@ -102,4 +102,4 @@ int main (void) { } vinci_destroy(g); -} \ No newline at end of file +} diff --git a/vinci-cocoa.m b/vinci-cocoa.m new file mode 100644 index 0000000..9756af3 --- /dev/null +++ b/vinci-cocoa.m @@ -0,0 +1,432 @@ +/* + * Vinci + * + * Copyright (C) 2025 Orastron Srl unipersonale + * + * Vinci is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * Vinci is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Vinci. If not, see . + * + * File author: Paolo Marrone + */ + +#include "vinci.h" + +#include +#import + +@interface VinciView : NSView +{ + NSTrackingArea *tracking; + @public window *win; +} +@end + +@interface VinciViewController : NSViewController +{ + @public window *win; +} +@property (nonatomic, strong) VinciView *vinciView; +@end + +struct vinci { + window *windows; + NSApplication *app; + NSAutoreleasePool *pool; + char standalone; // If any view has a parent, it is not standalone +}; + +struct window { + vinci *g; + window *next; + NSWindow *nswindow; + VinciView *view; + VinciViewController *controller; + unsigned char *img; + NSImage *nsImage; + void *data; + window_cbs cbs; + NSRect default_frame; +}; + +static uint32_t get_mouse_state(NSEvent* e) { + (void) e; + return (uint32_t) [NSEvent pressedMouseButtons]; +} + +@implementation VinciView + +- (void) updateTrackingAreas { + + [self removeTrackingArea]; + + tracking = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved + owner:self + userInfo:nil]; + [self addTrackingArea:tracking]; + [super updateTrackingAreas]; +} + +- (void) removeTrackingArea { + if (tracking) { + [super removeTrackingArea:tracking]; + [tracking release]; + tracking = 0; + } +} + +- (void) mouseDown : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_press) + w->cbs.on_mouse_press(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) mouseUp : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_release) + w->cbs.on_mouse_release(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) mouseMoved : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_move) + w->cbs.on_mouse_move(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) rightMouseDown : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_press) + w->cbs.on_mouse_press(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) rightMouseUp : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_release) + w->cbs.on_mouse_release(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) rightMouseDragged : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_move) + w->cbs.on_mouse_move(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) otherMouseDown : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_press) + w->cbs.on_mouse_press(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) otherMouseUp : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_release) + w->cbs.on_mouse_release(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) otherMouseDragged : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_move) + w->cbs.on_mouse_move(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) mouseDragged : (NSEvent *) e { + window *w = self->win; + NSPoint p = [self convertPoint:[e locationInWindow] fromView:nil]; + if (w->cbs.on_mouse_move) + w->cbs.on_mouse_move(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) mouseEntered : (NSEvent *) e { + window *w = self->win; + NSPoint p = e.locationInWindow; + if (w->cbs.on_mouse_enter) + w->cbs.on_mouse_enter(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) mouseExited : (NSEvent *) e { + window *w = self->win; + NSPoint p = e.locationInWindow; + if (w->cbs.on_mouse_leave) + w->cbs.on_mouse_leave(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); +} + +- (void) keyDown : (NSEvent *) e { + //window *w = self->win; + //NSPoint p = e.locationInWindow; + //on_key_press(w, (int32_t)p.x, self.frame.size.height - (int32_t)p.y, get_mouse_state(e)); + // TODO +} + +- (void) keyUp : (NSEvent *) e { + //window *w = self->win; + //NSPoint p = e.locationInWindow; + // TODO +} + +// custom +- (void) vinci_resized { + window *w = self->win; + NSSize size = [w->view bounds].size; + w->img = (unsigned char*)realloc(w->img, (uint32_t) (size.width * size.height) * 4); + if (w->cbs.on_window_resize) + w->cbs.on_window_resize(w, size.width, size.height); + [self setNeedsDisplay:YES]; +} + +- (void)removeFromSuperview { + [super removeFromSuperview]; + window *w = self->win; + if (w->cbs.on_window_close) + w->cbs.on_window_close(w); +} + +// Custom method for standalone application +- (void)windowWillClose:(NSNotification *)notification { + window *w = self->win; + if (w->cbs.on_window_close) + w->cbs.on_window_close(w); +} + +- (void) drawRect:(NSRect) r { + [self->win->nsImage drawInRect:r fromRect:r operation:NSCompositingOperationCopy fraction:1.0]; +} + +@end + +@implementation VinciViewController + +- (void)loadView { + self.vinciView = [[VinciView alloc] initWithFrame:self->win->default_frame]; + self.view = self.vinciView; +} + +- (void)viewDidLayout { + [super viewDidLayout]; + [self.vinciView vinci_resized]; +} + +@end + +vinci* vinci_new(void) { + vinci *g = (vinci*) malloc(sizeof(vinci)); + if (g == NULL) + return NULL; + g->windows = NULL; + g->pool = [[NSAutoreleasePool alloc] init]; + g->app = [NSApplication sharedApplication]; + //[g->app setActivationPolicy:NSApplicationActivationPolicyRegular]; // TODO: check if this is a problem when not standalone + g->standalone = 1; + return g; +} + +void vinci_destroy(vinci *g) { + free(g); +} + +void vinci_idle(vinci *g) { + (void) g; + + NSApplication *app = [NSApplication sharedApplication]; + NSDate *expiration = [NSDate now]; + + while (true) { + NSEvent *event = [app nextEventMatchingMask:NSEventMaskAny + untilDate:expiration + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (!event) + break; + [app sendEvent:event]; + } +} + +window* window_new(vinci *g, void *parent, uint32_t width, uint32_t height, window_cbs *cbs) { + + window *ret = (window*) malloc(sizeof(window)); + if (ret == NULL) + return NULL; + + ret->default_frame = NSMakeRect(0, 0, width, height); + + VinciViewController *controller = [[VinciViewController alloc] init]; + controller->win = ret; + VinciView *view = (VinciView*) controller.view; + + view->win = ret; + + ret->g = g; + ret->view = view; + ret->controller = controller; + + if (parent) { + [((NSView*)parent) addSubview:view positioned:NSWindowAbove relativeTo:nil]; + ret->nswindow = [(NSView*)parent window]; + g->standalone = 0; + } + else { + int style = NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable; + ret->nswindow = [[NSWindow alloc] initWithContentRect:ret->default_frame + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + [ret->nswindow setTitle:@"Vinci window"]; + [ret->nswindow setContentView:view]; + + [[NSNotificationCenter defaultCenter] addObserver:view + selector:@selector(windowWillClose:) + name:NSWindowWillCloseNotification + object:ret->nswindow]; + } + + ret->img = (unsigned char*) malloc(width * height * 4); + ret->data = NULL; + ret->nsImage = NULL; + ret->next = NULL; + ret->cbs = *cbs; + + [view autorelease]; + + if (g->windows == NULL) + g->windows = ret; + else { + window *cur = g->windows; + while (cur->next != NULL) + cur = cur->next; + cur->next = ret; + } + + return ret; +} + +void window_free(window *w) { + if (w->g->windows == w) { + w->g->windows = w->next; + } + else { + window *cur = w->g->windows; + while (cur != NULL && cur->next != w) + cur = cur->next; + if (cur == NULL) + return; + cur->next = w->next; + } + + [w->view removeTrackingArea]; + free(w->img); + free(w); +} + +void window_draw(window *w, unsigned char *img, int32_t dx, int32_t dy, int32_t dw, int32_t dh, int32_t wx, int32_t wy, int32_t width, int32_t height) { + if (w->img == NULL) + return; + + NSSize size = [w->view bounds].size; + + uint32_t iw = wy * size.width + wx; + uint32_t o = dy * dw + dx; + uint32_t p1 = dw; + uint32_t p2 = size.width; + + for (int32_t y = dy; y < dy + height && y < dh; y++) { + memcpy(w->img + iw * 4, img + o * 4, width * 4); + iw += p2; + o += p1; + } + + CGDataProviderRef provider = CGDataProviderCreateWithData( + NULL, // void *info + w->img, // const void *data + size.width * size.height * 4, // size_t size + NULL // CGDataProviderReleaseDataCallback releaseData + ); + + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; + CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; + + CGImageRef imageRef = CGImageCreate( + size.width, // size_t width + size.height, // size_t height + 8, // size_t bitsPerComponent + 4 * 8, // size_t bitsPerPixel + size.width * 4, // size_t bytesPerRow + colorSpaceRef, // CGColorSpaceRef space + bitmapInfo, // CGBitmapInfo bitmapInfo + provider, // CGDataProviderRef provider + NULL, // const CGFloat *decode + NO, // bool shouldInterpolate + renderingIntent // CGColorRenderingIntent intent + ); + + NSImage* image = [[NSImage alloc] initWithCGImage: imageRef size:NSZeroSize]; + [w->nsImage release]; + w->nsImage = image; + + [w->view setNeedsDisplayInRect:NSMakeRect(wx, size.height - (wy + height), width, height)]; + + CGDataProviderRelease(provider); + CGColorSpaceRelease(colorSpaceRef); + CGImageRelease(imageRef); +} + +void window_resize (window *w, uint32_t width, uint32_t height) { + NSSize newSize = NSMakeSize(width, height); + [w->view setFrameSize:newSize]; // TODO: this sends NSViewFrameDidChangeNotification, we should handle that instead in 1 place + w->img = (unsigned char*)realloc(w->img, width * height * 4); + w->view.needsDisplay = YES; + if (w->cbs.on_window_resize) + w->cbs.on_window_resize(w, width, height); // view should be automatically informed +} + +void window_move(window *w, uint32_t x, uint32_t y) { + (void) w; + (void) x; + (void) y; +} + +void* window_get_handle(window *w) { + return w->view; +} + +uint32_t window_get_width(window *w) { + return [w->view bounds].size.width; +} +uint32_t window_get_height(window *w) { + return [w->view bounds].size.height; +} + +void window_show(window *w) { + // Can we? nswindow comes from outside + [w->nswindow setIsVisible:YES]; +} + +void window_hide(window *w) { + (void) w; + //[w->nswindow setIsVisible:NO]; +} + +void window_set_data(window *w, void *data) { + w->data = data; +} + +void *window_get_data(window *w) { + return w->data; +} diff --git a/vinci.h b/vinci.h index cf349b8..1dfe74b 100644 --- a/vinci.h +++ b/vinci.h @@ -44,7 +44,7 @@ typedef struct window_cbs { vinci* vinci_new (void); void vinci_destroy (vinci *g); -void vinci_idle (vinci *g); +void vinci_idle (vinci *g); // Non blocking: call it repeatedly with a timer window* window_new (vinci *g, void* p, uint32_t width, uint32_t height, window_cbs *cbs); void window_free (window *w); void window_draw (window *w, unsigned char *data, int32_t dx, int32_t dy, int32_t dw, int32_t dh, int32_t wx, int32_t wy, int32_t width, int32_t height); @@ -58,4 +58,4 @@ void window_hide (window *w); void window_set_data (window *w, void *data); void* window_get_data (window *w); -#endif \ No newline at end of file +#endif