Skip to content

Commit 2d93b08

Browse files
committed
add: showWindowMenu, setInputRegion, setTitleRegion, setBorderWidth
1 parent 9eebc70 commit 2d93b08

File tree

10 files changed

+385
-80
lines changed

10 files changed

+385
-80
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ run window, WindowEventsHandler(
7979
glEnd()
8080
)
8181
```
82-
note: call redraw(window) every time you want window.render to be called. siwin will automatically call window.render only when window resizes.
82+
note: call redraw(window) every time you want window.render to be called. siwin will automatically call window.render only when window resizes.
83+
note: opengl 1.x and 2.x functions (like `glBegin`), is not supported on Wayland, due to Wayland only beeng able to initialize with EGL
8384

8485
## Vulkan
8586
see [t_vulkan.nim](https://github.com/levovix0/siwin/blob/master/tests/t_vulkan.nim)
@@ -255,6 +256,7 @@ run window, WindowEventsHandler(
255256
## all methods and events
256257
see [siwin/platforms/any/window](https://github.com/levovix0/siwin/blob/master/src/siwin/platforms/any/window.nim)
257258

259+
258260
## I want to get system handle of window and do some magic, but it is private?
259261
```nim
260262
import std/importutils
@@ -272,7 +274,6 @@ If you want to support this project, here is some tasks to do:
272274
* if you doing very big refactoring, first create issue to ask is all your changes needed, and if it is, refactor
273275
* Documentation
274276
* Optimization
275-
* Wayland support
276277
* MacOS support
277278
* Android/IOS support
278279
* Web support

src/siwin/platforms/any/window.nim

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ type
6565
wait arrowWait
6666
pointingHand grab
6767
text cross
68-
sizeAll sizeHorisontal sizeVertical
68+
sizeAll sizeHorizontal sizeVertical
6969
sizeTopLeft sizeTopRight sizeBottomLeft sizeBottomRight
7070
hided
7171

@@ -200,6 +200,9 @@ type
200200
m_resizable: bool
201201
m_minSize: IVec2
202202
m_maxSize: IVec2
203+
204+
inputRegion, titleRegion: Option[tuple[pos, size: IVec2]]
205+
borderWidth: Option[tuple[innerWidth, outerWidrth, diagonalSize: int]]
203206
{.pop.}
204207

205208

@@ -295,15 +298,46 @@ method `icon=`*(window: Window, v: nil.typeof) {.base.} = discard
295298
method `icon=`*(window: Window, v: PixelBuffer) {.base.} = discard
296299
## set window icon
297300

301+
298302
method startInteractiveMove*(window: Window, pos: Option[IVec2] = none IVec2) {.base.} = discard
299303
## allow user to move window interactivly
300304
## useful to create client-side decorated windows
301305
## it's recomended to start interactive move after user grabbed window header and started to move mouse
302306

307+
303308
method startInteractiveResize*(window: Window, edge: Edge, pos: Option[IVec2] = none IVec2) {.base.} = discard
304309
## allow user to resize window interactivly
305310
## useful to create client-side decorated windows
306-
## it's recomended to start interactive move after user grabbed window border and started to move mouse
311+
## it's recomended to start interactive resize after user grabbed window border and started to move mouse
312+
313+
314+
method showWindowMenu*(window: Window, pos: Option[IVec2] = none IVec2) {.base.} = discard
315+
## show OS/platform/DE-specific window menu
316+
## it's recomended to show menu after user right-clicked on window header
317+
## for now works only on Linux(Wayland)
318+
319+
320+
method setInputRegion*(window: Window, pos, size: IVec2) {.base.} =
321+
## set the rect (in window-local coordinates) where actual window is placed (inluding titlebar, if has one).
322+
## this is used by Windows and Linux(Wayland) to correctly anchor the window and to correctly send mouse and touch events.
323+
## it's recomended to set input region if you draw shadows for window.
324+
## setInputRegion, if called once, must be called after each resize of the window
325+
assert size.x * size.y > 0, "there must be at least one pixel of the actual window"
326+
window.inputRegion = some (pos, size)
327+
328+
329+
method setTitleRegion*(window: Window, pos, size: IVec2) {.base.} =
330+
## set the rect (in window-local coordinates) where titlebar is placed.
331+
## this is used by Windows to allow user to move window interactivly. siwin will replicate this behaviour on other platforms.
332+
## it's recomended to set title region if you have custom titlebar.
333+
window.titleRegion = some (pos, size)
334+
335+
336+
method setBorderWidth*(window: Window, innerWidth, outerWidth: int, diagonalSize: int) {.base.} =
337+
## set window border width. This will not change the look of window, it is for resizing window.
338+
## this is used on Windows to allow user to resize window interactivly. siwin will replicate this behaviour on other platforms.
339+
## it's recomended to set border width if you have custom titlebar.
340+
window.borderWidth = some (innerWidth, outerWidth, diagonalSize)
307341

