-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cocoa Metal example (no SDL). #52
Conversation
PR to fix build from failing (vet tabs): odin-lang/Odin#4117 |
ols.json
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove this file? You may also add it to .gitignore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, didn't mean to commit it.
Just took the sample for a spin and here are a few observations. Hope you don't mind me chiming in. The other samples all have a way of exiting the main loop (e.g. pressing escape to set a Also, the program currently fetches just one event per main-loop iteration instead of draining all events from the queue. But this might be intentional? Those were essentially my modifications: // `metal_main`
{
// Setup... (but remove call to NS.scoped_autoreleasepool() to prevent segfault)
// Main Loop
for quit := false; !quit;
{
// Poll all events from the queue
for
{
e := app->nextEventMatchingMask(NS.EventMaskAny, nil, NS.DefaultRunLoopMode, true)
if e == nil { break }
#partial switch e->type()
{
case .KeyDown, .KeyUp:
code := NS.kVK(e->keyCode())
#partial switch code {
case .Escape:
quit = true
case .LeftArrow:
angle -= 0.02
case .RightArrow:
angle += 0.02
case:
fmt.println(code)
}
case:
fmt.println(e->locationInWindow(), e->modifierFlags())
app->sendEvent(e)
}
}
// Render frame...
}
// Exit `metal_main`
return nil
} |
@mbger Thanks! I added the Escape key handling. The reason I used I wonder if there's a memory leak somewhere? If I press left or right key and hold it for a minute, memory usage for the process will slowly keep increasing. With or without the |
In your original example, the scoped autorelease pool never has a chance to release anything because the relevant scope never exits. I think what might help is to set up a scoped autorelease pool specifically for the event-polling section of our code (though I can't test now): // Main Loop
for quit := false; !quit;
{
// Poll all events from the queue
{
NS.scoped_autoreleasepool()
for
{
e := app->nextEventMatchingMask(NS.EventMaskAny, nil, NS.DefaultRunLoopMode, true)
if e == nil { break }
#partial switch e->type()
{
case .KeyDown, .KeyUp:
code := NS.kVK(e->keyCode())
#partial switch code {
case .Escape:
quit = true
case .LeftArrow:
angle -= 0.02
case .RightArrow:
angle += 0.02
case:
fmt.println(code)
}
case:
fmt.println(e->locationInWindow(), e->modifierFlags())
}
app->sendEvent(e)
}
}
// Render frame |
Yes, that's definitely an improvement. Just made that change. It still looks like it might be leaking a little though. |
One thing I've noticed is that pressing the x to close the app just closes the window but doesn't quit the program. |
Yes, one needs to create an AppDelegate and implement one of its methods to handle x button behavior. But IDK how to combine that and explicit event loop handling. |
That seems like a blocker to me, any application should support properly closing like that |
No problem. Should I keep this open or close and reopen when I have an idea how to do that? I don't have any immediate plans to explore further, the AppDelegate stuff is way over my head and I'm making more progress elsewhere. |
You don't have to close it, maybe somebody else sees it and is able to implement the missing things. |
I don't know how useful it is, but I did something like this a while back |
I think you're in good company. This stuff is tricky to get right and after doing a bit of research I think every approach will have its drawbacks. See for example this discussion in the sokol project. (AFAICT they don't set up their own main loop but start the As for the immediate PR-blocking issue here (exiting the program when the window is closed): I think we could register a NSWindowDelegate and set our
// Global scope
quit := false
windowWillClose :: proc (^NS.Notification)
{
quit = true
}
// metal_main
window->setDelegate(NS.window_delegate_register_and_alloc({
windowWillClose = windowWillClose
}, "MainWindowDelegate", context)) |
@mbger Thank you! Just pushed those changes. Now there are 4 different ways to close the app. From the menu, via Cmd+Q shortcut, by pressing Escape, and by clicking the x button. @colrdavidson Thanks! But I don't see where the |
On a separate note, when pressing keyboard keys, the app makes those beep sounds, as if the action is invalid. If I don't call I guess I can handle Cmd+Q separately, there is enough information for me do that, but I wonder if there's a better way to suppress those beep sounds? EDIT: actually, that wouldn't really work, because that would require special handling for all shortcuts, not just Cmd+Q. |
Don't know if that's the way to do it, but what worked for me is to create a custom subclass of USE_CUSTOM_WINDOW :: true
// ...
window: ^NS.Window
when USE_CUSTOM_WINDOW
{
customWindowClass := NS.objc_allocateClassPair(intrinsics.objc_find_class("NSWindow"), strings.clone_to_cstring("CustomWindow", context.temp_allocator), 0)
keyDown :: proc "c" (event: ^NS.Event) { /* ignore key down events */ }
assert(customWindowClass != nil)
NS.class_addMethod(customWindowClass, intrinsics.objc_find_selector("keyDown:"), auto_cast keyDown, "v@:@")
NS.objc_registerClassPair(customWindowClass)
keyIgnoringWindow := NS.class_createInstance(customWindowClass, size_of(NS.Window))
window = cast(^NS.Window)keyIgnoringWindow
}
else
{
window = NS.Window.alloc()
}
defer window->release() |
Apparently, one can override the But I haven't figured out how to do that yet. Is that the same as overriding a method? There are these ObjC Runtime functions:
But I'm reading that you can't actually add properties at runtime: https://stackoverflow.com/questions/7819092/how-can-i-add-properties-to-an-object-at-runtime So I'm confused. This doesn't make it stop beeping on key press: CustomWindowClass := NS.objc_allocateClassPair(intrinsics.objc_find_class("NSWindow"), strings.clone_to_cstring("CustomWindow", context.temp_allocator), 0)
assert(CustomWindowClass != nil)
assert(NS.class_getProperty(CustomWindowClass, "acceptsFirstResponder") != nil)
acceptsFirstResponder :: proc "c" () -> NS.BOOL { return NS.YES }
sel := intrinsics.objc_find_selector("acceptsFirstResponder")
assert(NS.class_respondsToSelector(CustomWindowClass, sel) == true)
NS.class_addMethod(CustomWindowClass, sel, auto_cast acceptsFirstResponder, "B@:")
NS.objc_registerClassPair(CustomWindowClass)
window := cast(^NS.Window)(NS.class_createInstance(CustomWindowClass, size_of(NS.Window)))
defer window->release() In the meantime, I've submitted this as I keep researching: odin-lang/Odin#4171 |
Maybe the accepted answer is just incorrect and there's nothing wrong with how your Odin code? Have you tried to confirm the accepted answer with Xcode > File > New Project? |
Well,
However, Cmd+Q stops working. |
Just curious: What are the downsides with the approach I outlined in a previous comment? At least on my end everything seems to work just fine – no beeps and CMD+Q quits the program. |
I would prefer not to ignore the key down event, if possible. My intent was to show how to create a macOS GUI that idles with zero CPU usage in the absence of events. And when typing, characters show up on screen on key down event. Though it's probably equally as important for games. Of course, I can just use your solution (which I'm grateful for btw) just to get this PR to merge. But I wonder if there's something else we can try? This has been a very productive conversation so far. |
3e74173
to
2b21d06
Compare
Yep, your gist also doesn't work: focus.mov |
Thanks! Are you running your browser & terminal/editor in some special windowing mode? Looks a bit like the macOS native fullscreen split mode but w/o the divider in the middle. For me it looks like this: CleanShot.2024-09-04.at.16.13.12.mp4 |
Nothing special going on afaict, I do have Yabai but disabling that doesn't help, my terminal is Alacritty. I think we should just put in the deprecated |
Well... I guess this explains why in your case the app is also half hidden behind alacritty at first and then ordered in front... 😄
|
Weird,
is what I tried and it didn't work for me. But I'm on macOS 15.0 Beta (24A5331b), if that matters. |
@laytan I replaced |
Cool. I think we're good to go now. |
This is amazing actually. This has been annoying me for years! On any platform.
More here: https://developer.apple.com/videos/play/wwdc2023/10054/ So I think we should change back to |
Well as we have seen here, it clearly doesn't work well enough yet. I am guessing apps that open others have to implement something (that yield concept) and until they do just an ->activate doesn't work. |
Not sure how Either way, I'm happy for this to be merged as is too. |
It looks like this is not yet the case. I think it's good to leave like this so it behaves like glfw, SDL, and Sokol does, we can always revisit later. I will look over the entire pr once more in a bit and merge if there is nothing else, great work here any way! |
Ah man it is not quite there yet, sorry :( If you minimise the app you can't get it back into focus by clicking it in the dock. You also can't quit the app by right clicking the dock entry and clicking on quit. |
Weird that we're experiencing such different behavior. If I minimize it's still active and receives events. And right-click -> quit from the dock icon quits the program. |
Oh that is weird, I assumed this would reproduce for you too. I did confirm that the sdl version worked as expected, but let me investigate some more. |
Same here. And I can restore it by clicking the dock icon.
But also same here. Selecting "Quit" from dock icon's context menu does nothing.
Indeed. |
I managed to find an example in pure C: (funny enough, it's bases on the Obj-C example I was already using) But after literal translation to Odin, the Odin app simply doesn't quit from the dock icon menu, whereas the C app does. Here's my Odin code: package main
import NS "core:sys/darwin/Foundation"
main :: proc() {
app := NS.Application.sharedApplication()
window1 := NS.Window.alloc()->initWithContentRect({{100, 100}, {300, 300}}, { .Titled, .Closable, .Miniaturizable, .Resizable }, .Buffered, false)
window1->setIsVisible(true)
app->setActivationPolicy(.Regular)
app->finishLaunching()
for {
NS.scoped_autoreleasepool()
event := app->nextEventMatchingMask(NS.EventMaskAny, NS.Date.distantFuture(), NS.DefaultRunLoopMode, true)
if event->type() == .KeyDown && NS.kVK(event->keyCode()) == .ANSI_Q { break }
app->sendEvent(event)
app->updateWindows()
}
} |
That small example and comparison allowed me to debug it and I found the issue, working on a fix. |
Is it something to do with |
It was because our |
I believe the last thing you need here is a call to |
Ah, so the right events were not coming through.
Done, thanks. Indeed, the dock menu Quit didn't work without this one even with the new mask. |
Thanks for sticking with me and all the work here, this is a very nice example to accompany the native win32 example! |
Nice, thank you! |
.github/workflows/check.yml
?