detached 794cca9

refactor(core): build each screen's scene in sceneFor, out of publishFrame

commit
794cca943ac1e828e601f5fc336a361291136f15
author
brice · · 2026-10-08 05:53 UTC
parent
208528f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +146 -127
M internal/core/core.go +4 -127
@@ -587,134 +587,11 @@ func (c *Core) publishFrame(ctx context.Context, only *screen) error {
587587 // Before the first output (and after the last is unplugged) the
588588 // placeholder's scene has no output name: no renderer draws it.
589589 for i, sc := range c.screens {
590- o := sc.mon.Output()
591- // frame is the viewport of the workspace on screen: the whole output
592- // unless it has a size override (never in the overview).
593- frame := sc.mon.Frame()
594- var clip Rect
595- if frame != (Rect{W: o.W, H: o.H}) {
596- clip = frame
597- }
598- // layout is what is drawn; settled (same indexes) is where the
599- // windows are going, and alone sizes the configures.
600- layout, settled := sc.shown, sc.settledLayout
601- scene := ports.Scene{Security: c.security, Output: sc.name(), OutputWidth: o.W, OutputHeight: o.H, WorkspaceClip: clip, Scale: sc.scale, Transform: sc.transform, Off: sc.off, Background: c.cfg.Background.Color, Border: ports.Border{Width: c.cfg.Border.Width, Active: c.cfg.Border.Active, Inactive: c.cfg.Border.Inactive}, Windows: make([]ports.SceneWindow, 0, len(layout)+len(c.popupOrder)), Layers: shownLayers(sc)}
602- var real map[WindowID]Placement
603- if sc.mon.ov.open {
604- // Local to this screen's build: reused, cleared each time.
605- if c.overviewReal == nil {
606- c.overviewReal = make(map[WindowID]Placement)
607- }
608- clear(c.overviewReal)
609- real = c.overviewReal
610- for w := range sc.mon.all() {
611- c.realBuf = w.layoutInto(c.realBuf)
612- for _, p := range c.realBuf {
613- real[p.ID] = p
614- }
615- }
616- }
617- scene.Dim = floatDim(layout, frame, c.cfg.Floating.Dim)
618- if sc.mon.ov.open {
619- // Darken the wallpaper around the previews.
620- scene.Dim = c.cfg.Floating.Dim
621- scene.DimBehind = scene.Dim > 0
622- }
623- if d := c.drag; d != nil && d.target.screen == sc && d.target.kind != dropNone {
624- scene.DropHints = slices.Clone(d.target.hints)
625- }
626- if sc.mon.ov.open {
627- // Frame the selection and separate numbered and named row groups.
628- _, scene.Separators = sc.mon.overviewRows()
629- scene.Separators = append(scene.Separators, overviewOutline(layout, max(c.cfg.Border.Width, 2))...)
630- } else {
631- // Only the focused output lights the focused window's lines.
632- scene.Separators = separators(layout, c.cfg.Border.Width, sc.mon.Current().gap(), frame, i == c.focusScreen)
633- }
634- // A window alone on screen needs no pulse to show it has the focus.
635- // A tile under a panel (the next cascade band) does not count.
636- alone := i == c.focusScreen && c.pulse.target != 0 && visibleCount(layout, sc.mon.Current().Usable) == 1
637- for k, p := range layout {
638- ps := settled[k]
639- // Only the focused output has an activated window.
640- focused := p.Focused && i == c.focusScreen
641- sw := ports.SceneWindow{ID: p.ID, Rect: p.Rect, Focused: focused, Fullscreen: p.Fullscreen, Hidden: p.Hidden, Floating: p.Floating, Below: p.Below, Inset: p.Inset, Preview: p.Preview, Fade: p.Fade}
642- if p.Zoom > 0 && (p.Zoom < 1 || p.Preview > 0) {
643- // A scale motion: the content follows the drawn size. A
644- // card in flight drawn at its size still needs Zoom 1:
645- // without it the renderer would shrink it by Preview.
646- sw.Zoom = p.Zoom
647- }
648- // A peek's veil is the configured one plus its animated offset.
649- sw.Dim = max(0, min(p.Dim+c.peekDim(p), 1))
650- if p.Leaving {
651- // A window fading out after it closed or hid: drawn, but
652- // hidden to everything else, its configures included.
653- sw.Hidden = false
654- scene.Windows = append(scene.Windows, sw)
655- // A window the workspace still holds (a hidden stash
656- // window) keeps its last configure: prune would forget it,
657- // and its next one would start from nothing. A closed one
658- // was forgotten at its unmap and stays so.
659- if w, _ := sc.mon.find(p.ID); w != nil {
660- c.configures.keep(p.ID)
661- }
662- continue
663- }
664- if focused && p.ID == c.pulse.target && !alone && !p.Fullscreen && !p.Hidden && p.Preview == 0 && !sc.mon.ov.open {
665- drawable = true
666- if p.ID == c.pulse.id {
667- sw.FocusEffect = pulse
668- }
669- }
670- scene.Windows = append(scene.Windows, sw)
671- t := configureTarget{output: sc.name(), area: frame, focused: focused, captured: capture != nil && capture.window == p.ID}
672- if !p.Hidden && p.Preview == 0 {
673- // Only a sized configure needs the client size.
674- t.client, t.imposed = c.clientRect(ps), sc.mon.Current().imposedFloat(p.ID)
675- } else if t.captured && p.Hidden {
676- // A captured hidden window is sized like its capture.
677- t.client = capture.windowSz
678- } else if p.Preview > 0 && !p.Hidden {
679- if rp, ok := real[p.ID]; ok && !rp.Hidden {
680- t.realTiled = !rp.Floating
681- t.client = c.clientRect(rp)
682- }
683- }
684- cp, t := c.captureConfigure(sc, ps, t)
685- if v, send := c.configures.nextWithCapture(ps, t, cp); send {
686- if err := c.command(ctx, v); err != nil {
687- return err
688- }
689- c.configures.mark(v)
690- }
691- }
692- scene.Windows = append(scene.Windows, c.scenePopups(sc)...)
693- scene.CaptureIndicators = c.captureIndicators(sc)
694- scene.Capture = c.captureSceneFor(sc, capture)
695- // A scene carrying a capture image always gets a fresh Seq, and so
696- // does one of an animating screen (the one being stepped, or any
697- // when none is): the outputs drop a scene they already show, and a
698- // spring that starts at the previous scene, or rounds to it in its
699- // tail, would get no flip and wait for the fallback timer. Any other
700- // keeps its output's Seq while it draws the same, so an idle output
701- // is not recomposed.
702- withCapture := scene.Capture != nil && (sc == capture.hiddenScr || sc == capture.windowScr)
703- animating := (sc.springing() || c.pulsing(sc)) && (only == nil || only == sc)
704- if !withCapture && !animating && sc.last.Seq != 0 && scene.SameAs(sc.last) {
705- scene.Seq = sc.last.Seq
706- } else {
707- c.seq++
708- scene.Seq = c.seq
709- }
710- if withCapture {
711- switch sc {
712- case capture.hiddenScr:
713- scene.CaptureScene = c.captureScene(scene.Seq)
714- case capture.windowScr:
715- scene.CaptureScene = c.captureWindowScene(capture, scene.Seq)
716- }
590+ scene, shown, err := c.sceneFor(ctx, i, sc, only, capture, pulse)
591+ if err != nil {
592+ return err
717593 }
594+ drawable = drawable || shown
718595 sc.last = scene
719596 scenes = append(scenes, scene)
720597 }
@@ -587,134 +587,11 @@ func (c *Core) publishFrame(ctx context.Context, only *screen) error {
587 // Before the first output (and after the last is unplugged) the587 // Before the first output (and after the last is unplugged) the
588 // placeholder's scene has no output name: no renderer draws it.588 // placeholder's scene has no output name: no renderer draws it.
589 for i, sc := range c.screens {589 for i, sc := range c.screens {
590- o := sc.mon.Output()590+ scene, shown, err := c.sceneFor(ctx, i, sc, only, capture, pulse)
591- // frame is the viewport of the workspace on screen: the whole output591+ if err != nil {
592- // unless it has a size override (never in the overview).592+ return err
593- frame := sc.mon.Frame()
594- var clip Rect
595- if frame != (Rect{W: o.W, H: o.H}) {
596- clip = frame
597- }
598- // layout is what is drawn; settled (same indexes) is where the
599- // windows are going, and alone sizes the configures.
600- layout, settled := sc.shown, sc.settledLayout
601- scene := ports.Scene{Security: c.security, Output: sc.name(), OutputWidth: o.W, OutputHeight: o.H, WorkspaceClip: clip, Scale: sc.scale, Transform: sc.transform, Off: sc.off, Background: c.cfg.Background.Color, Border: ports.Border{Width: c.cfg.Border.Width, Active: c.cfg.Border.Active, Inactive: c.cfg.Border.Inactive}, Windows: make([]ports.SceneWindow, 0, len(layout)+len(c.popupOrder)), Layers: shownLayers(sc)}
602- var real map[WindowID]Placement
603- if sc.mon.ov.open {
604- // Local to this screen's build: reused, cleared each time.
605- if c.overviewReal == nil {
606- c.overviewReal = make(map[WindowID]Placement)
607- }
608- clear(c.overviewReal)
609- real = c.overviewReal
610- for w := range sc.mon.all() {
611- c.realBuf = w.layoutInto(c.realBuf)
612- for _, p := range c.realBuf {
613- real[p.ID] = p
614- }
615- }
616- }
617- scene.Dim = floatDim(layout, frame, c.cfg.Floating.Dim)
618- if sc.mon.ov.open {
619- // Darken the wallpaper around the previews.
620- scene.Dim = c.cfg.Floating.Dim
621- scene.DimBehind = scene.Dim > 0
622- }
623- if d := c.drag; d != nil && d.target.screen == sc && d.target.kind != dropNone {
624- scene.DropHints = slices.Clone(d.target.hints)
625- }
626- if sc.mon.ov.open {
627- // Frame the selection and separate numbered and named row groups.
628- _, scene.Separators = sc.mon.overviewRows()
629- scene.Separators = append(scene.Separators, overviewOutline(layout, max(c.cfg.Border.Width, 2))...)
630- } else {
631- // Only the focused output lights the focused window's lines.
632- scene.Separators = separators(layout, c.cfg.Border.Width, sc.mon.Current().gap(), frame, i == c.focusScreen)
633- }
634- // A window alone on screen needs no pulse to show it has the focus.
635- // A tile under a panel (the next cascade band) does not count.
636- alone := i == c.focusScreen && c.pulse.target != 0 && visibleCount(layout, sc.mon.Current().Usable) == 1
637- for k, p := range layout {
638- ps := settled[k]
639- // Only the focused output has an activated window.
640- focused := p.Focused && i == c.focusScreen
641- sw := ports.SceneWindow{ID: p.ID, Rect: p.Rect, Focused: focused, Fullscreen: p.Fullscreen, Hidden: p.Hidden, Floating: p.Floating, Below: p.Below, Inset: p.Inset, Preview: p.Preview, Fade: p.Fade}
642- if p.Zoom > 0 && (p.Zoom < 1 || p.Preview > 0) {
643- // A scale motion: the content follows the drawn size. A
644- // card in flight drawn at its size still needs Zoom 1:
645- // without it the renderer would shrink it by Preview.
646- sw.Zoom = p.Zoom
647- }
648- // A peek's veil is the configured one plus its animated offset.
649- sw.Dim = max(0, min(p.Dim+c.peekDim(p), 1))
650- if p.Leaving {
651- // A window fading out after it closed or hid: drawn, but
652- // hidden to everything else, its configures included.
653- sw.Hidden = false
654- scene.Windows = append(scene.Windows, sw)
655- // A window the workspace still holds (a hidden stash
656- // window) keeps its last configure: prune would forget it,
657- // and its next one would start from nothing. A closed one
658- // was forgotten at its unmap and stays so.
659- if w, _ := sc.mon.find(p.ID); w != nil {
660- c.configures.keep(p.ID)
661- }
662- continue
663- }
664- if focused && p.ID == c.pulse.target && !alone && !p.Fullscreen && !p.Hidden && p.Preview == 0 && !sc.mon.ov.open {
665- drawable = true
666- if p.ID == c.pulse.id {
667- sw.FocusEffect = pulse
668- }
669- }
670- scene.Windows = append(scene.Windows, sw)
671- t := configureTarget{output: sc.name(), area: frame, focused: focused, captured: capture != nil && capture.window == p.ID}
672- if !p.Hidden && p.Preview == 0 {
673- // Only a sized configure needs the client size.
674- t.client, t.imposed = c.clientRect(ps), sc.mon.Current().imposedFloat(p.ID)
675- } else if t.captured && p.Hidden {
676- // A captured hidden window is sized like its capture.
677- t.client = capture.windowSz
678- } else if p.Preview > 0 && !p.Hidden {
679- if rp, ok := real[p.ID]; ok && !rp.Hidden {
680- t.realTiled = !rp.Floating
681- t.client = c.clientRect(rp)
682- }
683- }
684- cp, t := c.captureConfigure(sc, ps, t)
685- if v, send := c.configures.nextWithCapture(ps, t, cp); send {
686- if err := c.command(ctx, v); err != nil {
687- return err
688- }
689- c.configures.mark(v)
690- }
691- }
692- scene.Windows = append(scene.Windows, c.scenePopups(sc)...)
693- scene.CaptureIndicators = c.captureIndicators(sc)
694- scene.Capture = c.captureSceneFor(sc, capture)
695- // A scene carrying a capture image always gets a fresh Seq, and so
696- // does one of an animating screen (the one being stepped, or any
697- // when none is): the outputs drop a scene they already show, and a
698- // spring that starts at the previous scene, or rounds to it in its
699- // tail, would get no flip and wait for the fallback timer. Any other
700- // keeps its output's Seq while it draws the same, so an idle output
701- // is not recomposed.
702- withCapture := scene.Capture != nil && (sc == capture.hiddenScr || sc == capture.windowScr)
703- animating := (sc.springing() || c.pulsing(sc)) && (only == nil || only == sc)
704- if !withCapture && !animating && sc.last.Seq != 0 && scene.SameAs(sc.last) {
705- scene.Seq = sc.last.Seq
706- } else {
707- c.seq++
708- scene.Seq = c.seq
709- }
710- if withCapture {
711- switch sc {
712- case capture.hiddenScr:
713- scene.CaptureScene = c.captureScene(scene.Seq)
714- case capture.windowScr:
715- scene.CaptureScene = c.captureWindowScene(capture, scene.Seq)
716- }
717 }593 }
594+ drawable = drawable || shown
718 sc.last = scene595 sc.last = scene
719 scenes = append(scenes, scene)596 scenes = append(scenes, scene)
720 }597 }
A internal/core/scene.go +142 -0
@@ -0,0 +1,142 @@
1+package core
2+
3+import (
4+ "context"
5+ "slices"
6+
7+ "github.com/bnema/neferwl/internal/ports"
8+)
9+
10+// sceneFor builds the scene of screen i and sends the configures it sizes.
11+// drawable reports that it can show the focus pulse on the focused window.
12+func (c *Core) sceneFor(ctx context.Context, i int, sc *screen, only *screen, capture *capView, pulse float64) (scene ports.Scene, drawable bool, err error) {
13+ o := sc.mon.Output()
14+ // frame is the viewport of the workspace on screen: the whole output
15+ // unless it has a size override (never in the overview).
16+ frame := sc.mon.Frame()
17+ var clip Rect
18+ if frame != (Rect{W: o.W, H: o.H}) {
19+ clip = frame
20+ }
21+ // layout is what is drawn; settled (same indexes) is where the
22+ // windows are going, and alone sizes the configures.
23+ layout, settled := sc.shown, sc.settledLayout
24+ scene = ports.Scene{Security: c.security, Output: sc.name(), OutputWidth: o.W, OutputHeight: o.H, WorkspaceClip: clip, Scale: sc.scale, Transform: sc.transform, Off: sc.off, Background: c.cfg.Background.Color, Border: ports.Border{Width: c.cfg.Border.Width, Active: c.cfg.Border.Active, Inactive: c.cfg.Border.Inactive}, Windows: make([]ports.SceneWindow, 0, len(layout)+len(c.popupOrder)), Layers: shownLayers(sc)}
25+ var real map[WindowID]Placement
26+ if sc.mon.ov.open {
27+ // Local to this screen's build: reused, cleared each time.
28+ if c.overviewReal == nil {
29+ c.overviewReal = make(map[WindowID]Placement)
30+ }
31+ clear(c.overviewReal)
32+ real = c.overviewReal
33+ for w := range sc.mon.all() {
34+ c.realBuf = w.layoutInto(c.realBuf)
35+ for _, p := range c.realBuf {
36+ real[p.ID] = p
37+ }
38+ }
39+ }
40+ scene.Dim = floatDim(layout, frame, c.cfg.Floating.Dim)
41+ if sc.mon.ov.open {
42+ // Darken the wallpaper around the previews.
43+ scene.Dim = c.cfg.Floating.Dim
44+ scene.DimBehind = scene.Dim > 0
45+ }
46+ if d := c.drag; d != nil && d.target.screen == sc && d.target.kind != dropNone {
47+ scene.DropHints = slices.Clone(d.target.hints)
48+ }
49+ if sc.mon.ov.open {
50+ // Frame the selection and separate numbered and named row groups.
51+ _, scene.Separators = sc.mon.overviewRows()
52+ scene.Separators = append(scene.Separators, overviewOutline(layout, max(c.cfg.Border.Width, 2))...)
53+ } else {
54+ // Only the focused output lights the focused window's lines.
55+ scene.Separators = separators(layout, c.cfg.Border.Width, sc.mon.Current().gap(), frame, i == c.focusScreen)
56+ }
57+ // A window alone on screen needs no pulse to show it has the focus.
58+ // A tile under a panel (the next cascade band) does not count.
59+ alone := i == c.focusScreen && c.pulse.target != 0 && visibleCount(layout, sc.mon.Current().Usable) == 1
60+ for k, p := range layout {
61+ ps := settled[k]
62+ // Only the focused output has an activated window.
63+ focused := p.Focused && i == c.focusScreen
64+ sw := ports.SceneWindow{ID: p.ID, Rect: p.Rect, Focused: focused, Fullscreen: p.Fullscreen, Hidden: p.Hidden, Floating: p.Floating, Below: p.Below, Inset: p.Inset, Preview: p.Preview, Fade: p.Fade}
65+ if p.Zoom > 0 && (p.Zoom < 1 || p.Preview > 0) {
66+ // A scale motion: the content follows the drawn size. A
67+ // card in flight drawn at its size still needs Zoom 1:
68+ // without it the renderer would shrink it by Preview.
69+ sw.Zoom = p.Zoom
70+ }
71+ // A peek's veil is the configured one plus its animated offset.
72+ sw.Dim = max(0, min(p.Dim+c.peekDim(p), 1))
73+ if p.Leaving {
74+ // A window fading out after it closed or hid: drawn, but
75+ // hidden to everything else, its configures included.
76+ sw.Hidden = false
77+ scene.Windows = append(scene.Windows, sw)
78+ // A window the workspace still holds (a hidden stash
79+ // window) keeps its last configure: prune would forget it,
80+ // and its next one would start from nothing. A closed one
81+ // was forgotten at its unmap and stays so.
82+ if w, _ := sc.mon.find(p.ID); w != nil {
83+ c.configures.keep(p.ID)
84+ }
85+ continue
86+ }
87+ if focused && p.ID == c.pulse.target && !alone && !p.Fullscreen && !p.Hidden && p.Preview == 0 && !sc.mon.ov.open {
88+ drawable = true
89+ if p.ID == c.pulse.id {
90+ sw.FocusEffect = pulse
91+ }
92+ }
93+ scene.Windows = append(scene.Windows, sw)
94+ t := configureTarget{output: sc.name(), area: frame, focused: focused, captured: capture != nil && capture.window == p.ID}
95+ if !p.Hidden && p.Preview == 0 {
96+ // Only a sized configure needs the client size.
97+ t.client, t.imposed = c.clientRect(ps), sc.mon.Current().imposedFloat(p.ID)
98+ } else if t.captured && p.Hidden {
99+ // A captured hidden window is sized like its capture.
100+ t.client = capture.windowSz
101+ } else if p.Preview > 0 && !p.Hidden {
102+ if rp, ok := real[p.ID]; ok && !rp.Hidden {
103+ t.realTiled = !rp.Floating
104+ t.client = c.clientRect(rp)
105+ }
106+ }
107+ cp, t := c.captureConfigure(sc, ps, t)
108+ if v, send := c.configures.nextWithCapture(ps, t, cp); send {
109+ if err := c.command(ctx, v); err != nil {
110+ return ports.Scene{}, false, err
111+ }
112+ c.configures.mark(v)
113+ }
114+ }
115+ scene.Windows = append(scene.Windows, c.scenePopups(sc)...)
116+ scene.CaptureIndicators = c.captureIndicators(sc)
117+ scene.Capture = c.captureSceneFor(sc, capture)
118+ // A scene carrying a capture image always gets a fresh Seq, and so
119+ // does one of an animating screen (the one being stepped, or any
120+ // when none is): the outputs drop a scene they already show, and a
121+ // spring that starts at the previous scene, or rounds to it in its
122+ // tail, would get no flip and wait for the fallback timer. Any other
123+ // keeps its output's Seq while it draws the same, so an idle output
124+ // is not recomposed.
125+ withCapture := scene.Capture != nil && (sc == capture.hiddenScr || sc == capture.windowScr)
126+ animating := (sc.springing() || c.pulsing(sc)) && (only == nil || only == sc)
127+ if !withCapture && !animating && sc.last.Seq != 0 && scene.SameAs(sc.last) {
128+ scene.Seq = sc.last.Seq
129+ } else {
130+ c.seq++
131+ scene.Seq = c.seq
132+ }
133+ if withCapture {
134+ switch sc {
135+ case capture.hiddenScr:
136+ scene.CaptureScene = c.captureScene(scene.Seq)
137+ case capture.windowScr:
138+ scene.CaptureScene = c.captureWindowScene(capture, scene.Seq)
139+ }
140+ }
141+ return scene, drawable, nil
142+}
@@ -0,0 +1,142 @@
1+package core
2+
3+import (
4+ "context"
5+ "slices"
6+
7+ "github.com/bnema/neferwl/internal/ports"
8+)
9+
10+// sceneFor builds the scene of screen i and sends the configures it sizes.
11+// drawable reports that it can show the focus pulse on the focused window.
12+func (c *Core) sceneFor(ctx context.Context, i int, sc *screen, only *screen, capture *capView, pulse float64) (scene ports.Scene, drawable bool, err error) {
13+ o := sc.mon.Output()
14+ // frame is the viewport of the workspace on screen: the whole output
15+ // unless it has a size override (never in the overview).
16+ frame := sc.mon.Frame()
17+ var clip Rect
18+ if frame != (Rect{W: o.W, H: o.H}) {
19+ clip = frame
20+ }
21+ // layout is what is drawn; settled (same indexes) is where the
22+ // windows are going, and alone sizes the configures.
23+ layout, settled := sc.shown, sc.settledLayout
24+ scene = ports.Scene{Security: c.security, Output: sc.name(), OutputWidth: o.W, OutputHeight: o.H, WorkspaceClip: clip, Scale: sc.scale, Transform: sc.transform, Off: sc.off, Background: c.cfg.Background.Color, Border: ports.Border{Width: c.cfg.Border.Width, Active: c.cfg.Border.Active, Inactive: c.cfg.Border.Inactive}, Windows: make([]ports.SceneWindow, 0, len(layout)+len(c.popupOrder)), Layers: shownLayers(sc)}
25+ var real map[WindowID]Placement
26+ if sc.mon.ov.open {
27+ // Local to this screen's build: reused, cleared each time.
28+ if c.overviewReal == nil {
29+ c.overviewReal = make(map[WindowID]Placement)
30+ }
31+ clear(c.overviewReal)
32+ real = c.overviewReal
33+ for w := range sc.mon.all() {
34+ c.realBuf = w.layoutInto(c.realBuf)
35+ for _, p := range c.realBuf {
36+ real[p.ID] = p
37+ }
38+ }
39+ }
40+ scene.Dim = floatDim(layout, frame, c.cfg.Floating.Dim)
41+ if sc.mon.ov.open {
42+ // Darken the wallpaper around the previews.
43+ scene.Dim = c.cfg.Floating.Dim
44+ scene.DimBehind = scene.Dim > 0
45+ }
46+ if d := c.drag; d != nil && d.target.screen == sc && d.target.kind != dropNone {
47+ scene.DropHints = slices.Clone(d.target.hints)
48+ }
49+ if sc.mon.ov.open {
50+ // Frame the selection and separate numbered and named row groups.
51+ _, scene.Separators = sc.mon.overviewRows()
52+ scene.Separators = append(scene.Separators, overviewOutline(layout, max(c.cfg.Border.Width, 2))...)
53+ } else {
54+ // Only the focused output lights the focused window's lines.
55+ scene.Separators = separators(layout, c.cfg.Border.Width, sc.mon.Current().gap(), frame, i == c.focusScreen)
56+ }
57+ // A window alone on screen needs no pulse to show it has the focus.
58+ // A tile under a panel (the next cascade band) does not count.
59+ alone := i == c.focusScreen && c.pulse.target != 0 && visibleCount(layout, sc.mon.Current().Usable) == 1
60+ for k, p := range layout {
61+ ps := settled[k]
62+ // Only the focused output has an activated window.
63+ focused := p.Focused && i == c.focusScreen
64+ sw := ports.SceneWindow{ID: p.ID, Rect: p.Rect, Focused: focused, Fullscreen: p.Fullscreen, Hidden: p.Hidden, Floating: p.Floating, Below: p.Below, Inset: p.Inset, Preview: p.Preview, Fade: p.Fade}
65+ if p.Zoom > 0 && (p.Zoom < 1 || p.Preview > 0) {
66+ // A scale motion: the content follows the drawn size. A
67+ // card in flight drawn at its size still needs Zoom 1:
68+ // without it the renderer would shrink it by Preview.
69+ sw.Zoom = p.Zoom
70+ }
71+ // A peek's veil is the configured one plus its animated offset.
72+ sw.Dim = max(0, min(p.Dim+c.peekDim(p), 1))
73+ if p.Leaving {
74+ // A window fading out after it closed or hid: drawn, but
75+ // hidden to everything else, its configures included.
76+ sw.Hidden = false
77+ scene.Windows = append(scene.Windows, sw)
78+ // A window the workspace still holds (a hidden stash
79+ // window) keeps its last configure: prune would forget it,
80+ // and its next one would start from nothing. A closed one
81+ // was forgotten at its unmap and stays so.
82+ if w, _ := sc.mon.find(p.ID); w != nil {
83+ c.configures.keep(p.ID)
84+ }
85+ continue
86+ }
87+ if focused && p.ID == c.pulse.target && !alone && !p.Fullscreen && !p.Hidden && p.Preview == 0 && !sc.mon.ov.open {
88+ drawable = true
89+ if p.ID == c.pulse.id {
90+ sw.FocusEffect = pulse
91+ }
92+ }
93+ scene.Windows = append(scene.Windows, sw)
94+ t := configureTarget{output: sc.name(), area: frame, focused: focused, captured: capture != nil && capture.window == p.ID}
95+ if !p.Hidden && p.Preview == 0 {
96+ // Only a sized configure needs the client size.
97+ t.client, t.imposed = c.clientRect(ps), sc.mon.Current().imposedFloat(p.ID)
98+ } else if t.captured && p.Hidden {
99+ // A captured hidden window is sized like its capture.
100+ t.client = capture.windowSz
101+ } else if p.Preview > 0 && !p.Hidden {
102+ if rp, ok := real[p.ID]; ok && !rp.Hidden {
103+ t.realTiled = !rp.Floating
104+ t.client = c.clientRect(rp)
105+ }
106+ }
107+ cp, t := c.captureConfigure(sc, ps, t)
108+ if v, send := c.configures.nextWithCapture(ps, t, cp); send {
109+ if err := c.command(ctx, v); err != nil {
110+ return ports.Scene{}, false, err
111+ }
112+ c.configures.mark(v)
113+ }
114+ }
115+ scene.Windows = append(scene.Windows, c.scenePopups(sc)...)
116+ scene.CaptureIndicators = c.captureIndicators(sc)
117+ scene.Capture = c.captureSceneFor(sc, capture)
118+ // A scene carrying a capture image always gets a fresh Seq, and so
119+ // does one of an animating screen (the one being stepped, or any
120+ // when none is): the outputs drop a scene they already show, and a
121+ // spring that starts at the previous scene, or rounds to it in its
122+ // tail, would get no flip and wait for the fallback timer. Any other
123+ // keeps its output's Seq while it draws the same, so an idle output
124+ // is not recomposed.
125+ withCapture := scene.Capture != nil && (sc == capture.hiddenScr || sc == capture.windowScr)
126+ animating := (sc.springing() || c.pulsing(sc)) && (only == nil || only == sc)
127+ if !withCapture && !animating && sc.last.Seq != 0 && scene.SameAs(sc.last) {
128+ scene.Seq = sc.last.Seq
129+ } else {
130+ c.seq++
131+ scene.Seq = c.seq
132+ }
133+ if withCapture {
134+ switch sc {
135+ case capture.hiddenScr:
136+ scene.CaptureScene = c.captureScene(scene.Seq)
137+ case capture.windowScr:
138+ scene.CaptureScene = c.captureWindowScene(capture, scene.Seq)
139+ }
140+ }
141+ return scene, drawable, nil
142+}

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 26

Tags 10

↑↓ moveenter openesc close