308342

309343
proc drawImage*(window: Window, pixels: auto, size: IVec2, pos: IVec2 = ivec2(), srcPos: IVec2 = ivec2()) {.deprecated: "use pixelBuffer method instead".} = discard
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import std/[options, importutils]
2+
import pkg/[vmath]
3+
import ./[window]
4+
import ../../[siwindefs]
5+
6+
7+
when siwin_use_pure_enums:
8+
{.pragma: siwinPureEnum, pure.}
9+
else:
10+
{.pragma: siwinPureEnum.}
11+
12+
13+
privateAccess Window
14+
15+
16+
type
17+
WindowPart* {.siwinPureEnum.} = enum
18+
none
19+
client
20+
title
21+
border_left
22+
border_right
23+
border_top
24+
border_bottom
25+
border_top_left
26+
border_top_right
27+
border_bottom_left
28+
border_bottom_right
29+
30+
31+
32+
proc windowPartAt*(window: Window, mousePos: IVec2): WindowPart =
33+
if window.titleRegion.isNone and window.borderWidth.isNone: return
34+
35+
let tr = window.titleRegion.get((ivec2(), ivec2(-1, -1)))
36+
let (w, ow, dw) = window.borderWidth.get((0, 0, 0))
37+
let ir = window.inputRegion.get((ivec2(), window.m_size))
38+
39+
if (
40+
(
41+
mousePos.x in (ir.pos.x - ow)..<(ir.pos.x + dw) and
42+
mousePos.y in (ir.pos.y - ow)..<(ir.pos.y + w)
43+
) or (
44+
mousePos.x in (tr.pos.x - ow)..<(tr.pos.x + w) and
45+
mousePos.y in (tr.pos.y - ow)..<(tr.pos.y + dw)
46+
)
47+
):
48+
return WindowPart.border_top_left
49+
50+
elif (
51+
(
52+
mousePos.x in (ir.pos.x + ir.size.x - dw)..(ir.pos.x + ir.size.x + ow) and
53+
mousePos.y in (ir.pos.y - ow)..<(ir.pos.y + w)
54+
) or (
55+
mousePos.x in (tr.pos.x + tr.size.x - w)..(tr.pos.x + tr.size.x + ow) and
56+
mousePos.y in (tr.pos.y - ow)..<(tr.pos.y + dw)
57+
)
58+
):
59+
return WindowPart.border_top_right
60+
61+
elif (
62+
(
63+
mousePos.x in (ir.pos.x - ow)..<(ir.pos.x + dw) and
64+
mousePos.y in (ir.pos.y + ir.size.y - w)..(ir.pos.y + ir.size.y + ow)
65+
) or (
66+
mousePos.x in (tr.pos.x - ow)..<(tr.pos.x + w) and
67+
mousePos.y in (tr.pos.y + tr.size.y - dw)..(tr.pos.y + tr.size.y + ow)
68+
)
69+
):
70+
return WindowPart.border_bottom_left
71+
72+
elif (
73+
(
74+
mousePos.x in (ir.pos.x + ir.size.x - dw)..(ir.pos.x + ir.size.x + ow) and
75+
mousePos.y in (ir.pos.y + ir.size.y - w)..(ir.pos.y + ir.size.y + ow)
76+
) or (
77+
mousePos.x in (tr.pos.x + tr.size.x - w)..(tr.pos.x + tr.size.x + ow) and
78+
mousePos.y in (tr.pos.y + tr.size.y - dw)..(tr.pos.y + tr.size.y + ow)
79+
)
80+
):
81+
return WindowPart.border_bottom_right
82+
83+
elif (
84+
mousePos.x in (ir.pos.x - ow)..<(ir.pos.x + ir.size.x + ow) and
85+
mousePos.y in (ir.pos.y - ow)..<(ir.pos.y + w + 2)
86+
):
87+
return WindowPart.border_top
88+
89+
elif (
90+
mousePos.x in (ir.pos.x - ow)..<(ir.pos.x + ir.size.x + ow) and
91+
mousePos.y in (ir.pos.y + ir.size.y - w)..(ir.pos.y + ir.size.y + ow + 2)
92+
):
93+
return WindowPart.border_bottom
94+
95+
elif (
96+
mousePos.x in (ir.pos.x - ow)..<(ir.pos.x + w + 2) and
97+
mousePos.y in (ir.pos.y - ow)..<(ir.pos.y + ir.size.y + ow)
98+
):
99+
return WindowPart.border_left
100+
101+
elif (
102+
mousePos.x in (ir.pos.x + ir.size.x - w)..(ir.pos.x + ir.size.x + ow + 2) and
103+
mousePos.y in (ir.pos.y - ow)..<(ir.pos.y + ir.size.y + ow)
104+
):
105+
return WindowPart.border_right
106+
107+
elif (
108+
mousePos.x in tr.pos.x..(tr.pos.x + tr.size.x) and
109+
mousePos.y in tr.pos.y..(tr.pos.y + tr.size.y)
110+
):
111+
return WindowPart.title
112+
113+
elif (
114+
mousePos.x in ir.pos.x..<(ir.pos.x + ir.size.x) and
115+
mousePos.y in ir.pos.y..<(ir.pos.y + ir.size.y)
116+
):
117+
return WindowPart.client
118+
119+
else:
120+
return WindowPart.none
121+

