1package core
2
3import (
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.
11func (c *Core) sceneFor(ctx context.Context, i int, sc *screen, only *screen, capture *capView) (scene ports.Scene, err error) {
12 o := sc.mon.Output()
13 // view.frame is the viewport of the workspace on screen: the whole
14 // output unless it has a size override (never in the overview).
15 view := sc.mon.viewport()
16 var clip Rect
17 if view.frame != (Rect{W: o.W, H: o.H}) {
18 clip = view.frame
19 }
20 // layout is what is drawn; settled (same indexes) is where the
21 // windows are going, and alone sizes the configures.
22 layout, settled := sc.shown, sc.settledLayout
23 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)+1), Layers: shownLayers(sc)}
24 if !sc.mon.previewing() {
25 // Tiles stop at the panels; the overview lays out previews over
26 // the whole output.
27 u := view.usable
28 scene.TileInset = ports.Insets{Top: u.Y, Left: u.X, Right: o.W - u.X - u.W, Bottom: o.H - u.Y - u.H}
29 }
30 var real map[WindowID]Placement
31 if sc.mon.previewing() {
32 // Local to this screen's build: reused, cleared each time.
33 if c.overviewReal == nil {
34 c.overviewReal = make(map[WindowID]Placement)
35 }
36 clear(c.overviewReal)
37 real = c.overviewReal
38 for w := range sc.mon.all() {
39 c.realBuf = w.layoutInto(c.realBuf)
40 for _, p := range c.realBuf {
41 real[p.ID] = p
42 }
43 }
44 }
45 scene.Dim = floatDim(layout, view, c.cfg.Floating.Dim)
46 if sc.mon.previewing() {
47 // Darken the wallpaper around the previews.
48 scene.Dim = c.cfg.Floating.Dim
49 scene.DimBehind = scene.Dim > 0
50 }
51 if d := c.drag; d != nil && d.target.screen == sc && d.target.kind != dropNone {
52 scene.DropHints = slices.Clone(d.target.hints)
53 }
54 if sc.mon.ov.open {
55 // Frame the selection and separate numbered and named row groups.
56 // The scene's own slice (immutable once sent), sized for both.
57 _, dividers := sc.mon.overviewRows()
58 var seps []ports.Separator
59 if len(dividers) > 0 {
60 seps = append(make([]ports.Separator, 0, len(dividers)+outlineLines), dividers...)
61 }
62 scene.Separators = overviewOutline(seps, layout, max(c.cfg.Border.Width, 2))
63 } else if sc.mon.switcherShown() {
64 // The switcher's cards: only the selection is framed.
65 scene.Separators = overviewOutline(nil, layout, max(c.cfg.Border.Width, 2))
66 } else {
67 // Only the focused output lights the focused window's lines.
68 scene.Separators = separators(layout, c.cfg.Border.Width, sc.mon.Current().gap(), view, i == c.focusScreen)
69 }
70 for k, p := range layout {
71 ps := settled[k]
72 // Only the focused output has an activated window.
73 focused := p.Focused && i == c.focusScreen
74 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}
75 if p.Zoom > 0 && (p.Zoom < 1 || p.Preview > 0) {
76 // A scale motion: the content follows the drawn size. A
77 // card in flight drawn at its size still needs Zoom 1:
78 // without it the renderer would shrink it by Preview.
79 sw.Zoom = p.Zoom
80 }
81 // A peek's veil is the configured one plus its animated offset.
82 sw.Dim = max(0, min(p.Dim+c.peekDim(p), 1))
83 if p.Leaving {
84 // A window fading out after it closed or hid: drawn, but
85 // hidden to everything else, its configures included.
86 sw.Hidden = false
87 scene.Windows = append(scene.Windows, sw)
88 // A window the workspace still holds (a hidden stash
89 // window) keeps its last configure: prune would forget it,
90 // and its next one would start from nothing. A closed one
91 // was forgotten at its unmap and stays so.
92 if w, _ := sc.mon.find(p.ID); w != nil {
93 c.configures.keep(p.ID)
94 }
95 continue
96 }
97 scene.Windows = append(scene.Windows, sw)
98 t := configureTarget{output: sc.name(), view: view, focused: focused, captured: capture != nil && capture.window == p.ID}
99 if !p.Hidden && p.Preview == 0 {
100 // Only a sized configure needs the client size.
101 t.client, t.imposed = c.clientRect(ps), sc.mon.Current().imposedFloat(p.ID)
102 } else if t.captured && p.Hidden {
103 // A captured hidden window is sized like its capture.
104 t.client = capture.windowSz
105 } else if p.Preview > 0 && !p.Hidden {
106 if rp, ok := real[p.ID]; ok && !rp.Hidden {
107 t.realTiled = !rp.Floating
108 t.client = c.clientRect(rp)
109 }
110 }
111 cp, t := c.captureConfigure(sc, ps, t)
112 if v, send := c.configures.nextWithCapture(ps, t, cp); send {
113 if err := c.command(ctx, v); err != nil {
114 return ports.Scene{}, err
115 }
116 c.configures.mark(v)
117 }
118 }
119 scene.Windows = append(scene.Windows, c.scenePopups(sc)...)
120 if icon, ok := c.dataDragIconOn(i, sc); ok {
121 // The drag icon is over everything, popups and layers included.
122 scene.Windows = append(scene.Windows, icon)
123 }
124 scene.Flash = c.flashFor(sc)
125 scene.CaptureIndicators = c.captureIndicators(sc)
126 scene.HintLabels = c.hintLabels(sc)
127 scene.HintStyle = ports.HintStyle{Size: c.cfg.Hint.Size, Background: c.cfg.Hint.Background, Foreground: c.cfg.Hint.Foreground, Width: sc.info.Width, Height: sc.info.Height, PhysicalW: sc.info.PhysicalW, PhysicalH: sc.info.PhysicalH}
128 scene.Capture = c.captureSceneFor(sc, capture)
129 // The springs of this frame are advanced: a scene shown while one still
130 // runs wakes core when it flips; the one a spring settles on does not.
131 scene.Animating = c.sliding(sc.name())
132 // A scene carrying a capture image always gets a fresh Seq, and so
133 // does one of an animating screen (the one being stepped, or any
134 // when none is): the outputs drop a scene they already show, and a
135 // spring that starts at the previous scene, or rounds to it in its
136 // tail, would get no flip and wait for the fallback timer. So does the
137 // scene that flips Animating off: SameAs ignores the flag, and keeping
138 // the Seq of the animating scene would leave outputs showing it, and
139 // waking core on every later flip. Any other keeps its output's Seq
140 // while it draws the same, so an idle output is not recomposed.
141 withCapture := scene.Capture != nil && (sc == capture.hiddenScr || sc == capture.windowScr)
142 animating := scene.Animating && (only == nil || only == sc)
143 if !withCapture && !animating && sc.last.Animating == scene.Animating && sc.last.Seq != 0 && scene.SameAs(sc.last) {
144 scene.Seq = sc.last.Seq
145 } else {
146 c.seq++
147 scene.Seq = c.seq
148 }
149 if withCapture {
150 switch sc {
151 case capture.hiddenScr:
152 scene.CaptureScene = c.captureScene(scene.Seq)
153 case capture.windowScr:
154 scene.CaptureScene = c.captureWindowScene(capture, scene.Seq)
155 }
156 }
157 return scene, nil
158}