/*
* 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;
}