src/siwin/platforms/wayland/cursors.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ proc loadBuiltinCursor*(kind: BuiltinCursor): CursorWayland =
8484
of BuiltinCursor.text: "text"
8585
of BuiltinCursor.cross: "cross"
8686
of BuiltinCursor.sizeAll: "size_all"
87-
of BuiltinCursor.sizeHorisontal: "sb_h_double_arrow"
87+
of BuiltinCursor.sizeHorizontal: "sb_h_double_arrow"
8888
of BuiltinCursor.sizeVertical: "sb_v_double_arrow"
8989
of BuiltinCursor.sizeTopLeft: "top_left_corner"
9090
of BuiltinCursor.sizeTopRight: "top_right_corner"

src/siwin/platforms/wayland/window.nim

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import std/[times, importutils, strformat, options, tables, os]
22
import pkg/[vmath]
33
import ../../[utils, colorutils]
44
import ../any/window {.all.}
5+
import ../any/[windowUtils]
56
import ./[libwayland, protocol, globals, sharedBuffer, bitfields, xkb]
67

78
{.experimental: "overloadableEnums".}
@@ -473,6 +474,35 @@ method startInteractiveResize*(window: WindowWayland, edge: Edge, pos: Option[IV
473474
)
474475

475476

477+
method showWindowMenu*(window: WindowWayland, pos: Option[IVec2]) =
478+
let pos = pos.get(window.mouse.pos)
479+
window.xdgToplevel.show_window_menu(seat, window.lastMouseButtonEventSerial, pos.x, pos.y)
480+
481+
482+
method setInputRegion*(window: WindowWayland, pos, size: IVec2) =
483+
procCall window.Window.setInputRegion(pos, size)
484+
let region = compositor.create_region
485+
region.add(pos.x, pos.y, size.x, size.y)
486+
window.surface.set_input_region(region)
487+
window.xdgSurface.set_window_geometry(pos.x, pos.y, size.x, size.y)
488+
489+
490+
proc replicateWindowTitleAndBorderBehaviour(window: WindowWayland, prevMousePos: IVec2) =
491+
if window.mouse.pressed != {MouseButton.left} or window.clicking != {MouseButton.left}: return
492+
493+
case window.windowPartAt(prevMousePos)
494+
of WindowPart.title: window.startInteractiveMove(some prevMousePos)
495+
of WindowPart.border_top_left: window.startInteractiveResize(Edge.topLeft, some prevMousePos)
496+
of WindowPart.border_top_right: window.startInteractiveResize(Edge.topRight, some prevMousePos)
497+
of WindowPart.border_bottom_left: window.startInteractiveResize(Edge.bottomLeft, some prevMousePos)
498+
of WindowPart.border_bottom_right: window.startInteractiveResize(Edge.bottomRight, some prevMousePos)
499+
of WindowPart.border_top: window.startInteractiveResize(Edge.top, some prevMousePos)
500+
of WindowPart.border_bottom: window.startInteractiveResize(Edge.bottom, some prevMousePos)
501+
of WindowPart.border_left: window.startInteractiveResize(Edge.left, some prevMousePos)
502+
of WindowPart.border_right: window.startInteractiveResize(Edge.right, some prevMousePos)
503+
else: discard
504+
505+
476506
proc initSeatEvents* =
477507
if seatEventsInitialized: return
478508
if not waylandAvailable: return
@@ -488,26 +518,38 @@ proc initSeatEvents* =
488518
let window = associatedWindows[surface.proxy.raw.id]
489519
seat_pointer_currentWindow = window
490520

521+
window.clicking = {}
491522
window.enterSerial = serial
492523
window.mouse.pos = vec2(surface_x, surface_y).ivec2
524+
525+
replicateWindowTitleAndBorderBehaviour(window, window.mouse.pos)
526+
493527
window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.enter)
494528

