Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions examples/slow_window_start.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import
std/[os, strformat],
windy, vmath, boxy, opengl

const
SlowStartMs = 3500

var
window: Window
bxy: Boxy
squareSize = 28.0
clicked = false
moveCount = 0

proc drawFrame() =
## Draws the cursor-following square and crosshair.
bxy.beginFrame(window.size)
bxy.saveTransform()

let
scale = window.contentScale
logicalSize = window.size.vec2 / scale
cursorPos = window.mousePos.vec2 / scale
halfSize = squareSize / 2.0
squareColor =
if clicked:
color(0.2, 0.7, 1.0, 0.9)
else:
color(1.0, 0.3, 0.3, 0.8)

bxy.scale(scale)
bxy.drawRect(
rect(0, 0, logicalSize.x, logicalSize.y),
color(0.1, 0.1, 0.1, 1.0)
)
bxy.drawRect(
rect(
cursorPos.x - halfSize,
cursorPos.y - halfSize,
squareSize,
squareSize
),
squareColor
)
bxy.drawRect(
rect(cursorPos.x - 1, 0, 2, logicalSize.y),
color(0.3, 1.0, 0.3, 0.5)
)
bxy.drawRect(
rect(0, cursorPos.y - 1, logicalSize.x, 2),
color(0.3, 1.0, 0.3, 0.5)
)

bxy.restoreTransform()
bxy.endFrame()
window.swapBuffers()

window = newWindow("Slow Window Start", ivec2(1280, 800))
window.makeContextCurrent()
loadExtensions()
bxy = newBoxy()

window.onMouseMove = proc() =
moveCount += 1

window.onButtonPress = proc(button: Button) =
case button:
of MouseLeft:
clicked = not clicked
echo &"Left click toggled color. Mouse moves seen: {moveCount}"
else:
discard

window.onFrame = proc() =
drawFrame()

echo "Slow window start repro."
echo "The window is visible before the event loop is allowed to run."
echo "After the delay, move the mouse and click the square."

drawFrame()
echo "Delaying for 3,500 ms."
sleep(SlowStartMs)
echo "Delay done."

while not window.closeRequested:
pollEvents()
11 changes: 11 additions & 0 deletions src/windy/platforms/macos/macdefs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type
NSNotification* = distinct NSObject
NSEvent* = distinct NSObject
NSDate* = distinct NSObject
NSRunLoop* = distinct NSObject
NSRunLoopMode* = distinct NSString
NSMenu* = distinct NSObject
NSMenuItem* = distinct NSObject
Expand Down Expand Up @@ -190,6 +191,16 @@ objc:
): NSEvent
proc sendEvent*(self: NSApplication, x: NSEvent)
proc distantPast*(class: typedesc[NSDate]): NSDate
proc dateWithTimeIntervalSinceNow*(
class: typedesc[NSDate],
x: float64
): NSDate
proc currentRunLoop*(class: typedesc[NSRunLoop]): NSRunLoop
proc runMode*(
self: NSRunLoop,
x: NSRunLoopMode,
beforeDate: NSDate
): bool
proc addItem*(self: NSMenu, x: NSMenuItem)
proc initWithTitle*(
self: NSMenuItem,
Expand Down
38 changes: 33 additions & 5 deletions src/windy/platforms/macos/platform.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ type
# AppKit applies this state asynchronously via delegate callbacks.
fullscreenState: bool
minimizedState: bool
activationSettlePolls: int

const
ActivationSettlePolls = 60
ActivationSettleSeconds = 0.001
decoratedResizableWindowMask =
NSWindowStyleMaskTitled or NSWindowStyleMaskClosable or
NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable
Expand Down Expand Up @@ -160,12 +163,25 @@ proc `title=`*(window: Window, title: string) =
proc `icon=`*(window: Window, icon: Image) =
window.state.icon = icon

proc requestActivation(window: Window) =
## Shows the window and asks AppKit to activate this app.
window.inner.makeKeyAndOrderFront(0.ID)
NSApp.activateIgnoringOtherApps(true)

proc settleRunLoop() =
## Gives AppKit a short turn to deliver activation notifications.
discard NSRunLoop.currentRunLoop.runMode(
NSDefaultRunLoopMode,
NSDate.dateWithTimeIntervalSinceNow(ActivationSettleSeconds)
)

proc `visible=`*(window: Window, visible: bool) =
autoreleasepool:
if visible:
window.inner.makeKeyAndOrderFront(0.ID)
NSApp.activateIgnoringOtherApps(true)
window.activationSettlePolls = ActivationSettlePolls
window.requestActivation()
else:
window.activationSettlePolls = 0
window.inner.orderOut(0.ID)

proc `style=`*(window: Window, windowStyle: WindowStyle) =
Expand Down Expand Up @@ -320,7 +336,6 @@ proc applicationDidFinishLaunching(
notification: NSNotification
): ID {.cdecl.} =
NSApp.setPresentationOptions(NSApplicationPresentationDefault)
NSApp.activateIgnoringOtherApps(true)

proc windowDidResize(
self: ID,
Expand Down Expand Up @@ -888,7 +903,22 @@ proc processFlagsChanged(event: NSEvent) =
else:
window.handleButtonPress(button)

proc settleActivationRequests() =
## Replays activation while AppKit catches up after delayed startup.
var pending = false
for window in windows:
if window.activationSettlePolls == 0:
continue
dec window.activationSettlePolls
pending = true
window.requestActivation()
if pending:
settleRunLoop()

proc pollEvents*() =
autoreleasepool:
settleActivationRequests()

# Draw first (in case a message closes a window or similar)
for window in windows:
if window.onFrame != nil:
Expand Down Expand Up @@ -1077,8 +1107,6 @@ proc newWindow*(
result.fullscreenState =
(result.inner.styleMask and NSWindowStyleMaskFullScreen) != 0

pollEvents() # This can cause lots of issues, potential workaround needed

proc title*(window: Window): string =
window.state.title

Expand Down
1 change: 1 addition & 0 deletions tests/run_all.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Examples = [
"property_changes",
"screens",
"scrollwheel",
"slow_window_start",
"system_cursors",
"tray",
"websocket",
Expand Down
Loading