feat/hints a471fa4

datadrag.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"time"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10// Data drag: a client drag and drop (wl_data_device.start_drag). Wayland
11// validated the request against the client's press and tells core; core
12// owns the pointer for the length of the drag, as for a window drag
13// (drag.go):
14//
15//   - the origin client gets the pointer leave and no pointer event after;
16//     nobody else gets one: the surface under the pointer is the drag
17//     target, told to wayland as DataDragFocus and DataDragMotion;
18//   - releasing the drag button drops (DataDragDrop), Escape cancels
19//     (DataDragCancel); neither reaches a client;
20//   - scrolling and touchpad gestures go nowhere, compositor swipes
21//     included; binds still run, Escape cancels the drag before the
22//     switcher; the keyboard keeps its
23//     focus, clients read the modifiers that choose the action from it;
24//   - the drag icon, a client surface wayland measures (DataDragIcon), is
25//     drawn at the cursor over everything.
26//
27// Layout changes under a still cursor reroute the drag (rehit), and the
28// drop re-hits.
29
30// dataDragState is the data drag in progress.
31type dataDragState struct {
32	// button is the pointer button that holds the drag.
33	button uint32
34	// target is the surface the drag was last routed to (0: none), at
35	// at in its coordinates.
36	target WindowID
37	at     [2]float64
38	icon   dataDragIcon
39}
40
41// dataDragIcon is the icon surface: its top left corner relative to the
42// cursor and its size, logical px. Hidden while W or H is 0.
43type dataDragIcon struct {
44	id         WindowID
45	x, y, w, h int
46}
47
48// startDataDrag answers the start_drag wayland validated: it takes the
49// pointer if a client holds a button, else cancels the drag. A drag
50// during a window drag or a locked session is refused.
51func (c *Core) startDataDrag(ctx context.Context, v ports.DataDragStarted) error {
52	button := v.Button
53	if c.dataDrag != nil || c.drag != nil || c.security.Protected || c.grab == 0 || c.held() == 0 || !c.buttons[button] || c.swallow[button] {
54		return c.command(ctx, ports.DataDragCancel{})
55	}
56	c.dataDrag = &dataDragState{button: button, icon: dataDragIcon{id: v.Icon}}
57	// Other held buttons release without a client to tell.
58	for b := range c.buttons {
59		if b != button {
60			c.swallow[b] = true
61		}
62	}
63	c.grab = 0
64	// A compositor swipe in progress lets go of what it moved.
65	if c.swipe != nil {
66		c.dropSwipe()
67		c.swipe = nil
68	}
69	// The origin gets its pointer leave, as the drag takes the pointer.
70	if c.pointer != 0 {
71		c.pointer = 0
72		if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil {
73			return err
74		}
75	}
76	// The surface under the pointer, the origin's usually, is the first
77	// target.
78	return c.dataDragRoute(ctx, c.motionTime)
79}
80
81// dataDragRoute sends the surface under the cursor as the drag target: a
82// focus when it changed, else a motion.
83func (c *Core) dataDragRoute(ctx context.Context, at time.Duration) error {
84	d := c.dataDrag
85	id, x, y := c.hit(c.cursorX, c.cursorY)
86	d.at = [2]float64{x, y}
87	switch {
88	case id != d.target:
89		d.target = id
90		return c.command(ctx, ports.DataDragFocus{ID: id, X: x, Y: y, Time: at})
91	case id != 0:
92		return c.command(ctx, ports.DataDragMotion{ID: id, X: x, Y: y, Time: at})
93	}
94	return nil
95}
96
97// dataDragRetarget re-routes a still cursor: the target is told only when
98// it or the point in it changed.
99func (c *Core) dataDragRetarget(ctx context.Context, at time.Duration) error {
100	d := c.dataDrag
101	if id, x, y := c.hit(c.cursorX, c.cursorY); id == d.target && (id == 0 || d.at == [2]float64{x, y}) {
102		return nil
103	}
104	return c.dataDragRoute(ctx, at)
105}
106
107// dataDragMotion follows the cursor: the target gets the motion and the
108// icon moves with it.
109func (c *Core) dataDragMotion(ctx context.Context, at time.Duration) error {
110	if err := c.dataDragRoute(ctx, at); err != nil {
111		return err
112	}
113	if c.dataDrag.icon.visible() {
114		return c.publish(ctx)
115	}
116	return nil
117}
118
119// dataDragButton handles a button event of a data drag: the release of
120// the drag button drops; the other buttons do nothing, their releases
121// neither.
122func (c *Core) dataDragButton(ctx context.Context, v ports.PointerButton) error {
123	if !v.Pressed {
124		if v.Button != c.dataDrag.button {
125			return nil
126		}
127		delete(c.buttons, v.Button)
128		// A bind may have moved the layout under a still cursor: the
129		// drop goes to what lies there now.
130		if err := c.dataDragRetarget(ctx, v.Time); err != nil {
131			return err
132		}
133		c.dataDrag = nil
134		if err := c.command(ctx, ports.DataDragDrop{}); err != nil {
135			return err
136		}
137		return c.finishDataDrag(ctx)
138	}
139	c.buttons[v.Button] = true
140	c.swallow[v.Button] = true
141	return nil
142}
143
144// cancelDataDrag ends the drag on Escape.
145func (c *Core) cancelDataDrag(ctx context.Context) error {
146	c.abortDataDrag()
147	if err := c.command(ctx, ports.DataDragCancel{}); err != nil {
148		return err
149	}
150	return c.finishDataDrag(ctx)
151}
152
153// abortDataDrag ends the drag where it is; the held button's release is
154// dropped.
155func (c *Core) abortDataDrag() {
156	if d := c.dataDrag; d != nil {
157		c.swallow[d.button] = true
158		c.dataDrag = nil
159	}
160}
161
162// finishDataDrag redraws without the icon and points the pointer at what
163// lies under the cursor.
164func (c *Core) finishDataDrag(ctx context.Context) error {
165	if err := c.publish(ctx); err != nil {
166		return err
167	}
168	return c.rehit(ctx)
169}
170
171// dataDragGone drops the target that was unmapped: it gets its leave.
172func (c *Core) dataDragGone(ctx context.Context, id WindowID) error {
173	if d := c.dataDrag; d != nil && d.target == id {
174		d.target = 0
175		return c.command(ctx, ports.DataDragFocus{Time: c.motionTime})
176	}
177	return nil
178}
179
180// setDataDragIcon records the icon geometry wayland measured. It reports
181// whether the scene changes.
182func (c *Core) setDataDragIcon(v ports.DataDragIcon) bool {
183	d := c.dataDrag
184	if d == nil || d.icon.id == 0 || v.ID != d.icon.id {
185		return false
186	}
187	icon := dataDragIcon{id: v.ID, x: v.X, y: v.Y, w: max(v.W, 0), h: max(v.H, 0)}
188	if icon == d.icon {
189		return false
190	}
191	d.icon = icon
192	return true
193}
194
195func (i dataDragIcon) visible() bool { return i.id != 0 && i.w > 0 && i.h > 0 }
196
197// dataDragIconOn is the icon as a window of screen sc (index i) when the
198// cursor is on it.
199func (c *Core) dataDragIconOn(i int, sc *screen) (ports.SceneWindow, bool) {
200	d := c.dataDrag
201	if d == nil || !d.icon.visible() || c.screenAt(c.cursorX, c.cursorY) != i {
202		return ports.SceneWindow{}, false
203	}
204	x, y := int(c.cursorX)-sc.x+d.icon.x, int(c.cursorY)-sc.y+d.icon.y
205	return ports.SceneWindow{ID: d.icon.id, Rect: Rect{X: x, Y: y, W: d.icon.w, H: d.icon.h}, Popup: true, OverLayers: true}, true
206}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close