495529

496530
seat_pointer.onLeave:
497531
seat_pointer_currentWindow = nil
498532
if surface == nil or surface.proxy.raw.id notin associatedWindows: return
499533
let window = associatedWindows[surface.proxy.raw.id]
534+
535+
replicateWindowTitleAndBorderBehaviour(window, window.mouse.pos)
500536

537+
window.clicking = {}
501538
window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.leave)
539+
540+
# we don't unpress buttons on leave, because we "capture" mouse
502541

503542

504543
seat_pointer.onMotion:
505544
for window in associatedWindows.values:
506545
if (
507546
(window.mouse.pressed.len == 0) and
508547
window != seat_pointer_currentWindow
509-
): return
548+
): continue
549+
550+
replicateWindowTitleAndBorderBehaviour(window, window.mouse.pos)
510551

552+
window.clicking = {}
511553
window.mouse.pos = vec2(surface_x, surface_y).ivec2
512554
window.eventsHandler.pushEvent onMouseMove, MouseMoveEvent(window: window, pos: window.mouse.pos, kind: MouseMoveKind.move)
513555

@@ -528,7 +570,7 @@ proc initSeatEvents* =
528570
if (
529571
(state != `WlPointer / Button_state`.released or button notin window.mouse.pressed) and
530572
window != seat_pointer_currentWindow
531-
): return
573+
): continue
532574

