From 51d3ed3f3ddcd691f3e986d98f41b29e482fbb5b Mon Sep 17 00:00:00 2001 From: Jaremy Creechley Date: Sat, 14 Mar 2026 18:59:48 -0600 Subject: [PATCH] add popup api in siwin frameless window fix and demo --- README.md | 2 + examples/frameless_demo.nim | 305 +++++ src/siwin/platforms/any/window.nim | 642 ++++++++--- src/siwin/platforms/cocoa/extras.nim | 33 +- src/siwin/platforms/cocoa/window.nim | 1431 ++++++++++++++---------- src/siwin/platforms/wayland/window.nim | 1424 ++++++++++++++--------- src/siwin/platforms/winapi/window.nim | 784 ++++++++----- src/siwin/platforms/x11/window.nim | 65 ++ src/siwin/window.nim | 490 +++++--- 9 files changed, 3389 insertions(+), 1787 deletions(-) create mode 100644 examples/frameless_demo.nim diff --git a/README.md b/README.md index 619f68d..729a71a 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,8 @@ run window, WindowEventsHandler( ) ``` +For a complete example with a custom title region, resize borders and a software-rendered close button, see [`examples/frameless_demo.nim`](examples/frameless_demo.nim). +

all methods and events

see [siwin/platforms/any/window](https://github.com/levovix0/siwin/blob/master/src/siwin/platforms/any/window.nim) diff --git a/examples/frameless_demo.nim b/examples/frameless_demo.nim new file mode 100644 index 0000000..da19ff0 --- /dev/null +++ b/examples/frameless_demo.nim @@ -0,0 +1,305 @@ +import vmath +import siwin +import siwin/colorutils + +const + TitleBarHeight = 52'i32 + CloseButtonSize = 40'i32 + CloseButtonMargin = 12'i32 + ResizeBorderWidth = 8'f32 + ResizeCornerWidth = 18'f32 + + PopupSize = ivec2(520, 420) + ButtonSize = ivec2(220, 68) + +type + MainDemoState = object + rgbaBuffer: seq[Color32bit] + hoverButton: bool + + PopupDemoState = object + rgbaBuffer: seq[Color32bit] + hoverClose: bool + +proc rgba(r, g, b: byte, a: byte = 255): Color32bit = + [r, g, b, a] + +proc insideRect(pos: Vec2, x, y, w, h: int32): bool = + pos.x >= x.float32 and pos.x < (x + w).float32 and pos.y >= y.float32 and + pos.y < (y + h).float32 + +proc ensureBuffer[T](rgbaBuffer: var seq[T], size: IVec2) = + let pixelCount = max(1, (size.x * size.y).int) + if rgbaBuffer.len != pixelCount: + rgbaBuffer.setLen(pixelCount) + +proc fill(rgbaBuffer: var seq[Color32bit], size: IVec2, color: Color32bit) = + for i in 0 ..< size.x * size.y: + rgbaBuffer[i] = color + +proc fillRect( + rgbaBuffer: var seq[Color32bit], size: IVec2, x, y, w, h: int32, color: Color32bit +) = + let + x0 = max(0, x) + y0 = max(0, y) + x1 = min(size.x, x + w) + y1 = min(size.y, y + h) + + if x0 >= x1 or y0 >= y1: + return + + for py in y0 ..< y1: + let row = py * size.x + for px in x0 ..< x1: + rgbaBuffer[row + px] = color + +proc popupButtonRect(size: IVec2): tuple[x, y, w, h: int32] = + ( + x: max(28, (size.x - ButtonSize.x) div 2), + y: max(32, (size.y - ButtonSize.y) div 2), + w: ButtonSize.x, + h: ButtonSize.y, + ) + +proc closeButtonRect(size: IVec2): tuple[x, y, w, h: int32] = + ( + x: size.x - CloseButtonSize - CloseButtonMargin, + y: (TitleBarHeight - CloseButtonSize) div 2, + w: CloseButtonSize, + h: CloseButtonSize, + ) + +proc popupPlacement(size: IVec2): PopupPlacement = + let buttonRect = popupButtonRect(size) + PopupPlacement( + anchorRectPos: ivec2(buttonRect.x, buttonRect.y), + anchorRectSize: ivec2(buttonRect.w, buttonRect.h), + size: PopupSize, + anchor: PopupAnchor.paBottomLeft, + gravity: PopupGravity.pgTopLeft, + offset: ivec2(0, 14), + constraintAdjustment: { + PopupConstraintAdjustment.pcaSlideX, PopupConstraintAdjustment.pcaFlipY, + PopupConstraintAdjustment.pcaResizeY, + }, + reactive: true, + ) + +proc applyPopupDragRegions(window: Window) = + let size = window.size + let titleWidth = max(1'i32, size.x - CloseButtonSize - CloseButtonMargin * 2) + window.setTitleRegion(vec2(0, 0), vec2(titleWidth.float32, TitleBarHeight.float32)) + window.setBorderWidth(ResizeBorderWidth, 0, ResizeCornerWidth) + +proc drawMainWindow(state: var MainDemoState, size: IVec2, popupOpen: bool) = + state.rgbaBuffer.ensureBuffer(size) + state.rgbaBuffer.fill(size, rgba(245, 247, 250)) + + state.rgbaBuffer.fillRect(size, 48, 52, size.x - 96, 84, rgba(225, 231, 239)) + state.rgbaBuffer.fillRect(size, 48, 164, size.x - 96, 132, rgba(255, 255, 255)) + state.rgbaBuffer.fillRect( + size, 48, size.y - 132, size.x - 96, 72, rgba(225, 231, 239) + ) + + let buttonRect = popupButtonRect(size) + let buttonColor = + if popupOpen: + rgba(48, 103, 214) + elif state.hoverButton: + rgba(71, 125, 232) + else: + rgba(37, 88, 196) + state.rgbaBuffer.fillRect( + size, buttonRect.x, buttonRect.y, buttonRect.w, buttonRect.h, buttonColor + ) + state.rgbaBuffer.fillRect( + size, + buttonRect.x + 6, + buttonRect.y + 6, + buttonRect.w - 12, + buttonRect.h - 12, + rgba(243, 247, 255), + ) + state.rgbaBuffer.fillRect( + size, + buttonRect.x + 18, + buttonRect.y + 18, + buttonRect.w - 36, + buttonRect.h - 36, + buttonColor, + ) + +proc drawCloseGlyph( + rgbaBuffer: var seq[Color32bit], size: IVec2, rect: tuple[x, y, w, h: int32] +) = + let glyphInset = 12 + for i in 0 ..< (rect.w - glyphInset * 2): + let x1 = rect.x + glyphInset + i + let y1 = rect.y + glyphInset + i + let x2 = rect.x + rect.w - glyphInset - 1 - i + let y2 = rect.y + glyphInset + i + if x1 >= 0 and x1 < size.x and y1 >= 0 and y1 < size.y: + rgbaBuffer[y1 * size.x + x1] = rgba(245, 247, 250) + if x2 >= 0 and x2 < size.x and y2 >= 0 and y2 < size.y: + rgbaBuffer[y2 * size.x + x2] = rgba(245, 247, 250) + +proc drawPopupWindow(state: var PopupDemoState, size: IVec2) = + state.rgbaBuffer.ensureBuffer(size) + state.rgbaBuffer.fill(size, rgba(238, 241, 245)) + + state.rgbaBuffer.fillRect(size, 0, 0, size.x, TitleBarHeight, rgba(23, 34, 46)) + state.rgbaBuffer.fillRect( + size, 18, 82, size.x - 36, size.y - 100, rgba(255, 255, 255) + ) + state.rgbaBuffer.fillRect(size, 42, 118, size.x - 84, 92, rgba(226, 232, 240)) + state.rgbaBuffer.fillRect(size, 42, 232, size.x - 84, 92, rgba(212, 226, 255)) + state.rgbaBuffer.fillRect(size, 42, 346, (size.x - 96) div 2, 42, rgba(255, 224, 201)) + state.rgbaBuffer.fillRect( + size, size.x div 2 + 8, 346, (size.x - 96) div 2, 42, rgba(208, 244, 226) + ) + + let closeRect = closeButtonRect(size) + state.rgbaBuffer.fillRect( + size, + closeRect.x, + closeRect.y, + closeRect.w, + closeRect.h, + (if state.hoverClose: rgba(206, 62, 68) else: rgba(153, 33, 40)), + ) + state.rgbaBuffer.drawCloseGlyph(size, closeRect) + +let globals = newSiwinGlobals() +let window = + globals.newSoftwareRenderingWindow(size = ivec2(300, 200), title = "siwin popup demo") + +var + mainState: MainDemoState + popupState: PopupDemoState + popup: PopupWindow + +proc popupIsOpen(): bool = + popup != nil and popup.opened + +proc updatePopupPlacement() = + if popupIsOpen(): + popup.reposition(window.size.popupPlacement()) + +proc closePopup() = + if popupIsOpen(): + popup.close() + +proc installPopupHandlers() = + popup.eventsHandler = WindowEventsHandler( + onResize: proc(e: ResizeEvent) = + e.window.applyPopupDragRegions() + redraw e.window + , + onRender: proc(e: RenderEvent) = + let pixelBuffer = e.window.pixelBuffer + popupState.drawPopupWindow(pixelBuffer.size) + copyMem( + pixelBuffer.data, + popupState.rgbaBuffer[0].addr, + popupState.rgbaBuffer.len * sizeof(Color32bit), + ) + convertPixelsInplace( + pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.rgba_32bit, + pixelBuffer.format, + ), + onMouseMove: proc(e: MouseMoveEvent) = + let closeRect = closeButtonRect(e.window.size) + let newHover = + insideRect(e.pos, closeRect.x, closeRect.y, closeRect.w, closeRect.h) + if popupState.hoverClose != newHover: + popupState.hoverClose = newHover + redraw e.window + , + onClick: proc(e: ClickEvent) = + let closeRect = closeButtonRect(e.window.size) + if e.button == MouseButton.left and + insideRect(e.pos, closeRect.x, closeRect.y, closeRect.w, closeRect.h): + e.window.close() + , + onKey: proc(e: KeyEvent) = + if e.pressed and not e.generated and e.key == Key.escape: + e.window.close() + , + onClose: proc(e: CloseEvent) = + popup = nil + popupState.hoverClose = false + redraw window + , + ) + +proc openPopup() = + if popupIsOpen(): + return + + popup = globals.newPopupWindow(window, window.size.popupPlacement(), grab = true) + popupState.hoverClose = false + popup.applyPopupDragRegions() + installPopupHandlers() + popup.firstStep(makeVisible = true) + redraw popup + redraw window + +window.eventsHandler = WindowEventsHandler( + onResize: proc(e: ResizeEvent) = + updatePopupPlacement() + redraw e.window + , + onWindowMove: proc(e: WindowMoveEvent) = + updatePopupPlacement(), + onRender: proc(e: RenderEvent) = + let pixelBuffer = e.window.pixelBuffer + mainState.drawMainWindow(pixelBuffer.size, popupIsOpen()) + copyMem( + pixelBuffer.data, + mainState.rgbaBuffer[0].addr, + mainState.rgbaBuffer.len * sizeof(Color32bit), + ) + convertPixelsInplace( + pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.rgba_32bit, + pixelBuffer.format, + ), + onMouseMove: proc(e: MouseMoveEvent) = + let buttonRect = popupButtonRect(e.window.size) + let newHover = + insideRect(e.pos, buttonRect.x, buttonRect.y, buttonRect.w, buttonRect.h) + if mainState.hoverButton != newHover: + mainState.hoverButton = newHover + redraw e.window + , + onClick: proc(e: ClickEvent) = + let buttonRect = popupButtonRect(e.window.size) + if e.button == MouseButton.left and + insideRect(e.pos, buttonRect.x, buttonRect.y, buttonRect.w, buttonRect.h): + if popupIsOpen(): + closePopup() + else: + openPopup() + , + onClose: proc(e: CloseEvent) = + closePopup(), + onTick: proc(e: TickEvent) = + redraw e.window + if popupIsOpen(): + redraw popup + , + onKey: proc(e: KeyEvent) = + if e.pressed and not e.generated and e.key == Key.escape: + if popupIsOpen(): + closePopup() + else: + close e.window + , +) + +window.firstStep(makeVisible = true) +while window.opened or popupIsOpen(): + if window.opened: + window.step() + if popupIsOpen(): + popup.step() diff --git a/src/siwin/platforms/any/window.nim b/src/siwin/platforms/any/window.nim index f3717c3..ff01707 100644 --- a/src/siwin/platforms/any/window.nim +++ b/src/siwin/platforms/any/window.nim @@ -3,16 +3,18 @@ import pkg/[vmath] import ../../[siwindefs, colorutils] import ./[clipboards] - when siwin_use_pure_enums: {.pragma: siwin_enum, pure.} else: {.pragma: siwin_enum.} - type MouseButton* {.siwin_enum.} = enum - left right middle forward backward + left + right + middle + forward + backward ModifierKey* {.siwin_enum.} = enum shift @@ -24,37 +26,128 @@ type Key* {.siwin_enum.} = enum unknown = 0 - - a b c d e f g h i j k l m n o p q r s t u v w x y z - tilde n1 n2 n3 n4 n5 n6 n7 n8 n9 n0 minus equal - f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 - lcontrol rcontrol lshift rshift lalt ralt lsystem rsystem lbracket rbracket - space escape enter tab backspace menu - slash dot comma semicolon quote backslash - - pageUp pageDown home End insert del - left right up down - npad0 npad1 npad2 npad3 npad4 npad5 npad6 npad7 npad8 npad9 npadDot - add subtract multiply divide - capsLock numLock scrollLock printScreen pause - - level3_shift level5_shift - + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + tilde + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n0 + minus + equal + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + f13 + f14 + f15 + lcontrol + rcontrol + lshift + rshift + lalt + ralt + lsystem + rsystem + lbracket + rbracket + space + escape + enter + tab + backspace + menu + slash + dot + comma + semicolon + quote + backslash + pageUp + pageDown + home + End + insert + del + left + right + up + down + npad0 + npad1 + npad2 + npad3 + npad4 + npad5 + npad6 + npad7 + npad8 + npad9 + npadDot + add + subtract + multiply + divide + capsLock + numLock + scrollLock + printScreen + pause + level3_shift + level5_shift TouchDeviceKind* {.siwin_enum.} = enum touchScreen touchPad graphicsTablet - + Touch* = ref object - id*: int # begins at 1, increments for each new touch + id*: int # begins at 1, increments for each new touch pos*: Vec2 pressed*: bool - pressure*: float # 0..1 + pressure*: float # 0..1 button*: Option[MouseButton] device*: TouchDeviceKind - Mouse* = object pos*: Vec2 pressed*: set[MouseButton] @@ -62,10 +155,9 @@ type Keyboard* = object pressed*: set[Key] modifiers*: set[ModifierKey] - + TouchScreen* = object - touches*: Table[int, Touch] # id -> touch - + touches*: Table[int, Touch] # id -> touch Edge* {.siwin_enum.} = enum left @@ -77,7 +169,6 @@ type bottomLeft bottomRight - CursorKind* {.siwin_enum.} = enum builtin image @@ -88,70 +179,120 @@ type of image: image*: ImageCursor BuiltinCursor* {.siwin_enum.} = enum - arrow arrowUp arrowRight - wait arrowWait - pointingHand grab - text cross - sizeAll sizeHorizontal sizeVertical - sizeTopLeft sizeTopRight sizeBottomLeft sizeBottomRight + arrow + arrowUp + arrowRight + wait + arrowWait + pointingHand + grab + text + cross + sizeAll + sizeHorizontal + sizeVertical + sizeTopLeft + sizeTopRight + sizeBottomLeft + sizeBottomRight hided - + ImageCursor* = object origin*: IVec2 pixels*: PixelBuffer - WindowTypeDefect* = object of Defect ## raised when trying to get pixel buffer from non-softwareRendering window - SiwinGlobals* = ref object of RootObj - Screen* = ref object of RootObj - MouseMoveKind* {.siwin_enum.} = enum move enter leave - moveWhileDragging ## (from this or other window) + moveWhileDragging ## (from this or other window) - DragStatus* {.siwin_enum.} = enum rejected accepted + PopupAnchor* {.siwin_enum.} = enum + paTopLeft + paTop + paTopRight + paLeft + paCenter + paRight + paBottomLeft + paBottom + paBottomRight + + PopupGravity* {.siwin_enum.} = enum + pgTopLeft + pgTop + pgTopRight + pgLeft + pgCenter + pgRight + pgBottomLeft + pgBottom + pgBottomRight + + PopupConstraintAdjustment* {.siwin_enum.} = enum + pcaSlideX + pcaSlideY + pcaFlipX + pcaFlipY + pcaResizeX + pcaResizeY + + PopupDismissReason* {.siwin_enum.} = enum + pdrClientClosed + pdrCompositorDismissed + pdrParentClosed + + PopupPlacement* = object + anchorRectPos*: IVec2 + anchorRectSize*: IVec2 + size*: IVec2 + anchor*: PopupAnchor + gravity*: PopupGravity + offset*: IVec2 + constraintAdjustment*: set[PopupConstraintAdjustment] + reactive*: bool AnyWindowEvent* = object of RootObj window*: Window - + CloseEvent* = object of AnyWindowEvent RenderEvent* = object of AnyWindowEvent TickEvent* = object of AnyWindowEvent deltaTime*: Duration - + ResizeEvent* = object of AnyWindowEvent size*: IVec2 initial*: bool - + WindowMoveEvent* = object of AnyWindowEvent pos*: IVec2 MouseMoveEvent* = object of AnyWindowEvent pos*: Vec2 kind*: MouseMoveKind - + MouseButtonEvent* = object of AnyWindowEvent button*: MouseButton pressed*: bool - generated*: bool ## generated, for example, by releaseAllKeys when alt-tab. Means user don't actually do this action - + generated*: bool + ## generated, for example, by releaseAllKeys when alt-tab. Means user don't actually do this action + ScrollEvent* = object of AnyWindowEvent delta*: float deltaX*: float - + ClickEvent* = object of AnyWindowEvent button*: MouseButton pos*: Vec2 @@ -160,27 +301,29 @@ type KeyEvent* = object of AnyWindowEvent key*: Key pressed*: bool - repeated*: bool ## means user is holding this key and system is repeating keydown+keyup - generated*: bool ## generated, for example, by releaseAllKeys when alt-tab. Means user don't actually do this action + repeated*: bool + ## means user is holding this key and system is repeating keydown+keyup + generated*: bool + ## generated, for example, by releaseAllKeys when alt-tab. Means user don't actually do this action modifiers*: set[ModifierKey] - + TextInputEvent* = object of AnyWindowEvent text*: string repeated*: bool - + TouchEvent* = object of AnyWindowEvent touch*: Touch pressed*: bool - + TouchMoveEvent* = object of AnyWindowEvent touch*: Touch kind*: MouseMoveKind pos*: Vec2 - + TouchPressureChangedEvent* = object of AnyWindowEvent touch*: Touch - pressure*: float # 0..1 - + pressure*: float # 0..1 + StateBoolChangedEventKind* {.siwin_enum.} = enum focus fullscreen @@ -190,37 +333,49 @@ type StateBoolChangedEvent* = object of AnyWindowEvent value*: bool kind*: StateBoolChangedEventKind - isExternal*: bool ## changed by user via compositor (server-side change) - + isExternal*: bool ## changed by user via compositor (server-side change) - DropEvent* = object of AnyWindowEvent + PopupEvent* = object of AnyWindowEvent + reason*: PopupDismissReason + DropEvent* = object of AnyWindowEvent WindowEventsHandler* = object - onClose*: proc(e: CloseEvent) ## this window was closed (by pressing window close button, alt+f4, or by code) - onRender*: proc(e: RenderEvent) ## this window is beeng redrawn, a full frame should be drawn on window until this callback finishes - onTick*: proc(e: TickEvent) ## some time has passed and all pending events was handled - onResize*: proc(e: ResizeEvent) ## this window changed it's width or height - onWindowMove*: proc(e: WindowMoveEvent) ## this window changed it's position on screen - - onMouseMove*: proc(e: MouseMoveEvent) ## the mouse cursor changed it's position - onMouseButton*: proc(e: MouseButtonEvent) ## a mouse button become pressed or released - onScroll*: proc(e: ScrollEvent) ## a mouse wheel rotated (or scrolled by touchpad) - onClick*: proc(e: ClickEvent) ## a mouse released a button without moving from position is was pressed this button - - onKey*: proc(e: KeyEvent) ## a key on a keyboard become pressed or released - onTextInput*: proc(e: TextInputEvent) ## a (input method managed) unicode characters was inputed - - onTouch*: proc(e: TouchEvent) ## a touch either become pressed or released - onTouchMove*: proc(e: TouchMoveEvent) ## a touch changed it's position (can be either pressed or released) - onTouchPressureChanged*: proc(e: TouchPressureChangedEvent) ## a touch changed it's pressure (can be either pressed or released) + onClose*: proc(e: CloseEvent) + ## this window was closed (by pressing window close button, alt+f4, or by code) + onRender*: proc(e: RenderEvent) + ## this window is beeng redrawn, a full frame should be drawn on window until this callback finishes + onTick*: proc(e: TickEvent) + ## some time has passed and all pending events was handled + onResize*: proc(e: ResizeEvent) ## this window changed it's width or height + onWindowMove*: proc(e: WindowMoveEvent) + ## this window changed it's position on screen + + onMouseMove*: proc(e: MouseMoveEvent) ## the mouse cursor changed it's position + onMouseButton*: proc(e: MouseButtonEvent) + ## a mouse button become pressed or released + onScroll*: proc(e: ScrollEvent) ## a mouse wheel rotated (or scrolled by touchpad) + onClick*: proc(e: ClickEvent) + ## a mouse released a button without moving from position is was pressed this button + + onKey*: proc(e: KeyEvent) ## a key on a keyboard become pressed or released + onTextInput*: proc(e: TextInputEvent) + ## a (input method managed) unicode characters was inputed + + onTouch*: proc(e: TouchEvent) ## a touch either become pressed or released + onTouchMove*: proc(e: TouchMoveEvent) + ## a touch changed it's position (can be either pressed or released) + onTouchPressureChanged*: proc(e: TouchPressureChangedEvent) + ## a touch changed it's pressure (can be either pressed or released) onStateBoolChanged*: proc(e: StateBoolChangedEvent) ## binary state of focus/fullscreen/maximized/frameless changed ## fullscreen and maximized changes are sent before ResizeEvent - onDrop*: proc(e: DropEvent) ## drag&drop clipboard content is beeng pasted to this window + onPopupDone*: proc(e: PopupEvent) ## popup was dismissed or explicitly closed + onDrop*: proc(e: DropEvent) + ## drag&drop clipboard content is beeng pasted to this window Window* = ref object of RootObj mouse*: Mouse @@ -229,18 +384,23 @@ type eventsHandler*: WindowEventsHandler clicking: set[MouseButton] - + redrawRequested: bool lastTickTime: times.Time m_closed: bool - + m_transparent: bool m_frameless: bool m_cursor: Cursor m_separateTouch: bool - + m_isPopup: bool + m_popupGrab: bool + m_popupDismissed: bool + m_popupParent: Window + m_popupPlacement: PopupPlacement + m_size: IVec2 m_pos: IVec2 m_focused: bool @@ -259,192 +419,342 @@ type inputRegion, titleRegion: Option[tuple[pos, size: Vec2]] borderWidth: Option[tuple[innerWidth, outerWidrth, diagonalSize: float32]] +type PopupWindow* = Window + +func popupSize*(placement: PopupPlacement): IVec2 = + if placement.size.x > 0 and placement.size.y > 0: + placement.size + elif placement.anchorRectSize.x > 0 and placement.anchorRectSize.y > 0: + placement.anchorRectSize + else: + ivec2(1, 1) + +func popupAnchorOffset*(anchor: PopupAnchor, size: IVec2): IVec2 = + case anchor + of PopupAnchor.paTopLeft: + ivec2(0, 0) + of PopupAnchor.paTop: + ivec2(size.x div 2, 0) + of PopupAnchor.paTopRight: + ivec2(size.x, 0) + of PopupAnchor.paLeft: + ivec2(0, size.y div 2) + of PopupAnchor.paCenter: + ivec2(size.x div 2, size.y div 2) + of PopupAnchor.paRight: + ivec2(size.x, size.y div 2) + of PopupAnchor.paBottomLeft: + ivec2(0, size.y) + of PopupAnchor.paBottom: + ivec2(size.x div 2, size.y) + of PopupAnchor.paBottomRight: + ivec2(size.x, size.y) + +func popupAnchorOffset*(anchor: PopupGravity, size: IVec2): IVec2 = + case anchor + of PopupGravity.pgTopLeft: + ivec2(0, 0) + of PopupGravity.pgTop: + ivec2(size.x div 2, 0) + of PopupGravity.pgTopRight: + ivec2(size.x, 0) + of PopupGravity.pgLeft: + ivec2(0, size.y div 2) + of PopupGravity.pgCenter: + ivec2(size.x div 2, size.y div 2) + of PopupGravity.pgRight: + ivec2(size.x, size.y div 2) + of PopupGravity.pgBottomLeft: + ivec2(0, size.y) + of PopupGravity.pgBottom: + ivec2(size.x div 2, size.y) + of PopupGravity.pgBottomRight: + ivec2(size.x, size.y) + +func popupRelativePos*(placement: PopupPlacement): IVec2 = + let anchorPoint = + placement.anchorRectPos + + placement.anchor.popupAnchorOffset(placement.anchorRectSize) + anchorPoint - placement.gravity.popupAnchorOffset(placement.popupSize()) + + placement.offset + +method number*(screen: Screen): int32 {.base.} = + discard + +method width*(screen: Screen): int32 {.base.} = + discard + +method height*(screen: Screen): int32 {.base.} = + discard + +proc size*(screen: Screen): IVec2 = + ivec2(screen.width, screen.height) + +proc closed*(window: Window): bool = + window.m_closed + +proc opened*(window: Window): bool = + not window.closed -method number*(screen: Screen): int32 {.base.} = discard +method close*(window: Window) {.base.} = + ## request window close + window.m_closed = true -method width*(screen: Screen): int32 {.base.} = discard -method height*(screen: Screen): int32 {.base.} = discard +proc transparent*(window: Window): bool = + window.m_transparent -proc size*(screen: Screen): IVec2 = ivec2(screen.width, screen.height) +proc frameless*(window: Window): bool = + window.m_frameless +proc cursor*(window: Window): Cursor = + window.m_cursor -proc closed*(window: Window): bool = window.m_closed -proc opened*(window: Window): bool = not window.closed +proc separateTouch*(window: Window): bool = + ## enable/disable handling touch events separately from mouse events + window.m_separateTouch -method close*(window: Window) {.base.} = - ## request window close - window.m_closed = true +proc isPopup*(window: Window): bool = + window.m_isPopup -proc transparent*(window: Window): bool = window.m_transparent -proc frameless*(window: Window): bool = window.m_frameless -proc cursor*(window: Window): Cursor = window.m_cursor -proc separateTouch*(window: Window): bool = window.m_separateTouch - ## enable/disable handling touch events separately from mouse events +proc popupGrab*(window: Window): bool = + window.m_popupGrab -method reportedSize*(window: Window): IVec2 {.base.} = window.m_size +method reportedSize*(window: Window): IVec2 {.base.} = ## Size reported to API users/events (backing pixels on HiDPI platforms). + window.m_size -proc size*(window: Window): IVec2 = window.reportedSize() -proc pos*(window: Window): IVec2 = window.m_pos -proc fullscreen*(window: Window): bool = window.m_fullscreen -proc maximized*(window: Window): bool = window.m_maximized -proc minimized*(window: Window): bool = window.m_minimized -proc visible*(window: Window): bool = window.m_visible -proc resizable*(window: Window): bool = window.m_resizable -proc minSize*(window: Window): IVec2 = window.m_minSize -proc maxSize*(window: Window): IVec2 = window.m_maxSize +proc size*(window: Window): IVec2 = + window.reportedSize() -proc focused*(window: Window): bool = window.m_focused +proc pos*(window: Window): IVec2 = + window.m_pos -method uiScale*(window: Window): float32 {.base.} = 1'f32 - ## UI scale factor (device pixels per logical point). +proc fullscreen*(window: Window): bool = + window.m_fullscreen +proc maximized*(window: Window): bool = + window.m_maximized -# note: locks: "unknown" usualy means that function can cause event outside of event loop +proc minimized*(window: Window): bool = + window.m_minimized + +proc visible*(window: Window): bool = + window.m_visible + +proc resizable*(window: Window): bool = + window.m_resizable + +proc minSize*(window: Window): IVec2 = + window.m_minSize + +proc maxSize*(window: Window): IVec2 = + window.m_maxSize + +proc focused*(window: Window): bool = + window.m_focused + +method parentWindow*(window: Window): Window {.base.} = + window.m_popupParent + +method placement*(window: Window): PopupPlacement {.base.} = + window.m_popupPlacement + +proc popupOpen*(window: Window): bool = + window.opened and window.visible + +proc initPopupState*(window, parent: Window, placement: PopupPlacement, grab: bool) = + window.m_isPopup = true + window.m_popupGrab = grab + window.m_popupDismissed = false + window.m_popupParent = parent + window.m_popupPlacement = placement + window.m_size = placement.popupSize() + +proc notifyPopupDone*(window: Window, reason: PopupDismissReason) = + if not window.m_isPopup or window.m_popupDismissed: + return + window.m_popupDismissed = true + if window.eventsHandler.onPopupDone != nil: + window.eventsHandler.onPopupDone(PopupEvent(window: window, reason: reason)) +method uiScale*(window: Window): float32 {.base.} = + ## UI scale factor (device pixels per logical point). + 1'f32 + +# note: locks: "unknown" usualy means that function can cause event outside of event loop -method redraw*(window: Window) {.base.} = window.redrawRequested = true - ## request render +method redraw*(window: Window) {.base.} = ## request render + window.redrawRequested = true -method `frameless=`*(window: Window, v: bool) {.base.} = discard +method `frameless=`*(window: Window, v: bool) {.base.} = + discard -method `cursor=`*(window: Window, v: Cursor) {.base.} = discard +method `cursor=`*(window: Window, v: Cursor) {.base.} = ## set cursor ## used when mouse hover window + discard - -method `separateTouch=`*(window: Window, v: bool) {.base.} = discard +method `separateTouch=`*(window: Window, v: bool) {.base.} = ## enable/disable handling touch events separately from mouse events + discard - -method `size=`*(window: Window, v: IVec2) {.base.} = discard +method `size=`*(window: Window, v: IVec2) {.base.} = ## resize window ## exit fullscreen if window is fullscreen + discard -method `pos=`*(window: Window, v: IVec2) {.base.} = discard +method `pos=`*(window: Window, v: IVec2) {.base.} = ## move window ## do nothing if window is fullscreen + discard -method `title=`*(window: Window, v: string) {.base.} = discard - ## set window title +method `title=`*(window: Window, v: string) {.base.} = ## set window title + discard -method `fullscreen=`*(window: Window, v: bool) {.base.} = discard +method `fullscreen=`*(window: Window, v: bool) {.base.} = ## fullscreen/unfullscreen window + discard -method `maximized=`*(window: Window, v: bool) {.base.} = discard +method `maximized=`*(window: Window, v: bool) {.base.} = ## maximize/unmaximize window ## exit fullscreen if window is fullscreen + discard -method `minimized=`*(window: Window, v: bool) {.base.} = discard - ## minimize/unminimize window +method `minimized=`*(window: Window, v: bool) {.base.} = ## minimize/unminimize window + discard -method `visible=`*(window: Window, v: bool) {.base.} = discard - ## show/hide window +method `visible=`*(window: Window, v: bool) {.base.} = ## show/hide window + discard -method `resizable=`*(window: Window, v: bool) {.base.} = discard - ## enable/disable resizing +method `resizable=`*(window: Window, v: bool) {.base.} = ## enable/disable resizing + discard -method `minSize=`*(window: Window, v: IVec2) {.base.} = discard +method `minSize=`*(window: Window, v: IVec2) {.base.} = ## set minimum size ## `window.resizable=` will disable this + discard -method `maxSize=`*(window: Window, v: IVec2) {.base.} = discard +method `maxSize=`*(window: Window, v: IVec2) {.base.} = ## set maximum size ## `window.resizable=` will disable this + discard -method canBecomeKeyWindow*(window: Window): bool {.base.} = true +method `placement=`*(window: Window, v: PopupPlacement) {.base.} = + window.m_popupPlacement = v + let size = v.popupSize() + if window.m_size != size: + window.m_size = size + +method reposition*(window: Window, v: PopupPlacement) {.base.} = + window.placement = v + +method dismiss*(window: Window) {.base.} = + window.close() + +method canBecomeKeyWindow*(window: Window): bool {.base.} = ## whether this window is allowed to become key window. ## only macOS backend uses this property. + true -method canBecomeMainWindow*(window: Window): bool {.base.} = true +method canBecomeMainWindow*(window: Window): bool {.base.} = ## whether this window is allowed to become main window. ## only macOS backend uses this property. + true -method `canBecomeKeyWindow=`*(window: Window, v: bool) {.base.} = discard -method `canBecomeMainWindow=`*(window: Window, v: bool) {.base.} = discard +method `canBecomeKeyWindow=`*(window: Window, v: bool) {.base.} = + discard -method `icon=`*(window: Window, v: nil.typeof) {.base.} = discard - ## clear window icon +method `canBecomeMainWindow=`*(window: Window, v: bool) {.base.} = + discard -method `icon=`*(window: Window, v: PixelBuffer) {.base.} = discard - ## set window icon +method `icon=`*(window: Window, v: nil.typeof) {.base.} = ## clear window icon + discard +method `icon=`*(window: Window, v: PixelBuffer) {.base.} = ## set window icon + discard -method startInteractiveMove*(window: Window, pos: Option[Vec2] = none Vec2) {.base.} = discard +method startInteractiveMove*(window: Window, pos: Option[Vec2] = none Vec2) {.base.} = ## allow user to move window interactivly ## useful to create client-side decorated windows ## it's recomended to start interactive move after user grabbed window header and started to move mouse + discard - -method startInteractiveResize*(window: Window, edge: Edge, pos: Option[Vec2] = none Vec2) {.base.} = discard +method startInteractiveResize*( + window: Window, edge: Edge, pos: Option[Vec2] = none Vec2 +) {.base.} = ## allow user to resize window interactivly ## useful to create client-side decorated windows ## it's recomended to start interactive resize after user grabbed window border and started to move mouse + discard - -method showWindowMenu*(window: Window, pos: Option[Vec2] = none Vec2) {.base.} = discard +method showWindowMenu*(window: Window, pos: Option[Vec2] = none Vec2) {.base.} = ## show OS/platform/DE-specific window menu ## it's recomended to show menu after user right-clicked on window header ## for now works only on Linux(Wayland) - + discard method setInputRegion*(window: Window, pos, size: Vec2) {.base.} = ## set the rect (in window-local coordinates) where actual window is placed (inluding titlebar, if has one). ## this is used by Windows and Linux(Wayland) to correctly anchor the window and to correctly send mouse and touch events. ## it's recomended to set input region if you draw shadows for window. ## setInputRegion, if called once, must be called after each resize of the window - assert size.x > 0 and size.y > 0, "there must be at least one pixel of the actual window" + assert size.x > 0 and size.y > 0, + "there must be at least one pixel of the actual window" window.inputRegion = some (pos, size) - method setTitleRegion*(window: Window, pos, size: Vec2) {.base.} = ## set the rect (in window-local coordinates) where titlebar is placed. ## this is used by Windows to allow user to move window interactivly. siwin will replicate this behaviour on other platforms. ## it's recomended to set title region if you have custom titlebar. window.titleRegion = some (pos, size) - -method setBorderWidth*(window: Window, innerWidth, outerWidth: float32, diagonalSize: float32) {.base.} = +method setBorderWidth*( + window: Window, innerWidth, outerWidth: float32, diagonalSize: float32 +) {.base.} = ## set window border width. This will not change the look of window, it is for resizing window. ## this is used on Windows to allow user to resize window interactivly. siwin will replicate this behaviour on other platforms. ## it's recomended to set border width if you have custom titlebar. window.borderWidth = some (innerWidth, outerWidth, diagonalSize) - method pixelBuffer*(window: Window): PixelBuffer {.base.} = ## returns pixel buffer attached to window - raise WindowTypeDefect.newException("this Window has no pixel buffer. only SoftwareRendering windows have one") - + raise WindowTypeDefect.newException( + "this Window has no pixel buffer. only SoftwareRendering windows have one" + ) -method makeCurrent*(window: Window) {.base.} = discard +method makeCurrent*(window: Window) {.base.} = ## set window as current opengl rendering target + discard -method `vsync=`*(window: Window, v: bool, silent = false) {.base.} = discard +method `vsync=`*(window: Window, v: bool, silent = false) {.base.} = ## enable/disable vsync + discard - -method vulkanSurface*(window: Window): pointer {.base.} = discard +method vulkanSurface*(window: Window): pointer {.base.} = ## get a VkSurfaceKHR attached to window + discard +proc clipboard*(window: Window): Clipboard = + window.m_clipboard -proc clipboard*(window: Window): Clipboard = window.m_clipboard - -proc selectionClipboard*(window: Window): Clipboard = window.m_selectionClipboard - -proc dragndropClipboard*(window: Window): Clipboard = window.m_dragndropClipboard +proc selectionClipboard*(window: Window): Clipboard = + window.m_selectionClipboard +proc dragndropClipboard*(window: Window): Clipboard = + window.m_dragndropClipboard -method `dragStatus=`*(window: Window, v: DragStatus) {.base.} = discard +method `dragStatus=`*(window: Window, v: DragStatus) {.base.} = + discard - -method firstStep*(window: Window, makeVisible = true) {.base.} = discard +method firstStep*(window: Window, makeVisible = true) {.base.} = ## init window main loop ## don't call this proc if you will manage window events via run() + discard -method step*(window: Window) {.base.} = discard +method step*(window: Window) {.base.} = ## make window main loop step ## ! don't forget to call firstStep() - + discard proc run*(window: sink Window, makeVisible = true) = ## run whole window main loops @@ -474,7 +784,11 @@ proc runMultiple*(windows: varargs[tuple[window: Window, makeVisible: bool]]) = window.step() inc i -proc runMultiple*(windows: varargs[tuple[window: Window, eventsHandler: WindowEventsHandler, makeVisible: bool]]) = +proc runMultiple*( + windows: varargs[ + tuple[window: Window, eventsHandler: WindowEventsHandler, makeVisible: bool] + ] +) = ## run for multiple windows for (window, eventsHandler, makeVisible) in windows: if eventsHandler != WindowEventsHandler(): diff --git a/src/siwin/platforms/cocoa/extras.nim b/src/siwin/platforms/cocoa/extras.nim index 0f3f47d..a83dc1d 100644 --- a/src/siwin/platforms/cocoa/extras.nim +++ b/src/siwin/platforms/cocoa/extras.nim @@ -4,12 +4,20 @@ import pkg/darwin/objc/runtime export app_kit, foundation, runtime when not declared(activateIgnoringOtherApps): - proc activateIgnoringOtherApps*(self: NSApplication, x: bool) {.objc: "activateIgnoringOtherApps:".} + proc activateIgnoringOtherApps*( + self: NSApplication, x: bool + ) {.objc: "activateIgnoringOtherApps:".} when not compiles(screens(NSScreen)): proc screens*(n: typedesc[NSScreen]): NSArray[NSScreen] {.objc: "screens".} - proc registerForDraggedTypes*(self: NSView, types: NSArray[NSString]): NSArray[NSString] {.objc: "registerForDraggedTypes:".} - proc draggingPasteboard*(self: NSDraggingInfo): NSPasteboard {.objc: "draggingPasteboard".} + proc registerForDraggedTypes*( + self: NSView, types: NSArray[NSString] + ): NSArray[NSString] {.objc: "registerForDraggedTypes:".} + + proc draggingPasteboard*( + self: NSDraggingInfo + ): NSPasteboard {.objc: "draggingPasteboard".} + proc draggingLocation*(self: NSDraggingInfo): NSPoint {.objc: "draggingLocation".} proc toggleFullScreen*(s: NSWindow, sender: ID) {.objc: "toggleFullScreen:".} proc zoom*(s: NSWindow, sender: ID) {.objc: "zoom:".} @@ -18,8 +26,20 @@ when not compiles(screens(NSScreen)): proc deminiaturize*(s: NSWindow, sender: ID) {.objc: "deminiaturize:".} proc setMinSize*(s: NSWindow, size: NSSize) {.objc: "setMinSize:".} proc setMaxSize*(s: NSWindow, size: NSSize) {.objc: "setMaxSize:".} + proc addChildWindow*( + s: NSWindow, child: NSWindow, ordered: NSInteger + ) {.objc: "addChildWindow:ordered:".} + + proc removeChildWindow*(s: NSWindow, child: NSWindow) {.objc: "removeChildWindow:".} + proc setExcludedFromWindowsMenu*( + s: NSWindow, excluded: BOOL + ) {.objc: "setExcludedFromWindowsMenu:".} + proc initWithSize*(self: NSImage, size: NSSize): NSImage {.objc: "initWithSize:".} - proc addRepresentation*(self: NSImage, imageRep: NSImageRep) {.objc: "addRepresentation:".} + proc addRepresentation*( + self: NSImage, imageRep: NSImageRep + ) {.objc: "addRepresentation:".} + proc bitmapData*(self: NSBitmapImageRep): pointer {.objc.} proc initWithBitmapDataPlanes*( self: NSBitmapImageRep, @@ -30,4 +50,7 @@ when not compiles(screens(NSScreen)): colorSpaceName: NSString, bitmapFormat: NSUInteger, bytesPerRow, bitsPerPixel: NSInteger, - ): NSBitmapImageRep {.objc: "initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:".} + ): NSBitmapImageRep {. + objc: + "initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:" + .} diff --git a/src/siwin/platforms/cocoa/window.nim b/src/siwin/platforms/cocoa/window.nim index 2629642..5b437b5 100644 --- a/src/siwin/platforms/cocoa/window.nim +++ b/src/siwin/platforms/cocoa/window.nim @@ -2,7 +2,8 @@ import std/[importutils, tables, times, os, unicode, uri, sequtils, strutils] import pkg/[vmath] from pkg/darwin/quartz_core/calayer import CALayer from pkg/darwin/quartz_core/cametal_layer import CAMetalLayer -from pkg/darwin/objc/runtime import ObjcClass, ID, SEL, alloc, new, addClass, selector, callSuper +from pkg/darwin/objc/runtime import + ObjcClass, ID, SEL, alloc, new, addClass, selector, callSuper import ../../[siwindefs] import ../../[colorutils] import ../any/[window {.all.}, clipboards] @@ -33,7 +34,7 @@ type lastDragStatus: DragStatus m_canBecomeKeyWindow: bool m_canBecomeMainWindow: bool - + WindowCocoaSoftwareRendering* = ref object of WindowCocoa softwareView: NSImageView softwareImage: NSImage @@ -50,13 +51,19 @@ type ClipboardCocoaDnd* = ref object of Clipboard activePasteboard: NSPasteboard - var initialized: bool - appDelegateClass, windowClass, softwareViewClass, openglViewClass, metalViewClass: ObjcClass + appDelegateClass, windowClass, softwareViewClass, openglViewClass, metalViewClass: + ObjcClass windows: seq[WindowCocoa] -proc init +proc init() +proc popupWindowPos(window: WindowCocoa, placement: PopupPlacement): IVec2 = + let parent = window.parentWindow() + if parent == nil: + placement.popupRelativePos() + else: + parent.pos + placement.popupRelativePos() proc `=destroy`(window: WindowCocoaObj) {.siwin_destructor.} = if window.addr[].m_closed: @@ -78,11 +85,15 @@ proc `=destroy`(window: WindowCocoaObj) {.siwin_destructor.} = if handle != nil: try: + let parent = window.addr[].m_popupParent + if window.addr[].m_isPopup and parent != nil and parent of WindowCocoa: + let parentHandle = parent.WindowCocoa.handle + if parentHandle != nil: + parentHandle.removeChildWindow(handle) close handle except: discard - proc findWindow(windows: seq[WindowCocoa], window: Id): WindowCocoa = for w in windows: if w == nil or w.handle == nil: @@ -110,7 +121,6 @@ proc findWindow(windows: seq[WindowCocoa], window: Id): WindowCocoa = if metalView != nil and cast[ID](metalView) == window: return w - proc keycodeToKey(code: uint16): Key = case code of 0x1d: Key.n0 @@ -253,25 +263,26 @@ proc releaseAllInput(window: WindowCocoa) = for key in window.keyboard.pressed: window.keyboard.pressed.excl key if window.eventsHandler.onKey != nil: - window.eventsHandler.onKey(KeyEvent( - window: window, - key: key, - pressed: false, - repeated: false, - generated: true, - modifiers: window.keyboard.modifiers, - )) + window.eventsHandler.onKey( + KeyEvent( + window: window, + key: key, + pressed: false, + repeated: false, + generated: true, + modifiers: window.keyboard.modifiers, + ) + ) for button in window.mouse.pressed: window.mouse.pressed.excl button window.clicking.excl button if window.eventsHandler.onMouseButton != nil: - window.eventsHandler.onMouseButton(MouseButtonEvent( - window: window, - button: button, - pressed: false, - generated: true, - )) + window.eventsHandler.onMouseButton( + MouseButtonEvent( + window: window, button: button, pressed: false, generated: true + ) + ) const CocoaMimeTextUriList = "text/uri-list" @@ -301,19 +312,20 @@ proc stringToNSData(data: string): NSData = copyMem(bytes[0].addr, data[0].unsafeAddr, data.len) NSData.withBytes(bytes) -proc emptyClipboardContent(kind: ClipboardContentKind, mimeType: string): ClipboardContent = +proc emptyClipboardContent( + kind: ClipboardContentKind, mimeType: string +): ClipboardContent = if kind == ClipboardContentKind.other: ClipboardContent(kind: ClipboardContentKind.other, mimeType: mimeType) else: ClipboardContent(kind: kind) proc constructClipboardContent( - data: sink string, kind: ClipboardContentKind, mimeType: string + data: sink string, kind: ClipboardContentKind, mimeType: string ): ClipboardContent = case kind of ClipboardContentKind.text: ClipboardContent(kind: ClipboardContentKind.text, text: data) - of ClipboardContentKind.files: let uris = data.splitLines var files: seq[string] @@ -329,7 +341,6 @@ proc constructClipboardContent( files.add parsed.path ClipboardContent(kind: ClipboardContentKind.files, files: files) - of ClipboardContentKind.other: ClipboardContent(kind: ClipboardContentKind.other, mimeType: mimeType, data: data) @@ -356,17 +367,13 @@ proc inferAvailableKinds(mimeTypes: openArray[string]): set[ClipboardContentKind for mimeType in mimeTypes: if mimeType in [ - "public.utf8-plain-text", - "public.plain-text", - "text/plain", - "text/plain;charset=utf-8", - "STRING", - "TEXT", - "NSStringPboardType", + "public.utf8-plain-text", "public.plain-text", "text/plain", + "text/plain;charset=utf-8", "STRING", "TEXT", "NSStringPboardType", ]: result.incl ClipboardContentKind.text - if mimeType in [CocoaMimeTextUriList, CocoaMimePublicFileUrl, CocoaMimeLegacyFileNames]: + if mimeType in + [CocoaMimeTextUriList, CocoaMimePublicFileUrl, CocoaMimeLegacyFileNames]: result.incl ClipboardContentKind.files if NSPasteboardTypeString != nil and $NSPasteboardTypeString in mimeTypes: @@ -399,31 +406,27 @@ proc dataStringsForMimeType(pasteboard: NSPasteboard, mimeType: string): seq[str result.add nsDataToString(data) proc bestMimeType( - availableMimeTypes: openArray[string], kind: ClipboardContentKind, mimeType: string + availableMimeTypes: openArray[string], kind: ClipboardContentKind, mimeType: string ): string = case kind of ClipboardContentKind.text: let nativeType = - if NSPasteboardTypeString != nil: $NSPasteboardTypeString - else: "public.utf8-plain-text" + if NSPasteboardTypeString != nil: + $NSPasteboardTypeString + else: + "public.utf8-plain-text" for candidate in [ - nativeType, - "public.utf8-plain-text", - "public.plain-text", - "text/plain;charset=utf-8", - "text/plain", - "STRING", - "TEXT", - "NSStringPboardType", + nativeType, "public.utf8-plain-text", "public.plain-text", + "text/plain;charset=utf-8", "text/plain", "STRING", "TEXT", "NSStringPboardType", ]: if candidate in availableMimeTypes: return candidate - of ClipboardContentKind.files: - for candidate in [CocoaMimeTextUriList, CocoaMimePublicFileUrl, CocoaMimeLegacyFileNames]: + for candidate in [ + CocoaMimeTextUriList, CocoaMimePublicFileUrl, CocoaMimeLegacyFileNames + ]: if candidate in availableMimeTypes: return candidate - of ClipboardContentKind.other: if mimeType in availableMimeTypes: return mimeType @@ -446,11 +449,13 @@ proc clearDragClipboard(clipboard: ClipboardCocoaDnd): bool = proc notifyDragClipboardChanged(window: WindowCocoa) = let clipboard = window.m_dragndropClipboard if clipboard.onContentChanged != nil: - clipboard.onContentChanged(ClipboardContentChangedEvent( - clipboard: clipboard, - availableKinds: clipboard.availableKinds, - availableMimeTypes: clipboard.availableMimeTypes, - )) + clipboard.onContentChanged( + ClipboardContentChangedEvent( + clipboard: clipboard, + availableKinds: clipboard.availableKinds, + availableMimeTypes: clipboard.availableMimeTypes, + ) + ) proc updateDragClipboard(window: WindowCocoa, pasteboard: NSPasteboard) = let clipboard = window.m_dragndropClipboard.ClipboardCocoaDnd @@ -463,11 +468,7 @@ proc clearDragClipboard(window: WindowCocoa) = window.notifyDragClipboardChanged() proc dragOperation(status: DragStatus): NSDragOperation = - if status == DragStatus.accepted: - NSDragOperationCopy - else: - NSDragOperationNone - + if status == DragStatus.accepted: NSDragOperationCopy else: NSDragOperationNone proc screenCountCocoa*(): int32 = let screens = NSScreen.screens() @@ -483,7 +484,7 @@ proc defaultScreenCocoa*(): ScreenCocoa = if screens != nil and screens.len > 0: new result if mainScreen != nil: - for i in 0.. 0") + if window.isPopup: + var placement = window.placement + placement.size = v + window.placement = placement + return + if window.fullscreen: window.fullscreen = false @@ -788,15 +832,14 @@ method `size=`*(window: WindowCocoa, v: IVec2) = let borderH = frame.size.height - contentRect.size.height window.handle.setFrame( NSMakeRect( - frame.origin.x, - frame.origin.y, - v.x.float64 + borderW, - v.y.float64 + borderH + frame.origin.x, frame.origin.y, v.x.float64 + borderW, v.y.float64 + borderH ), - true + true, ) method `pos=`*(window: WindowCocoa, v: IVec2) = + if window.isPopup: + return if window.m_pos == v: return window.m_pos = v @@ -811,10 +854,32 @@ method `pos=`*(window: WindowCocoa, v: IVec2) = y = screenFrame.size.height - v.y.float64 - frame.size.height - 1 window.handle.setFrame( - NSMakeRect(v.x.float64, y, frame.size.width, frame.size.height), - true + NSMakeRect(v.x.float64, y, frame.size.width, frame.size.height), true + ) + +method `placement=`*(window: WindowCocoa, v: PopupPlacement) = + window.m_popupPlacement = v + let size = v.popupSize() + window.m_size = size + let frame = window.handle.frame + let contentRect = window.handle.contentRectForFrameRect(frame) + let borderW = frame.size.width - contentRect.size.width + let borderH = frame.size.height - contentRect.size.height + let pos = window.popupWindowPos(v) + window.m_pos = pos + var y = frame.origin.y + let screen = window.handle.screen + if screen != nil: + let screenFrame = screen.frame + y = screenFrame.size.height - pos.y.float64 - (size.y.float64 + borderH) - 1 + window.handle.setFrame( + NSMakeRect(pos.x.float64, y, size.x.float64 + borderW, size.y.float64 + borderH), + true, ) +method reposition*(window: WindowCocoa, v: PopupPlacement) = + window.placement = v + method `fullscreen=`*(window: WindowCocoa, v: bool) = if window.m_fullscreen == v: return @@ -822,14 +887,16 @@ method `fullscreen=`*(window: WindowCocoa, v: bool) = if v and window.m_maximized: window.m_maximized = false - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.maximized, value: false - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.maximized, value: false + ) window.handle.toggleFullScreen(cast[ID](nil)) - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.fullscreen, value: v - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.fullscreen, value: v + ) method `maximized=`*(window: WindowCocoa, v: bool) = if window.m_maximized == v: @@ -841,9 +908,10 @@ method `maximized=`*(window: WindowCocoa, v: bool) = if window.handle.isZoomed().bool != v: window.handle.zoom(cast[ID](nil)) - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.maximized, value: v - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.maximized, value: v + ) method `minimized=`*(window: WindowCocoa, v: bool) = if window.m_minimized == v: @@ -861,11 +929,18 @@ method `resizable=`*(window: WindowCocoa, v: bool) = window.m_resizable = v window.handle.setStyleMask( if window.m_frameless: - if v: NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable or NSWindowStyleMaskBorderless - else: NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskBorderless + if v: + NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable or + NSWindowStyleMaskBorderless + else: + NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskBorderless else: - if v: NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable or NSWindowStyleMaskTitled or NSWindowStyleMaskClosable - else: NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskTitled or NSWindowStyleMaskClosable + if v: + NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskResizable or + NSWindowStyleMaskTitled or NSWindowStyleMaskClosable + else: + NSWindowStyleMaskMiniaturizable or NSWindowStyleMaskTitled or + NSWindowStyleMaskClosable ) method `minSize=`*(window: WindowCocoa, v: IVec2) = @@ -902,33 +977,39 @@ method `icon=`*(window: WindowCocoa, v: PixelBuffer) = let sourceFormat = v.format var buffer = v - convertPixelsInplace(buffer.data, buffer.size, sourceFormat, PixelBufferFormat.rgba_32bit) + convertPixelsInplace( + buffer.data, buffer.size, sourceFormat, PixelBufferFormat.rgba_32bit + ) let rep = NSBitmapImageRep.alloc().initWithBitmapDataPlanes( - nil, - buffer.size.x.int, - buffer.size.y.int, - 8, - 4, - true, - false, - @"NSCalibratedRGBColorSpace", - 0, - (buffer.size.x * 4).int, - 32 - ) + nil, + buffer.size.x.int, + buffer.size.y.int, + 8, + 4, + true, + false, + @"NSCalibratedRGBColorSpace", + 0, + (buffer.size.x * 4).int, + 32, + ) if rep != nil: copyMem(rep.bitmapData, buffer.data, buffer.size.x * buffer.size.y * 4) - let img = NSImage.alloc().initWithSize(NSMakeSize(buffer.size.x.float64, buffer.size.y.float64)) + let img = NSImage.alloc().initWithSize( + NSMakeSize(buffer.size.x.float64, buffer.size.y.float64) + ) img.addRepresentation(cast[NSImageRep](rep)) NSApplication.setApplicationIconImage(img) rep.release() img.release() - convertPixelsInplace(buffer.data, buffer.size, PixelBufferFormat.rgba_32bit, sourceFormat) + convertPixelsInplace( + buffer.data, buffer.size, PixelBufferFormat.rgba_32bit, sourceFormat + ) method content*( - clipboard: ClipboardCocoa, kind: ClipboardContentKind, mimeType: string + clipboard: ClipboardCocoa, kind: ClipboardContentKind, mimeType: string ): ClipboardContent = autoreleasepool: let pasteboard = NSPasteboard.withName(NSPasteboardNameGeneral) @@ -945,9 +1026,13 @@ method content*( of ClipboardContentKind.text: return ClipboardContent(kind: ClipboardContentKind.text, text: payloads[0]) of ClipboardContentKind.files: - return constructClipboardContent(payloads.join("\n"), ClipboardContentKind.files, targetType) + return constructClipboardContent( + payloads.join("\n"), ClipboardContentKind.files, targetType + ) of ClipboardContentKind.other: - return ClipboardContent(kind: ClipboardContentKind.other, mimeType: targetType, data: payloads[0]) + return ClipboardContent( + kind: ClipboardContentKind.other, mimeType: targetType, data: payloads[0] + ) method `content=`*(clipboard: ClipboardCocoa, content: ClipboardConvertableContent) = autoreleasepool: @@ -968,12 +1053,16 @@ method `content=`*(clipboard: ClipboardCocoa, content: ClipboardConvertableConte case converted.kind of ClipboardContentKind.text: targetType = - if NSPasteboardTypeString != nil: $NSPasteboardTypeString - else: "public.utf8-plain-text" + if NSPasteboardTypeString != nil: + $NSPasteboardTypeString + else: + "public.utf8-plain-text" serialized = converted.text of ClipboardContentKind.files: targetType = CocoaMimeTextUriList - serialized = converted.files.mapIt($Uri(scheme: "file", path: it.encodeUrl(usePlus = false))).join("\n") + serialized = converted.files + .mapIt($Uri(scheme: "file", path: it.encodeUrl(usePlus = false))) + .join("\n") of ClipboardContentKind.other: targetType = converted.mimeType serialized = converted.data @@ -997,7 +1086,7 @@ method `content=`*(clipboard: ClipboardCocoa, content: ClipboardConvertableConte clipboard.availableMimeTypes = availableMimeTypes method content*( - clipboard: ClipboardCocoaDnd, kind: ClipboardContentKind, mimeType: string + clipboard: ClipboardCocoaDnd, kind: ClipboardContentKind, mimeType: string ): ClipboardContent = let pasteboard = clipboard.activePasteboard if pasteboard == nil: @@ -1016,9 +1105,13 @@ method content*( of ClipboardContentKind.text: result = ClipboardContent(kind: ClipboardContentKind.text, text: payloads[0]) of ClipboardContentKind.files: - result = constructClipboardContent(payloads.join("\n"), ClipboardContentKind.files, targetType) + result = constructClipboardContent( + payloads.join("\n"), ClipboardContentKind.files, targetType + ) of ClipboardContentKind.other: - result = ClipboardContent(kind: ClipboardContentKind.other, mimeType: targetType, data: payloads[0]) + result = ClipboardContent( + kind: ClipboardContentKind.other, mimeType: targetType, data: payloads[0] + ) method `dragStatus=`*(window: WindowCocoa, v: DragStatus) = window.lastDragStatus = v @@ -1032,8 +1125,7 @@ proc swapBuffers*(window: WindowCocoaOpengl) = method `vsync=`*(window: WindowCocoaOpengl, v: bool, silent = false) = var swapInterval: int32 = if v: 1 else: 0 window.openglView.openGLContext.setValues( - swapInterval.addr, - NSOpenGLContextParameterSwapInterval + swapInterval.addr, NSOpenGLContextParameterSwapInterval ) proc nativeWindowHandle*(window: WindowCocoa): pointer = @@ -1047,15 +1139,17 @@ proc setContentViewLayer*(window: WindowCocoa, layerPtr: pointer) = contentView.setWantsLayer(true) contentView.setLayer(cast[CALayer](layerPtr)) - -proc init = - if initialized: return - defer: initialized = true +proc init() = + if initialized: + return + defer: + initialized = true template getWindow(this: untyped) = let window {.inject.} = windows.findWindow(this) - if window == nil: return - + if window == nil: + return + proc updateSize(window: WindowCocoa) = let contentView = window.handle.contentView @@ -1066,9 +1160,10 @@ proc init = window.m_size = size if window of WindowCocoaSoftwareRendering: window.WindowCocoaSoftwareRendering.resizeSoftwarePixelBuffer(size) - window.eventsHandler.pushEvent onResize, ResizeEvent(window: window, size: window.m_size) + window.eventsHandler.pushEvent onResize, + ResizeEvent(window: window, size: window.m_size) window.redrawRequested = true - + proc scaledMousePos(window: WindowCocoa, location: NsPoint): Vec2 = let contentView = window.handle.contentView if contentView == nil: @@ -1077,18 +1172,22 @@ proc init = let bounds = contentView.bounds backingBounds = contentView.convertRectToBacking(bounds) - backingPointRect = contentView.convertRectToBacking(NSMakeRect(location.x, location.y, 0, 0)) + backingPointRect = + contentView.convertRectToBacking(NSMakeRect(location.x, location.y, 0, 0)) vec2( backingPointRect.origin.x.float32, - (backingBounds.size.height - backingPointRect.origin.y).float32 + (backingBounds.size.height - backingPointRect.origin.y).float32, ) proc updateMousePos(window: WindowCocoa, location: NsPoint, kind: MouseMoveKind) = window.mouse.pos = scaledMousePos(window, location) - window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: kind) + window.eventsHandler.pushEvent onMouseMove, + MouseMoveEvent(window: window, pos: window.mouse.pos, kind: kind) - proc handleMouseButton(window: WindowCocoa, button: MouseButton, pressed: bool, location: NsPoint) = + proc handleMouseButton( + window: WindowCocoa, button: MouseButton, pressed: bool, location: NsPoint + ) = window.mouse.pos = scaledMousePos(window, location) if pressed: window.mouse.pressed.incl button @@ -1098,420 +1197,502 @@ proc init = window.mouse.pressed.excl button if button in window.clicking: - window.eventsHandler.pushEvent onClick, ClickEvent( - window: window, button: button, pos: window.mouse.pos, - double: (nows - window.lastClickTime[button]).inMilliseconds < 200 - ) + window.eventsHandler.pushEvent onClick, + ClickEvent( + window: window, + button: button, + pos: window.mouse.pos, + double: (nows - window.lastClickTime[button]).inMilliseconds < 200, + ) window.clicking.excl button - + window.lastClickTime[button] = nows - window.eventsHandler.pushEvent onMouseButton, MouseButtonEvent(window: window, button: button, pressed: pressed) + window.eventsHandler.pushEvent onMouseButton, + MouseButtonEvent(window: window, button: button, pressed: pressed) autoreleasepool: discard NSApplication.sharedApplication() addClass "SiwinAppDelegate", "NSObject", appDelegateClass: - addMethod "applicationWillFinishLaunching:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - let - menuBar = NSMenu.alloc().init() - appMenuItem = NSMenuItem.alloc().init() - menuBar.addItem(appMenuItem) - NSApp.setMainMenu(menuBar) - - let - appMenu = NSMenu.alloc().init() - processName = NSProcessInfo.processinfo.processName - quitTitle = @("Quit " & $processName) - quitMenuitem = NsMenuItem.alloc().initWithTitle( - quitTitle, - selector"terminate:", - @"q" - ) - appMenu.addItem(quitMenuItem) - appMenuItem.setSubmenu(appMenu) - - addMethod "applicationDidFinishLaunching:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - NSApp.setPresentationOptions(NSApplicationPresentationDefault) - NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular) - NSApp.activateIgnoringOtherApps(true) - - - addClass "SiwinWindow", "NSWindow", windowClass: - addMethod "windowDidResize:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - getWindow(self) - updateSize window - - addMethod "windowDidMove:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - getWindow(self) - autoreleasepool: + addMethod "applicationWillFinishLaunching:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = let - windowFrame = window.handle.frame - screenFrame = window.handle.screen.frame - window.m_pos = vec2( - windowFrame.origin.x, - screenFrame.size.height - windowFrame.origin.y - windowFrame.size.height - 1 - ).ivec2 - window.eventsHandler.pushEvent onWindowMove, WindowMoveEvent(window: window, pos: window.m_pos) - - addMethod "canBecomeKeyWindow", proc(self: Id, cmd: Sel): bool {.cdecl.} = - getWindow(self) - window.canBecomeKeyWindow - - addMethod "canBecomeMainWindow", proc(self: Id, cmd: Sel): bool {.cdecl.} = - getWindow(self) - window.canBecomeMainWindow - - addMethod "windowDidBecomeKey:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - getWindow(self) - let contentView = window.handle.contentView - if contentView != nil: - discard window.handle.makeFirstResponder(contentView) - window.m_focused = true - window.refreshModifiers() - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, value: true, kind: StateBoolChangedEventKind.focus - ) - updateMousePos window, window.handle.mouseLocationOutsideOfEventStream, MouseMoveKind.enter - - addMethod "windowDidResignKey:", proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = - getWindow(self) - updateMousePos window, window.handle.mouseLocationOutsideOfEventStream, MouseMoveKind.leave - window.releaseAllInput() - window.m_focused = false - window.keyboard.modifiers = {} - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, value: false, kind: StateBoolChangedEventKind.focus - ) - - addMethod "windowShouldClose:", proc(self: Id, cmd: Sel, notification: NsNotification): bool {.cdecl.} = - getWindow(self) - window.eventsHandler.pushEvent onClose, CloseEvent(window: window) - destruct window - true + menuBar = NSMenu.alloc().init() + appMenuItem = NSMenuItem.alloc().init() + menuBar.addItem(appMenuItem) + NSApp.setMainMenu(menuBar) + let + appMenu = NSMenu.alloc().init() + processName = NSProcessInfo.processinfo.processName + quitTitle = @("Quit " & $processName) + quitMenuitem = + NsMenuItem.alloc().initWithTitle(quitTitle, selector"terminate:", @"q") + appMenu.addItem(quitMenuItem) + appMenuItem.setSubmenu(appMenu) + + addMethod "applicationDidFinishLaunching:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = + NSApp.setPresentationOptions(NSApplicationPresentationDefault) + NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular) + NSApp.activateIgnoringOtherApps(true) - template addSiwinViewClass(className, superName: string, viewClassRef: untyped) = - addClass className, superName, viewClassRef: - addProtocol "NSTextInputClient" - - addMethod "acceptsFirstResponder", proc(self: Id, cmd: Sel): bool {.cdecl.} = - true - - addMethod "canBecomeKeyView", proc(self: Id, cmd: Sel): bool {.cdecl.} = - true - - addMethod "acceptsFirstMouse:", proc(self: Id, cmd: Sel, event: NsEvent): bool {.cdecl.} = - true - - addMethod "viewDidChangeBackingProperties", proc(self: Id, cmd: Sel): Id {.cdecl.} = - discard callSuper(cast[NSObject](self), cmd) + addClass "SiwinWindow", "NSWindow", windowClass: + addMethod "windowDidResize:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = getWindow(self) updateSize window - - addMethod "updateTrackingAreas", proc(self: Id, cmd: Sel): Id {.cdecl.} = - getWindow(self) - - if window.trackingArea != nil: - cast[NSView](self).removeTrackingArea(window.trackingArea) - window.trackingArea.release() - window.trackingArea = nil - - window.trackingArea = NSTrackingArea.alloc().initWithRect( - NSMakeRect(0, 0, 0, 0), - NSTrackingMouseEnteredAndExited or NSTrackingMouseMoved or NSTrackingActiveInKeyWindow or - NSTrackingCursorUpdate or NSTrackingInVisibleRect or NSTrackingAssumeInside, - self, cast[ID](nil) - ) - - cast[NSView](self).addTrackingArea(window.trackingArea) - - callSuper(cast[NSObject](self), cmd) - - addMethod "draggingEntered:", proc( - self: Id, cmd: Sel, sender: NSDraggingInfo - ): NSDragOperation {.cdecl.} = - getWindow(self) - if sender == nil: - return NSDragOperationNone - - window.lastDragStatus = DragStatus.rejected - window.updateDragClipboard(sender.draggingPasteboard) - updateMousePos(window, sender.draggingLocation, MouseMoveKind.moveWhileDragging) - dragOperation(window.lastDragStatus) - - addMethod "draggingUpdated:", proc( - self: Id, cmd: Sel, sender: NSDraggingInfo - ): NSDragOperation {.cdecl.} = - getWindow(self) - if sender == nil: - return NSDragOperationNone - - window.updateDragClipboard(sender.draggingPasteboard) - updateMousePos(window, sender.draggingLocation, MouseMoveKind.moveWhileDragging) - dragOperation(window.lastDragStatus) - - addMethod "draggingExited:", proc( - self: Id, cmd: Sel, sender: NSDraggingInfo - ): Id {.cdecl.} = - getWindow(self) - window.clearDragClipboard() - window.lastDragStatus = DragStatus.rejected - - addMethod "performDragOperation:", proc( - self: Id, cmd: Sel, sender: NSDraggingInfo - ): bool {.cdecl.} = - getWindow(self) - if sender != nil: - window.updateDragClipboard(sender.draggingPasteboard) - if window.eventsHandler.onDrop != nil: - window.eventsHandler.onDrop(DropEvent(window: window)) - let accepted = window.lastDragStatus == DragStatus.accepted - window.clearDragClipboard() - window.lastDragStatus = DragStatus.rejected - accepted - - addMethod "mouseMoved:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - updateMousePos window, event.locationInWindow, MouseMoveKind.move - - addMethod "mouseDragged:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - updateMousePos window, event.locationInWindow, MouseMoveKind.move - - addMethod "rightMouseDragged:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - updateMousePos window, event.locationInWindow, MouseMoveKind.move - - addMethod "otherMouseDragged:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - updateMousePos window, event.locationInWindow, MouseMoveKind.move - - addMethod "scrollWheel:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - var - deltaX = event.scrollingDeltaX - deltaY = event.scrollingDeltaY - - if event.hasPreciseScrollingDeltas: - deltaX *= 0.1 - deltaY *= 0.1 - - if abs(deltaX) > 0 or abs(deltaY) > 0: - window.eventsHandler.pushEvent onScroll, ScrollEvent(window: window, delta: deltaY, deltaX: deltaX) - - addMethod "mouseDown:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - window.handleMouseButton(MouseButton.left, true, event.locationInWindow) - - addMethod "mouseUp:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - window.handleMouseButton(MouseButton.left, false, event.locationInWindow) - - addMethod "rightMouseDown:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - getWindow(self) - window.handleMouseButton(MouseButton.right, true, event.locationInWindow) - - addMethod "rightMouseUp:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + + addMethod "windowDidMove:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = getWindow(self) - window.handleMouseButton(MouseButton.right, false, event.locationInWindow) - - addMethod "otherMouseDown:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + autoreleasepool: + let + windowFrame = window.handle.frame + screenFrame = window.handle.screen.frame + window.m_pos = vec2( + windowFrame.origin.x, + screenFrame.size.height - windowFrame.origin.y - windowFrame.size.height - + 1, + ).ivec2 + window.eventsHandler.pushEvent onWindowMove, + WindowMoveEvent(window: window, pos: window.m_pos) + + addMethod "canBecomeKeyWindow", + proc(self: Id, cmd: Sel): bool {.cdecl.} = getWindow(self) - case event.buttonNumber - of 2: window.handleMouseButton(MouseButton.middle, true, event.locationInWindow) - of 3: window.handleMouseButton(MouseButton.forward, true, event.locationInWindow) - of 4: window.handleMouseButton(MouseButton.backward, true, event.locationInWindow) - else: discard - - addMethod "otherMouseUp:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + window.canBecomeKeyWindow + + addMethod "canBecomeMainWindow", + proc(self: Id, cmd: Sel): bool {.cdecl.} = getWindow(self) - case event.buttonNumber - of 2: window.handleMouseButton(MouseButton.middle, false, event.locationInWindow) - of 3: window.handleMouseButton(MouseButton.forward, false, event.locationInWindow) - of 4: window.handleMouseButton(MouseButton.backward, false, event.locationInWindow) - else: discard - - addMethod "keyDown:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + window.canBecomeMainWindow + + addMethod "windowDidBecomeKey:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = getWindow(self) - let key = event.keyCode.keycodeToKey - let modifiers = window.updateModifiers(event) - if key != Key.unknown: - window.keyboard.pressed.incl key - window.eventsHandler.pushEvent onKey, KeyEvent( - window: window, - key: key, - pressed: true, - repeated: event.isARepeat.bool, - modifiers: modifiers, + let contentView = window.handle.contentView + if contentView != nil: + discard window.handle.makeFirstResponder(contentView) + window.m_focused = true + window.refreshModifiers() + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, value: true, kind: StateBoolChangedEventKind.focus ) + updateMousePos window, + window.handle.mouseLocationOutsideOfEventStream, MouseMoveKind.enter - # Let key events handle navigation/editing control keys directly; - # routing them through NSTextInputClient can suppress following key presses. - let shouldRouteToInputContext = key notin { - Key.backspace, - Key.del, - Key.left, - Key.right, - Key.up, - Key.down, - Key.home, - Key.End, - Key.pageUp, - Key.pageDown, - Key.tab, - Key.enter, - Key.escape, - } - if window.eventsHandler.onTextInput != nil and shouldRouteToInputContext: - var handledByInputContext = false - let inputContext = cast[NSView](self).inputContext - if inputContext != nil: - handledByInputContext = inputContext.handleEvent(event).bool - - # Fallback for view classes where AppKit does not route insertText - # through NSTextInputClient even though keyDown is delivered. - if not handledByInputContext and (modifiers * {ModifierKey.control, ModifierKey.alt, ModifierKey.system}).len == 0: - for r in ($event.characters).runes: - if r.int >= 32 and r.int notin 0xf700..0xf7ff: - window.eventsHandler.pushEvent onTextInput, TextInputEvent( - window: window, - text: $r, - repeated: event.isARepeat.bool, - ) - - addMethod "keyUp:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + addMethod "windowDidResignKey:", + proc(self: Id, cmd: Sel, notification: NsNotification): Id {.cdecl.} = getWindow(self) - let key = event.keyCode.keycodeToKey - let modifiers = window.updateModifiers(event) - if key != Key.unknown: - window.keyboard.pressed.excl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: false, repeated: false, modifiers: modifiers) # todo: handle repeated - - addMethod "flagsChanged:", proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = - #? wtf is this?!? - getWindow(self) - let key = event.keyCode.keycodeToKey - let modifiers = window.updateModifiers(event) - if key != Key.unknown: - if key in window.keyboard.pressed: - window.keyboard.pressed.excl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: false, repeated: false, modifiers: modifiers) # todo: handle repeated - else: - window.keyboard.pressed.incl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: true, repeated: false, modifiers: modifiers) # todo: handle repeated - - addMethod "hasMarkedText", proc(self: Id, cmd: Sel): bool {.cdecl.} = - getWindow(self) - window.markedText != nil - - addMethod "markedRange", proc(self: Id, cmd: Sel): NsRange {.cdecl.} = - getWindow(self) - if window.markedText != nil: - NSMakeRange(0, window.markedText.length) - else: - NSRangeEmpty - - addMethod "selectedRange", proc(self: Id, cmd: Sel): NsRange {.cdecl.} = - getWindow(self) - NSMakeRange(0, 0) - - addMethod "setMarkedText:selectedRange:replacementRange:", proc( - self: Id, cmd: Sel, obj: Id, selectedRange: NsRange, replacementRange: NsRange - ): Id {.cdecl.} = - getWindow(self) - var characters: NSString - if cast[NSObject](obj).isKindOfClass(NSAttributedString): - characters = $(cast[NSAttributedString](obj).toNSString()) - else: - characters = cast[NSString](obj) - - if window.markedText != nil: - window.markedText.release() - - window.markedText = NSString.stringWithString(characters).retain() - - addMethod "unmarkText", proc(self: Id, cmd: Sel): Id {.cdecl.} = - discard - - addMethod "validAttributesForMarkedText", proc(self: Id, cmd: Sel): NSArrayAbstract {.cdecl.} = - cast[NSArrayAbstract](nil) - - addMethod "attributedSubstringForProposedRange:actualRange:", proc( - self: Id, cmd: Sel, range: NsRange, actualRange: NsRangePointer - ): ID = - discard - - addMethod "insertText:replacementRange:", proc( - self: Id, cmd: Sel, obj: Id, replacementRange: NsRange - ): Id {.cdecl.} = - getWindow(self) - var characters: NSString - if cast[NSObject](obj).isKindOfClass(NSAttributedString): - characters = $cast[NSAttributedString](obj).toNSString() - else: - characters = cast[NSString](obj) - - var range = NSMakeRange(0, characters.length.uint) - while range.length > 0: - var - codepoint: uint32 - usedLength: uint - discard characters.getBytes( - codepoint.addr, - sizeof(codepoint).uint, - usedLength.addr, - NSUTF32StringEncoding, - 0.NSStringEncodingConversionOptions, - range, - range.addr + updateMousePos window, + window.handle.mouseLocationOutsideOfEventStream, MouseMoveKind.leave + window.releaseAllInput() + window.m_focused = false + window.keyboard.modifiers = {} + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, value: false, kind: StateBoolChangedEventKind.focus ) - if codepoint >= 0xf700 and codepoint <= 0xf7ff: - continue - window.eventsHandler.pushEvent onTextInput, TextInputEvent(window: window, text: $Rune(codepoint)) - - if window.markedText != nil: - window.markedText.release() - window.markedText = nil - - addMethod "characterIndexForPoint:", proc(self: Id, cmd: Sel, point: NsPoint): uint {.cdecl.} = - NSNotFound.uint - - addMethod "firstRectForCharacterRange:actualRange:", proc( - self: Id, cmd: Sel, range: NsRange, actualRange: NsRangePointer - ): NsRect {.cdecl.} = - getWindow(self) - let contentRect = window.handle.contentRectForFrameRect(window.handle.frame) - NSMakeRect( - contentRect.origin.x + 0, - contentRect.origin.y + contentRect.size.height - 1 - 0, - 0, - 0 - ) - - addMethod "doCommandBySelector:", proc(self: Id, cmd: Sel, selector: Sel): Id {.cdecl.} = - discard - - addMethod "resetCursorRects", proc(self: Id, cmd: Sel): Id {.cdecl.} = + + addMethod "windowShouldClose:", + proc(self: Id, cmd: Sel, notification: NsNotification): bool {.cdecl.} = getWindow(self) - autoreleasepool: - case window.m_cursor.kind: - of builtin: + window.eventsHandler.pushEvent onClose, CloseEvent(window: window) + destruct window + true + + template addSiwinViewClass(className, superName: string, viewClassRef: untyped) = + addClass className, superName, viewClassRef: + addProtocol "NSTextInputClient" + + addMethod "acceptsFirstResponder", + proc(self: Id, cmd: Sel): bool {.cdecl.} = + true + + addMethod "canBecomeKeyView", + proc(self: Id, cmd: Sel): bool {.cdecl.} = + true + + addMethod "acceptsFirstMouse:", + proc(self: Id, cmd: Sel, event: NsEvent): bool {.cdecl.} = + true + + addMethod "viewDidChangeBackingProperties", + proc(self: Id, cmd: Sel): Id {.cdecl.} = + discard callSuper(cast[NSObject](self), cmd) + getWindow(self) + updateSize window + + addMethod "updateTrackingAreas", + proc(self: Id, cmd: Sel): Id {.cdecl.} = + getWindow(self) + + if window.trackingArea != nil: + cast[NSView](self).removeTrackingArea(window.trackingArea) + window.trackingArea.release() + window.trackingArea = nil + + window.trackingArea = NSTrackingArea.alloc().initWithRect( + NSMakeRect(0, 0, 0, 0), + NSTrackingMouseEnteredAndExited or NSTrackingMouseMoved or + NSTrackingActiveInKeyWindow or NSTrackingCursorUpdate or + NSTrackingInVisibleRect or NSTrackingAssumeInside, + self, + cast[ID](nil), + ) + + cast[NSView](self).addTrackingArea(window.trackingArea) + + callSuper(cast[NSObject](self), cmd) + + addMethod "draggingEntered:", + proc(self: Id, cmd: Sel, sender: NSDraggingInfo): NSDragOperation {.cdecl.} = + getWindow(self) + if sender == nil: + return NSDragOperationNone + + window.lastDragStatus = DragStatus.rejected + window.updateDragClipboard(sender.draggingPasteboard) + updateMousePos( + window, sender.draggingLocation, MouseMoveKind.moveWhileDragging + ) + dragOperation(window.lastDragStatus) + + addMethod "draggingUpdated:", + proc(self: Id, cmd: Sel, sender: NSDraggingInfo): NSDragOperation {.cdecl.} = + getWindow(self) + if sender == nil: + return NSDragOperationNone + + window.updateDragClipboard(sender.draggingPasteboard) + updateMousePos( + window, sender.draggingLocation, MouseMoveKind.moveWhileDragging + ) + dragOperation(window.lastDragStatus) + + addMethod "draggingExited:", + proc(self: Id, cmd: Sel, sender: NSDraggingInfo): Id {.cdecl.} = + getWindow(self) + window.clearDragClipboard() + window.lastDragStatus = DragStatus.rejected + + addMethod "performDragOperation:", + proc(self: Id, cmd: Sel, sender: NSDraggingInfo): bool {.cdecl.} = + getWindow(self) + if sender != nil: + window.updateDragClipboard(sender.draggingPasteboard) + if window.eventsHandler.onDrop != nil: + window.eventsHandler.onDrop(DropEvent(window: window)) + let accepted = window.lastDragStatus == DragStatus.accepted + window.clearDragClipboard() + window.lastDragStatus = DragStatus.rejected + accepted + + addMethod "mouseMoved:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + updateMousePos window, event.locationInWindow, MouseMoveKind.move + + addMethod "mouseDragged:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + updateMousePos window, event.locationInWindow, MouseMoveKind.move + + addMethod "rightMouseDragged:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + updateMousePos window, event.locationInWindow, MouseMoveKind.move + + addMethod "otherMouseDragged:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + updateMousePos window, event.locationInWindow, MouseMoveKind.move + + addMethod "scrollWheel:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + var + deltaX = event.scrollingDeltaX + deltaY = event.scrollingDeltaY + + if event.hasPreciseScrollingDeltas: + deltaX *= 0.1 + deltaY *= 0.1 + + if abs(deltaX) > 0 or abs(deltaY) > 0: + window.eventsHandler.pushEvent onScroll, + ScrollEvent(window: window, delta: deltaY, deltaX: deltaX) + + addMethod "mouseDown:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + window.handleMouseButton(MouseButton.left, true, event.locationInWindow) + + addMethod "mouseUp:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + window.handleMouseButton(MouseButton.left, false, event.locationInWindow) + + addMethod "rightMouseDown:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + window.handleMouseButton(MouseButton.right, true, event.locationInWindow) + + addMethod "rightMouseUp:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + window.handleMouseButton(MouseButton.right, false, event.locationInWindow) + + addMethod "otherMouseDown:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + case event.buttonNumber + of 2: + window.handleMouseButton(MouseButton.middle, true, event.locationInWindow) + of 3: + window.handleMouseButton( + MouseButton.forward, true, event.locationInWindow + ) + of 4: + window.handleMouseButton( + MouseButton.backward, true, event.locationInWindow + ) + else: + discard + + addMethod "otherMouseUp:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + case event.buttonNumber + of 2: + window.handleMouseButton( + MouseButton.middle, false, event.locationInWindow + ) + of 3: + window.handleMouseButton( + MouseButton.forward, false, event.locationInWindow + ) + of 4: + window.handleMouseButton( + MouseButton.backward, false, event.locationInWindow + ) + else: discard + + addMethod "keyDown:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + let key = event.keyCode.keycodeToKey + let modifiers = window.updateModifiers(event) + if key != Key.unknown: + window.keyboard.pressed.incl key + window.eventsHandler.pushEvent onKey, + KeyEvent( + window: window, + key: key, + pressed: true, + repeated: event.isARepeat.bool, + modifiers: modifiers, + ) + + # Let key events handle navigation/editing control keys directly; + # routing them through NSTextInputClient can suppress following key presses. + let shouldRouteToInputContext = + key notin { + Key.backspace, Key.del, Key.left, Key.right, Key.up, Key.down, Key.home, + Key.End, Key.pageUp, Key.pageDown, Key.tab, Key.enter, Key.escape, + } + if window.eventsHandler.onTextInput != nil and shouldRouteToInputContext: + var handledByInputContext = false + let inputContext = cast[NSView](self).inputContext + if inputContext != nil: + handledByInputContext = inputContext.handleEvent(event).bool + + # Fallback for view classes where AppKit does not route insertText + # through NSTextInputClient even though keyDown is delivered. + if not handledByInputContext and + ( + modifiers * + {ModifierKey.control, ModifierKey.alt, ModifierKey.system} + ).len == 0: + for r in ($event.characters).runes: + if r.int >= 32 and r.int notin 0xf700 .. 0xf7ff: + window.eventsHandler.pushEvent onTextInput, + TextInputEvent( + window: window, text: $r, repeated: event.isARepeat.bool + ) + + addMethod "keyUp:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + getWindow(self) + let key = event.keyCode.keycodeToKey + let modifiers = window.updateModifiers(event) + if key != Key.unknown: + window.keyboard.pressed.excl key + window.eventsHandler.pushEvent onKey, + KeyEvent( + window: window, + key: key, + pressed: false, + repeated: false, + modifiers: modifiers, + ) # todo: handle repeated + + addMethod "flagsChanged:", + proc(self: Id, cmd: Sel, event: NsEvent): Id {.cdecl.} = + #? wtf is this?!? + getWindow(self) + let key = event.keyCode.keycodeToKey + let modifiers = window.updateModifiers(event) + if key != Key.unknown: + if key in window.keyboard.pressed: + window.keyboard.pressed.excl key + window.eventsHandler.pushEvent onKey, + KeyEvent( + window: window, + key: key, + pressed: false, + repeated: false, + modifiers: modifiers, + ) # todo: handle repeated + else: + window.keyboard.pressed.incl key + window.eventsHandler.pushEvent onKey, + KeyEvent( + window: window, + key: key, + pressed: true, + repeated: false, + modifiers: modifiers, + ) # todo: handle repeated + + addMethod "hasMarkedText", + proc(self: Id, cmd: Sel): bool {.cdecl.} = + getWindow(self) + window.markedText != nil + + addMethod "markedRange", + proc(self: Id, cmd: Sel): NsRange {.cdecl.} = + getWindow(self) + if window.markedText != nil: + NSMakeRange(0, window.markedText.length) + else: + NSRangeEmpty + + addMethod "selectedRange", + proc(self: Id, cmd: Sel): NsRange {.cdecl.} = + getWindow(self) + NSMakeRange(0, 0) + + addMethod "setMarkedText:selectedRange:replacementRange:", + proc( + self: Id, + cmd: Sel, + obj: Id, + selectedRange: NsRange, + replacementRange: NsRange, + ): Id {.cdecl.} = + getWindow(self) + var characters: NSString + if cast[NSObject](obj).isKindOfClass(NSAttributedString): + characters = $(cast[NSAttributedString](obj).toNSString()) + else: + characters = cast[NSString](obj) + + if window.markedText != nil: + window.markedText.release() + + window.markedText = NSString.stringWithString(characters).retain() + + addMethod "unmarkText", + proc(self: Id, cmd: Sel): Id {.cdecl.} = + discard + + addMethod "validAttributesForMarkedText", + proc(self: Id, cmd: Sel): NSArrayAbstract {.cdecl.} = + cast[NSArrayAbstract](nil) + + addMethod "attributedSubstringForProposedRange:actualRange:", + proc(self: Id, cmd: Sel, range: NsRange, actualRange: NsRangePointer): ID = + discard + + addMethod "insertText:replacementRange:", + proc(self: Id, cmd: Sel, obj: Id, replacementRange: NsRange): Id {.cdecl.} = + getWindow(self) + var characters: NSString + if cast[NSObject](obj).isKindOfClass(NSAttributedString): + characters = $cast[NSAttributedString](obj).toNSString() else: - ## todo - # let - # encodedPng = cursor.image.encodePng() - # image = NSImage.alloc().initWithData(NSData.dataWithBytes( - # encodedPng[0].unsafeAddr, - # encodedPng.len - # )) - # hotspot = NSMakePoint( - # window.state.cursor.hotspot.x.float, - # window.state.cursor.hotspot.y.float - # ) - # cursor = NSCursor.alloc().initWithImage(image, hotspot) - # self.NSView.addCursorRect(self.NSView.bounds, cursor) + characters = cast[NSString](obj) + + var range = NSMakeRange(0, characters.length.uint) + while range.length > 0: + var + codepoint: uint32 + usedLength: uint + discard characters.getBytes( + codepoint.addr, + sizeof(codepoint).uint, + usedLength.addr, + NSUTF32StringEncoding, + 0.NSStringEncodingConversionOptions, + range, + range.addr, + ) + if codepoint >= 0xf700 and codepoint <= 0xf7ff: + continue + window.eventsHandler.pushEvent onTextInput, + TextInputEvent(window: window, text: $Rune(codepoint)) + + if window.markedText != nil: + window.markedText.release() + window.markedText = nil + + addMethod "characterIndexForPoint:", + proc(self: Id, cmd: Sel, point: NsPoint): uint {.cdecl.} = + NSNotFound.uint + + addMethod "firstRectForCharacterRange:actualRange:", + proc( + self: Id, cmd: Sel, range: NsRange, actualRange: NsRangePointer + ): NsRect {.cdecl.} = + getWindow(self) + let contentRect = window.handle.contentRectForFrameRect(window.handle.frame) + NSMakeRect( + contentRect.origin.x + 0, + contentRect.origin.y + contentRect.size.height - 1 - 0, + 0, + 0, + ) + + addMethod "doCommandBySelector:", + proc(self: Id, cmd: Sel, selector: Sel): Id {.cdecl.} = + discard + + addMethod "resetCursorRects", + proc(self: Id, cmd: Sel): Id {.cdecl.} = + getWindow(self) + autoreleasepool: + case window.m_cursor.kind + of builtin: + discard + else: + ## todo + # let + # encodedPng = cursor.image.encodePng() + # image = NSImage.alloc().initWithData(NSData.dataWithBytes( + # encodedPng[0].unsafeAddr, + # encodedPng.len + # )) + # hotspot = NSMakePoint( + # window.state.cursor.hotspot.x.float, + # window.state.cursor.hotspot.y.float + # ) + # cursor = NSCursor.alloc().initWithImage(image, hotspot) + # self.NSView.addCursorRect(self.NSView.bounds, cursor) addSiwinViewClass("SiwinViewSoftware", "NSImageView", softwareViewClass) addSiwinViewClass("SiwinViewOpenGL", "NSOpenGLView", openglViewClass) @@ -1520,7 +1701,6 @@ proc init = NSApp.setDelegate(cast[NSObject](appDelegateClass.new)) NSApp.finishLaunching() - method firstStep*(window: WindowCocoa, makeVisible = true) = if makeVisible: window.visible = true @@ -1529,10 +1709,7 @@ method firstStep*(window: WindowCocoa, makeVisible = true) = autoreleasepool: while true: let event = NSApp.nextEventMatchingMask( - NSEventMaskAny, - NSDate.distantPast, - NSDefaultRunLoopMode, - true + NSEventMaskAny, NSDate.distantPast, NSDefaultRunLoopMode, true ) if event == nil: break @@ -1546,16 +1723,12 @@ method firstStep*(window: WindowCocoa, makeVisible = true) = if window.canBecomeKeyWindow: window.handle.makeKeyAndOrderFront(cast[ID](nil)) - method step*(window: WindowCocoa) = proc pumpEvents(mode: NSRunLoopMode, firstUntilDate: NSDate): bool = var first = true while true: let event = NSApp.nextEventMatchingMask( - NSEventMaskAny, - (if first: firstUntilDate else: NSDate.distantPast), - mode, - true + NSEventMaskAny, (if first: firstUntilDate else: NSDate.distantPast), mode, true ) if event == nil: break @@ -1578,14 +1751,14 @@ method step*(window: WindowCocoa) = window.refreshModifiers() - window.eventsHandler.pushEvent onTick, TickEvent(window: window) # todo: lastTickTime - + window.eventsHandler.pushEvent onTick, TickEvent(window: window) # todo: lastTickTime + if window.redrawRequested: window.redrawRequested = false if window of WindowCocoaSoftwareRendering: window.WindowCocoaSoftwareRendering.resizeSoftwarePixelBuffer(window.m_size) window.eventsHandler.pushEvent onRender, RenderEvent(window: window) - + if window of WindowCocoaSoftwareRendering: window.WindowCocoaSoftwareRendering.presentSoftwarePixelBuffer() elif window of WindowCocoaOpengl: @@ -1600,48 +1773,62 @@ method pixelBuffer*(window: WindowCocoaSoftwareRendering): PixelBuffer = format: PixelBufferFormat.rgbx_32bit, ) - proc newSoftwareRenderingWindowCocoa*( - size = ivec2(1280, 720), - title = "", - screen = defaultScreenCocoa(), - resizable = true, - fullscreen = false, - frameless = false, - transparent = false, + size = ivec2(1280, 720), + title = "", + screen = defaultScreenCocoa(), + resizable = true, + fullscreen = false, + frameless = false, + transparent = false, ): WindowCocoaSoftwareRendering = new result - result.initWindowCocoaSoftwareRendering(size, screen, fullscreen, frameless, transparent) + result.initWindowCocoaSoftwareRendering( + size, screen, fullscreen, frameless, transparent + ) result.title = title - if not resizable: result.resizable = false + if not resizable: + result.resizable = false + +proc newPopupWindowCocoa*( + parent: WindowCocoa, placement: PopupPlacement, transparent = false, grab = true +): WindowCocoaSoftwareRendering = + if parent == nil: + raise ValueError.newException("Popup windows require a parent window") + new result + result.initPopupWindowCocoaSoftwareRendering(parent, placement, transparent, grab) proc newOpenglWindowCocoa*( - size = ivec2(1280, 720), - title = "", - screen = defaultScreenCocoa(), - resizable = true, - fullscreen = false, - frameless = false, - transparent = false, - vsync = false, - msaa = 0'i32, + size = ivec2(1280, 720), + title = "", + screen = defaultScreenCocoa(), + resizable = true, + fullscreen = false, + frameless = false, + transparent = false, + vsync = false, + msaa = 0'i32, ): WindowCocoaOpengl = new result - result.initWindowCocoaOpengl(size, screen, fullscreen, frameless, transparent, vsync, msaa) + result.initWindowCocoaOpengl( + size, screen, fullscreen, frameless, transparent, vsync, msaa + ) result.title = title - if not resizable: result.resizable = false + if not resizable: + result.resizable = false proc newMetalWindowCocoa*( - size = ivec2(1280, 720), - title = "", - screen = defaultScreenCocoa(), - resizable = true, - fullscreen = false, - frameless = false, - transparent = false, + size = ivec2(1280, 720), + title = "", + screen = defaultScreenCocoa(), + resizable = true, + fullscreen = false, + frameless = false, + transparent = false, ): WindowCocoaMetal = new result result.initWindowCocoaMetal(size, screen, fullscreen, frameless, transparent) result.title = title - if not resizable: result.resizable = false + if not resizable: + result.resizable = false diff --git a/src/siwin/platforms/wayland/window.nim b/src/siwin/platforms/wayland/window.nim index 7716b32..cbff14f 100644 --- a/src/siwin/platforms/wayland/window.nim +++ b/src/siwin/platforms/wayland/window.nim @@ -1,4 +1,6 @@ -import std/[times, importutils, strformat, options, tables, os, uri, sequtils, strutils, math] +import + std/ + [times, importutils, strformat, options, tables, os, uri, sequtils, strutils, math] from std/posix import pipe, close, write, read import pkg/[vmath] import ../../[colorutils, siwindefs] @@ -16,24 +18,26 @@ type WindowWaylandKind* {.pure.} = enum XdgSurface + PopupSurface LayerSurface - + WindowWayland* = ref WindowWaylandObj WindowWaylandObj* = object of Window globals: SiwinGlobalsWayland surface: Wl_surface xdgSurface: Xdg_surface xdgToplevel: Xdg_toplevel + xdgPopup: Xdg_popup serverDecoration: Zxdg_toplevel_decoration_v1 # will be nil if compositor doesn't support this protocol plasmaSurface: Org_kde_plasma_surface # will be nil if compositor doesn't support this protocol - + layerShellSurface: Zwlr_layer_surface_v1 # will be nil if compositor doesn't support this protocol (eg. GNOME) - + idleInhibitor: Zwp_idle_inhibitor_v1 libdecorFrame: LibdecorFrame @@ -60,143 +64,144 @@ type lastKeyRepeatedTime: Time bufferScaleFactor: int32 fractionalScaleFactor: float32 + popupRepositionToken: uint32 viewport: Wp_viewport fractionalScaleObj: Wp_fractional_scale_v1 toplevelIcon: Xdg_toplevel_icon_v1 toplevelIconBuffers: seq[SharedBuffer] - + ClipboardWayland* = ref object of Clipboard globals: SiwinGlobalsWayland userContent: ClipboardConvertableContent dataSource: Wl_data_source - + ClipboardWaylandDnd* = ref object of Clipboard globals: SiwinGlobalsWayland - WindowWaylandSoftwareRendering* = ref WindowWaylandSoftwareRenderingObj WindowWaylandSoftwareRenderingObj* = object of WindowWayland buffer: SharedBuffer oldBuffer: SharedBuffer - proc waylandKeyToKey(keycode: uint32): Key = case global_xkb_state_unmodified.xkb_state_key_get_one_sym(keycode + 8) - of XKB_KEY_shiftL: Key.lshift - of XKB_KEY_shiftR: Key.rshift - of XKB_KEY_controlL: Key.lcontrol - of XKB_KEY_controlR: Key.rcontrol - of XKB_KEY_altL: Key.lalt - of XKB_KEY_altR: Key.ralt - of XKB_KEY_superL: Key.lsystem - of XKB_KEY_superR: Key.rsystem - of XKB_KEY_menu: Key.menu - of XKB_KEY_escape: Key.escape - of XKB_KEY_semicolon: Key.semicolon - of XKB_KEY_slash: Key.slash - of XKB_KEY_equal: Key.equal - of XKB_KEY_minus: Key.minus - of XKB_KEY_bracketleft: Key.lbracket + of XKB_KEY_shiftL: Key.lshift + of XKB_KEY_shiftR: Key.rshift + of XKB_KEY_controlL: Key.lcontrol + of XKB_KEY_controlR: Key.rcontrol + of XKB_KEY_altL: Key.lalt + of XKB_KEY_altR: Key.ralt + of XKB_KEY_superL: Key.lsystem + of XKB_KEY_superR: Key.rsystem + of XKB_KEY_menu: Key.menu + of XKB_KEY_escape: Key.escape + of XKB_KEY_semicolon: Key.semicolon + of XKB_KEY_slash: Key.slash + of XKB_KEY_equal: Key.equal + of XKB_KEY_minus: Key.minus + of XKB_KEY_bracketleft: Key.lbracket of XKB_KEY_bracketright: Key.rbracket - of XKB_KEY_comma: Key.comma - of XKB_KEY_period: Key.dot - of XKB_KEY_apostrophe: Key.quote - of XKB_KEY_backslash: Key.backslash - of XKB_KEY_grave: Key.tilde - of XKB_KEY_space: Key.space - of XKB_KEY_return: Key.enter - of XKB_KEY_kpEnter: Key.enter - of XKB_KEY_backspace: Key.backspace - of XKB_KEY_tab: Key.tab - of XKB_KEY_prior: Key.page_up - of XKB_KEY_next: Key.page_down - of XKB_KEY_end: Key.End - of XKB_KEY_home: Key.home - of XKB_KEY_insert: Key.insert - of XKB_KEY_delete: Key.del - of XKB_KEY_kpAdd: Key.add - of XKB_KEY_kpSubtract: Key.subtract - of XKB_KEY_kpMultiply: Key.multiply - of XKB_KEY_kpDivide: Key.divide - of XKB_KEY_capsLock: Key.capsLock - of XKB_KEY_numLock: Key.numLock - of XKB_KEY_scrollLock: Key.scrollLock - of XKB_KEY_print: Key.printScreen - of XKB_KEY_kpSeparator: Key.npadDot - of XKB_KEY_pause: Key.pause - of XKB_KEY_f1: Key.f1 - of XKB_KEY_f2: Key.f2 - of XKB_KEY_f3: Key.f3 - of XKB_KEY_f4: Key.f4 - of XKB_KEY_f5: Key.f5 - of XKB_KEY_f6: Key.f6 - of XKB_KEY_f7: Key.f7 - of XKB_KEY_f8: Key.f8 - of XKB_KEY_f9: Key.f9 - of XKB_KEY_f10: Key.f10 - of XKB_KEY_f11: Key.f11 - of XKB_KEY_f12: Key.f12 - of XKB_KEY_f13: Key.f13 - of XKB_KEY_f14: Key.f14 - of XKB_KEY_f15: Key.f15 - of XKB_KEY_left: Key.left - of XKB_KEY_right: Key.right - of XKB_KEY_up: Key.up - of XKB_KEY_down: Key.down - of XKB_KEY_kpInsert: Key.npad0 - of XKB_KEY_kpEnd: Key.npad1 - of XKB_KEY_kpDown: Key.npad2 - of XKB_KEY_kpPagedown: Key.npad3 - of XKB_KEY_kpLeft: Key.npad4 - of XKB_KEY_kpBegin: Key.npad5 - of XKB_KEY_kpRight: Key.npad6 - of XKB_KEY_kpHome: Key.npad7 - of XKB_KEY_kpUp: Key.npad8 - of XKB_KEY_kpPageup: Key.npad9 - of XKB_KEY_a: Key.a - of XKB_KEY_b: Key.b - of XKB_KEY_c: Key.c - of XKB_KEY_d: Key.d - of XKB_KEY_e: Key.e - of XKB_KEY_f: Key.f - of XKB_KEY_g: Key.g - of XKB_KEY_h: Key.h - of XKB_KEY_i: Key.i - of XKB_KEY_j: Key.j - of XKB_KEY_k: Key.k - of XKB_KEY_l: Key.l - of XKB_KEY_m: Key.m - of XKB_KEY_n: Key.n - of XKB_KEY_o: Key.o - of XKB_KEY_p: Key.p - of XKB_KEY_q: Key.q - of XKB_KEY_r: Key.r - of XKB_KEY_s: Key.s - of XKB_KEY_t: Key.t - of XKB_KEY_u: Key.u - of XKB_KEY_v: Key.v - of XKB_KEY_w: Key.w - of XKB_KEY_x: Key.x - of XKB_KEY_y: Key.y - of XKB_KEY_z: Key.z - of XKB_KEY_0: Key.n0 - of XKB_KEY_1: Key.n1 - of XKB_KEY_2: Key.n2 - of XKB_KEY_3: Key.n3 - of XKB_KEY_4: Key.n4 - of XKB_KEY_5: Key.n5 - of XKB_KEY_6: Key.n6 - of XKB_KEY_7: Key.n7 - of XKB_KEY_8: Key.n8 - of XKB_KEY_9: Key.n9 - of XKB_KEY_ISO_Level3_Shift: Key.level3_shift - of XKB_KEY_ISO_Level5_Shift: Key.level5_shift - else: Key.unknown + of XKB_KEY_comma: Key.comma + of XKB_KEY_period: Key.dot + of XKB_KEY_apostrophe: Key.quote + of XKB_KEY_backslash: Key.backslash + of XKB_KEY_grave: Key.tilde + of XKB_KEY_space: Key.space + of XKB_KEY_return: Key.enter + of XKB_KEY_kpEnter: Key.enter + of XKB_KEY_backspace: Key.backspace + of XKB_KEY_tab: Key.tab + of XKB_KEY_prior: Key.page_up + of XKB_KEY_next: Key.page_down + of XKB_KEY_end: Key.End + of XKB_KEY_home: Key.home + of XKB_KEY_insert: Key.insert + of XKB_KEY_delete: Key.del + of XKB_KEY_kpAdd: Key.add + of XKB_KEY_kpSubtract: Key.subtract + of XKB_KEY_kpMultiply: Key.multiply + of XKB_KEY_kpDivide: Key.divide + of XKB_KEY_capsLock: Key.capsLock + of XKB_KEY_numLock: Key.numLock + of XKB_KEY_scrollLock: Key.scrollLock + of XKB_KEY_print: Key.printScreen + of XKB_KEY_kpSeparator: Key.npadDot + of XKB_KEY_pause: Key.pause + of XKB_KEY_f1: Key.f1 + of XKB_KEY_f2: Key.f2 + of XKB_KEY_f3: Key.f3 + of XKB_KEY_f4: Key.f4 + of XKB_KEY_f5: Key.f5 + of XKB_KEY_f6: Key.f6 + of XKB_KEY_f7: Key.f7 + of XKB_KEY_f8: Key.f8 + of XKB_KEY_f9: Key.f9 + of XKB_KEY_f10: Key.f10 + of XKB_KEY_f11: Key.f11 + of XKB_KEY_f12: Key.f12 + of XKB_KEY_f13: Key.f13 + of XKB_KEY_f14: Key.f14 + of XKB_KEY_f15: Key.f15 + of XKB_KEY_left: Key.left + of XKB_KEY_right: Key.right + of XKB_KEY_up: Key.up + of XKB_KEY_down: Key.down + of XKB_KEY_kpInsert: Key.npad0 + of XKB_KEY_kpEnd: Key.npad1 + of XKB_KEY_kpDown: Key.npad2 + of XKB_KEY_kpPagedown: Key.npad3 + of XKB_KEY_kpLeft: Key.npad4 + of XKB_KEY_kpBegin: Key.npad5 + of XKB_KEY_kpRight: Key.npad6 + of XKB_KEY_kpHome: Key.npad7 + of XKB_KEY_kpUp: Key.npad8 + of XKB_KEY_kpPageup: Key.npad9 + of XKB_KEY_a: Key.a + of XKB_KEY_b: Key.b + of XKB_KEY_c: Key.c + of XKB_KEY_d: Key.d + of XKB_KEY_e: Key.e + of XKB_KEY_f: Key.f + of XKB_KEY_g: Key.g + of XKB_KEY_h: Key.h + of XKB_KEY_i: Key.i + of XKB_KEY_j: Key.j + of XKB_KEY_k: Key.k + of XKB_KEY_l: Key.l + of XKB_KEY_m: Key.m + of XKB_KEY_n: Key.n + of XKB_KEY_o: Key.o + of XKB_KEY_p: Key.p + of XKB_KEY_q: Key.q + of XKB_KEY_r: Key.r + of XKB_KEY_s: Key.s + of XKB_KEY_t: Key.t + of XKB_KEY_u: Key.u + of XKB_KEY_v: Key.v + of XKB_KEY_w: Key.w + of XKB_KEY_x: Key.x + of XKB_KEY_y: Key.y + of XKB_KEY_z: Key.z + of XKB_KEY_0: Key.n0 + of XKB_KEY_1: Key.n1 + of XKB_KEY_2: Key.n2 + of XKB_KEY_3: Key.n3 + of XKB_KEY_4: Key.n4 + of XKB_KEY_5: Key.n5 + of XKB_KEY_6: Key.n6 + of XKB_KEY_7: Key.n7 + of XKB_KEY_8: Key.n8 + of XKB_KEY_9: Key.n9 + of XKB_KEY_ISO_Level3_Shift: Key.level3_shift + of XKB_KEY_ISO_Level5_Shift: Key.level5_shift + else: Key.unknown proc waylandKeyToString(keycode: uint32): string = result = newStringOfCap(8) result.setLen 1 - result.setLen global_xkb_state.xkb_state_key_get_utf8(keycode + 8, cast[cstring](result[0].addr), 7) + result.setLen global_xkb_state.xkb_state_key_get_utf8( + keycode + 8, cast[cstring](result[0].addr), 7 + ) proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = if (keys * {Key.lshift, Key.rshift}).len != 0: @@ -211,31 +216,49 @@ proc modifiersFromPressedKeys(keys: set[Key]): set[ModifierKey] = proc refreshKeyboardModifiers(window: WindowWayland) = var modifiers = modifiersFromPressedKeys(window.keyboard.pressed) if global_xkb_state != nil: - if global_xkb_state.xkb_state_mod_name_is_active("Shift".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + if global_xkb_state.xkb_state_mod_name_is_active( + "Shift".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0: modifiers.incl ModifierKey.shift - if global_xkb_state.xkb_state_mod_name_is_active("Control".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + if global_xkb_state.xkb_state_mod_name_is_active( + "Control".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0: modifiers.incl ModifierKey.control if ( - global_xkb_state.xkb_state_mod_name_is_active("Mod1".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or - global_xkb_state.xkb_state_mod_name_is_active("Alt".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + global_xkb_state.xkb_state_mod_name_is_active( + "Mod1".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 or + global_xkb_state.xkb_state_mod_name_is_active( + "Alt".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 ): modifiers.incl ModifierKey.alt if ( - global_xkb_state.xkb_state_mod_name_is_active("Mod4".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or - global_xkb_state.xkb_state_mod_name_is_active("Super".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + global_xkb_state.xkb_state_mod_name_is_active( + "Mod4".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 or + global_xkb_state.xkb_state_mod_name_is_active( + "Super".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 ): modifiers.incl ModifierKey.system - if global_xkb_state.xkb_state_mod_name_is_active("Lock".cstring, XKB_STATE_MODS_EFFECTIVE) != 0: + if global_xkb_state.xkb_state_mod_name_is_active( + "Lock".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0: modifiers.incl ModifierKey.capsLock if ( - global_xkb_state.xkb_state_mod_name_is_active("NumLock".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 or - global_xkb_state.xkb_state_mod_name_is_active("Mod2".cstring, XKB_STATE_MODS_EFFECTIVE) != 0 + global_xkb_state.xkb_state_mod_name_is_active( + "NumLock".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 or + global_xkb_state.xkb_state_mod_name_is_active( + "Mod2".cstring, XKB_STATE_MODS_EFFECTIVE + ) != 0 ): modifiers.incl ModifierKey.numLock window.keyboard.modifiers = modifiers - -method swapBuffers(window: WindowWayland) {.base.} = discard +method swapBuffers(window: WindowWayland) {.base.} = + discard proc screenCountWayland*(globals: SiwinGlobalsWayland): int32 = ## todo @@ -243,17 +266,21 @@ proc screenCountWayland*(globals: SiwinGlobalsWayland): int32 = proc screenWayland*(globals: SiwinGlobalsWayland, number: int32): ScreenWayland = new result - if number notin 0.. 0'f32 and window.viewport != typeof(window.viewport).default + window.fractionalScaleFactor > 0'f32 and + window.viewport != typeof(window.viewport).default proc effectiveUiScale(window: WindowWayland): float32 {.inline.} = if window.hasFractionalScaling(): @@ -379,24 +405,28 @@ proc bufferScale(window: WindowWayland): int32 {.inline.} = else: max(1'i32, ceil(window.effectiveUiScale()).int32) -proc scaledBufferLength(logical: int32; uiScale: float32): int32 {.inline.} = +proc scaledBufferLength(logical: int32, uiScale: float32): int32 {.inline.} = max(1'i32, ((logical.float32 * uiScale) + 0.5'f32).int32) proc bufferSize(window: WindowWayland, logicalSize: IVec2): IVec2 {.inline.} = let scale = window.effectiveUiScale() - ivec2(scaledBufferLength(logicalSize.x, scale), scaledBufferLength(logicalSize.y, scale)) + ivec2( + scaledBufferLength(logicalSize.x, scale), scaledBufferLength(logicalSize.y, scale) + ) -proc toLogicalLength(window: WindowWayland; backing: float32): float32 {.inline.} = +proc toLogicalLength(window: WindowWayland, backing: float32): float32 {.inline.} = let scale = window.effectiveUiScale() if scale <= 1'f32: backing else: backing / scale -proc toLogicalLength(window: WindowWayland; backing: int32): int32 {.inline.} = +proc toLogicalLength(window: WindowWayland, backing: int32): int32 {.inline.} = max(1'i32, (window.toLogicalLength(backing.float32) + 0.5'f32).int32) -proc reportedPointerPos(window: WindowWayland, surfaceX, surfaceY: float32): Vec2 {.inline.} = +proc reportedPointerPos( + window: WindowWayland, surfaceX, surfaceY: float32 +): Vec2 {.inline.} = let scale = window.effectiveUiScale() if scale <= 1'f32: vec2(surfaceX, surfaceY) @@ -409,13 +439,74 @@ method uiScale*(window: WindowWayland): float32 = method reportedSize*(window: WindowWayland): IVec2 = window.bufferSize(window.m_size) +proc popupAnchor(anchor: PopupAnchor): `Xdg_positioner / Anchor` = + case anchor + of PopupAnchor.paTopLeft: `Xdg_positioner / Anchor`.top_left + of PopupAnchor.paTop: `Xdg_positioner / Anchor`.top + of PopupAnchor.paTopRight: `Xdg_positioner / Anchor`.top_right + of PopupAnchor.paLeft: `Xdg_positioner / Anchor`.left + of PopupAnchor.paCenter: `Xdg_positioner / Anchor`.none + of PopupAnchor.paRight: `Xdg_positioner / Anchor`.right + of PopupAnchor.paBottomLeft: `Xdg_positioner / Anchor`.bottom_left + of PopupAnchor.paBottom: `Xdg_positioner / Anchor`.bottom + of PopupAnchor.paBottomRight: `Xdg_positioner / Anchor`.bottom_right + +proc popupGravity(gravity: PopupGravity): `Xdg_positioner / Gravity` = + case gravity + of PopupGravity.pgTopLeft: `Xdg_positioner / Gravity`.top_left + of PopupGravity.pgTop: `Xdg_positioner / Gravity`.top + of PopupGravity.pgTopRight: `Xdg_positioner / Gravity`.top_right + of PopupGravity.pgLeft: `Xdg_positioner / Gravity`.left + of PopupGravity.pgCenter: `Xdg_positioner / Gravity`.none + of PopupGravity.pgRight: `Xdg_positioner / Gravity`.right + of PopupGravity.pgBottomLeft: `Xdg_positioner / Gravity`.bottom_left + of PopupGravity.pgBottom: `Xdg_positioner / Gravity`.bottom + of PopupGravity.pgBottomRight: `Xdg_positioner / Gravity`.bottom_right + +proc popupConstraintAdjustment(adjustments: set[PopupConstraintAdjustment]): uint32 = + if PopupConstraintAdjustment.pcaSlideX in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.slide_x.uint32 + if PopupConstraintAdjustment.pcaSlideY in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.slide_y.uint32 + if PopupConstraintAdjustment.pcaFlipX in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.flip_x.uint32 + if PopupConstraintAdjustment.pcaFlipY in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.flip_y.uint32 + if PopupConstraintAdjustment.pcaResizeX in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.resize_x.uint32 + if PopupConstraintAdjustment.pcaResizeY in adjustments: + result = result or `Xdg_positioner / Constraint_adjustment`.resize_y.uint32 + +proc newPositioner(window: WindowWayland, placement: PopupPlacement): Xdg_positioner = + result = window.globals.xdgWmBase.create_positioner() + let size = placement.popupSize() + result.set_size(size.x, size.y) + result.set_anchor_rect( + placement.anchorRectPos.x, + placement.anchorRectPos.y, + max(1, placement.anchorRectSize.x), + max(1, placement.anchorRectSize.y), + ) + result.set_anchor(placement.anchor.popupAnchor()) + result.set_gravity(placement.gravity.popupGravity()) + result.set_constraint_adjustment( + placement.constraintAdjustment.popupConstraintAdjustment() + ) + result.set_offset(placement.offset.x, placement.offset.y) + if placement.reactive: + result.set_reactive() + if result.proxy.wl_proxy_get_version() >= 3 and window.parentWindow() != nil: + let parent = window.parentWindow().WindowWayland + result.set_parent_size(parent.m_size.x, parent.m_size.y) method close*(window: WindowWayland) = + if window.m_closed: + return window.m_closed = true + window.notifyPopupDone(PopupDismissReason.pdrClientClosed) window.eventsHandler.onClose.pushEvent CloseEvent(window: window) release window - proc initClipboardsIfNeeded(globals: SiwinGlobalsWayland) = if globals.primaryClipboard == nil: globals.primaryClipboard = ClipboardWayland(globals: globals) @@ -424,8 +515,7 @@ proc initClipboardsIfNeeded(globals: SiwinGlobalsWayland) = if globals.dragndropClipboard == nil: globals.dragndropClipboard = ClipboardWaylandDnd(globals: globals) - -proc basicInitWindow(window: WindowWayland; size: IVec2; screen: ScreenWayland) = +proc basicInitWindow(window: WindowWayland, size: IVec2, screen: ScreenWayland) = window.m_size = size window.m_focused = false window.m_resizable = true @@ -439,7 +529,6 @@ proc basicInitWindow(window: WindowWayland; size: IVec2; screen: ScreenWayland) window.m_selectionClipboard = window.globals.selectionClipboard window.m_dragndropClipboard = window.globals.dragndropClipboard - method doResize(window: WindowWayland, size: IVec2) {.base.} = window.m_size = size @@ -452,7 +541,6 @@ method doResize(window: WindowWayland, size: IVec2) {.base.} = window.surface.set_opaque_region(opaqueRegion) destroy opaqueRegion - method doResize(window: WindowWaylandSoftwareRendering, size: IVec2) = procCall window.WindowWayland.doResize(size) let scaledSize = window.bufferSize(size) @@ -461,7 +549,12 @@ method doResize(window: WindowWaylandSoftwareRendering, size: IVec2) = swap window.buffer, window.oldBuffer if window.buffer == nil: - window.buffer = window.globals.create(window.globals.shm, scaledSize, (if window.m_transparent: argb8888 else: xrgb8888), bufferCount = 2) + window.buffer = window.globals.create( + window.globals.shm, + scaledSize, + (if window.m_transparent: argb8888 else: xrgb8888), + bufferCount = 2, + ) else: try: window.buffer.resize(scaledSize) @@ -477,7 +570,6 @@ method doResize(window: WindowWaylandSoftwareRendering, size: IVec2) = # no need to attach buffer yet - proc setFrameless(window: WindowWayland, v: bool) # libdecor/xdg abstraction, prefixed with toplevel @@ -499,7 +591,8 @@ proc toplevelForIcon(window: WindowWayland): Xdg_toplevel = if rawToplevel == nil: return typeof(result).default - window.xdgToplevel = Xdg_toplevel(proxy: Wl_proxy(raw: cast[ptr Wl_object](rawToplevel))) + window.xdgToplevel = + Xdg_toplevel(proxy: Wl_proxy(raw: cast[ptr Wl_object](rawToplevel))) window.xdgToplevel proc toplevelSetTitle(window: WindowWayland, title: string) = @@ -528,23 +621,31 @@ proc toplevelSetMaxSize(window: WindowWayland, x, y: int32) = proc toplevelSetFullscreen(window: WindowWayland, v: bool) = if window.useLibdecor: - if v: libdecor_frame_set_fullscreen(window.libdecorFrame, nil) - else: libdecor_frame_unset_fullscreen(window.libdecorFrame) + if v: + libdecor_frame_set_fullscreen(window.libdecorFrame, nil) + else: + libdecor_frame_unset_fullscreen(window.libdecorFrame) else: if v: - if window.m_frameless: window.setFrameless(true) + if window.m_frameless: + window.setFrameless(true) window.xdgToplevel.set_fullscreen(Wl_output(proxy: Wl_proxy(raw: nil))) else: window.xdgToplevel.unset_fullscreen() - if not window.m_frameless: window.setFrameless(false) + if not window.m_frameless: + window.setFrameless(false) proc toplevelSetMaximized(window: WindowWayland, v: bool) = if window.useLibdecor: - if v: libdecor_frame_set_maximized(window.libdecorFrame) - else: libdecor_frame_unset_maximized(window.libdecorFrame) + if v: + libdecor_frame_set_maximized(window.libdecorFrame) + else: + libdecor_frame_unset_maximized(window.libdecorFrame) else: - if v: window.xdgToplevel.set_maximized() - else: window.xdgToplevel.unsetMaximized() + if v: + window.xdgToplevel.set_maximized() + else: + window.xdgToplevel.unsetMaximized() proc toplevelSetMinimized(window: WindowWayland) = if window.useLibdecor: @@ -558,15 +659,21 @@ proc toplevelMove(window: WindowWayland, serial: uint32) = else: window.xdgToplevel.move(window.globals.seat, serial) -proc toplevelResize(window: WindowWayland, serial: uint32, edge: `Xdg_toplevel/Resize_edge`) = +proc toplevelResize( + window: WindowWayland, serial: uint32, edge: `Xdg_toplevel / Resize_edge` +) = if window.useLibdecor: - libdecor_frame_resize(window.libdecorFrame, window.globals.seat.proxy.raw, serial, edge.uint32) + libdecor_frame_resize( + window.libdecorFrame, window.globals.seat.proxy.raw, serial, edge.uint32 + ) else: window.xdgToplevel.resize(window.globals.seat, serial, edge) proc toplevelShowWindowMenu(window: WindowWayland, serial: uint32, x, y: int32) = if window.useLibdecor: - libdecor_frame_show_window_menu(window.libdecorFrame, window.globals.seat.proxy.raw, serial, x.cint, y.cint) + libdecor_frame_show_window_menu( + window.libdecorFrame, window.globals.seat.proxy.raw, serial, x.cint, y.cint + ) else: window.xdgToplevel.show_window_menu(window.globals.seat, serial, x, y) @@ -574,7 +681,9 @@ proc resize(window: WindowWayland, size: IVec2) proc createLibdecorFrameIface(): LibdecorFrameInterface = LibdecorFrameInterface( - configure: proc(frame: LibdecorFrame, cfg: LibdecorConfiguration, userData: pointer) {.cdecl.} = + configure: proc( + frame: LibdecorFrame, cfg: LibdecorConfiguration, userData: pointer + ) {.cdecl.} = let win = cast[WindowWayland](userData) var w, h: cint if libdecor_configuration_get_content_size(cfg, frame, w.addr, h.addr): @@ -590,9 +699,13 @@ proc createLibdecorFrameIface(): LibdecorFrameInterface = let newVal = (winState and LibdecorWindowState.flag.uint32) != 0 if win.m != newVal: win.m = newVal - if win.opened: win.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( - window: win, kind: StateBoolChangedEventKind.k, value: win.m, isExternal: true - ) + if win.opened: + win.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( + window: win, + kind: StateBoolChangedEventKind.k, + value: win.m, + isExternal: true, + ) handleState maximized, maximized, m_maximized handleState fullscreen, fullscreen, m_fullscreen @@ -605,13 +718,13 @@ proc createLibdecorFrameIface(): LibdecorFrameInterface = , close: proc(frame: LibdecorFrame, userData: pointer) {.cdecl.} = let win = cast[WindowWayland](userData) - win.m_closed = true - , + win.m_closed = true, commit: proc(frame: LibdecorFrame, userData: pointer) {.cdecl.} = let win = cast[WindowWayland](userData) commit win.surface , - dismissPopup: nil) + dismissPopup: nil, + ) proc setupOpaqueRegion(window: WindowWayland, size: IVec2, transparent: bool) = window.m_transparent = transparent @@ -621,36 +734,47 @@ proc setupOpaqueRegion(window: WindowWayland, size: IVec2, transparent: bool) = window.surface.set_opaque_region(opaqueRegion) destroy opaqueRegion - proc resize(window: WindowWayland, size: IVec2) = if size.x <= 0 or size.y <= 0: ## todo: means we should decide the size by ourselves return - + window.doResize size - + case window.kind of WindowWaylandKind.XdgSurface: if not window.m_resizable: window.toplevelSetMinSize(window.m_size.x, window.m_size.y) window.toplevelSetMaxSize(window.m_size.x, window.m_size.y) - window.eventsHandler.onResize.pushEvent ResizeEvent(window: window, size: window.size) - + window.eventsHandler.onResize.pushEvent ResizeEvent( + window: window, size: window.size + ) + of WindowWaylandKind.PopupSurface: + window.eventsHandler.onResize.pushEvent ResizeEvent( + window: window, size: window.size + ) + window.redraw() of WindowWaylandKind.LayerSurface: window.layerShellSurface.set_size(window.m_size.x.uint32, window.m_size.y.uint32) - window.eventsHandler.onResize.pushEvent ResizeEvent(window: window, size: window.size) + window.eventsHandler.onResize.pushEvent ResizeEvent( + window: window, size: window.size + ) window.redraw() - method `title=`*(window: WindowWayland, v: string) = case window.kind of WindowWaylandKind.XdgSurface: window.toplevelSetTitle(v) - else: discard - + of WindowWaylandKind.PopupSurface: + discard + else: + discard proc setFrameless(window: WindowWayland, v: bool) = + if window.kind != WindowWaylandKind.XdgSurface: + return + if window.useLibdecor: if window.libdecorFrame != nil: # prevent something crazy after realease libdecor_frame_set_visibility(window.libdecorFrame, not v) @@ -658,41 +782,54 @@ proc setFrameless(window: WindowWayland, v: bool) = if window.globals.serverDecorationManager != nil: if window.serverDecoration == nil: - window.serverDecoration = window.globals.serverDecorationManager.get_toplevel_decoration(window.xdg_toplevel) - + window.serverDecoration = window.globals.serverDecorationManager.get_toplevel_decoration( + window.xdg_toplevel + ) + window.serverDecoration.onConfigure: - let newFrameless = case mode - of client_side: true - else: false - if newFrameless == window.m_frameless: return - + let newFrameless = + case mode + of client_side: true + else: false + if newFrameless == window.m_frameless: + return + window.m_frameless = newFrameless window.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.frameless, value: window.m_frameless, isExternal: true + window: window, + kind: StateBoolChangedEventKind.frameless, + value: window.m_frameless, + isExternal: true, ) window.serverDecoration.set_mode: - if v: client_side - else: server_side - + if v: client_side else: server_side + window.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.frameless, value: window.m_frameless, isExternal: false + window: window, + kind: StateBoolChangedEventKind.frameless, + value: window.m_frameless, + isExternal: false, ) - method `fullscreen=`*(window: WindowWayland, v: bool) = - if window.m_fullscreen == v: return + if window.kind != WindowWaylandKind.XdgSurface: + return + if window.m_fullscreen == v: + return window.m_fullscreen = v window.toplevelSetFullscreen(v) - method `frameless=`*(window: WindowWayland, v: bool) = - if window.m_frameless == v: return + if window.m_frameless == v: + return window.m_frameless = v - if window.m_fullscreen: return # no system decorations needed for fullscreen windows + if window.kind != WindowWaylandKind.XdgSurface: + return + if window.m_fullscreen: + return # no system decorations needed for fullscreen windows window.setFrameless(v) - method `size=`*(window: WindowWayland, v: IVec2) = if window.fullscreen: window.fullscreen = false @@ -700,32 +837,65 @@ method `size=`*(window: WindowWayland, v: IVec2) = if v.x <= 0 or v.y <= 0: raise RangeDefect.newException("size must be > 0") + if window.kind == WindowWaylandKind.PopupSurface: + var placement = window.placement + placement.size = v + window.placement = placement + return + window.resize(v) redraw window - method `pos=`*(window: WindowWayland, v: IVec2) = - if window.m_pos == v: return + if window.kind == WindowWaylandKind.PopupSurface: + return + if window.m_pos == v: + return window.m_pos = v if window.fullscreen: window.fullscreen = false - + if window.plasmaSurface != nil: window.plasmaSurface.set_position(v.x, v.y) - else: # there are no protocol to force move window for Mutter (Gnome) and Weston compositors. # there are zwlr_layer_shell_v1 for wlroots-based (and kde) compositors, # but it doesnt seem to be the right protocol to use to move window discard - + # since no compositor notifies us about window movement, let's emulate such event - if window.opened: window.eventsHandler.onWindowMove.pushEvent WindowMoveEvent(window: window, pos: v) + if window.opened: + window.eventsHandler.onWindowMove.pushEvent WindowMoveEvent(window: window, pos: v) +method `placement=`*(window: WindowWayland, v: PopupPlacement) = + window.m_popupPlacement = v + + if window.kind != WindowWaylandKind.PopupSurface: + let size = v.popupSize() + if window.m_size != size: + window.m_size = size + return + + if window.xdgPopup != nil: + let positioner = window.newPositioner(v) + if window.popupRepositionToken == 0 or window.popupRepositionToken == high(uint32): + window.popupRepositionToken = 1 + else: + inc window.popupRepositionToken + window.xdgPopup.reposition(positioner, window.popupRepositionToken) + destroy positioner + commit window.surface + + window.resize(v.popupSize()) + +method reposition*(window: WindowWayland, v: PopupPlacement) = + window.placement = v method `cursor=`*(window: WindowWayland, v: Cursor) = - if v.kind == builtin and window.m_cursor.kind == builtin and v.builtin == window.m_cursor.builtin: return + if v.kind == builtin and window.m_cursor.kind == builtin and + v.builtin == window.m_cursor.builtin: + return ## todo proc applyToplevelIcon(window: WindowWayland, icon: Xdg_toplevel_icon_v1) = @@ -740,7 +910,6 @@ proc applyToplevelIcon(window: WindowWayland, icon: Xdg_toplevel_icon_v1) = manager.set_icon(toplevel, icon) commit window.surface - method `icon=`*(window: WindowWayland, v: nil.typeof) = clearToplevelIconResources(window) window.applyToplevelIcon(typeof(window.toplevelIcon).default) @@ -768,14 +937,17 @@ method `icon=`*(window: WindowWayland, v: PixelBuffer) = var sourcePixels = newSeq[Color32bit](sourcePixelCount) copyMem(sourcePixels[0].addr, v.data, sourcePixelCount * sizeof(Color32bit)) - var converted = PixelBuffer(data: sourcePixels[0].addr, size: v.size, format: v.format) - convertPixelsInplace(converted.data, converted.size, v.format, PixelBufferFormat.bgra_32bit) + var converted = + PixelBuffer(data: sourcePixels[0].addr, size: v.size, format: v.format) + convertPixelsInplace( + converted.data, converted.size, v.format, PixelBufferFormat.bgra_32bit + ) var squarePixels = newSeq[Color32bit](iconSize.int * iconSize.int) let rowBytes = v.size.x.int * sizeof(Color32bit) let offsetX = ((iconSize - v.size.x) div 2).int let offsetY = ((iconSize - v.size.y) div 2).int - for y in 0.. 0 and delay >= 0: globals.seat_keyboard_repeatSettings = (rate: rate, delay: delay) - if globals.tabletManager != nil: globals.seat_tablet = globals.tabletManager.get_tablet_seat(globals.seat) - + globals.seat_tablet.onTool_added: # note: here nim closures ability to capture variables inside function scope is used # todo: change wayland callbacks to {.nimcall.} instead of {.closure.} and explicitly handle captures inc globals.lastTouchId let touchId = globals.lastTouchId let tool = construct( - id.proxy.raw, globals.interfaces.addr, - Zwp_tablet_tool_v2, `Zwp_tablet_tool_v2 / dispatch`, `Zwp_tablet_tool_v2 / Callbacks` + id.proxy.raw, globals.interfaces.addr, Zwp_tablet_tool_v2, + `Zwp_tablet_tool_v2 / dispatch`, `Zwp_tablet_tool_v2 / Callbacks`, ) var currentWindow: WindowWayland = nil var touch = Touch(id: touchId, device: TouchDeviceKind.graphicsTablet) @@ -1226,63 +1468,74 @@ proc initSeatEvents*(globals: SiwinGlobalsWayland) = destroy tool tool.onProximity_in: - if surface == nil: return + if surface == nil: + return let window = globals.associatedWindows.getOrDefault(surface.proxy.raw.id, nil).WindowWayland - if window == nil: return + if window == nil: + return currentWindow = window - + window.touchScreen.touches[touchId] = touch window.enterSerial = serial globals.lastSeatEventSerial = serial replicateWindowTitleAndBorderBehaviour(window, window.mouse.pos) - if window.opened: window.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( - window: window, touch: touch, pos: touch.pos, kind: MouseMoveKind.enter - ) - + if window.opened: + window.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( + window: window, touch: touch, pos: touch.pos, kind: MouseMoveKind.enter + ) + tool.onProximity_out: if currentWindow != nil: - if currentWindow.opened: currentWindow.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( - window: currentWindow, touch: touch, pos: touch.pos, kind: MouseMoveKind.leave - ) + if currentWindow.opened: + currentWindow.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( + window: currentWindow, + touch: touch, + pos: touch.pos, + kind: MouseMoveKind.leave, + ) currentWindow = nil - + tool.onMotion: touch.pos = vec2(x, y) if currentWindow != nil: - if currentWindow.opened: currentWindow.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( - window: currentWindow, touch: touch, pos: touch.pos, kind: MouseMoveKind.move - ) - + if currentWindow.opened: + currentWindow.eventsHandler.onTouchMove.pushEvent TouchMoveEvent( + window: currentWindow, + touch: touch, + pos: touch.pos, + kind: MouseMoveKind.move, + ) + tool.onDown: touch.pressed = true globals.lastSeatEventSerial = serial if currentWindow != nil: - if currentWindow.opened: currentWindow.eventsHandler.onTouch.pushEvent TouchEvent( - window: currentWindow, touch: touch, pressed: touch.pressed - ) - + if currentWindow.opened: + currentWindow.eventsHandler.onTouch.pushEvent TouchEvent( + window: currentWindow, touch: touch, pressed: touch.pressed + ) + tool.onUp: touch.pressed = false if currentWindow != nil: - if currentWindow.opened: currentWindow.eventsHandler.onTouch.pushEvent TouchEvent( - window: currentWindow, touch: touch, pressed: touch.pressed - ) - + if currentWindow.opened: + currentWindow.eventsHandler.onTouch.pushEvent TouchEvent( + window: currentWindow, touch: touch, pressed: touch.pressed + ) + tool.onPressure: touch.pressure = pressure.int / 65535 if currentWindow != nil: - if currentWindow.opened: currentWindow.eventsHandler.onTouchPressureChanged.pushEvent TouchPressureChangedEvent( - window: currentWindow, touch: touch, pressure: touch.pressure - ) - - - + if currentWindow.opened: + currentWindow.eventsHandler.onTouchPressureChanged.pushEvent TouchPressureChangedEvent( + window: currentWindow, touch: touch, pressure: touch.pressure + ) proc setIdleInhibit*(window: WindowWayland, state: bool) = #? should the proc be named `idleInhibit=`? @@ -1292,24 +1545,33 @@ proc setIdleInhibit*(window: WindowWayland, state: bool) = if state: if window.idleInhibitor != nil: #? should we return without an error here instead? - raise newException(ValueError, "`setIdleInhibit(true)` was called even though this window already has an active inhibitor") + raise newException( + ValueError, + "`setIdleInhibit(true)` was called even though this window already has an active inhibitor", + ) - window.idleInhibitor = window.globals.idleInhibitManager.create_inhibitor(window.surface) + window.idleInhibitor = + window.globals.idleInhibitManager.create_inhibitor(window.surface) else: if window.idleInhibitor == nil: #? should we return without an error here instead? - raise newException(ValueError, "`setIdleInhibit(false)` was called even though the window has no active inhibitor") - + raise newException( + ValueError, + "`setIdleInhibit(false)` was called even though the window has no active inhibitor", + ) + window.idleInhibitor.destroy() window.idleInhibitor.proxy.raw = nil - proc initDataDeviceManagerEvents*(globals: SiwinGlobalsWayland) = - if globals.dataDeviceManagerEventsInitialized: return + if globals.dataDeviceManagerEventsInitialized: + return globals.dataDeviceManagerEventsInitialized = true - if globals.dataDeviceManager == nil: return - if globals.seat == nil: return + if globals.dataDeviceManager == nil: + return + if globals.seat == nil: + return globals.data_device = globals.dataDeviceManager.get_data_device(globals.seat) @@ -1319,7 +1581,8 @@ proc initDataDeviceManagerEvents*(globals: SiwinGlobalsWayland) = globals.unindentified_data_offer_mimeTypes = @[] globals.unindentified_data_offer = id.proxy.raw.construct( - globals.interfaces.`iface Wl_data_offer`.addr, Wl_data_offer, `Wl_data_offer/dispatch`, `Wl_data_offer/Callbacks` + globals.interfaces.`iface Wl_data_offer`.addr, Wl_data_offer, + `Wl_data_offer / dispatch`, `Wl_data_offer / Callbacks`, ) globals.unindentified_data_offer.onOffer: @@ -1328,15 +1591,15 @@ proc initDataDeviceManagerEvents*(globals: SiwinGlobalsWayland) = globals.data_device.onSelection: if globals.current_selection_data_offer != nil: destroy globals.current_selection_data_offer - + globals.current_selection_data_offer = globals.unindentified_data_offer var offered_mime_types = globals.unindentified_data_offer_mimeTypes - + globals.unindentified_data_offer.proxy.raw = nil globals.unindentified_data_offer_mimeTypes = @[] - + globals.initClipboardsIfNeeded() - + globals.primaryClipboard.availableKinds = {} globals.primaryClipboard.availableMimeTypes = @[] @@ -1345,11 +1608,12 @@ proc initDataDeviceManagerEvents*(globals: SiwinGlobalsWayland) = globals.current_selection_data_offer.onOffer: offered_mime_types.add $mime_type - - discard wl_display_roundtrip globals.display # get all the mime types - + + discard wl_display_roundtrip globals.display # get all the mime types + for mime_type in offered_mime_types: - if mime_type in ["UTF8_STRING", "STRING", "TEXT", "text/plain", "text/plain;charset=utf-8"]: + if mime_type in + ["UTF8_STRING", "STRING", "TEXT", "text/plain", "text/plain;charset=utf-8"]: globals.primaryClipboard.availableKinds.incl ClipboardContentKind.text if mime_type in ["text/uri-list"]: @@ -1366,8 +1630,12 @@ proc initDataDeviceManagerEvents*(globals: SiwinGlobalsWayland) = discard wl_display_roundtrip globals.display - -proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool, size: IVec2, class: string) = +proc setupWindow( + window: WindowWayland, + fullscreen, frameless, transparent: bool, + size: IVec2, + class: string, +) = const FractionalScaleDenominator = 120'f32 proc applySurfaceScale(window: WindowWayland) = @@ -1377,21 +1645,24 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool if window.m_size.x > 0 and window.m_size.y > 0: window.doResize(window.m_size) if window.opened: - window.eventsHandler.onResize.pushEvent ResizeEvent(window: window, size: window.size) + window.eventsHandler.onResize.pushEvent ResizeEvent( + window: window, size: window.size + ) window.redraw() expectExtension window.globals.compositor expectExtension window.globals.xdgWmBase - + window.globals.initSeatEvents() - + window.surface = window.globals.compositor.create_surface window.globals.associatedWindows[window.surface.proxy.raw.id] = window if window.globals.viewporter.proxy != nil: window.viewport = window.globals.viewporter.get_viewport(window.surface) window.viewport.set_destination(size.x, size.y) if window.globals.fractionalScaleManager.proxy != nil: - window.fractionalScaleObj = window.globals.fractionalScaleManager.get_fractional_scale(window.surface) + window.fractionalScaleObj = + window.globals.fractionalScaleManager.get_fractional_scale(window.surface) window.fractionalScaleObj.onPreferred_scale: let newScale = max(1'f32, scale.float32 / FractionalScaleDenominator) if abs(window.fractionalScaleFactor - newScale) < 0.0001'f32: @@ -1408,17 +1679,19 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool case window.kind of WindowWaylandKind.XdgSurface: - let wantLibdecor = not frameless and - window.globals.serverDecorationManager == nil and - libdecorAvailable() + let wantLibdecor = + not frameless and window.globals.serverDecorationManager == nil and + libdecorAvailable() if wantLibdecor: - when defined(siwin_debug_echoLibdecor): echo "siwin: using libdecor for window decorations (server-side decorations unavailable)" + when defined(siwin_debug_echoLibdecor): + echo "siwin: using libdecor for window decorations (server-side decorations unavailable)" window.useLibdecor = true window.globals.initLibdecor() if window.globals.libdecorCtx == nil: - when defined(siwin_debug_echoLibdecor): echo "siwin: libdecor context creation failed, falling back to frameless" + when defined(siwin_debug_echoLibdecor): + echo "siwin: libdecor context creation failed, falling back to frameless" window.useLibdecor = false if window.useLibdecor: @@ -1429,11 +1702,12 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.globals.libdecorCtx, window.surface.proxy.raw, window.libdecorFrameIface.addr, - cast[pointer](window) + cast[pointer](window), ) if window.libdecorFrame == nil: - when defined(siwin_debug_echoLibdecor): echo "siwin: libdecor_decorate failed, falling back to frameless" + when defined(siwin_debug_echoLibdecor): + echo "siwin: libdecor_decorate failed, falling back to frameless" GC_unref(window) window.useLibdecor = false @@ -1452,7 +1726,6 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool # dispatch libdecor to process initial configure discard libdecor_dispatch(window.globals.libdecorCtx, 0) - else: # Standard xdg path (no libdecor) window.xdgSurface = window.globals.xdgWmBase.get_xdg_surface(window.surface) @@ -1462,8 +1735,12 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.xdgSurface.ack_configure(serial) redraw window - window.fullscreen = fullscreen + # Force the initial decoration preference to be applied before the first + # surface commit. Without this, frameless Wayland windows can map once + # with compositor decorations because the setter short-circuits. + window.m_frameless = not frameless window.frameless = frameless + window.fullscreen = fullscreen window.setupOpaqueRegion(size, transparent) @@ -1478,13 +1755,19 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool let states = states.toSeq(`XdgToplevel / State`) - template checkState(state: `XdgToplevel / State`): bool = state in states + template checkState(state: `XdgToplevel / State`): bool = + state in states + template handleState(k, n, m: untyped) = if window.m != checkState(`XdgToplevel / State`.n): window.m = checkState(`XdgToplevel / State`.n) - if window.opened: window.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.k, value: window.m, isExternal: true - ) + if window.opened: + window.eventsHandler.onStateBoolChanged.pushEvent StateBoolChangedEvent( + window: window, + kind: StateBoolChangedEventKind.k, + value: window.m, + isExternal: true, + ) handleState maximized, maximized, m_maximized handleState fullscreen, fullscreen, m_fullscreen @@ -1492,13 +1775,48 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool if window.globals.plasmaShell != nil: window.plasmaSurface = window.globals.plasmaShell.get_surface(window.surface) + of PopupSurface: + let parent = window.parentWindow().WindowWayland + if parent == nil: + raise ValueError.newException("Wayland popup windows require a parent window") + if parent.globals != window.globals: + raise ValueError.newException( + "Wayland popup parent must belong to the same globals/display" + ) + if parent.xdgSurface == nil: + raise ValueError.newException("Wayland popup parent must expose an xdg_surface") + + window.xdgSurface = window.globals.xdgWmBase.get_xdg_surface(window.surface) + let positioner = window.newPositioner(window.placement()) + window.xdgPopup = window.xdgSurface.get_popup(parent.xdgSurface, positioner) + destroy positioner + + window.xdgSurface.onConfigure: + window.xdgSurface.ack_configure(serial) + redraw window + + window.xdgPopup.onConfigure: + if width > 0 and height > 0: + let configuredSize = ivec2(width, height) + if configuredSize != window.m_size: + window.resize(configuredSize) + redraw window + + window.xdgPopup.onPopup_done: + window.notifyPopupDone(PopupDismissReason.pdrCompositorDismissed) + window.m_closed = true + + if window.popupGrab and window.globals.seat != nil and + window.globals.lastSeatEventSerial != 0: + window.xdgPopup.grab(window.globals.seat, window.globals.lastSeatEventSerial) + window.setupOpaqueRegion(size, transparent) of LayerSurface: window.layerShellSurface = window.globals.layerShell.get_layer_surface( window.surface, Wl_output(proxy: Wl_proxy(raw: nil)), - `Zwlr_layer_shell_v1/Layer`(window.layer.int), - window.namespace.cstring + `Zwlr_layer_shell_v1 / Layer`(window.layer.int), + window.namespace.cstring, ) window.layerShellSurface.set_size(window.m_size.x.uint32, window.m_size.y.uint32) window.redraw() @@ -1511,62 +1829,79 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool window.m_closed = true window.surface.destroy() - proc initSoftwareRenderingWindow( - window: WindowWaylandSoftwareRendering, - size: IVec2, screen: ScreenWayland, - fullscreen, frameless, transparent: bool, class: string + window: WindowWaylandSoftwareRendering, + size: IVec2, + screen: ScreenWayland, + fullscreen, frameless, transparent: bool, + class: string, ) = expectExtension window.globals.shm window.basicInitWindow size, screen - + window.setupWindow fullscreen, frameless, transparent, size, class - window.buffer = window.globals.create(window.globals.shm, window.bufferSize(size), (if transparent: argb8888 else: xrgb8888), bufferCount = 2) + window.buffer = window.globals.create( + window.globals.shm, + window.bufferSize(size), + (if transparent: argb8888 else: xrgb8888), + bufferCount = 2, + ) + +proc initPopupWindow( + window: WindowWaylandSoftwareRendering, + parent: WindowWayland, + placement: PopupPlacement, + grab: bool, + transparent: bool, +) = + expectExtension window.globals.shm + window.initPopupState(parent, placement, grab) + window.basicInitWindow(placement.popupSize(), ScreenWayland(id: 0.cint)) + window.kind = WindowWaylandKind.PopupSurface + window.setupWindow(false, true, transparent, placement.popupSize(), "") + window.buffer = window.globals.create( + window.globals.shm, + window.bufferSize(window.m_size), + (if transparent: argb8888 else: xrgb8888), + bufferCount = 2, + ) proc setAnchor*(window: WindowWayland, edge: LayerEdge | seq[LayerEdge]) = if window.layerShellSurface == nil: raise newException( ValueError, "Attempt to set surface anchor when layer shell surface hasn't been initialized." & - "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window." + "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window.", ) - + when edge is LayerEdge: window.layerShellSurface.set_anchor( case edge - of LayerEdge.Top: - `Zwlr_layer_surface_v1/Anchor`.top - of LayerEdge.Left: - `Zwlr_layer_surface_v1/Anchor`.left - of LayerEdge.Right: - `Zwlr_layer_surface_v1/Anchor`.right - of LayerEdge.Bottom: - `Zwlr_layer_surface_v1/Anchor`.bottom + of LayerEdge.Top: `Zwlr_layer_surface_v1 / Anchor`.top + of LayerEdge.Left: `Zwlr_layer_surface_v1 / Anchor`.left + of LayerEdge.Right: `Zwlr_layer_surface_v1 / Anchor`.right + of LayerEdge.Bottom: `Zwlr_layer_surface_v1 / Anchor`.bottom ) else: if edge.len < 2: raise newException(ValueError, "Not enough edges provided") - func convert(x: LayerEdge): `Zwlr_layer_surface_v1/Anchor` {.inline.} = + func convert(x: LayerEdge): `Zwlr_layer_surface_v1 / Anchor` {.inline.} = case x - of LayerEdge.Top: - `Zwlr_layer_surface_v1/Anchor`.top - of LayerEdge.Left: - `Zwlr_layer_surface_v1/Anchor`.left - of LayerEdge.Right: - `Zwlr_layer_surface_v1/Anchor`.right - of LayerEdge.Bottom: - `Zwlr_layer_surface_v1/Anchor`.bottom + of LayerEdge.Top: `Zwlr_layer_surface_v1 / Anchor`.top + of LayerEdge.Left: `Zwlr_layer_surface_v1 / Anchor`.left + of LayerEdge.Right: `Zwlr_layer_surface_v1 / Anchor`.right + of LayerEdge.Bottom: `Zwlr_layer_surface_v1 / Anchor`.bottom var final = edge[0].uint for val in edge[1 ..< edge.len]: final = final or val.uint - window.layerShellSurface.set_anchor(cast[`Zwlr_layer_surface_v1/Anchor`](final)) + window.layerShellSurface.set_anchor(cast[`Zwlr_layer_surface_v1 / Anchor`](final)) window.redraw() @@ -1575,74 +1910,67 @@ proc setKeyboardInteractivity*(window: WindowWayland, mode: LayerInteractivityMo raise newException( ValueError, "Attempt to set keyboard interactivity when layer shell surface hasn't been initialized." & - "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window." + "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window.", ) window.layerShellSurface.set_keyboard_interactivity( case mode of LayerInteractivityMode.None: - `Zwlr_layer_surface_v1/Keyboard_interactivity`.none + `Zwlr_layer_surface_v1 / Keyboard_interactivity`.none of LayerInteractivityMode.Exclusive: - `Zwlr_layer_surface_v1/Keyboard_interactivity`.exclusive + `Zwlr_layer_surface_v1 / Keyboard_interactivity`.exclusive of LayerInteractivityMode.OnDemand: - `Zwlr_layer_surface_v1/Keyboard_interactivity`.on_demand + `Zwlr_layer_surface_v1 / Keyboard_interactivity`.on_demand ) - proc setExclusiveZone*(window: WindowWayland, zone: int32) = if window.layerShellSurface == nil: raise newException( ValueError, "Attempt to set keyboard interactivity when layer shell surface hasn't been initialized." & - "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window." + "\nHint: Pass `kind` as `WindowWaylandKind.LayerSurface` when constructing this window.", ) window.layerShellSurface.set_exclusive_zone(zone) - proc constructClipboardContent*( - data: sink string, kind: ClipboardContentKind, mimeType: string + data: sink string, kind: ClipboardContentKind, mimeType: string ): ClipboardContent = case kind of ClipboardContentKind.text: result = ClipboardContent(kind: ClipboardContentKind.text, text: data) - of ClipboardContentKind.files: let uris = data.splitLines var files: seq[string] - + for uri in uris: let uri = parseUri(uri) if uri.scheme == "file": files.add uri.path.decodeUrl - + result = ClipboardContent(kind: ClipboardContentKind.files, files: files) - of ClipboardContentKind.other: - result = ClipboardContent(kind: ClipboardContentKind.other, mimeType: mimeType, data: data) + result = + ClipboardContent(kind: ClipboardContentKind.other, mimeType: mimeType, data: data) - -proc toString*( - content: ClipboardConvertableContent, targetType: string -): string = +proc toString*(content: ClipboardConvertableContent, targetType: string): string = var conv: ClipboardContentConverter for cv in content.converters: case cv.kind of ClipboardContentKind.text: - if targetType in ["UTF8_STRING", "STRING", "TEXT", "text/plain", "text/plain;charset=utf-8"]: + if targetType in + ["UTF8_STRING", "STRING", "TEXT", "text/plain", "text/plain;charset=utf-8"]: conv = cv break - of ClipboardContentKind.files: if targetType in ["text/uri-list"]: conv = cv break - of ClipboardContentKind.other: if targetType == cv.mimeType: conv = cv break - + if conv.f == nil: return "" @@ -1651,49 +1979,53 @@ proc toString*( case conv.kind of ClipboardContentKind.text: result = content.text - of ClipboardContentKind.files: - result = content.files.mapIt($Uri(scheme: "file", path: it.encodeUrl(usePlus=false))).join("\n") - + result = content.files + .mapIt($Uri(scheme: "file", path: it.encodeUrl(usePlus = false))) + .join("\n") of ClipboardContentKind.other: result = content.data - - method content*( - clipboard: ClipboardWayland, kind: ClipboardContentKind, mimeType: string = "text/plain" + clipboard: ClipboardWayland, + kind: ClipboardContentKind, + mimeType: string = "text/plain", ): ClipboardContent = clipboard.globals.initDataDeviceManagerEvents() - + var mimeType = case kind of ClipboardContentKind.text: - if "text/plain;charset=utf-8" in clipboard.availableMimeTypes: "text/plain;charset=utf-8" - elif "UTF8_STRING" in clipboard.availableMimeTypes: "UTF8_STRING" - elif "STRING" in clipboard.availableMimeTypes: "STRING" - elif "TEXT" in clipboard.availableMimeTypes: "TEXT" - else: "text/plain" - + if "text/plain;charset=utf-8" in clipboard.availableMimeTypes: + "text/plain;charset=utf-8" + elif "UTF8_STRING" in clipboard.availableMimeTypes: + "UTF8_STRING" + elif "STRING" in clipboard.availableMimeTypes: + "STRING" + elif "TEXT" in clipboard.availableMimeTypes: + "TEXT" + else: + "text/plain" of ClipboardContentKind.files: "text/uri-list" - of ClipboardContentKind.other: mimeType if mimeType notin clipboard.availableMimeTypes: return constructClipboardContent("", kind, mimeType) - if clipboard == clipboard.globals.primaryClipboard.ClipboardWayland: if clipboard.globals.current_selection_data_offer == nil: # Compositors are not required to always send us a fresh selection offer # for our own clipboard content. Fall back to local converters when possible. if clipboard.userContent.converters.len > 0: - return constructClipboardContent(clipboard.userContent.toString(mimeType), kind, mimeType) + return constructClipboardContent( + clipboard.userContent.toString(mimeType), kind, mimeType + ) return constructClipboardContent("", kind, mimeType) - var fds: array[2, FileHandle] # [0] - read, [1] - write - if pipe(fds) < 0: #? use O_NONBLOCK? + var fds: array[2, FileHandle] # [0] - read, [1] - write + if pipe(fds) < 0: #? use O_NONBLOCK? raiseOSError(osLastError()) clipboard.globals.current_selection_data_offer.receive(mimeType.cstring, fds[1]) @@ -1708,20 +2040,19 @@ method content*( let c = read(fds[0], cbuffer[0].addr, cbuffer.len) if c <= 0: break - for i in 0.. 0: for mimeType in offeredMimeTypes: clipboard.dataSource.offer(mimeType.cstring) - + clipboard.dataSource.onSend: let data = content.toString($mimeType) discard write(fd, data.cstring, data.len) discard close fd - else: clipboard.dataSource.proxy.raw = nil if clipboard == clipboard.globals.primaryClipboard.CLipboardWayland: - clipboard.globals.dataDevice.set_selection(clipboard.dataSource, clipboard.globals.lastSeatEventSerial) - - discard wl_display_roundtrip clipboard.globals.display + clipboard.globals.dataDevice.set_selection( + clipboard.dataSource, clipboard.globals.lastSeatEventSerial + ) + discard wl_display_roundtrip clipboard.globals.display method firstStep*(window: WindowWayland, makeVisible = true) = if makeVisible: @@ -1776,16 +2105,18 @@ method firstStep*(window: WindowWayland, makeVisible = true) = discard libdecor_dispatch(window.globals.libdecorCtx, 0) discard wl_display_roundtrip window.globals.display - if window.opened: window.eventsHandler.onResize.pushEvent ResizeEvent(window: window, size: window.size, initial: true) + if window.opened: + window.eventsHandler.onResize.pushEvent ResizeEvent( + window: window, size: window.size, initial: true + ) window.lastTickTime = getTime() redraw window - method step*(window: WindowWayland) = ## make window main loop step ## ! don't forget to call firstStep() - template closeIfNeeded = + template closeIfNeeded() = if window.m_closed: window.eventsHandler.onClose.pushEvent CloseEvent(window: window) release window @@ -1798,24 +2129,30 @@ method step*(window: WindowWayland) = let eventCount = wl_display_roundtrip(window.globals.display) if eventCount < 0: - raise newException(RoundtripFailed, "wl_display_roundtrip() returned " & $eventCount) + raise + newException(RoundtripFailed, "wl_display_roundtrip() returned " & $eventCount) closeIfNeeded() - if eventCount <= 2: # seems like idle event count is 2 + if eventCount <= 2: # seems like idle event count is 2 sleep(1) # repeat keys if needed if ( - window.globals.seat_keyboard_repeatSettings.rate > 0 and - window.lastPressedRawKeyDown + window.globals.seat_keyboard_repeatSettings.rate > 0 and window.lastPressedRawKeyDown ): - let repeatStartTime = window.lastPressedKeyTime + initDuration(milliseconds = window.globals.seat_keyboard_repeatSettings.delay) + let repeatStartTime = + window.lastPressedKeyTime + + initDuration(milliseconds = window.globals.seat_keyboard_repeatSettings.delay) let nows = getTime() - let interval = initDuration(milliseconds = max(1'i64, (1000 div window.globals.seat_keyboard_repeatSettings.rate).int64)) + let interval = initDuration( + milliseconds = + max(1'i64, (1000 div window.globals.seat_keyboard_repeatSettings.rate).int64) + ) - if repeatStartTime <= nows and window.lastKeyRepeatedTime < repeatStartTime - interval: + if repeatStartTime <= nows and + window.lastKeyRepeatedTime < repeatStartTime - interval: window.lastKeyRepeatedTime = repeatStartTime - interval - + while repeatStartTime <= nows and window.lastKeyRepeatedTime + interval <= nows: window.lastKeyRepeatedTime += interval @@ -1829,23 +2166,37 @@ method step*(window: WindowWayland) = if repeatedKey != Key.unknown and window.keyboard.pressed.contains(repeatedKey): window.keyboard.pressed.excl repeatedKey window.refreshKeyboardModifiers() - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: repeatedKey, pressed: false, repeated: true, modifiers: window.keyboard.modifiers - ) + if window.opened: + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, + key: repeatedKey, + pressed: false, + repeated: true, + modifiers: window.keyboard.modifiers, + ) window.keyboard.pressed.incl repeatedKey window.refreshKeyboardModifiers() - if window.opened: window.eventsHandler.onKey.pushEvent KeyEvent( - window: window, key: repeatedKey, pressed: true, repeated: true, modifiers: window.keyboard.modifiers - ) + if window.opened: + window.eventsHandler.onKey.pushEvent KeyEvent( + window: window, + key: repeatedKey, + pressed: true, + repeated: true, + modifiers: window.keyboard.modifiers, + ) if repeatedText != "": window.lastTextEntered = repeatedText - if window.opened: window.eventsHandler.onTextInput.pushEvent TextInputEvent( - window: window, text: repeatedText, repeated: true - ) + if window.opened: + window.eventsHandler.onTextInput.pushEvent TextInputEvent( + window: window, text: repeatedText, repeated: true + ) let nows = getTime() - if window.opened: window.eventsHandler.onTick.pushEvent TickEvent(window: window, deltaTime: nows - window.lastTickTime) + if window.opened: + window.eventsHandler.onTick.pushEvent TickEvent( + window: window, deltaTime: nows - window.lastTickTime + ) closeIfNeeded() window.lastTickTime = nows @@ -1853,30 +2204,51 @@ method step*(window: WindowWayland) = window.redrawRequested = false if window.m_visible: - if window.opened: window.eventsHandler.onRender.pushEvent RenderEvent(window: window) + if window.opened: + window.eventsHandler.onRender.pushEvent RenderEvent(window: window) closeIfNeeded() window.swapBuffers() wl_display_flush window.globals.display - proc newSoftwareRenderingWindowWayland*( - globals: SiwinGlobalsWayland, - size = ivec2(1280, 720), - title = "", - screen: ScreenWayland, - resizable = true, - fullscreen = false, - frameless = false, - transparent = false, - - class = "", # window class (used on linux), equals to title if not specified + globals: SiwinGlobalsWayland, + size = ivec2(1280, 720), + title = "", + screen: ScreenWayland, + resizable = true, + fullscreen = false, + frameless = false, + transparent = false, + class = "", # window class (used on linux), equals to title if not specified ): WindowWaylandSoftwareRendering = new result result.globals = globals - result.initSoftwareRenderingWindow(size, screen, fullscreen, frameless, transparent, (if class == "": title else: class)) + result.initSoftwareRenderingWindow( + size, + screen, + fullscreen, + frameless, + transparent, + (if class == "": title else: class), + ) result.title = title - if not resizable: result.resizable = false + if not resizable: + result.resizable = false + +proc newPopupWindowWayland*( + globals: SiwinGlobalsWayland, + parent: WindowWayland, + placement: PopupPlacement, + transparent = false, + grab = true, +): WindowWaylandSoftwareRendering = + if parent == nil: + raise ValueError.newException("Popup windows require a parent window") + + new result + result.globals = globals + result.initPopupWindow(parent, placement, grab, transparent) export Layer, LayerEdge, LayerInteractivityMode diff --git a/src/siwin/platforms/winapi/window.nim b/src/siwin/platforms/winapi/window.nim index 03d56dc..b79d24c 100644 --- a/src/siwin/platforms/winapi/window.nim +++ b/src/siwin/platforms/winapi/window.nim @@ -11,7 +11,7 @@ privateAccess Window type ScreenWinapi* = ref object of Screen - + Buffer = object x, y: int bitmap: HBitmap @@ -33,117 +33,123 @@ type WindowWinapiSoftwareRendering* = ref object of WindowWinapi buffer: Buffer +proc popupWindowPos(window: WindowWinapi, placement: PopupPlacement): IVec2 = + let parent = window.parentWindow() + if parent == nil: + placement.popupRelativePos() + else: + parent.pos + placement.popupRelativePos() proc wkeyToKey(key: WParam): Key = case key - of Vk_lshift: Key.lshift - of Vk_rshift: Key.rshift - of Vk_lmenu: Key.lalt - of Vk_rmenu: Key.ralt - of Vk_lcontrol: Key.lcontrol - of Vk_rcontrol: Key.rcontrol - of Vk_lwin: Key.lsystem - of Vk_rwin: Key.rsystem - of Vk_apps: Key.menu - of Vk_escape: Key.escape - of Vk_oem1: Key.semicolon - of Vk_oem2: Key.slash - of Vk_oem_plus: Key.equal - of Vk_oem_minus: Key.minus - of Vk_oem4: Key.lbracket - of Vk_oem6: Key.rbracket - of Vk_oem_comma: Key.comma - of Vk_oem_period: Key.dot - of Vk_oem7: Key.quote - of Vk_oem5: Key.backslash - of Vk_oem3: Key.tilde - of Vk_space: Key.space - of Vk_return: Key.enter - of Vk_back: Key.backspace - of Vk_tab: Key.tab - of Vk_prior: Key.page_up - of Vk_next: Key.page_down - of Vk_end: Key.End - of Vk_home: Key.home - of Vk_insert: Key.insert - of Vk_delete: Key.del - of Vk_add: Key.add - of Vk_subtract: Key.subtract - of Vk_multiply: Key.multiply - of Vk_divide: Key.divide - of Vk_capital: Key.capsLock - of Vk_numLock: Key.numLock - of Vk_scroll: Key.scrollLock - of Vk_snapshot: Key.printScreen - of Vk_print: Key.printScreen - of Vk_decimal: Key.npadDot - of Vk_pause: Key.pause - of Vk_f1: Key.f1 - of Vk_f2: Key.f2 - of Vk_f3: Key.f3 - of Vk_f4: Key.f4 - of Vk_f5: Key.f5 - of Vk_f6: Key.f6 - of Vk_f7: Key.f7 - of Vk_f8: Key.f8 - of Vk_f9: Key.f9 - of Vk_f10: Key.f10 - of Vk_f11: Key.f11 - of Vk_f12: Key.f12 - of Vk_f13: Key.f13 - of Vk_f14: Key.f14 - of Vk_f15: Key.f15 - of Vk_left: Key.left - of Vk_right: Key.right - of Vk_up: Key.up - of Vk_down: Key.down - of Vk_numpad0: Key.npad0 - of Vk_numpad1: Key.npad1 - of Vk_numpad2: Key.npad2 - of Vk_numpad3: Key.npad3 - of Vk_numpad4: Key.npad4 - of Vk_numpad5: Key.npad5 - of Vk_numpad6: Key.npad6 - of Vk_numpad7: Key.npad7 - of Vk_numpad8: Key.npad8 - of Vk_numpad9: Key.npad9 - of 'A'.ord: Key.a - of 'B'.ord: Key.b - of 'C'.ord: Key.c - of 'D'.ord: Key.d - of 'E'.ord: Key.e - of 'F'.ord: Key.f - of 'G'.ord: Key.g - of 'H'.ord: Key.h - of 'I'.ord: Key.i - of 'J'.ord: Key.j - of 'K'.ord: Key.k - of 'L'.ord: Key.l - of 'M'.ord: Key.m - of 'N'.ord: Key.n - of 'O'.ord: Key.o - of 'P'.ord: Key.p - of 'Q'.ord: Key.q - of 'R'.ord: Key.r - of 'S'.ord: Key.s - of 'T'.ord: Key.t - of 'U'.ord: Key.u - of 'V'.ord: Key.v - of 'W'.ord: Key.w - of 'X'.ord: Key.x - of 'Y'.ord: Key.y - of 'Z'.ord: Key.z - of '0'.ord: Key.n0 - of '1'.ord: Key.n1 - of '2'.ord: Key.n2 - of '3'.ord: Key.n3 - of '4'.ord: Key.n4 - of '5'.ord: Key.n5 - of '6'.ord: Key.n6 - of '7'.ord: Key.n7 - of '8'.ord: Key.n8 - of '9'.ord: Key.n9 - else: Key.unknown + of Vk_lshift: Key.lshift + of Vk_rshift: Key.rshift + of Vk_lmenu: Key.lalt + of Vk_rmenu: Key.ralt + of Vk_lcontrol: Key.lcontrol + of Vk_rcontrol: Key.rcontrol + of Vk_lwin: Key.lsystem + of Vk_rwin: Key.rsystem + of Vk_apps: Key.menu + of Vk_escape: Key.escape + of Vk_oem1: Key.semicolon + of Vk_oem2: Key.slash + of Vk_oem_plus: Key.equal + of Vk_oem_minus: Key.minus + of Vk_oem4: Key.lbracket + of Vk_oem6: Key.rbracket + of Vk_oem_comma: Key.comma + of Vk_oem_period: Key.dot + of Vk_oem7: Key.quote + of Vk_oem5: Key.backslash + of Vk_oem3: Key.tilde + of Vk_space: Key.space + of Vk_return: Key.enter + of Vk_back: Key.backspace + of Vk_tab: Key.tab + of Vk_prior: Key.page_up + of Vk_next: Key.page_down + of Vk_end: Key.End + of Vk_home: Key.home + of Vk_insert: Key.insert + of Vk_delete: Key.del + of Vk_add: Key.add + of Vk_subtract: Key.subtract + of Vk_multiply: Key.multiply + of Vk_divide: Key.divide + of Vk_capital: Key.capsLock + of Vk_numLock: Key.numLock + of Vk_scroll: Key.scrollLock + of Vk_snapshot: Key.printScreen + of Vk_print: Key.printScreen + of Vk_decimal: Key.npadDot + of Vk_pause: Key.pause + of Vk_f1: Key.f1 + of Vk_f2: Key.f2 + of Vk_f3: Key.f3 + of Vk_f4: Key.f4 + of Vk_f5: Key.f5 + of Vk_f6: Key.f6 + of Vk_f7: Key.f7 + of Vk_f8: Key.f8 + of Vk_f9: Key.f9 + of Vk_f10: Key.f10 + of Vk_f11: Key.f11 + of Vk_f12: Key.f12 + of Vk_f13: Key.f13 + of Vk_f14: Key.f14 + of Vk_f15: Key.f15 + of Vk_left: Key.left + of Vk_right: Key.right + of Vk_up: Key.up + of Vk_down: Key.down + of Vk_numpad0: Key.npad0 + of Vk_numpad1: Key.npad1 + of Vk_numpad2: Key.npad2 + of Vk_numpad3: Key.npad3 + of Vk_numpad4: Key.npad4 + of Vk_numpad5: Key.npad5 + of Vk_numpad6: Key.npad6 + of Vk_numpad7: Key.npad7 + of Vk_numpad8: Key.npad8 + of Vk_numpad9: Key.npad9 + of 'A'.ord: Key.a + of 'B'.ord: Key.b + of 'C'.ord: Key.c + of 'D'.ord: Key.d + of 'E'.ord: Key.e + of 'F'.ord: Key.f + of 'G'.ord: Key.g + of 'H'.ord: Key.h + of 'I'.ord: Key.i + of 'J'.ord: Key.j + of 'K'.ord: Key.k + of 'L'.ord: Key.l + of 'M'.ord: Key.m + of 'N'.ord: Key.n + of 'O'.ord: Key.o + of 'P'.ord: Key.p + of 'Q'.ord: Key.q + of 'R'.ord: Key.r + of 'S'.ord: Key.s + of 'T'.ord: Key.t + of 'U'.ord: Key.u + of 'V'.ord: Key.v + of 'W'.ord: Key.w + of 'X'.ord: Key.x + of 'Y'.ord: Key.y + of 'Z'.ord: Key.z + of '0'.ord: Key.n0 + of '1'.ord: Key.n1 + of '2'.ord: Key.n2 + of '3'.ord: Key.n3 + of '4'.ord: Key.n4 + of '5'.ord: Key.n5 + of '6'.ord: Key.n6 + of '7'.ord: Key.n7 + of '8'.ord: Key.n8 + of '9'.ord: Key.n9 + else: Key.unknown proc wkeyToKey(key: WParam, flags: LParam): Key = let scancode = ((flags and 0xff0000) shr 16).Uint @@ -155,26 +161,33 @@ proc wkeyToKey(key: WParam, flags: LParam): Key = if (flags and 0x1000000) != 0: Key.ralt else: Key.lalt of Vk_control: if (flags and 0x1000000) != 0: Key.rcontrol else: Key.lcontrol - else: wkeyToKey(key) - + else: + wkeyToKey(key) # todo: multiscreen support -proc screenCountWinapi*(): int32 = 1 +proc screenCountWinapi*(): int32 = + 1 + +proc screenWinapi*(number: int32): ScreenWinapi = + new result + +proc defaultScreenWinapi*(): ScreenWinapi = + screenWinapi(0) -proc screenWinapi*(number: int32): ScreenWinapi = new result -proc defaultScreenWinapi*(): ScreenWinapi = screenWinapi(0) -method number*(screen: ScreenWinapi): int32 = 0 +method number*(screen: ScreenWinapi): int32 = + 0 -method width*(screen: ScreenWinapi): int32 = GetSystemMetrics(SmCxScreen) -method height*(screen: ScreenWinapi): int32 = GetSystemMetrics(SmCyScreen) +method width*(screen: ScreenWinapi): int32 = + GetSystemMetrics(SmCxScreen) +method height*(screen: ScreenWinapi): int32 = + GetSystemMetrics(SmCyScreen) proc `=destroy`(buffer: Buffer) {.siwin_destructor.} = if buffer.hdc != 0: DeleteDC buffer.hdc DeleteObject buffer.bitmap - proc `=destroy`(window: WindowWinapiObj) {.siwin_destructor.} = if window.hdc != 0: DeleteDC window.hdc @@ -185,14 +198,23 @@ proc `=destroy`(window: WindowWinapiObj) {.siwin_destructor.} = if window.wcursor != 0: DestroyCursor window.wcursor +proc poolEvent( + window: WindowWinapi, message: Uint, wParam: WParam, lParam: LParam +): LResult -proc poolEvent(window: WindowWinapi, message: Uint, wParam: WParam, lParam: LParam): LResult - -proc windowProc(handle: HWnd, message: Uint, wParam: WParam, lParam: LParam): LResult {.stdcall.} = - let win = if handle != 0: cast[WindowWinapi](GetWindowLongPtr(handle, GwlpUserData)) else: nil +proc windowProc( + handle: HWnd, message: Uint, wParam: WParam, lParam: LParam +): LResult {.stdcall.} = + let win = + if handle != 0: + cast[WindowWinapi](GetWindowLongPtr(handle, GwlpUserData)) + else: + nil - if win != nil: win.poolEvent(message, wParam, lParam) - else: DefWindowProc(handle, message, wParam, lParam) + if win != nil: + win.poolEvent(message, wParam, lParam) + else: + DefWindowProc(handle, message, wParam, lParam) const wClassName = L"w" @@ -200,11 +222,11 @@ const block winapiInit: var wcex = WndClassEx( - cbSize: WndClassEx.sizeof.int32, - style: CsHRedraw or CsVRedraw or CsDblClks, - hInstance: hInstance, - hCursor: LoadCursor(0, IdcArrow), - lpfnWndProc: windowProc, + cbSize: WndClassEx.sizeof.int32, + style: CsHRedraw or CsVRedraw or CsDblClks, + hInstance: hInstance, + hCursor: LoadCursor(0, IdcArrow), + lpfnWndProc: windowProc, lpszClassName: wClassName, ) RegisterClassEx(wcex.addr) @@ -217,7 +239,8 @@ template pushEvent(eventsHandler: WindowEventsHandler, event, args) = eventsHandler.event(args) method `fullscreen=`*(window: WindowWinapi, v: bool) = - if window.m_fullscreen == v: return + if window.m_fullscreen == v: + return window.m_fullscreen = v if v: window.handle.SetWindowLongPtr(GwlStyle, WsVisible) @@ -225,18 +248,25 @@ method `fullscreen=`*(window: WindowWinapi, v: bool) = else: window.handle.ShowWindow(SwShowNormal) discard window.handle.SetWindowLongPtr(GwlStyle, WsVisible or WsOverlappedWindow) - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.fullscreen, value: v - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.fullscreen, value: v + ) method `size=`*(window: WindowWinapi, size: IVec2) = + if window.isPopup: + var placement = window.placement + placement.size = size + window.placement = placement + return window.fullscreen = false let rcClient = window.handle.clientRect var rcWind = window.handle.windowRect let borderx = (rcWind.right - rcWind.left) - rcClient.right let bordery = (rcWind.bottom - rcWind.top) - rcClient.bottom - window.handle.MoveWindow(rcWind.left, rcWind.top, (size.x + borderx).int32, (size.y + bordery).int32, True) - + window.handle.MoveWindow( + rcWind.left, rcWind.top, (size.x + borderx).int32, (size.y + bordery).int32, True + ) proc enableTransparency*(window: WindowWinapi) = let region = CreateRectRgn(0, 0, -1, -1) @@ -245,35 +275,49 @@ proc enableTransparency*(window: WindowWinapi) = var bb = DwmBlurBehind() bb.dwFlags = DwmbbEnable or DwmbbBlurRegion bb.fEnable = True - bb.hRgnBlur = region # "blur" somewhere outside the window, ideally nothing + bb.hRgnBlur = region # "blur" somewhere outside the window, ideally nothing window.handle.DwmEnableBlurBehindWindow(bb.addr) DeleteObject(region) - -proc initWindow(window: WindowWinapi; size: IVec2; screen: ScreenWinapi, fullscreen, frameless, transparent: bool, class = wClassName) = +proc initWindow( + window: WindowWinapi, + size: IVec2, + screen: ScreenWinapi, + fullscreen, frameless, transparent: bool, + class = wClassName, + owner: HWnd = 0, + popup = false, +) = window.handle = CreateWindow( class, "", - if frameless: WsPopup or WsMinimizeBox - else: WsOverlappedWindow, - CwUseDefault, CwUseDefault, - size.x, size.y, - 0, 0, + if popup: + WsPopup + elif frameless: + WsPopup or WsMinimizeBox + else: + WsOverlappedWindow, + CwUseDefault, + CwUseDefault, + size.x, + size.y, + owner, + 0, hInstance, - nil + nil, ) discard ShowWindow(window.handle, SwHide) window.m_frameless = frameless - window.m_focused = true #? is it correct? + window.m_focused = true #? is it correct? window.wcursor = LoadCursor(0, IdcArrow) window.handle.SetWindowLongPtrW(GwlpUserData, cast[LongPtr](window)) window.handle.trackMouseEvent(TmeHover) window.handle.trackMouseEvent(TmeLeave) window.hdc = window.handle.GetDC - + window.m_size = size if fullscreen: window.m_fullscreen = true @@ -283,77 +327,140 @@ proc initWindow(window: WindowWinapi; size: IVec2; screen: ScreenWinapi, fullscr if transparent: window.m_transparent = true window.enableTransparency() - - window.m_clipboard = ClipboardWinapi(availableKinds: {ClipboardContentKind.text}) # todo: other types - window.m_selectionClipboard = window.m_clipboard - window.m_dragndropClipboard = ClipboardWinapiDnd() # todo + window.m_clipboard = ClipboardWinapi(availableKinds: {ClipboardContentKind.text}) + # todo: other types + window.m_selectionClipboard = window.m_clipboard + window.m_dragndropClipboard = ClipboardWinapiDnd() # todo + +proc initPopupWindow( + window: WindowWinapiSoftwareRendering, + parent: WindowWinapi, + placement: PopupPlacement, + transparent: bool, + grab: bool, +) = + window.initPopupState(parent, placement, grab) + window.initWindow( + placement.popupSize(), + ScreenWinapi(), + false, + true, + transparent, + owner = parent.handle, + popup = true, + ) + window.m_resizable = false + window.pos = window.popupWindowPos(placement) method `title=`*(window: WindowWinapi, title: string) = window.handle.SetWindowText(title) method close*(window: WindowWinapi) = - if not window.m_closed: window.handle.SendMessage(WmClose, 0, 0) - + if window.m_closed: + return + window.notifyPopupDone(PopupDismissReason.pdrClientClosed) + window.handle.SendMessage(WmClose, 0, 0) method `pos=`*(window: WindowWinapi, v: IVec2) = - if window.m_fullscreen: return + if window.isPopup: + return + if window.m_fullscreen: + return window.handle.SetWindowPos(0, v.x, v.y, 0, 0, SwpNoSize) +method `placement=`*(window: WindowWinapi, v: PopupPlacement) = + window.m_popupPlacement = v + let size = v.popupSize() + window.m_size = size + let pos = window.popupWindowPos(v) + window.m_pos = pos + window.handle.SetWindowPos(0, pos.x, pos.y, size.x, size.y, 0) + +method reposition*(window: WindowWinapi, v: PopupPlacement) = + window.placement = v method `cursor=`*(window: WindowWinapi, v: Cursor) = - if window.m_cursor.kind == builtin and v.kind == builtin and v.builtin == window.m_cursor.builtin: return - if window.wcursor != 0: DestroyCursor window.wcursor + if window.m_cursor.kind == builtin and v.kind == builtin and + v.builtin == window.m_cursor.builtin: + return + if window.wcursor != 0: + DestroyCursor window.wcursor window.m_cursor = v case v.kind of builtin: - var cu: HCursor = case v.builtin - of BuiltinCursor.arrow: LoadCursor(0, IdcArrow) - of BuiltinCursor.arrowUp: LoadCursor(0, IdcUpArrow) - of BuiltinCursor.pointingHand: LoadCursor(0, IdcHand) - of BuiltinCursor.arrowRight: LoadCursor(0, IdcArrow) #! no needed cursor - of BuiltinCursor.wait: LoadCursor(0, IdcWait) - of BuiltinCursor.arrowWait: LoadCursor(0, IdcAppStarting) - of BuiltinCursor.grab: LoadCursor(0, IdcHand) #! no needed cursor - of BuiltinCursor.text: LoadCursor(0, IdcIBeam) - of BuiltinCursor.cross: LoadCursor(0, IdcCross) - of BuiltinCursor.sizeAll: LoadCursor(0, IdcSizeAll) - of BuiltinCursor.sizeVertical: LoadCursor(0, IdcSizens) - of BuiltinCursor.sizeHorizontal: LoadCursor(0, IdcSizewe) - of BuiltinCursor.sizeTopLeft: LoadCursor(0, IdcSizenwse) - of BuiltinCursor.sizeTopRight: LoadCursor(0, IdcSizenesw) - of BuiltinCursor.sizeBottomLeft: LoadCursor(0, IdcSizenesw) - of BuiltinCursor.sizeBottomRight: LoadCursor(0, IdcSizenwse) - of BuiltinCursor.hided: LoadCursor(0, IdcNo) + var cu: HCursor = + case v.builtin + of BuiltinCursor.arrow: + LoadCursor(0, IdcArrow) + of BuiltinCursor.arrowUp: + LoadCursor(0, IdcUpArrow) + of BuiltinCursor.pointingHand: + LoadCursor(0, IdcHand) + of BuiltinCursor.arrowRight: + LoadCursor(0, IdcArrow) + #! no needed cursor + of BuiltinCursor.wait: + LoadCursor(0, IdcWait) + of BuiltinCursor.arrowWait: + LoadCursor(0, IdcAppStarting) + of BuiltinCursor.grab: + LoadCursor(0, IdcHand) + #! no needed cursor + of BuiltinCursor.text: + LoadCursor(0, IdcIBeam) + of BuiltinCursor.cross: + LoadCursor(0, IdcCross) + of BuiltinCursor.sizeAll: + LoadCursor(0, IdcSizeAll) + of BuiltinCursor.sizeVertical: + LoadCursor(0, IdcSizens) + of BuiltinCursor.sizeHorizontal: + LoadCursor(0, IdcSizewe) + of BuiltinCursor.sizeTopLeft: + LoadCursor(0, IdcSizenwse) + of BuiltinCursor.sizeTopRight: + LoadCursor(0, IdcSizenesw) + of BuiltinCursor.sizeBottomLeft: + LoadCursor(0, IdcSizenesw) + of BuiltinCursor.sizeBottomRight: + LoadCursor(0, IdcSizenwse) + of BuiltinCursor.hided: + LoadCursor(0, IdcNo) if cu != 0: SetCursor cu window.wcursor = cu - of image: if v.image.pixels.size.x * v.image.pixels.size.y == 0: window.cursor = Cursor(kind: builtin, builtin: BuiltinCursor.hided) return - if window.wcursor != 0: DestroyCursor window.wcursor + if window.wcursor != 0: + DestroyCursor window.wcursor - let sourceFormat = v.image.pixels.format # to convert pixels back later + let sourceFormat = v.image.pixels.format # to convert pixels back later var buffer = v.image.pixels - convertPixelsInplace(buffer.data, buffer.size, sourceFormat, PixelBufferFormat.bgra_32bit) + convertPixelsInplace( + buffer.data, buffer.size, sourceFormat, PixelBufferFormat.bgra_32bit + ) - window.wcursor = CreateIcon(hInstance, buffer.size.x, buffer.size.y, 1, 32, nil, cast[ptr Byte](buffer.data)) + window.wcursor = CreateIcon( + hInstance, buffer.size.x, buffer.size.y, 1, 32, nil, cast[ptr Byte](buffer.data) + ) SetCursor window.wcursor - convertPixelsInplace(buffer.data, buffer.size, PixelBufferFormat.bgra_32bit, sourceFormat) - + convertPixelsInplace( + buffer.data, buffer.size, PixelBufferFormat.bgra_32bit, sourceFormat + ) method `icon=`*(window: WindowWinapi, _: nil.typeof) = ## clear icon if window.wicon != 0: DestroyIcon window.wicon window.wicon = 0 - + window.handle.SendMessageW(WmSetIcon, IconBig, 0) window.handle.SendMessageW(WmSetIcon, IconSmall, 0) @@ -363,66 +470,83 @@ method `icon=`*(window: WindowWinapi, v: PixelBuffer) = window.icon = nil return - if window.wicon != 0: DestroyIcon window.wicon - - let sourceFormat = v.format # to convert pixels back later + if window.wicon != 0: + DestroyIcon window.wicon + + let sourceFormat = v.format # to convert pixels back later var buffer = v - convertPixelsInplace(buffer.data, buffer.size, sourceFormat, PixelBufferFormat.bgra_32bit) + convertPixelsInplace( + buffer.data, buffer.size, sourceFormat, PixelBufferFormat.bgra_32bit + ) - window.wicon = CreateIcon(hInstance, v.size.x, v.size.y, 1, 32, nil, cast[ptr Byte](v.data)) + window.wicon = + CreateIcon(hInstance, v.size.x, v.size.y, 1, 32, nil, cast[ptr Byte](v.data)) window.handle.SendMessageW(WmSetIcon, IconBig, window.wicon) window.handle.SendMessageW(WmSetIcon, IconSmall, window.wicon) - convertPixelsInplace(buffer.data, buffer.size, PixelBufferFormat.bgra_32bit, sourceFormat) - + convertPixelsInplace( + buffer.data, buffer.size, PixelBufferFormat.bgra_32bit, sourceFormat + ) proc resizeBufferIfNeeded(buffer: var Buffer, size: IVec2) = if size.x != buffer.x or size.y != buffer.y: if buffer.hdc != 0: DeleteDC buffer.hdc DeleteObject buffer.bitmap - + buffer.x = size.x buffer.y = size.y - + var bmi = BitmapInfo( bmiHeader: BitmapInfoHeader( - biSize: BitmapInfoHeader.sizeof.int32, biWidth: size.x.Long, biHeight: -size.y.Long, - biPlanes: 1, biBitCount: 32, biCompression: Bi_rgb + biSize: BitmapInfoHeader.sizeof.int32, + biWidth: size.x.Long, + biHeight: -size.y.Long, + biPlanes: 1, + biBitCount: 32, + biCompression: Bi_rgb, ) ) - buffer.bitmap = CreateDibSection(0, bmi.addr, Dib_rgb_colors, cast[ptr pointer](buffer.pixels.addr), 0, 0) + buffer.bitmap = CreateDibSection( + 0, bmi.addr, Dib_rgb_colors, cast[ptr pointer](buffer.pixels.addr), 0, 0 + ) buffer.hdc = CreateCompatibleDC(0) buffer.hdc.SelectObject buffer.bitmap - method pixelBuffer*(window: WindowWinapiSoftwareRendering): PixelBuffer = result = PixelBuffer( data: window.buffer.pixels, size: ivec2(window.buffer.x.int32, window.buffer.y.int32), - format: (if window.transparent: PixelBufferFormat.bgrx_32bit else: PixelBufferFormat.bgru_32bit) + format: ( + if window.transparent: PixelBufferFormat.bgrx_32bit + else: PixelBufferFormat.bgru_32bit + ), ) - proc releaseAllKeys(window: WindowWinapi) = for key in window.keyboard.pressed: window.keyboard.pressed.excl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: false, repeated: false, generated: true) + window.eventsHandler.pushEvent onKey, + KeyEvent( + window: window, key: key, pressed: false, repeated: false, generated: true + ) for button in window.mouse.pressed: window.mouse.pressed.excl button - window.eventsHandler.pushEvent onMouseButton, MouseButtonEvent(window: window, button: button, pressed: false, generated: true) - + window.eventsHandler.pushEvent onMouseButton, + MouseButtonEvent(window: window, button: button, pressed: false, generated: true) method `maximized=`*(window: WindowWinapi, v: bool) = - if window.m_maximized == v: return + if window.m_maximized == v: + return if window.m_frameless: if v: window.restoreSize = window.size window.restorePos = window.pos var workArea: Rect discard SystemParametersInfo(SpiGetWorkArea, 0, workArea.addr, 0) - window.size = ivec2(workArea.right - workArea.left, workArea.bottom - workArea.top) + window.size = + ivec2(workArea.right - workArea.left, workArea.bottom - workArea.top) window.pos = ivec2(workArea.left, workArea.top) else: window.size = window.restoreSize @@ -430,30 +554,36 @@ method `maximized=`*(window: WindowWinapi, v: bool) = else: discard ShowWindow(window.handle, if v: SwMaximize else: SwNormal) window.m_maximized = v - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.maximized, value: window.m_maximized - ) - + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, + kind: StateBoolChangedEventKind.maximized, + value: window.m_maximized, + ) method `minimized=`*(window: WindowWinapi, v: bool) = window.releaseAllKeys() window.m_minimized = v - discard ShowWindow(window.handle, if v: SwShowMinNoActive else: SwNormal) - + discard ShowWindow(window.handle, if v: SwShowMinNoActive else: SwNormal) method `visible=`*(window: WindowWinapi, v: bool) = window.m_visible = v discard ShowWindow(window.handle, if v: SwShow else: SwHide) - method `resizable=`*(window: WindowWinapi, v: bool) = if not window.m_frameless: let style = GetWindowLongW(window.handle, GwlStyle) - discard SetWindowLongW(window.handle, GwlStyle, if v: style or WsThickframe else: style and not WsThickframe) + discard SetWindowLongW( + window.handle, + GwlStyle, + if v: + style or WsThickframe + else: + style and not WsThickframe, + ) window.m_minSize = ivec2() window.m_maxSize = ivec2() - method `minSize=`*(window: WindowWinapi, v: IVec2) = window.m_minSize = v if not window.m_frameless: @@ -466,7 +596,6 @@ method `maxSize=`*(window: WindowWinapi, v: IVec2) = let style = GetWindowLongW(window.handle, GwlStyle) discard SetWindowLongW(window.handle, GwlStyle, style or WsThickframe) - method startInteractiveMove*(window: WindowWinapi, pos: Option[Vec2]) = window.releaseAllKeys() ReleaseCapture() @@ -488,29 +617,31 @@ method startInteractiveResize*(window: WindowWinapi, edge: Edge, pos: Option[Vec of Edge.topRight: 0xf005 of Edge.bottom: 0xf006 of Edge.bottomLeft: 0xf007 - of Edge.bottomRight: 0xf008, - 0 + of Edge.bottomRight: 0xf008 + , + 0, ) # todo: press all keys and mouse buttons that are pressed after resize - method showWindowMenu*(window: WindowWinapi, pos: Option[Vec2]) = discard - -method content*(clipboard: ClipboardWinapi, kind: ClipboardContentKind, mimeType: string): ClipboardContent = +method content*( + clipboard: ClipboardWinapi, kind: ClipboardContentKind, mimeType: string +): ClipboardContent = discard OpenClipboard(0) let hcpb = GetClipboardData(CfUnicodeText) if hcpb == 0: CloseClipboard() return - - result = ClipboardContent(kind: ClipboardContentKind.text, text: $cast[PWChar](GlobalLock hcpb)) # todo: other types + + result = ClipboardContent( + kind: ClipboardContentKind.text, text: $cast[PWChar](GlobalLock hcpb) + ) # todo: other types GlobalUnlock hcpb discard CloseClipboard() - method `content=`*(clipboard: ClipboardWinapi, content: ClipboardConvertableContent) = var conv: ClipboardContentConverter for cv in content.converters: @@ -520,16 +651,18 @@ method `content=`*(clipboard: ClipboardWinapi, content: ClipboardConvertableCont else: ## todo - if conv.f == nil: return + if conv.f == nil: + return let content = conv.f(content.data, conv.kind, conv.mimeType) - if content.kind != ClipboardContentKind.text: return + if content.kind != ClipboardContentKind.text: + return let s = content.text discard OpenClipboard(0) discard EmptyClipboard() - + let ws = +$s let ts = (ws.len + 1) * WChar.sizeof let hstr = GlobalAlloc(GMemMoveable, ts) @@ -542,54 +675,54 @@ method `content=`*(clipboard: ClipboardWinapi, content: ClipboardConvertableCont SetClipboardData(CfUnicodeText, hstr) CloseClipboard() - method displayImpl(window: WindowWinapi) {.base.} = var ps: PaintStruct window.handle.BeginPaint(ps.addr) - + window.eventsHandler.pushEvent onRender, RenderEvent(window: window) - + if window of WindowWinapiSoftwareRendering: BitBlt( window.hdc, 0, 0, window.m_size.x, window.m_size.y, - window.WindowWinapiSoftwareRendering.buffer.hdc, 0, 0, SrcCopy + window.WindowWinapiSoftwareRendering.buffer.hdc, 0, 0, SrcCopy, ) - - window.handle.EndPaint(ps.addr) + window.handle.EndPaint(ps.addr) method firstStep*(window: WindowWinapi, makeVisible = true) = if makeVisible: window.visible = true - + if window of WindowWinapiSoftwareRendering: resizeBufferIfNeeded window.WindowWinapiSoftwareRendering.buffer, window.m_size - window.eventsHandler.pushEvent onResize, ResizeEvent(window: window, size: window.m_size, initial: true) + window.eventsHandler.pushEvent onResize, + ResizeEvent(window: window, size: window.m_size, initial: true) window.redrawRequested = true window.handle.UpdateWindow() window.lastTickTime = getTime() - proc updateWindowState(window: WindowWinapi) = if not window.m_frameless and IsZoomed(window.handle).bool != window.m_maximized: window.m_maximized = not window.m_maximized - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.maximized, value: window.m_maximized - ) - + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, + kind: StateBoolChangedEventKind.maximized, + value: window.m_maximized, + ) + window.m_minimized = IsIconic(window.handle) != 0 window.m_visible = IsWindowVisible(window.handle) != 0 window.m_resizable = (GetWindowLongW(window.handle, GwlStyle) and WsThickframe) != 0 - + var p: WindowPlacement p.length = sizeof(WindowPlacement).int32 GetWindowPlacement(window.handle, p.addr) window.m_pos = ivec2(p.rcNormalPosition.left, p.rcNormalPosition.top) - method step*(window: WindowWinapi) = var msg: Msg var catched = false @@ -598,7 +731,8 @@ method step*(window: WindowWinapi) = # make tick if windows sent us WmPaint, it does it when the event queue is empty if msg.message == WmPaint: let nows = getTime() - window.eventsHandler.pushEvent onTick, TickEvent(window: window, deltaTime: nows - window.lastTickTime) + window.eventsHandler.pushEvent onTick, + TickEvent(window: window, deltaTime: nows - window.lastTickTime) window.lastTickTime = nows TranslateMessage(msg.addr) @@ -609,26 +743,33 @@ method step*(window: WindowWinapi) = else: catched = true - if window.m_closed: return - - if not catched: sleep(1) + if window.m_closed: + return + if not catched: + sleep(1) -proc poolEvent(window: WindowWinapi, message: Uint, wParam: WParam, lParam: LParam): LResult = +proc poolEvent( + window: WindowWinapi, message: Uint, wParam: WParam, lParam: LParam +): LResult = updateWindowState(window) - template button: MouseButton = + template button(): MouseButton = case message - of WM_lbuttonDown, WM_lbuttonUp, WM_lbuttonDblclk: MouseButton.left - of WM_rbuttonDown, WM_rbuttonUp, WM_rbuttonDblclk: MouseButton.right - of WM_mbuttonDown, WM_mbuttonUp, WM_mbuttonDblclk: MouseButton.middle + of WM_lbuttonDown, WM_lbuttonUp, WM_lbuttonDblclk: + MouseButton.left + of WM_rbuttonDown, WM_rbuttonUp, WM_rbuttonDblclk: + MouseButton.right + of WM_mbuttonDown, WM_mbuttonUp, WM_mbuttonDblclk: + MouseButton.middle of WM_xbuttonDown, WM_xbuttonUp, WM_xbuttonDblclk: let button = wParam.GetXButtonWParam() case button of MkXButton1: MouseButton.backward of MkXButton2: MouseButton.forward else: MouseButton.left - else: MouseButton.left + else: + MouseButton.left result = 0 @@ -637,114 +778,116 @@ proc poolEvent(window: WindowWinapi, message: Uint, wParam: WParam, lParam: LPar let rect = window.handle.clientRect if rect.right != window.m_size.x or rect.bottom != window.m_size.y: window.m_size = ivec2(rect.right, rect.bottom) - + if window of WindowWinapiSoftwareRendering: resizeBufferIfNeeded window.WindowWinapiSoftwareRendering.buffer, window.m_size - window.eventsHandler.pushEvent onResize, ResizeEvent(window: window, size: window.m_size, initial: false) + window.eventsHandler.pushEvent onResize, + ResizeEvent(window: window, size: window.m_size, initial: false) window.redrawRequested = true if window.redrawRequested: window.redrawRequested = false if window.m_size.x * window.m_size.y > 0: window.displayImpl() - - of WmDestroy: window.m_closed = true window.eventsHandler.pushEvent onClose, CloseEvent(window: window) PostQuitMessage(0) - of WmMouseMove: window.mouse.pos = vec2(lParam.GetX_LParam.float32, lParam.GetY_LParam.float32) window.clicking = {} - window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.move) - + window.eventsHandler.pushEvent onMouseMove, + MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.move) of WmMouseLeave: window.mouse.pos = vec2(lParam.GetX_LParam.float32, lParam.GetY_LParam.float32) window.clicking = {} - window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.leave) + window.eventsHandler.pushEvent onMouseMove, + MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.leave) window.handle.trackMouseEvent(TmeHover) - of WmMouseHover: window.mouse.pos = vec2(lParam.GetX_LParam.float32, lParam.GetY_LParam.float32) window.clicking = {} - window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.enter) + window.eventsHandler.pushEvent onMouseMove, + MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.enter) window.handle.trackMouseEvent(TmeLeave) - of WmMouseWheel: let delta = if wParam.GetWheelDeltaWParam > 0: -1.0 else: 1.0 window.eventsHandler.pushEvent onScroll, ScrollEvent(window: window, delta: delta) - of WmSetFocus: window.m_focused = true - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.focus, value: true - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.focus, value: true + ) let keys = getKeyboardState().mapit(wkeyToKey(it)) for k in keys: # press pressed in system keys - if k == Key.unknown: continue + if k == Key.unknown: + continue window.keyboard.pressed.incl k - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: k, pressed: false, repeated: false) - + window.eventsHandler.pushEvent onKey, + KeyEvent(window: window, key: k, pressed: false, repeated: false) of WmKillFocus: window.m_focused = false - window.eventsHandler.pushEvent onStateBoolChanged, StateBoolChangedEvent( - window: window, kind: StateBoolChangedEventKind.focus, value: false - ) + window.eventsHandler.pushEvent onStateBoolChanged, + StateBoolChangedEvent( + window: window, kind: StateBoolChangedEventKind.focus, value: false + ) let pressed = window.keyboard.pressed for key in pressed: # release all keys window.keyboard.pressed.excl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: false, repeated: false) - + window.eventsHandler.pushEvent onKey, + KeyEvent(window: window, key: key, pressed: false, repeated: false) of WmLButtonDown, WmRButtonDown, WmMButtonDown, WmXButtonDown: window.handle.SetCapture() window.mouse.pressed.incl button window.clicking.incl button - window.eventsHandler.pushEvent onMouseButton, MouseButtonEvent(window: window, button: button, pressed: true) - + window.eventsHandler.pushEvent onMouseButton, + MouseButtonEvent(window: window, button: button, pressed: true) of WmLButtonDblclk, WmRButtonDblclk, WmMButtonDblclk, WmXButtonDblclk: window.handle.SetCapture() window.mouse.pressed.incl button - window.eventsHandler.pushEvent onMouseButton, MouseButtonEvent(window: window, button: button, pressed: true) - window.eventsHandler.pushEvent onClick, ClickEvent(window: window, button: button, pos: window.mouse.pos, double: true) - + window.eventsHandler.pushEvent onMouseButton, + MouseButtonEvent(window: window, button: button, pressed: true) + window.eventsHandler.pushEvent onClick, + ClickEvent(window: window, button: button, pos: window.mouse.pos, double: true) of WmLButtonUp, WmRButtonUp, WmMButtonUp, WmXButtonUp: ReleaseCapture() window.mouse.pressed.excl button if button in window.clicking: - window.eventsHandler.pushEvent onClick, ClickEvent(window: window, button: button, pos: window.mouse.pos, double: false) + window.eventsHandler.pushEvent onClick, + ClickEvent(window: window, button: button, pos: window.mouse.pos, double: false) window.clicking.excl button - window.eventsHandler.pushEvent onMouseButton, MouseButtonEvent(window: window, button: button, pressed: false) - + window.eventsHandler.pushEvent onMouseButton, + MouseButtonEvent(window: window, button: button, pressed: false) of WmKeyDown, WmSysKeyDown: let key = wkeyToKey(wParam, lParam) if key != Key.unknown: let repeated = key in window.keyboard.pressed window.keyboard.pressed.incl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: true, repeated: repeated) - + window.eventsHandler.pushEvent onKey, + KeyEvent(window: window, key: key, pressed: true, repeated: repeated) of WmKeyUp, WmSysKeyUp: let key = wkeyToKey(wParam, lParam) if key != Key.unknown: let repeated = key notin window.keyboard.pressed window.keyboard.pressed.excl key - window.eventsHandler.pushEvent onKey, KeyEvent(window: window, key: key, pressed: false, repeated: repeated) - + window.eventsHandler.pushEvent onKey, + KeyEvent(window: window, key: key, pressed: false, repeated: repeated) of WmChar, WmSyschar, WmUnichar: - if window.eventsHandler.onTextInput == nil: return 1 # no need to handle + if window.eventsHandler.onTextInput == nil: + return 1 # no need to handle if (window.keyboard.pressed * {lcontrol, rcontrol, lalt, ralt}).len == 0: let s = %$[wParam.WChar] if s.len > 0 and s notin ["\u001B"]: - window.eventsHandler.pushEvent onTextInput, TextInputEvent(window: window, text: s) - + window.eventsHandler.pushEvent onTextInput, + TextInputEvent(window: window, text: s) of WmSetCursor: if lParam.LoWord == HtClient: SetCursor window.wcursor return 1 return window.handle.DefWindowProc(message, wParam, lParam) - of WmGetMinMaxInfo: let info = cast[LpMinMaxInfo](lParam) if window.m_minSize != ivec2(): @@ -753,44 +896,63 @@ proc poolEvent(window: WindowWinapi, message: Uint, wParam: WParam, lParam: LPar if window.m_maxSize != ivec2(): info[].ptMaxTrackSize.x = window.m_maxSize.x info[].ptMaxTrackSize.y = window.m_maxSize.y - of WmNcHitTest: - if window.titleRegion.isNone and window.borderWidth.isNone: return window.handle.DefWindowProc(message, wParam, lParam) + if window.titleRegion.isNone and window.borderWidth.isNone: + return window.handle.DefWindowProc(message, wParam, lParam) var pos = Point(x: lParam.GetX_LParam.LONG, y: lParam.GetY_LParam.LONG) ScreenToClient(window.handle, pos.addr) let pos_f32 = vec2(pos.x.float32, pos.y.float32) case window.windowPartAt(pos_f32) - of WindowPart.title: return HtCaption - of WindowPart.client: return HtClient - of WindowPart.border_top_left: return HtTopLeft - of WindowPart.border_top_right: return HtTopRight - of WindowPart.border_bottom_left: return HtBottomLeft - of WindowPart.border_bottom_right: return HtBottomRight - of WindowPart.border_top: return HtTop - of WindowPart.border_bottom: return HtBottom - of WindowPart.border_left: return HtLeft - of WindowPart.border_right: return HtRight - of WindowPart.none: return HtTransparent - + of WindowPart.title: + return HtCaption + of WindowPart.client: + return HtClient + of WindowPart.border_top_left: + return HtTopLeft + of WindowPart.border_top_right: + return HtTopRight + of WindowPart.border_bottom_left: + return HtBottomLeft + of WindowPart.border_bottom_right: + return HtBottomRight + of WindowPart.border_top: + return HtTop + of WindowPart.border_bottom: + return HtBottom + of WindowPart.border_left: + return HtLeft + of WindowPart.border_right: + return HtRight + of WindowPart.none: + return HtTransparent of WmDwmCompositionChanged: if window.transparent: window.enableTransparency() - - else: return window.handle.DefWindowProc(message, wParam, lParam) - + else: + return window.handle.DefWindowProc(message, wParam, lParam) proc newSoftwareRenderingWindowWinapi*( - size = ivec2(1280, 720), - title = "", - screen = defaultScreenWinapi(), - resizable = true, - fullscreen = false, - frameless = false, - transparent = false, + size = ivec2(1280, 720), + title = "", + screen = defaultScreenWinapi(), + resizable = true, + fullscreen = false, + frameless = false, + transparent = false, ): WindowWinapiSoftwareRendering = new result result.initWindow(size, screen, fullscreen, frameless, transparent) result.title = title - if not resizable: result.resizable = false + if not resizable: + result.resizable = false + +proc newPopupWindowWinapi*( + parent: WindowWinapi, placement: PopupPlacement, transparent = false, grab = true +): WindowWinapiSoftwareRendering = + if parent == nil: + raise ValueError.newException("Popup windows require a parent window") + + new result + result.initPopupWindow(parent, placement, transparent, grab) diff --git a/src/siwin/platforms/x11/window.nim b/src/siwin/platforms/x11/window.nim index 5f93714..cba828a 100644 --- a/src/siwin/platforms/x11/window.nim +++ b/src/siwin/platforms/x11/window.nim @@ -386,6 +386,13 @@ proc `=destroy`(x: WindowX11SoftwareRenderingObj) {.siwin_destructor.} = proc pushEvent[T](event: proc(e: T), args: T) = if event != nil: event(args) +proc popupWindowPos(window: WindowX11; placement: PopupPlacement): IVec2 = + let parent = window.parentWindow() + if parent == nil: + placement.popupRelativePos() + else: + parent.pos + placement.popupRelativePos() + proc resizePixelBuffer(window: WindowX11SoftwareRendering, size: IVec2) = window.pixels = window.pixels.realloc(size.x * size.y * Color32bit.sizeof) @@ -499,6 +506,21 @@ proc initSoftwareRenderingWindow( window.gc.gc = window.globals.display.XCreateGC(window.handle, GCForeground or GCBackground, window.gc.gcv.addr) +proc initPopupWindow( + window: WindowX11SoftwareRendering, + parent: WindowX11, + placement: PopupPlacement, + transparent: bool, + grab: bool, +) = + let screen = parent.globals.screenX11(parent.screen.int32) + window.initPopupState(parent, placement, grab) + window.initSoftwareRenderingWindow(placement.popupSize(), screen, false, true, transparent, "") + discard window.globals.display.XSetTransientForHint(window.handle, parent.handle) + window.m_resizable = false + window.pos = window.popupWindowPos(placement) + + method `title=`*(window: WindowX11, v: string) = discard window.globals.display.XChangeProperty( window.handle, window.globals.atoms.netWmName, window.globals.atoms.utf8String, 8, @@ -511,6 +533,11 @@ method `title=`*(window: WindowX11, v: string) = window.globals.display.Xutf8SetWMProperties(window.handle, v, v, nil, 0, nil, nil, nil) +method close*(window: WindowX11) = + window.notifyPopupDone(PopupDismissReason.pdrClientClosed) + procCall window.Window.close() + + method `fullscreen=`*(window: WindowX11, v: bool) = var event = window.globals.newClientMessage( window.handle, window.globals.atoms.netWmState, [Atom 2, window.globals.atoms.netWmStateFullscreen] @@ -561,16 +588,38 @@ method `frameless=`*(window: WindowX11, v: bool) = method `size=`*(window: WindowX11, v: IVec2) = + if window.isPopup: + var placement = window.placement + placement.size = v + window.placement = placement + return if window.fullscreen: window.fullscreen = false discard window.globals.display.XResizeWindow(window.handle, v.x.cuint, v.y.cuint) method `pos=`*(window: WindowX11, v: IVec2) = + if window.isPopup: + return if window.m_fullscreen: return discard window.globals.display.XMoveWindow(window.handle, v.x.cint, v.y.cint) +method `placement=`*(window: WindowX11, v: PopupPlacement) = + window.m_popupPlacement = v + let size = v.popupSize() + window.m_size = size + if window.handle != 0: + discard window.globals.display.XResizeWindow(window.handle, size.x.cuint, size.y.cuint) + let pos = window.popupWindowPos(v) + window.m_pos = pos + discard window.globals.display.XMoveWindow(window.handle, pos.x.cint, pos.y.cint) + + +method reposition*(window: WindowX11, v: PopupPlacement) = + window.placement = v + + proc setX11Cursor(window: WindowX11, v: Cursor) = if window.xCursor != 0: discard window.globals.display.XFreeCursor(window.xCursor) @@ -1525,5 +1574,21 @@ proc newSoftwareRenderingWindowX11*( result.title = title if not resizable: result.resizable = false + +proc newPopupWindowX11*( + globals: SiwinGlobalsX11, + parent: WindowX11, + placement: PopupPlacement, + transparent = false, + grab = true, +): WindowX11SoftwareRendering = + if parent == nil: + raise ValueError.newException("Popup windows require a parent window") + + new result + result.softwarePresentEnabled = true + result.globals = globals + result.initPopupWindow(parent, placement, transparent, grab) + proc setSoftwarePresentEnabled*(window: WindowX11SoftwareRendering, enabled: bool) = window.softwarePresentEnabled = enabled diff --git a/src/siwin/window.nim b/src/siwin/window.nim index bc7098e..6944c60 100644 --- a/src/siwin/window.nim +++ b/src/siwin/window.nim @@ -8,25 +8,20 @@ export anyWindow when not siwin_use_lib: when defined(android): import ./platforms/android/window as androidWindow - elif defined(linux) or defined(bsd): import ./platforms/x11/siwinGlobals as x11SiwinGlobals import ./platforms/x11/window as x11Window import ./platforms/wayland/siwinGlobals as waylandSiwinGlobals import ./platforms/wayland/window as waylandWindow - elif defined(windows): import ./platforms/winapi/window as winapiWindow - elif defined(macosx): import ./platforms/cocoa/window as cocoaWindow - when not siwin_use_lib: proc screenCount*(globals: SiwinGlobals): int32 = when defined(android): 1 - elif defined(linux) or defined(bsd): if globals of SiwinGlobalsX11: result = globals.SiwinGlobalsX11.screenCountX11() @@ -34,14 +29,14 @@ when not siwin_use_lib: result = globals.SiwinGlobalsWayland.screenCountWayland() else: raise SiwinPlatformSupportDefect.newException("Unsupported platform") - - elif defined(windows): screenCountWinapi() - elif defined(macosx): screenCountCocoa() + elif defined(windows): + screenCountWinapi() + elif defined(macosx): + screenCountCocoa() proc screen*(globals: SiwinGlobals, number: int32): Screen = when defined(android): Screen() - elif defined(linux) or defined(bsd): if globals of SiwinGlobalsX11: result = globals.SiwinGlobalsX11.screenX11(number) @@ -49,14 +44,14 @@ when not siwin_use_lib: result = globals.SiwinGlobalsWayland.screenWayland(number) else: raise SiwinPlatformSupportDefect.newException("Unsupported platform") - - elif defined(windows): screenWinapi(number) - elif defined(macosx): screenCocoa(number) + elif defined(windows): + screenWinapi(number) + elif defined(macosx): + screenCocoa(number) proc defaultScreen*(globals: SiwinGlobals): Screen = when defined(android): Screen() - elif defined(linux) or defined(bsd): if globals of SiwinGlobalsX11: result = globals.SiwinGlobalsX11.defaultScreenX11() @@ -64,91 +59,149 @@ when not siwin_use_lib: result = globals.SiwinGlobalsWayland.defaultScreenWayland() else: raise SiwinPlatformSupportDefect.newException("Unsupported platform") - - elif defined(windows): defaultScreenWinapi() - elif defined(macosx): defaultScreenCocoa() - + elif defined(windows): + defaultScreenWinapi() + elif defined(macosx): + defaultScreenCocoa() proc newSoftwareRenderingWindow*( - globals: SiwinGlobals, - size = ivec2(1280, 720), - title = "", - screen: int32 = -1, - fullscreen = false, - resizable = true, - frameless = false, - transparent = false, - - class = "", # window class (used in x11), equals to title if not specified + globals: SiwinGlobals, + size = ivec2(1280, 720), + title = "", + screen: int32 = -1, + fullscreen = false, + resizable = true, + frameless = false, + transparent = false, + class = "", # window class (used in x11), equals to title if not specified ): Window = when defined(android): newSoftwareRenderingWindowAndroid( - size, title, + size, + title, # (if screen == -1: defaultScreenAndroid() else: screenAndroid(screen)), - resizable, fullscreen, frameless, transparent + resizable, + fullscreen, + frameless, + transparent, ) - elif defined(linux) or defined(bsd): if globals of SiwinGlobalsX11: result = globals.SiwinGlobalsX11.newSoftwareRenderingWindowX11( - size, title, - (if screen == -1: globals.SiwinGlobalsX11.defaultScreenX11() else: globals.SiwinGlobalsX11.screenX11(screen)), - resizable, fullscreen, frameless, transparent, - (if class == "": title else: class) + size, + title, + ( + if screen == -1: globals.SiwinGlobalsX11.defaultScreenX11() + else: globals.SiwinGlobalsX11.screenX11(screen) + ), + resizable, + fullscreen, + frameless, + transparent, + (if class == "": title else: class), ) elif globals of SiwinGlobalsWayland: result = globals.SiwinGlobalsWayland.newSoftwareRenderingWindowWayland( - size, title, - (if screen == -1: globals.SiwinGlobalsWayland.defaultScreenWayland() else: globals.SiwinGlobalsWayland.screenWayland(screen)), - resizable, fullscreen, frameless, transparent + size, + title, + ( + if screen == -1: globals.SiwinGlobalsWayland.defaultScreenWayland() + else: globals.SiwinGlobalsWayland.screenWayland(screen) + ), + resizable, + fullscreen, + frameless, + transparent, ) else: raise SiwinPlatformSupportDefect.newException("Unsupported platform") - elif defined(windows): newSoftwareRenderingWindowWinapi( - size, title, + size, + title, (if screen == -1: defaultScreenWinapi() else: screenWinapi(screen)), - resizable, fullscreen, frameless, transparent + resizable, + fullscreen, + frameless, + transparent, ) - elif defined(macosx): newSoftwareRenderingWindowCocoa( - size, title, + size, + title, (if screen == -1: defaultScreenCocoa() else: screenCocoa(screen)), - resizable, fullscreen, frameless, transparent + resizable, + fullscreen, + frameless, + transparent, ) + proc newPopupWindow*( + globals: SiwinGlobals, + parent: Window, + placement: PopupPlacement, + transparent = false, + grab = true, + ): PopupWindow = + when defined(android): + raise SiwinPlatformSupportDefect.newException( + "Popup windows are not supported on Android" + ) + elif defined(linux) or defined(bsd): + if globals of SiwinGlobalsX11: + result = globals.SiwinGlobalsX11.newPopupWindowX11( + parent.WindowX11, placement, transparent, grab + ) + elif globals of SiwinGlobalsWayland: + result = globals.SiwinGlobalsWayland.newPopupWindowWayland( + parent.WindowWayland, placement, transparent, grab + ) + else: + raise SiwinPlatformSupportDefect.newException("Unsupported platform") + elif defined(windows): + result = newPopupWindowWinapi(parent.WindowWinapi, placement, transparent, grab) + elif defined(macosx): + result = newPopupWindowCocoa(parent.WindowCocoa, placement, transparent, grab) when defined(android): proc loadExtensions*() = discard +proc siwin_screen_count(globals: SiwinGlobals): cint {.siwin_import_export.} = + screenCount(globals) +proc siwin_get_screen(globals: SiwinGlobals, n: cint): Screen {.siwin_import_export.} = + screen(globals, n.int32) -proc siwin_screen_count(globals: SiwinGlobals): cint {.siwin_import_export.} = screenCount(globals) -proc siwin_get_screen(globals: SiwinGlobals, n: cint): Screen {.siwin_import_export.} = screen(globals, n.int32) -proc siwin_default_screen(globals: SiwinGlobals): Screen {.siwin_import_export.} = defaultScreen(globals) - +proc siwin_default_screen(globals: SiwinGlobals): Screen {.siwin_import_export.} = + defaultScreen(globals) proc siwin_new_software_rendering_window( - globals: SiwinGlobals, - size_x: cint, size_y: cint, title: cstring, screen: cint, - fullscreen: cchar, resizable: cchar, frameless: cchar, transparent: cchar, - winclass: cstring + globals: SiwinGlobals, + size_x: cint, + size_y: cint, + title: cstring, + screen: cint, + fullscreen: cchar, + resizable: cchar, + frameless: cchar, + transparent: cchar, + winclass: cstring, ): Window {.siwin_import_export.} = newSoftwareRenderingWindow( globals, - ivec2(size_x.int32, size_y.int32), $title, screen.int32, - fullscreen.bool, resizable.bool, frameless.bool, transparent.bool, - $winclass + ivec2(size_x.int32, size_y.int32), + $title, + screen.int32, + fullscreen.bool, + resizable.bool, + frameless.bool, + transparent.bool, + $winclass, ) - - proc screenCount*(globals: SiwinGlobals): int32 {.siwin_export_import.} = siwin_screen_count(globals).int32 - proc screen*(globals: SiwinGlobals, number: int32): Screen {.siwin_export_import.} = siwin_get_screen(globals, n.cint) @@ -156,143 +209,262 @@ proc screen*(globals: SiwinGlobals, number: int32): Screen {.siwin_export_import proc defaultScreen*(globals: SiwinGlobals): Screen {.siwin_export_import.} = siwin_default_screen(globals) - proc newSoftwareRenderingWindow*( - globals: SiwinGlobals, - size = ivec2(1280, 720), - title = "", - screen: int32 = -1, - fullscreen = false, - resizable = true, - frameless = false, - transparent = false, - - class = "", # window class (used in x11), equals to title if not specified + globals: SiwinGlobals, + size = ivec2(1280, 720), + title = "", + screen: int32 = -1, + fullscreen = false, + resizable = true, + frameless = false, + transparent = false, + class = "", # window class (used in x11), equals to title if not specified ): Window {.siwin_export_import.} = result = siwin_new_software_rendering_window( - globals, - size.x, size.y, title.cstring, screen.cint, - fullscreen.cchar, resizable.cchar, frameless.cchar, transparent.cchar, - class.cstring, + globals, size.x, size.y, title.cstring, screen.cint, fullscreen.cchar, + resizable.cchar, frameless.cchar, transparent.cchar, class.cstring, ) GC_ref(result) - proc newSoftwareRenderingWindow*( - size = ivec2(1280, 720), - title = "", - screen: int32 = -1, - fullscreen = false, - resizable = true, - frameless = false, - transparent = false, - - class = "", # window class (used in x11), equals to title if not specified - - preferedPlatform: Platform = defaultPreferedPlatform(), + size = ivec2(1280, 720), + title = "", + screen: int32 = -1, + fullscreen = false, + resizable = true, + frameless = false, + transparent = false, + class = "", # window class (used in x11), equals to title if not specified + preferedPlatform: Platform = defaultPreferedPlatform(), ): Window = - newSoftwareRenderingWindow(newSiwinGlobals(preferedPlatform), size, title, screen, fullscreen, resizable, frameless, transparent, class) - + newSoftwareRenderingWindow( + newSiwinGlobals(preferedPlatform), + size, + title, + screen, + fullscreen, + resizable, + frameless, + transparent, + class, + ) when siwin_build_lib: import std/options import ./colorutils import ./platforms/any/[clipboards] - {.push, exportc, cdecl, dynlib.} + {.push exportc, cdecl, dynlib.} + + proc siwin_destroy_window(window: Window) = + GC_unref(window) + + proc siwin_screen_number(screen: Screen): cint = + screen.number.cint + + proc siwin_sreen_width(screen: Screen): cint = + screen.width.cint + + proc siwin_sreen_height(screen: Screen): cint = + screen.height.cint + + proc siwin_window_closed(window: Window): cchar = + window.closed.cchar + + proc siwin_window_opened(window: Window): cchar = + window.opened.cchar + + proc siwin_window_close(window: Window) = + close(window) - proc siwin_destroy_window(window: Window) = GC_unref(window) + proc siwin_window_transparent(window: Window): cchar = + window.transparent.cchar - proc siwin_screen_number(screen: Screen): cint = screen.number.cint - proc siwin_sreen_width(screen: Screen): cint = screen.width.cint - proc siwin_sreen_height(screen: Screen): cint = screen.height.cint + proc siwin_window_frameless(window: Window): cchar = + window.frameless.cchar + + proc siwin_window_cursor(window: Window, out_cursor: ptr Cursor) = + out_cursor[] = window.cursor + + proc siwin_window_separateTouch(window: Window): cchar = + window.separateTouch.cchar - proc siwin_window_closed(window: Window): cchar = window.closed.cchar - proc siwin_window_opened(window: Window): cchar = window.opened.cchar - proc siwin_window_close(window: Window) = close(window) - proc siwin_window_transparent(window: Window): cchar = window.transparent.cchar - proc siwin_window_frameless(window: Window): cchar = window.frameless.cchar - proc siwin_window_cursor(window: Window, out_cursor: ptr Cursor) = out_cursor[] = window.cursor - proc siwin_window_separateTouch(window: Window): cchar = window.separateTouch.cchar - proc siwin_window_size(window: Window, out_size_x, out_size_y: ptr cint) = let size = window.size out_size_x[] = size.x.cint out_size_y[] = size.y.cint - + proc siwin_window_pos(window: Window, out_pos_x, out_pos_y: ptr cint) = let pos = window.pos out_pos_x[] = pos.x.cint out_pos_y[] = pos.y.cint - - proc siwin_window_fullscreen(window: Window): cchar = window.fullscreen.cchar - proc siwin_window_maximized(window: Window): cchar = window.maximized.cchar - proc siwin_window_minimized(window: Window): cchar = window.minimized.cchar - proc siwin_window_visible(window: Window): cchar = window.visible.cchar - proc siwin_window_resizable(window: Window): cchar = window.resizable.cchar - + + proc siwin_window_fullscreen(window: Window): cchar = + window.fullscreen.cchar + + proc siwin_window_maximized(window: Window): cchar = + window.maximized.cchar + + proc siwin_window_minimized(window: Window): cchar = + window.minimized.cchar + + proc siwin_window_visible(window: Window): cchar = + window.visible.cchar + + proc siwin_window_resizable(window: Window): cchar = + window.resizable.cchar + proc siwin_window_minSize(window: Window, out_minSize_x, out_minSize_y: ptr cint) = let minSize = window.minSize out_minSize_x[] = minSize.x.cint out_minSize_y[] = minSize.y.cint - + proc siwin_window_maxSize(window: Window, out_maxSize_x, out_maxSize_y: ptr cint) = let maxSize = window.maxSize out_maxSize_x[] = maxSize.x.cint out_maxSize_y[] = maxSize.y.cint - - proc siwin_window_focused(window: Window): cchar = window.focused.cchar - proc siwin_window_redraw(window: Window) = window.redraw() - proc siwin_window_set_frameless(window: Window, v: cchar) = window.frameless = v.bool - proc siwin_window_set_cursor(window: Window, v: ptr Cursor) = window.cursor = v[] - proc siwin_window_set_separate_touch(window: Window, v: cchar) = window.separateTouch = v.bool - proc siwin_window_set_size(window: Window, size_x, size_y: cint) = window.size = ivec2(size_x.int32, size_y.int32) - proc siwin_window_set_pos(window: Window, pos_x, pos_y: cint) = window.pos = ivec2(pos_x.int32, pos_y.int32) - proc siwin_window_set_title(window: Window, v: cstring) = window.title = $v - proc siwin_window_set_fullscreen(window: Window, v: cchar) = window.fullscreen = v.bool - proc siwin_window_set_maximized(window: Window, v: cchar) = window.maximized = v.bool - proc siwin_window_set_minimized(window: Window, v: cchar) = window.minimized = v.bool - proc siwin_window_set_visible(window: Window, v: cchar) = window.visible = v.bool - proc siwin_window_set_resizable(window: Window, v: cchar) = window.resizable = v.bool - proc siwin_window_set_min_size(window: Window, v_x, v_y: cint) = window.minSize = ivec2(v_x.int32, v_y.int32) - proc siwin_window_set_max_size(window: Window, v_x, v_y: cint) = window.maxSize = ivec2(v_x.int32, v_y.int32) - proc siwin_window_clear_icon(window: Window) = window.icon = nil - proc siwin_window_set_icon(window: Window, v: ptr PixelBuffer) = window.icon = v[] - - proc siwin_window_start_interactive_move(window: Window, has_pos: cchar, pos_x, pos_y: cfloat) = - window.startInteractiveMove(if has_pos.bool: some vec2(pos_x.float32, pos_y.float32) else: none Vec2) - - proc siwin_window_start_interactive_resize(window: Window, edge: Edge, has_pos: cchar, pos_x, pos_y: cfloat) = - window.startInteractiveResize(edge, if has_pos.bool: some vec2(pos_x.float32, pos_y.float32) else: none Vec2) - - proc siwin_window_show_window_menu(window: Window, has_pos: cchar, pos_x, pos_y: cfloat) = - window.showWindowMenu(if has_pos.bool: some vec2(pos_x.float32, pos_y.float32) else: none Vec2) - - proc siwin_window_set_input_region(window: Window, pos_x, pos_y, size_x, size_y: cfloat) = - window.setInputRegion(vec2(pos_x.float32, pos_y.float32), vec2(size_x.float32, size_y.float32)) - - proc siwin_window_set_title_region(window: Window, pos_x, pos_y, size_x, size_y: cfloat) = - window.setTitleRegion(vec2(pos_x.float32, pos_y.float32), vec2(size_x.float32, size_y.float32)) - - proc siwin_window_set_border_width(window: Window, innerWidth, outerWidth: cfloat, diagonalSize: cfloat) = + + proc siwin_window_focused(window: Window): cchar = + window.focused.cchar + + proc siwin_window_redraw(window: Window) = + window.redraw() + + proc siwin_window_set_frameless(window: Window, v: cchar) = + window.frameless = v.bool + + proc siwin_window_set_cursor(window: Window, v: ptr Cursor) = + window.cursor = v[] + + proc siwin_window_set_separate_touch(window: Window, v: cchar) = + window.separateTouch = v.bool + + proc siwin_window_set_size(window: Window, size_x, size_y: cint) = + window.size = ivec2(size_x.int32, size_y.int32) + + proc siwin_window_set_pos(window: Window, pos_x, pos_y: cint) = + window.pos = ivec2(pos_x.int32, pos_y.int32) + + proc siwin_window_set_title(window: Window, v: cstring) = + window.title = $v + + proc siwin_window_set_fullscreen(window: Window, v: cchar) = + window.fullscreen = v.bool + + proc siwin_window_set_maximized(window: Window, v: cchar) = + window.maximized = v.bool + + proc siwin_window_set_minimized(window: Window, v: cchar) = + window.minimized = v.bool + + proc siwin_window_set_visible(window: Window, v: cchar) = + window.visible = v.bool + + proc siwin_window_set_resizable(window: Window, v: cchar) = + window.resizable = v.bool + + proc siwin_window_set_min_size(window: Window, v_x, v_y: cint) = + window.minSize = ivec2(v_x.int32, v_y.int32) + + proc siwin_window_set_max_size(window: Window, v_x, v_y: cint) = + window.maxSize = ivec2(v_x.int32, v_y.int32) + + proc siwin_window_clear_icon(window: Window) = + window.icon = nil + + proc siwin_window_set_icon(window: Window, v: ptr PixelBuffer) = + window.icon = v[] + + proc siwin_window_start_interactive_move( + window: Window, has_pos: cchar, pos_x, pos_y: cfloat + ) = + window.startInteractiveMove( + if has_pos.bool: + some vec2(pos_x.float32, pos_y.float32) + else: + none Vec2 + ) + + proc siwin_window_start_interactive_resize( + window: Window, edge: Edge, has_pos: cchar, pos_x, pos_y: cfloat + ) = + window.startInteractiveResize( + edge, + if has_pos.bool: + some vec2(pos_x.float32, pos_y.float32) + else: + none Vec2 + , + ) + + proc siwin_window_show_window_menu( + window: Window, has_pos: cchar, pos_x, pos_y: cfloat + ) = + window.showWindowMenu( + if has_pos.bool: + some vec2(pos_x.float32, pos_y.float32) + else: + none Vec2 + ) + + proc siwin_window_set_input_region( + window: Window, pos_x, pos_y, size_x, size_y: cfloat + ) = + window.setInputRegion( + vec2(pos_x.float32, pos_y.float32), vec2(size_x.float32, size_y.float32) + ) + + proc siwin_window_set_title_region( + window: Window, pos_x, pos_y, size_x, size_y: cfloat + ) = + window.setTitleRegion( + vec2(pos_x.float32, pos_y.float32), vec2(size_x.float32, size_y.float32) + ) + + proc siwin_window_set_border_width( + window: Window, innerWidth, outerWidth: cfloat, diagonalSize: cfloat + ) = window.setBorderWidth(innerWidth.float32, outerWidth.float32, diagonalSize.float32) - proc siwin_window_pixel_buffer(window: Window, out_buffer: ptr PixelBuffer) = out_buffer[] = window.pixelBuffer - proc siwin_window_make_current(window: Window) = window.makeCurrent() - + proc siwin_window_pixel_buffer(window: Window, out_buffer: ptr PixelBuffer) = + out_buffer[] = window.pixelBuffer + + proc siwin_window_make_current(window: Window) = + window.makeCurrent() + proc siwin_window_set_vsync(window: Window, v: cchar): cchar = - try: window.`vsync=`(v.bool, false) - except: return 1.cchar - - proc siwin_window_vulkan_surface(window: Window): pointer = window.vulkanSurface - proc siwin_window_clipboard(window: Window): Clipboard = window.clipboard - proc siwin_window_selection_clipboard(window: Window): Clipboard = window.selectionClipboard - proc siwin_window_dragndrop_clipboard(window: Window): Clipboard = window.dragndropClipboard - proc siwin_window_set_drag_status(window: Window, v: DragStatus) = window.dragStatus = v - proc siwin_window_first_step(window: Window, makeVisible: cchar) = window.firstStep(makeVisible.bool) - proc siwin_window_step(window: Window) = window.step() - proc siwin_window_run(window: Window, makeVisible: cchar) = window.run(makeVisible.bool) - - proc siwin_window_set_event_handler(window: Window, eventHandler: ptr WindowEventsHandler) = window.eventsHandler = eventHandler[] + try: + window.`vsync=`(v.bool, false) + except: + return 1.cchar + + proc siwin_window_vulkan_surface(window: Window): pointer = + window.vulkanSurface + + proc siwin_window_clipboard(window: Window): Clipboard = + window.clipboard + + proc siwin_window_selection_clipboard(window: Window): Clipboard = + window.selectionClipboard + + proc siwin_window_dragndrop_clipboard(window: Window): Clipboard = + window.dragndropClipboard + + proc siwin_window_set_drag_status(window: Window, v: DragStatus) = + window.dragStatus = v + + proc siwin_window_first_step(window: Window, makeVisible: cchar) = + window.firstStep(makeVisible.bool) + + proc siwin_window_step(window: Window) = + window.step() + + proc siwin_window_run(window: Window, makeVisible: cchar) = + window.run(makeVisible.bool) + + proc siwin_window_set_event_handler( + window: Window, eventHandler: ptr WindowEventsHandler + ) = + window.eventsHandler = eventHandler[] {.pop.}