feat/hint-hybrid aed7ca4

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

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 23

Tags 10

↑↓ moveenter openesc close