533575
window.lastMouseButtonEventSerial = serial
534576
if state == `WlPointer / Button_state`.pressed:
@@ -679,7 +721,6 @@ proc setupWindow(window: WindowWayland, fullscreen, frameless, transparent: bool
679721
window.m_closed = true
680722

681723
window.xdgToplevel.onConfigure:
682-
window.xdgSurface.ackConfigure(0) #? is it needed?
683724
window.resize(ivec2(width, height))
684725

685726
let states = states.toSeq(`XdgToplevel / State`)

src/siwin/platforms/winapi/window.nim

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import times, os, options, std/importutils
2-
import vmath
3-
import winapi
1+
import std/[times, os, options, importutils]
2+
import pkg/[vmath]
3+
import ./[winapi]
44
import ../../[utils, colorutils]
55
import ../any/window {.all.}
6+
import ../any/[windowUtils]
67

78
privateAccess Window
89

@@ -289,6 +290,7 @@ method `pos=`*(window: WindowWinapi, v: IVec2) =
289290
if window.m_fullscreen: return
290291
window.handle.SetWindowPos(0, v.x, v.y, 0, 0, SwpNoSize)
291292
293+
292294
method `cursor=`*(window: WindowWinapi, v: Cursor) =
293295
if window.m_cursor.kind == builtin and v.kind == builtin and v.builtin == window.m_cursor.builtin: return
294296
if window.wcursor != 0: DestroyCursor window.wcursor
@@ -308,7 +310,7 @@ method `cursor=`*(window: WindowWinapi, v: Cursor) =
308310
of BuiltinCursor.cross: LoadCursor(0, IdcCross)
309311
of BuiltinCursor.sizeAll: LoadCursor(0, IdcSizeAll)
310312
of BuiltinCursor.sizeVertical: LoadCursor(0, IdcSizens)
311-
of BuiltinCursor.sizeHorisontal: LoadCursor(0, IdcSizewe)
313+
of BuiltinCursor.sizeHorizontal: LoadCursor(0, IdcSizewe)
312314
of BuiltinCursor.sizeTopLeft: LoadCursor(0, IdcSizenwse)
313315
of BuiltinCursor.sizeTopRight: LoadCursor(0, IdcSizenesw)
314316
of BuiltinCursor.sizeBottomLeft: LoadCursor(0, IdcSizenesw)
@@ -482,6 +484,10 @@ method startInteractiveResize*(window: WindowWinapi, edge: Edge, pos: Option[IVe
482484
# todo: press all keys and mouse buttons that are pressed after resize
483485

484486

487+
method showWindowMenu*(window: WindowWinapi, pos: Option[IVec2]) =
488+
discard
489+
490+
485491
method displayImpl(window: WindowWinapi) {.base.} =
486492
var ps: PaintStruct
487493
window.handle.BeginPaint(ps.addr)
@@ -522,6 +528,10 @@ method step*(window: WindowWinapi) =
522528
window.m_minimized = IsIconic(window.handle) != 0
523529
window.m_visible = IsWindowVisible(window.handle) != 0
524530
window.m_resizable = (GetWindowLongW(window.handle, GwlStyle) and WsThickframe) != 0
531+
532+
var p: WindowPlacement
533+
GetWindowPlacement(window.handle, p.addr)
534+
window.m_pos = ivec2(p.rcNormalPosition.left, p.rcNormalPosition.top)
525535

526536
while PeekMessage(msg.addr, 0, 0, 0, PmRemove).bool:
527537
checkStateChanged()
@@ -676,6 +686,22 @@ proc poolEvent(window: WindowWinapi, message: Uint, wParam: WParam, lParam: LPar
676686
info[].ptMaxTrackSize.x = window.m_maxSize.x
677687
info[].ptMaxTrackSize.y = window.m_maxSize.y
678688
689+
of WmNcHitTest:
690+
window.mouse.pos = ivec2(lParam.GetX_LParam.int32, lParam.GetY_LParam.int32)
691+
ScreenToClient(window.handle, cast[ptr Point](window.mouse.pos.addr))
692+
case window.windowPartAt(window.mouse.pos)
693+
of title: return HtCaption
694+
of client: return HtClient
695+
of border_top_left: return HtTopLeft
696+
of border_top_right: return HtTopRight
697+
of border_bottom_left: return HtBottomLeft
698+
of border_bottom_right: return HtBottomRight
699+
of border_top: return HtTop
700+
of border_bottom: return HtBottom
701+
of border_left: return HtLeft
702+
of border_right: return HtRight
703+
of none: return HtNowhere
704+
679705
else: return window.handle.DefWindowProc(message, wParam, lParam)
680706
681707

0 commit comments

Comments
 (0)