v0.2.0 13c6a90

separators.go RawHistoryFolder

1package core
2
3import "github.com/bnema/neferwl/internal/ports"
4
5// Like tmux screen-redraw.c, lines separate visible panes, never outline a
6// lone tile or a fullscreen/zoomed tile. Adjacent tiles share one line,
7// owned (and inset) by the left/top tile; focus lights only the adjacent
8// sides of the focused tile, including corners. With exactly two visible
9// tiles and no gaps, each lights its own half of the shared line (left/top
10// first). With gaps, each tile owns its neighbor-facing lines and lights
11// them whole. Hidden, scrolled-off, other-workspace and other-output tiles
12// do not count. Lines at output edges are omitted. Floating windows differ
13// from tmux tiles: each gets its own full border, drawn with that window
14// (Separator.Window), even when it is the only visible window. Only the
15// focused output's focused window can light lines; keyboard grabs by popups
16// and layers do not change the underlying window's visual focus.
17
18var sides = [...]ports.Sides{ports.SideLeft, ports.SideRight, ports.SideTop, ports.SideBottom}
19
20// separators are the lines of a layout on output o, inactive ones first
21// so active ones draw over them. lit says whether the focused tile shows
22// focus (only on the focused output).
23func separators(ps []Placement, width, gap int, o Rect, lit bool) []ports.Separator {
24	if width <= 0 {
25		return nil
26	}
27	var out []ports.Separator
28	add := func(p *Placement, r Rect, active bool) {
29		// A line cannot belong to a scrolled-off window or bleed onto
30		// another output, even when a neighboring tile remains visible.
31		x0, y0 := max(r.X, o.X), max(r.Y, o.Y)
32		x1, y1 := min(r.X+r.W, o.X+o.W), min(r.Y+r.H, o.Y+o.H)
33		if x0 >= x1 || y0 >= y1 {
34			return
35		}
36		r = Rect{X: x0, Y: y0, W: x1 - x0, H: y1 - y0}
37		var id WindowID
38		if p.Floating {
39			id = p.ID
40		}
41		out = append(out, ports.Separator{Rect: r, Active: active, Window: id})
42	}
43	var focused *Placement
44	tiles := 0
45	for i := range ps {
46		p := &ps[i]
47		if p.Fullscreen || !onScreen(*p, o) {
48			continue
49		}
50		if !p.Floating {
51			tiles++
52		}
53		for _, s := range sides {
54			if p.Inset&s != 0 {
55				add(p, strip(p.Rect, s, 0, width), false)
56			}
57		}
58		if p.Focused && lit {
59			focused = p
60		}
61	}
62	if focused == nil {
63		return out
64	}
65	f := focused
66	for _, s := range sides {
67		var line ports.Rect
68		switch {
69		case f.Neighbors&s != 0 && f.Inset&s != 0:
70			line = strip(f.Rect, s, 0, width)
71		case f.Neighbors&s != 0:
72			line = strip(f.Rect, s, width, width)
73		case f.Inset&s != 0:
74			// A float's own border.
75			add(f, strip(f.Rect, s, 0, width), true)
76			continue
77		default:
78			continue
79		}
80		line = closeCorners(line, *f, s, width)
81		// Two tiles share one line only without gaps.
82		if tiles == 2 && gap == 0 {
83			line = half(line, s)
84		}
85		add(f, line, true)
86	}
87	return out
88}
89
90// strip is the b-wide strip along side s of r: inside r when out is 0,
91// else just outside it (the neighbor's line).
92func strip(r Rect, s ports.Sides, out, b int) Rect {
93	b = min(b, max(r.W/2, out), max(r.H/2, out))
94	switch s {
95	case ports.SideLeft:
96		return Rect{X: r.X - out, Y: r.Y, W: b, H: r.H}
97	case ports.SideRight:
98		return Rect{X: r.X + r.W - b + out, Y: r.Y, W: b, H: r.H}
99	case ports.SideTop:
100		return Rect{X: r.X, Y: r.Y - out, W: r.W, H: b}
101	}
102	return Rect{X: r.X, Y: r.Y + r.H - b + out, W: r.W, H: b}
103}
104
105// closeCorners runs a line along side s of p b further at each end where
106// the crossing line lies outside p (a neighbor's), so lit lines meet.
107func closeCorners(line Rect, p Placement, s ports.Sides, b int) Rect {
108	outside := func(s ports.Sides) bool { return p.Neighbors&s != 0 && p.Inset&s == 0 }
109	if s == ports.SideLeft || s == ports.SideRight {
110		if outside(ports.SideTop) {
111			line.Y, line.H = line.Y-b, line.H+b
112		}
113		if outside(ports.SideBottom) {
114			line.H += b
115		}
116		return line
117	}
118	if outside(ports.SideLeft) {
119		line.X, line.W = line.X-b, line.W+b
120	}
121	if outside(ports.SideRight) {
122		line.W += b
123	}
124	return line
125}
126
127// half keeps the half of a line owned by the tile on side s of it: the
128// first half for the left or top tile, else the second.
129func half(r Rect, s ports.Sides) Rect {
130	switch s {
131	case ports.SideRight:
132		r.H /= 2
133	case ports.SideLeft:
134		r.Y, r.H = r.Y+r.H/2, r.H-r.H/2
135	case ports.SideBottom:
136		r.W /= 2
137	case ports.SideTop:
138		r.X, r.W = r.X+r.W/2, r.W-r.W/2
139	}
140	return r
141}

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