From 3f38e67ce0cab90c4042d749acdd06ca9490b870 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 25 Jun 2022 19:03:50 +0200 Subject: [PATCH] io/system: add ActionInputOp to register window move gesture areas The app.Window.Perform(ActionMove) is the wrong abstraction for initiating a move gesture: Windows needs to know the move gesture area at pointer move, and macOS needs to know the pointer button down event that triggers the move gesture. This change replaces Perform(ActionMove) with a new system.ActionInputOp that marks an area movable. Signed-off-by: Elias Naur --- app/internal/windows/windows.go | 1 + app/os_macos.go | 35 +++++++++++++-------------------- app/os_wayland.go | 18 +++++++++++------ app/os_windows.go | 5 +++++ app/window.go | 4 ++++ internal/ops/ops.go | 3 +++ io/router/pointer.go | 21 ++++++++++++++++++++ io/router/router.go | 8 ++++++++ io/system/decoration.go | 15 ++++++++++++++ widget/decorations.go | 10 ++-------- 10 files changed, 85 insertions(+), 35 deletions(-) diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index 5b4bcb5dc..d79a650d6 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -113,6 +113,7 @@ const ( HWND_TOPMOST = ^(uint32(1) - 1) // -1 + HTCAPTION = 2 HTCLIENT = 1 HTLEFT = 10 HTRIGHT = 11 diff --git a/app/os_macos.go b/app/os_macos.go index a72fc7769..efb0d48b4 100644 --- a/app/os_macos.go +++ b/app/os_macos.go @@ -232,9 +232,8 @@ type window struct { displayLink *displayLink // redraw is a single entry channel for making sure only one // display link redraw request is in flight. - redraw chan struct{} - cursor pointer.Cursor - lastPress C.CFTypeRef + redraw chan struct{} + cursor pointer.Cursor scale float32 config Config @@ -413,12 +412,6 @@ func (w *window) Perform(acts system.Action) { C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y)) case system.ActionRaise: C.raiseWindow(w.window) - case system.ActionMove: - if w.lastPress != 0 { - C.performWindowDragWithEvent(w.window, w.lastPress) - C.CFRelease(w.lastPress) - w.lastPress = 0 - } } }) if acts&system.ActionClose != 0 { @@ -500,6 +493,10 @@ func gio_onText(view, cstr C.CFTypeRef) { //export gio_onMouse func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) { w := mustView(view) + t := time.Duration(float64(ti)*float64(time.Second) + .5) + xf, yf := float32(x)*w.scale, float32(y)*w.scale + dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale + pos := f32.Point{X: xf, Y: yf} var typ pointer.Type switch cdir { case C.MOUSE_MOVE: @@ -508,11 +505,14 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx typ = pointer.Release case C.MOUSE_DOWN: typ = pointer.Press - if w.lastPress != 0 { - C.CFRelease(w.lastPress) - w.lastPress = 0 + act, ok := w.w.ActionAt(pos) + if ok && w.config.Mode != Fullscreen { + switch act { + case system.ActionMove: + C.performWindowDragWithEvent(w.window, evt) + return + } } - w.lastPress = C.CFRetain(evt) case C.MOUSE_SCROLL: typ = pointer.Scroll default: @@ -528,15 +528,12 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx if cbtns&(1<<2) != 0 { btns |= pointer.ButtonTertiary } - t := time.Duration(float64(ti)*float64(time.Second) + .5) - xf, yf := float32(x)*w.scale, float32(y)*w.scale - dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale w.w.Event(pointer.Event{ Type: typ, Source: pointer.Mouse, Time: t, Buttons: btns, - Position: f32.Point{X: xf, Y: yf}, + Position: pos, Scroll: f32.Point{X: dxf, Y: dyf}, Modifiers: convertMods(mods), }) @@ -768,10 +765,6 @@ func gio_onClose(view C.CFTypeRef) { w.w.Event(ViewEvent{}) deleteView(view) w.w.Event(system.DestroyEvent{}) - if w.lastPress != 0 { - C.CFRelease(w.lastPress) - w.lastPress = 0 - } w.displayLink.Close() C.CFRelease(w.view) C.CFRelease(w.window) diff --git a/app/os_wayland.go b/app/os_wayland.go index 8cb44cacc..9473d98e9 100644 --- a/app/os_wayland.go +++ b/app/os_wayland.go @@ -879,6 +879,14 @@ func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, w.resize(serial, edge) return } + act, ok := w.w.ActionAt(w.lastPos) + if ok && w.config.Mode == Windowed { + switch act { + case system.ActionMove: + w.move(serial) + return + } + } case BTN_RIGHT: btn = pointer.ButtonSecondary case BTN_MIDDLE: @@ -1073,19 +1081,17 @@ func (w *window) Perform(actions system.Action) { // https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:set_minimized walkActions(actions, func(action system.Action) { switch action { - case system.ActionMove: - w.move() case system.ActionClose: w.dead = true } }) } -func (w *window) move() { - if !w.inCompositor && w.seat != nil { +func (w *window) move(serial C.uint32_t) { + s := w.seat + if !w.inCompositor && s != nil { w.inCompositor = true - s := w.seat - C.xdg_toplevel_move(w.topLvl, s.seat, s.serial) + C.xdg_toplevel_move(w.topLvl, s.seat, serial) } } diff --git a/app/os_windows.go b/app/os_windows.go index 60854a395..6ce3d0e46 100644 --- a/app/os_windows.go +++ b/app/os_windows.go @@ -437,6 +437,11 @@ func (w *window) hitTest(x, y int) uintptr { default: fallthrough case !top && !bottom && !left && !right: + p := f32.Pt(float32(x), float32(y)) + switch a, _ := w.w.ActionAt(p); a { + case system.ActionMove: + return windows.HTCAPTION + } return windows.HTCLIENT case top && left: return windows.HTTOPLEFT diff --git a/app/window.go b/app/window.go index 1e0c067bd..d894c31cd 100644 --- a/app/window.go +++ b/app/window.go @@ -567,6 +567,10 @@ func (c *callbacks) ClickFocus() { c.w.updateAnimation(c.d) } +func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) { + return c.w.queue.q.ActionAt(p) +} + func (e *editorState) Replace(r key.Range, text string) { if r.Start > r.End { r.Start, r.End = r.End, r.Start diff --git a/internal/ops/ops.go b/internal/ops/ops.go index fc1812820..275a15ad7 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -75,6 +75,7 @@ const ( TypeSemanticDisabled TypeSnippet TypeSelection + TypeActionInput ) type StackID struct { @@ -158,6 +159,7 @@ const ( TypeSemanticDisabledLen = 2 TypeSnippetLen = 1 + 4 + 4 TypeSelectionLen = 1 + 2*4 + 2*4 + 4 + 4 + TypeActionInputLen = 1 + 1 ) func (op *ClipOp) Decode(data []byte) { @@ -418,6 +420,7 @@ func (t OpType) Size() int { TypeSemanticDisabledLen, TypeSnippetLen, TypeSelectionLen, + TypeActionInputLen, }[t-firstOpIndex] } diff --git a/io/router/pointer.go b/io/router/pointer.go index 8c5181345..a73860499 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -13,6 +13,7 @@ import ( "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/semantic" + "gioui.org/io/system" "gioui.org/io/transfer" ) @@ -97,6 +98,7 @@ type areaNode struct { id SemanticID content semanticContent } + action system.Action } type areaKind uint8 @@ -256,6 +258,12 @@ func (c *pointerCollector) keyInputOp(op key.InputOp) { }) } +func (c *pointerCollector) actionInputOp(act system.Action) { + areaID := c.currentArea() + area := &c.q.areas[areaID] + area.action = act +} + func (c *pointerCollector) inputOp(op pointer.InputOp, events *handlerEvents) { areaID := c.currentArea() area := &c.q.areas[areaID] @@ -424,6 +432,19 @@ func (q *pointerQueue) semanticIDFor(content semanticContent) SemanticID { return id.id } +func (q *pointerQueue) ActionAt(pos f32.Point) (system.Action, bool) { + for i := len(q.hitTree) - 1; i >= 0; i-- { + n := &q.hitTree[i] + hit, _ := q.hit(n.area, pos) + if !hit { + continue + } + area := q.areas[n.area] + return area.action, area.action != 0 + } + return 0, false +} + func (q *pointerQueue) SemanticAt(pos f32.Point) (SemanticID, bool) { q.assignSemIDs() for i := len(q.hitTree) - 1; i >= 0; i-- { diff --git a/io/router/router.go b/io/router/router.go index 4271cbb47..0b8960e73 100644 --- a/io/router/router.go +++ b/io/router/router.go @@ -27,6 +27,7 @@ import ( "gioui.org/io/pointer" "gioui.org/io/profile" "gioui.org/io/semantic" + "gioui.org/io/system" "gioui.org/io/transfer" "gioui.org/op" ) @@ -276,6 +277,10 @@ func min(p1, p2 image.Point) image.Point { return m } +func (q *Router) ActionAt(p f32.Point) (system.Action, bool) { + return q.pointer.queue.ActionAt(p) +} + func (q *Router) ClickFocus() { focus := q.key.queue.focus if focus == nil { @@ -444,6 +449,9 @@ func (q *Router) collect() { Data: encOp.Refs[2].(io.ReadCloser), } pc.offerOp(op, &q.handlers) + case ops.TypeActionInput: + act := system.Action(encOp.Data[1]) + pc.actionInputOp(act) // Key ops. case ops.TypeKeyFocus: diff --git a/io/system/decoration.go b/io/system/decoration.go index 245e14e18..c9e01660f 100644 --- a/io/system/decoration.go +++ b/io/system/decoration.go @@ -2,8 +2,17 @@ package system import ( "strings" + + "gioui.org/internal/ops" + "gioui.org/op" ) +// ActionAreaOp makes the current clip area available for +// system gestures. +// +// Note: only ActionMove is supported. +type ActionInputOp Action + // Action is a set of window decoration actions. type Action uint @@ -31,6 +40,12 @@ const ( ActionMove ) +func (op ActionInputOp) Add(o *op.Ops) { + data := ops.Write(&o.Internal, ops.TypeActionInputLen) + data[0] = byte(ops.TypeActionInput) + data[1] = byte(op) +} + func (a Action) String() string { var buf strings.Builder for b := Action(1); a != 0; b <<= 1 { diff --git a/widget/decorations.go b/widget/decorations.go index 8a7de2268..384578253 100644 --- a/widget/decorations.go +++ b/widget/decorations.go @@ -12,7 +12,6 @@ import ( // Decorations handles the states of window decorations. type Decorations struct { - move gesture.Drag clicks []Clickable resize [8]struct { gesture.Hover @@ -25,13 +24,8 @@ type Decorations struct { // LayoutMove lays out the widget that makes a window movable. func (d *Decorations) LayoutMove(gtx layout.Context, w layout.Widget) layout.Dimensions { dims := w(gtx) - d.move.Events(gtx.Metric, gtx, gesture.Both) - st := clip.Rect{Max: dims.Size}.Push(gtx.Ops) - d.move.Add(gtx.Ops) - if d.move.Pressed() { - d.actions |= system.ActionMove - } - st.Pop() + defer clip.Rect{Max: dims.Size}.Push(gtx.Ops).Pop() + system.ActionInputOp(system.ActionMove).Add(gtx.Ops) return dims }