feat/data-dnd e05cb15

scene.go RawHistoryFolder

1package vulkan
2
3import (
4	"image"
5	"math"
6	"strconv"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11// sceneWalk turns one scene into quads in paint order, copying new wl_shm
12// content into GPU buffers on the way. It lives for one frame.
13type sceneWalk struct {
14	r        *Renderer
15	s        ports.Scene
16	contents map[ports.WindowID]ports.SurfaceContent
17	dmg      *damageRegion
18	// scale maps the scene's logical pixels to scene-physical ones.
19	scale float64
20	// zoom shrinks the surfaces of the window being placed: an overview
21	// preview (ports.SceneWindow.Preview); 1 otherwise.
22	zoom float64
23	// pulse is the focus effect of the window being placed
24	// (ports.SceneWindow.FocusEffect); 0 otherwise.
25	pulse float32
26	// alpha is the opacity of the window being drawn, 1 - its Fade
27	// (ports.SceneWindow.Fade): its fills, lines and surfaces are scaled by
28	// it (premultiplied); 1 otherwise.
29	alpha  float32
30	bounds image.Rectangle
31	// tileBounds is bounds cut to Scene.TileArea: where tiles and their
32	// lines draw.
33	tileBounds image.Rectangle
34	draws      []draw
35}
36
37// draws walks the scene into quads in paint order.
38func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw {
39	clear(r.scratchDraws) // release references from longer earlier scenes
40	// The walk stays in scene-physical space (the target's size, swapped for
41	// a rotated output); orient maps the draws to the target afterwards.
42	sw, sh := r.sceneSize(s.Transform)
43	w := &sceneWalk{r: r, s: s, contents: contents, dmg: dmg, scale: s.Scale, zoom: 1, alpha: 1, bounds: image.Rect(0, 0, sw, sh), draws: r.scratchDraws[:0]}
44	r.scratchCovers = r.scratchCovers[:0]
45	if w.scale <= 0 {
46		w.scale = 1
47	}
48	w.layers(false)
49	outputBounds := w.bounds
50	if s.WorkspaceClip != (ports.Rect{}) {
51		c := s.WorkspaceClip
52		w.bounds = w.bounds.Intersect(w.physRect(c.X, c.Y, c.W, c.H))
53	}
54	w.tileBounds = w.bounds
55	if a, ok := s.TileArea(); ok {
56		w.tileBounds = w.bounds.Intersect(w.physRect(a.X, a.Y, a.W, a.H))
57	}
58	w.windows()
59	w.popups(false)
60	w.dropHints()
61	w.bounds = outputBounds
62	w.layers(true)
63	w.popups(true)
64	w.captureIndicators()
65	r.scratchDraws = w.draws
66	return w.draws
67}
68
69// captureIndicators draws the compositor's capture indicator over everything
70// (ports.CaptureIndicator). The border is four fills on the inside edge of the
71// target, so it stays visible on a monitor edge; the pill is a small rounded
72// square. Sizes are logical like the rest of the scene and scale with it. A
73// scene without indicators draws nothing: captures are rendered from scenes
74// that never carry them.
75func (w *sceneWalk) captureIndicators() {
76	if len(w.s.CaptureIndicators) == 0 {
77		return
78	}
79	col := parseColor(ports.CaptureBorderColor)
80	for _, m := range w.s.CaptureIndicators {
81		t := m.Rect
82		if t.W <= 0 || t.H <= 0 {
83			continue
84		}
85		if m.Pill {
86			w.pill(w.physRect(t.X, t.Y, t.W, t.H), col)
87			continue
88		}
89		b := ports.CaptureBorderOf(t.W, t.H)
90		if b <= 0 {
91			// Too thin for a border (core inflates such marks, so this is a
92			// 1-pixel output): a listed mark always draws something.
93			w.fill(w.physRect(t.X, t.Y, t.W, t.H), col)
94			continue
95		}
96		w.fill(w.physRect(t.X, t.Y, t.W, b), col)
97		w.fill(w.physRect(t.X, t.Y+t.H-b, t.W, b), col)
98		w.fill(w.physRect(t.X, t.Y+b, b, t.H-2*b), col)
99		w.fill(w.physRect(t.X+t.W-b, t.Y+b, b, t.H-2*b), col)
100	}
101}
102
103// dropHints fills where a dragged tile would land (ports.Scene.DropHints),
104// in the active border color, over the workspace's windows.
105func (w *sceneWalk) dropHints() {
106	if len(w.s.DropHints) == 0 {
107		return
108	}
109	col := parseColor(w.s.Border.Active)
110	for _, r := range w.s.DropHints {
111		if r.W > 0 && r.H > 0 {
112			w.fill(w.physRect(r.X, r.Y, r.W, r.H), col)
113		}
114	}
115}
116
117// pill fills a rounded square (physical pixels) with three rects: a full
118// middle and two bands narrowed by the corner radius.
119func (w *sceneWalk) pill(r image.Rectangle, c [3]uint8) {
120	rad := min(w.phys(ports.CapturePillRadius), r.Dx()/2, r.Dy()/2)
121	if rad <= 0 {
122		w.fill(r, c)
123		return
124	}
125	cut := (rad + 1) / 2
126	w.fill(image.Rect(r.Min.X+cut, r.Min.Y, r.Max.X-cut, r.Min.Y+rad), c)
127	w.fill(image.Rect(r.Min.X, r.Min.Y+rad, r.Max.X, r.Max.Y-rad), c)
128	w.fill(image.Rect(r.Min.X+cut, r.Max.Y-rad, r.Max.X-cut, r.Max.Y), c)
129}
130
131func (w *sceneWalk) phys(v int) int { return int(math.Round(float64(v) * w.scale)) }
132
133func (w *sceneWalk) physRect(x, y, width, height int) image.Rectangle {
134	return image.Rect(w.phys(x), w.phys(y), w.phys(x+width), w.phys(y+height))
135}
136
137// physRectF is physRect for fractional logical coordinates (zoomed surfaces).
138func (w *sceneWalk) physRectF(x, y, width, height float64) image.Rectangle {
139	p := func(v float64) int { return int(math.Round(v * w.scale)) }
140	return image.Rect(p(x), p(y), p(x+width), p(y+height))
141}
142
143// fill draws a solid rect (physical pixels), at the walk's alpha.
144func (w *sceneWalk) fill(rect image.Rectangle, c [3]uint8) {
145	rect = rect.Intersect(w.bounds)
146	if rect.Empty() {
147		return
148	}
149	dr := w.r.fillDraw(rect, c)
150	if w.alpha < 1 {
151		w.r.fadeSolid(&dr, w.alpha)
152	}
153	w.draws = append(w.draws, dr)
154}
155
156// dim paints a translucent black quad over rect (physical), clipped to the
157// output, at alpha scaled by the walk's alpha. Damage clipping later limits
158// it to the region being repainted, just like other fills.
159func (w *sceneWalk) dim(rect image.Rectangle, alpha float64) {
160	if rect = rect.Intersect(w.bounds); !rect.Empty() {
161		w.draws = append(w.draws, w.r.dimDraw(rect, alpha*float64(w.alpha)))
162	}
163}
164
165// layers draws the layer surfaces below the windows, or above them.
166// The scene holds only the shown ones: core hides them under a fullscreen
167// window.
168func (w *sceneWalk) layers(afterWindows bool) {
169	for _, layer := range w.s.Layers {
170		if (layer.Layer >= ports.LayerTop) != afterWindows || layer.Rect.W <= 0 || layer.Rect.H <= 0 {
171			continue
172		}
173		content := w.contents[layer.ID]
174		w.dmg.window(layer.ID, content, w.physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H))
175		w.place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)
176	}
177}
178
179// drawsAsWindow reports whether windows() paints win as a window (not
180// hidden, a popup or empty).
181func drawsAsWindow(win ports.SceneWindow) bool {
182	return !win.Hidden && !win.Popup && win.Rect.W > 0 && win.Rect.H > 0
183}
184
185// opensFloats reports whether win, once reached, closes the tiles: the tile
186// lines and the dim veil are painted under it (an overview preview of a
187// float is not one).
188func opensFloats(win ports.SceneWindow) bool {
189	return !win.Below && win.Floating && win.Preview == 0
190}
191
192// floatsStart is the index of the first window that opens the floats in s,
193// or -1 when none does: where windows() splits its paint order.
194func floatsStart(s ports.Scene) int {
195	for i, win := range s.Windows {
196		if drawsAsWindow(win) && opensFloats(win) {
197			return i
198		}
199	}
200	return -1
201}
202
203// windows draws ordered placements with borders. Tile lines go after
204// the column group, before the first float above it; an overview preview
205// of a float is not one. The veil goes under that float, or under every
206// window with DimBehind.
207func (w *sceneWalk) windows() {
208	if w.s.DimBehind && w.s.Dim > 0 {
209		w.dim(w.bounds, w.s.Dim)
210	}
211	tileLines := false
212	for _, win := range w.s.Windows {
213		if !drawsAsWindow(win) {
214			continue
215		}
216		if opensFloats(win) && !tileLines {
217			w.tileSeparators()
218			tileLines = true
219			if w.s.Dim > 0 && !w.s.DimBehind {
220				w.dim(w.bounds, w.s.Dim)
221			}
222		}
223		if win.Fade >= 1 {
224			// Faded out: it still opens the floats (its lines and veil are
225			// painted under where it would be), but draws nothing.
226			continue
227		}
228		w.alpha = 1 - float32(max(0, win.Fade))
229		windowBounds := w.bounds
230		if win.Tile() {
231			// A tile never draws over a panel (Scene.TileArea).
232			w.bounds = w.tileBounds
233		}
234		// Content sits inside the border; core sized the client to match.
235		b, inset := 0, ports.Sides(0)
236		if !win.Fullscreen {
237			b, inset = w.s.Border.Width, win.Inset
238		}
239		c := win.Rect.Inset(inset, b)
240		body := w.physRect(c.X, c.Y, c.W, c.H)
241		content := w.contents[win.ID]
242		// Damage counts only what is drawn: never the panel under a tile.
243		w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H).Intersect(w.bounds))
244		// Until its first buffer, a window shows the background: no flash.
245		w.fill(body, parseColor(w.s.Background))
246		if !content.Empty() {
247			// An animating frame zooms its content (Zoom); a card draws
248			// at its Preview.
249			if win.Zoom > 0 {
250				w.zoom = win.Zoom
251			} else if win.Preview > 0 {
252				w.zoom = win.Preview
253			}
254			w.pulse = float32(max(0, min(win.FocusEffect, 1)))
255			w.place(win.ID, &content, c.X, c.Y, c.W, c.H)
256			w.zoom, w.pulse = 1, 0
257		}
258		if win.Floating {
259			w.separators(win.ID)
260		}
261		if win.Dim > 0 {
262			// A stashed window peeking in: dimmed with its border.
263			w.dim(w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H), win.Dim)
264		}
265		w.alpha, w.bounds = 1, windowBounds
266	}
267	if !tileLines {
268		w.tileSeparators()
269	}
270}
271
272// tileSeparators draws the tile lines, cut like the tiles at the panels.
273func (w *sceneWalk) tileSeparators() {
274	b := w.bounds
275	w.bounds = w.tileBounds
276	w.separators(0)
277	w.bounds = b
278}
279
280// separators draws a window's border lines in order, active lines over
281// inactive ones.
282func (w *sceneWalk) separators(id ports.WindowID) {
283	for _, sep := range w.s.Separators {
284		if sep.Window != id {
285			continue
286		}
287		col := w.s.Border.Inactive
288		if sep.Active {
289			col = w.s.Border.Active
290		}
291		if col != "" {
292			w.fill(w.physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col))
293		}
294	}
295}
296
297// popups draws only what the client drew, shadows clipped. Window popups
298// stay with the windows; a layer's go over every layer.
299func (w *sceneWalk) popups(overLayers bool) {
300	for _, win := range w.s.Windows {
301		if win.Popup && win.OverLayers == overLayers && !win.Hidden && win.Rect.W > 0 && win.Rect.H > 0 {
302			content := w.contents[win.ID]
303			w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H))
304			w.place(win.ID, &content, win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)
305		}
306	}
307}
308
309// place draws a surface tree with its window geometry at (x, y) logical,
310// clipped to width×height logical: client shadows fall outside. Every
311// surface of the tree is keyed by its stable surface identity.
312func (w *sceneWalk) place(id ports.WindowID, content *ports.SurfaceContent, x, y, width, height int) {
313	clip := image.Rect(x, y, x+width, y+height)
314	// Offsets inside the tree shrink with a zoomed (preview) window.
315	z := w.zoom
316	ox, oy := float64(x)-float64(content.Geometry.X)*z, float64(y)-float64(content.Geometry.Y)*z
317	at := func(ch *ports.Subsurface) (float64, float64) { return ox + float64(ch.X)*z, oy + float64(ch.Y)*z }
318	covers := w.opaqueChildren(content, ox, oy, clip)
319	for i := range content.Children {
320		if ch := &content.Children[i]; ch.Below {
321			cx, cy := at(ch)
322			w.surface(&ch.SurfaceContent, cx, cy, clip, shmKey{id, ch.Surface}, ch.Version, covers, false)
323		}
324	}
325	w.surface(content, ox, oy, clip, shmKey{id, content.Surface}, content.Version, covers, true)
326	for i := range content.Children {
327		if ch := &content.Children[i]; !ch.Below {
328			cx, cy := at(ch)
329			w.surface(&ch.SurfaceContent, cx, cy, clip, shmKey{id, ch.Surface}, ch.Version, nil, false)
330		}
331	}
332}
333
334// opaqueChildren gives the physical rects shown opaque by the subsurfaces
335// above the root (e.g. a game's GPU surface over its window's shm buffer):
336// they hide the surfaces below them. A child that is not wholly opaque but
337// has an opaque region (Firefox's page, whose region stops a pixel short of
338// its edges) hides the surfaces below only inside that rect. A child whose
339// buffer will not draw is left out, so the surface below it still shows: a
340// dmabuf that fails to import (opaqueChildren pre-imports it) or an
341// unreadable shm buffer.
342func (w *sceneWalk) opaqueChildren(content *ports.SurfaceContent, ox, oy float64, clip image.Rectangle) []image.Rectangle {
343	covers := w.r.scratchCovers[:0]
344	for i := range content.Children {
345		ch := &content.Children[i]
346		if ch.Below || !ch.Opaque && ch.OpaqueRect.W <= 0 {
347			continue
348		}
349		full, dst, ok := w.surfaceRects(&ch.SurfaceContent, ox+float64(ch.X)*w.zoom, oy+float64(ch.Y)*w.zoom, clip)
350		if !ok || !w.drawable(&ch.SurfaceContent) {
351			continue
352		}
353		if !ch.Opaque {
354			if dst = opaqueCover(&ch.SurfaceContent, full, dst); dst.Empty() {
355				continue
356			}
357		}
358		covers = append(covers, dst)
359	}
360	w.r.scratchCovers = covers
361	return covers
362}
363
364// opaqueCover is the part of dst (physical, what the surface shows of full)
365// that its OpaqueRect maps to. Edges round inward, so every pixel of it is
366// inside the region; an edge on the surface's own edge stays on dst's.
367func opaqueCover(c *ports.SurfaceContent, full, dst image.Rectangle) image.Rectangle {
368	lw, lh := c.LogicalW, c.LogicalH
369	if lw <= 0 || lh <= 0 {
370		lw, lh = c.Width, c.Height
371	}
372	o := c.OpaqueRect
373	if lw <= 0 || lh <= 0 || o.W <= 0 || o.H <= 0 {
374		return image.Rectangle{}
375	}
376	// ceil and floor of full.Min + v*size/n, v >= 0.
377	lo := func(min, v, size, n int) int { return min + (v*size+n-1)/n }
378	hi := func(min, v, size, n int) int { return min + v*size/n }
379	r := image.Rect(lo(full.Min.X, max(o.X, 0), full.Dx(), lw), lo(full.Min.Y, max(o.Y, 0), full.Dy(), lh),
380		hi(full.Min.X, min(o.X+o.W, lw), full.Dx(), lw), hi(full.Min.Y, min(o.Y+o.H, lh), full.Dy(), lh))
381	return r.Intersect(dst)
382}
383
384// drawable reports whether the child's buffer will draw. GPU buffer
385// imports are cached, so pre-importing here costs nothing.
386func (w *sceneWalk) drawable(c *ports.SurfaceContent) bool {
387	if c.Solid != nil {
388		return true
389	}
390	if c.DMABuf != nil {
391		_, err := w.r.importDMABuf(c.DMABuf)
392		return err == nil
393	}
394	b := c.SHM
395	if b == nil {
396		return false
397	}
398	_, err := w.r.shmPixels(b, b.Offset+(c.Height-1)*b.Stride+c.Width*4)
399	return err == nil
400}
401
402// surfaceRects gives the physical rect of a surface buffer with its origin
403// at (x, y) logical, shrunk by the walk's zoom, and the part of it clip
404// (logical) shows. ok is false when there is nothing to draw.
405func (w *sceneWalk) surfaceRects(content *ports.SurfaceContent, x, y float64, clip image.Rectangle) (full, dst image.Rectangle, ok bool) {
406	lw, lh := content.LogicalW, content.LogicalH
407	if lw <= 0 || lh <= 0 {
408		lw, lh = content.Width, content.Height
409	}
410	if !content.HasBuffer() || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 {
411		return full, dst, false
412	}
413	if content.SHM != nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) {
414		return full, dst, false
415	}
416	full = w.physRectF(x, y, float64(lw)*w.zoom, float64(lh)*w.zoom)
417	// A buffer drawn at the physical size (fractional-scale clients round
418	// w*scale, we round per edge) is copied 1:1, never resampled for 1px.
419	near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 }
420	sourceW, sourceH := content.Width, content.Height
421	if content.Source[2] > 0 {
422		sourceW, sourceH = int(content.Source[2]), int(content.Source[3])
423	}
424	sourceW, sourceH = content.Transform.Size(sourceW, sourceH)
425	if near(full.Dx(), sourceW) && near(full.Dy(), sourceH) {
426		full.Max = full.Min.Add(image.Pt(sourceW, sourceH))
427	}
428	dst = full.Intersect(w.physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())).Intersect(w.bounds)
429	return full, dst, !dst.Empty()
430}
431
432// surface draws one surface buffer with its origin at (x, y) logical,
433// clipped to clip (logical), unless an opaque surface above it (covers,
434// physical) hides all of it: its buffer is then neither copied nor drawn.
435// A cover over only part of it clips the draw to the rest.
436func (w *sceneWalk) surface(content *ports.SurfaceContent, x, y float64, clip image.Rectangle, key shmKey, seq uint64, covers []image.Rectangle, root bool) {
437	full, dst, ok := w.surfaceRects(content, x, y, clip)
438	if !ok {
439		return
440	}
441	for _, c := range covers {
442		if dst.In(c) {
443			return
444		}
445	}
446	if root {
447		w.dmg.content(key.win, content, full, dst)
448	}
449	n := len(w.draws)
450	w.content(dst, full, content, key, seq, root)
451	if len(w.draws) == n+1 && len(covers) > 0 {
452		// The quad's mapping comes from full: only its rect shrinks.
453		d := w.draws[n]
454		w.draws = w.draws[:n]
455		w.split(d, dst, covers)
456	}
457}
458
459// Splitting a quad around covers is bounded: covers come from the client, and
460// each can cut a quad into four strips, recursively. Past these bounds the
461// quad is drawn whole: the covers above it hide the rest, at the cost of
462// overdraw.
463const (
464	maxSplitCovers = 8  // covers crossing the quad that are still split around
465	maxSplitStrips = 32 // draws one quad may become
466)
467
468// split appends d, a quad over rect, minus the covers, or the whole quad
469// when the covers cut it into too many strips.
470func (w *sceneWalk) split(d draw, rect image.Rectangle, covers []image.Rectangle) {
471	crossing := 0
472	for _, c := range covers {
473		if !rect.Intersect(c).Empty() {
474			crossing++
475		}
476	}
477	n := len(w.draws)
478	if crossing <= maxSplitCovers {
479		w.uncovered(d, rect, covers, n+maxSplitStrips)
480		if len(w.draws) <= n+maxSplitStrips {
481			return
482		}
483		w.draws = w.draws[:n]
484	}
485	w.quad(d, rect)
486}
487
488// uncovered appends d, a quad over rect, minus the covers: the strips of
489// rect around each cover, in turn. d keeps its mapping. It stops once the
490// draws pass limit, for the caller to drop them.
491func (w *sceneWalk) uncovered(d draw, rect image.Rectangle, covers []image.Rectangle, limit int) {
492	if len(w.draws) > limit {
493		return
494	}
495	for i, c := range covers {
496		in := rect.Intersect(c)
497		if in.Empty() {
498			continue
499		}
500		rest := covers[i+1:]
501		w.uncovered(d, image.Rect(rect.Min.X, rect.Min.Y, rect.Max.X, in.Min.Y), rest, limit)
502		w.uncovered(d, image.Rect(rect.Min.X, in.Max.Y, rect.Max.X, rect.Max.Y), rest, limit)
503		w.uncovered(d, image.Rect(rect.Min.X, in.Min.Y, in.Min.X, in.Max.Y), rest, limit)
504		w.uncovered(d, image.Rect(in.Max.X, in.Min.Y, rect.Max.X, in.Max.Y), rest, limit)
505		return
506	}
507	w.quad(d, rect)
508}
509
510// quad appends d over rect, unless it is empty.
511func (w *sceneWalk) quad(d draw, rect image.Rectangle) {
512	if rect.Empty() {
513		return
514	}
515	d.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
516	w.draws = append(w.draws, d)
517}
518
519// content draws the part of a buffer mapped onto full (physical pixels)
520// that dst shows. key names the surface, seq its content.
521func (w *sceneWalk) content(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64, root bool) {
522	r := w.r
523	rect := dst
524	if rect.Empty() {
525		return
526	}
527	if content.Solid != nil {
528		dr := r.solidDraw(rect, content)
529		if w.alpha < 1 {
530			r.fadeSolid(&dr, w.alpha)
531		}
532		if dr.pc.color[3] > 0 {
533			w.draws = append(w.draws, dr)
534		}
535		return
536	}
537	if content.DMABuf != nil {
538		im, err := r.importDMABuf(content.DMABuf)
539		if err != nil {
540			// The window shows its background until a buffer imports.
541			return
542		}
543		dr := r.contentDraw(rect, full, content.Width, content.Height, content.Source, content.Transform, modeImage, content.Opaque)
544		r.setContentColor(&dr, content)
545		dr.pc.color[3] *= w.alpha
546		dr.pc.mapy[2] = w.pulse
547		if im.yuv {
548			dr.pc.misc[1] |= flagYUV
549			if content.DMABuf.Format == fourcc('P', '0', '1', '0') {
550				dr.pc.misc[1] |= flagP010
551			}
552			coeff, ran := content.Color.Coefficients, content.Color.Range
553			if coeff == 0 {
554				coeff = 2 /* BT.709 */
555			}
556			if ran == 0 {
557				ran = 2 /* limited */
558			}
559			dr.pc.buf[3] = uint32(coeff) | uint32(ran)<<8 | uint32(content.Color.Chroma)<<16
560		}
561		dr.im = im
562		dr.acquire = content.Acquire
563		w.draws = append(w.draws, dr)
564		return
565	}
566	b := content.SHM
567	st := shmState{w: content.Width, h: content.Height, seq: seq, windowSeq: content.Seq}
568	// The copy of this content, if any, needs no pool: a kept (closing)
569	// window whose pool file is closed still draws.
570	c := r.shmCached(key, st)
571	if c != nil {
572		// As a drawn pool, for Trim; nothing is mapped for it.
573		if m := r.pools[b.Pool]; m != nil {
574			m.last = r.frame
575		}
576	} else {
577		pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4)
578		if err != nil {
579			return
580		}
581		// Damage history is the root surface's: children copy in full.
582		var damage func(uint64) ([]ports.Rect, bool)
583		if root {
584			damage = content.DamageSince
585		}
586		if c, err = r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage); err != nil {
587			return
588		}
589	}
590	dr := r.contentDraw(rect, full, content.Width, content.Height, content.Source, content.Transform, modeBuffer, content.Opaque)
591	r.setContentColor(&dr, content)
592	dr.pc.color[3] *= w.alpha
593	dr.pc.mapy[2] = w.pulse
594	dr.shm = c
595	dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0}
596	w.draws = append(w.draws, dr)
597}
598
599// setContentColor selects only color encodings the protocol accepts, and
600// the surface's fade.
601func (r *Renderer) setContentColor(dr *draw, content *ports.SurfaceContent) {
602	dr.pc.color[3] = 1 - max(0, min(1, content.Fade))
603	c := content.Color
604	if c.IsPQ2020() {
605		dr.pc.misc[1] |= flagPQ
606	} else if c.IsExtendedLinear() {
607		dr.pc.misc[1] |= flagExtendedLinear
608	} else {
609		return
610	}
611	dr.pc.color[0] = float32(r.hdrNits)
612	if dr.pc.color[0] <= 0 {
613		dr.pc.color[0] = 203
614	}
615}
616
617func parseColor(s string) [3]uint8 {
618	var c [3]uint8
619	if len(s) != 7 || s[0] != '#' {
620		return c
621	}
622	for i := range c {
623		v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8)
624		if e != nil {
625			return [3]uint8{}
626		}
627		c[i] = uint8(v)
628	}
629	return c
630}

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