Skip to content

Commit

Permalink
io/system: add ActionInputOp to register window move gesture areas
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
eliasnaur committed Jun 25, 2022
1 parent b53cdfe commit 3f38e67
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 35 deletions.
1 change: 1 addition & 0 deletions app/internal/windows/windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ const (

HWND_TOPMOST = ^(uint32(1) - 1) // -1

HTCAPTION = 2
HTCLIENT = 1
HTLEFT = 10
HTRIGHT = 11
Expand Down
35 changes: 14 additions & 21 deletions app/os_macos.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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),
})
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 12 additions & 6 deletions app/os_wayland.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions app/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions internal/ops/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const (
TypeSemanticDisabled
TypeSnippet
TypeSelection
TypeActionInput
)

type StackID struct {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -418,6 +420,7 @@ func (t OpType) Size() int {
TypeSemanticDisabledLen,
TypeSnippetLen,
TypeSelectionLen,
TypeActionInputLen,
}[t-firstOpIndex]
}

Expand Down
21 changes: 21 additions & 0 deletions io/router/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -97,6 +98,7 @@ type areaNode struct {
id SemanticID
content semanticContent
}
action system.Action
}

type areaKind uint8
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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-- {
Expand Down
8 changes: 8 additions & 0 deletions io/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down
15 changes: 15 additions & 0 deletions io/system/decoration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 2 additions & 8 deletions widget/decorations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down

0 comments on commit 3f38e67

Please sign in to comment.