v0.3.0 cf370a0

layers.go RawHistoryFolder

1package core
2
3import (
4	"sort"
5
6	"github.com/bnema/neferwl/internal/ports"
7)
8
9// arrangeLayers places exclusive surfaces first, then sorts the resulting scene by layer.
10func arrangeLayers(outW, outH int, layers []ports.LayerSurface) (placed []ports.SceneLayer, usable ports.Rect) {
11	full := ports.Rect{W: max(outW, 0), H: max(outH, 0)}
12	usable = full
13	for pass := 0; pass < 2; pass++ {
14		for _, s := range layers {
15			if (s.ExclusiveZone > 0) != (pass == 0) {
16				continue
17			}
18			bounds := usable
19			if s.ExclusiveZone == -1 {
20				bounds = full
21			}
22			top, right, bottom, left := int(s.Margin[0]), int(s.Margin[1]), int(s.Margin[2]), int(s.Margin[3])
23			w, h := s.Width, s.Height
24			x, y := bounds.X+(bounds.W-w)/2, bounds.Y+(bounds.H-h)/2
25			switch s.Anchor & (ports.AnchorLeft | ports.AnchorRight) {
26			case ports.AnchorLeft | ports.AnchorRight:
27				span := bounds.W - left - right
28				if w == 0 {
29					w = span
30				}
31				x = bounds.X + left + (span-w)/2
32			case ports.AnchorLeft:
33				x = bounds.X + left
34			case ports.AnchorRight:
35				x = bounds.X + bounds.W - w - right
36			}
37			switch s.Anchor & (ports.AnchorTop | ports.AnchorBottom) {
38			case ports.AnchorTop | ports.AnchorBottom:
39				span := bounds.H - top - bottom
40				if h == 0 {
41					h = span
42				}
43				y = bounds.Y + top + (span-h)/2
44			case ports.AnchorTop:
45				y = bounds.Y + top
46			case ports.AnchorBottom:
47				y = bounds.Y + bounds.H - h - bottom
48			}
49			placed = append(placed, ports.SceneLayer{ID: s.ID, Layer: s.Layer, Rect: ports.Rect{X: x, Y: y, W: w, H: h}})
50			if s.ExclusiveZone <= 0 {
51				continue
52			}
53			a := s.Anchor
54			z := int(s.ExclusiveZone)
55			switch {
56			case a&ports.AnchorTop != 0 && a&ports.AnchorBottom == 0 && (a&(ports.AnchorLeft|ports.AnchorRight) == 0 || a&(ports.AnchorLeft|ports.AnchorRight) == ports.AnchorLeft|ports.AnchorRight):
57				z = min(max(z+top, 0), usable.H)
58				usable.Y += z
59				usable.H -= z
60			case a&ports.AnchorBottom != 0 && a&ports.AnchorTop == 0 && (a&(ports.AnchorLeft|ports.AnchorRight) == 0 || a&(ports.AnchorLeft|ports.AnchorRight) == ports.AnchorLeft|ports.AnchorRight):
61				z = min(max(z+bottom, 0), usable.H)
62				usable.H -= z
63			case a&ports.AnchorLeft != 0 && a&ports.AnchorRight == 0 && (a&(ports.AnchorTop|ports.AnchorBottom) == 0 || a&(ports.AnchorTop|ports.AnchorBottom) == ports.AnchorTop|ports.AnchorBottom):
64				z = min(max(z+left, 0), usable.W)
65				usable.X += z
66				usable.W -= z
67			case a&ports.AnchorRight != 0 && a&ports.AnchorLeft == 0 && (a&(ports.AnchorTop|ports.AnchorBottom) == 0 || a&(ports.AnchorTop|ports.AnchorBottom) == ports.AnchorTop|ports.AnchorBottom):
68				z = min(max(z+right, 0), usable.W)
69				usable.W -= z
70			}
71		}
72	}
73	sort.SliceStable(placed, func(i, j int) bool { return placed[i].Layer < placed[j].Layer })
74	return
75}
76
77// layerOf finds a placed layer surface by ID.
78func (c *Core) layerOf(id WindowID) (*screen, ports.SceneLayer, bool) {
79	for _, sc := range c.screens {
80		for _, l := range sc.placed {
81			if l.ID == id {
82				return sc, l, true
83			}
84		}
85	}
86	return nil, ports.SceneLayer{}, false
87}
88
89// hasFullscreen reports whether a fullscreen window covers the screen. In
90// scroll overflow, one scrolled off by the user no longer does; nor does
91// one shown as an overview preview.
92func hasFullscreen(sc *screen) bool {
93	return !sc.mon.ov.open && sc.mon.Current().cover() != 0
94}
95
96// shown reports whether a layer surface is drawn. A fullscreen window is
97// exclusive: it hides every layer above the background (bars,
98// notifications), so the output can scan it out. A surface taking the
99// keyboard exclusively (a locker, a launcher) still shows: it has the
100// keyboard and must be seen.
101func shown(sc *screen, l ports.SceneLayer) bool {
102	return l.Layer == ports.LayerBackground || !hasFullscreen(sc) || sc.exclusiveKeyboard(l.ID) || sc.capture[l.ID]
103}
104
105// exclusiveKeyboard reports whether layer surface id takes the keyboard
106// exclusively.
107func (sc *screen) exclusiveKeyboard(id WindowID) bool {
108	for _, l := range sc.layers {
109		if l.ID == id {
110			return l.Keyboard == 1
111		}
112	}
113	return false
114}
115
116// shownLayers are the placed layer surfaces drawn on the screen.
117func shownLayers(sc *screen) []ports.SceneLayer {
118	out := make([]ports.SceneLayer, 0, len(sc.placed))
119	for _, l := range sc.placed {
120		if shown(sc, l) {
121			out = append(out, l)
122		}
123	}
124	return out
125}
126
127// layerAt returns the topmost shown layer surface under the output-local
128// point among those above (or below) the windows.
129func (c *Core) layerAt(sc *screen, lx, ly float64, above bool) (WindowID, float64, float64) {
130	for i := len(sc.placed) - 1; i >= 0; i-- {
131		l := sc.placed[i]
132		r := l.Rect
133		if (l.Layer >= ports.LayerTop) != above || !shown(sc, l) || r.W <= 0 || r.H <= 0 {
134			continue
135		}
136		if lx >= float64(r.X) && lx < float64(r.X+r.W) && ly >= float64(r.Y) && ly < float64(r.Y+r.H) && c.acceptsInput(l.ID, lx-float64(r.X), ly-float64(r.Y)) {
137			return l.ID, lx - float64(r.X), ly - float64(r.Y)
138		}
139	}
140	return 0, 0, 0
141}
142
143// onDemand reports whether id is a mapped layer surface with on-demand
144// keyboard interactivity.
145func (c *Core) onDemand(id WindowID) bool {
146	for _, sc := range c.screens {
147		for _, l := range sc.layers {
148			if l.ID == id {
149				return l.Keyboard == 2
150			}
151		}
152	}
153	return false
154}

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