1package selection
2
3import (
4 "time"
5
6 "github.com/bnema/nefergui"
7
8 "github.com/bnema/nefercap/internal/ports"
9)
10
11// view draws the scene. Geometry goes through Node.Rect, so no CSS strings
12// are built per frame; classes come from the embedded stylesheet. Boxes with no
13// area are not emitted at all, so an idle surface is one dim box plus the
14// header and footer, and no empty chip is painted.
15func view(f *nefergui.Frame, m *model) {
16 m.syncMode() // a Tab on another overlay redraws this one
17 m.refreshFooter()
18 m.animate(time.Now())
19 root := f.Root(nefergui.Class("root"))
20 st := root.Stack(nefergui.Class("scene"))
21 if drawsBackdrop(m) {
22 // The frozen screen, scaled to fill the logical surface.
23 box := st.Box(nefergui.Key("backdrop"), nefergui.Class("backdrop")).Rect(0, 0, float64(m.w), float64(m.h))
24 box.Image(m.backdrop)
25 }
26 s := m.scene()
27 for i, r := range s.dim {
28 box(st, dimKeys[i], "dim", r)
29 }
30 for i, r := range s.grid {
31 box(st, gridKeys[i], "grid", r)
32 }
33 mc := modeClass(m.mode)
34 for i, r := range s.edges {
35 if r.w > 0 && r.h > 0 {
36 st.Box(nefergui.Key(edgeKeys[i]), nefergui.Class("edge"), nefergui.Class(mc)).Rect(r.x, r.y, r.w, r.h)
37 }
38 }
39 hc := m.hudClass()
40 if n := tag(st, "label", "o0", s.label); n.ok {
41 n.node.Text(m.label.text)
42 }
43 for i, r := range s.modes {
44 chip(st, modeKeys[i], r, s.modeState[i], hc, badgeText(modeChips[i]))
45 }
46 for i, r := range s.kinds {
47 chip(st, kindKeys[i], r, s.kindState[i], hc, targetTexts[i])
48 }
49 if n := tag(st, "header", hc, s.header); n.ok {
50 n.node.Text(m.header)
51 }
52 if n := tag(st, "footer", hc, s.footer); n.ok {
53 n.node.Text(m.footerText)
54 }
55 if r := s.toast; r.w > 0 && r.h > 0 {
56 kind := "info"
57 if m.toast.warn {
58 kind = "warn"
59 }
60 st.Box(nefergui.Key("toast"), nefergui.Class("toast"), nefergui.Class(kind),
61 nefergui.Class(m.toastClass())).Rect(r.x, r.y, r.w, r.h).Text(m.toast.text)
62 }
63}
64
65// chip draws one selector chip; state is shot or rec (active, in the mode
66// color), idle, or off (unavailable).
67func chip(st nefergui.Node, key string, r frect, state, fade, text string) {
68 if r.w > 0 && r.h > 0 {
69 st.Box(nefergui.Key(key), nefergui.Class("tag"), nefergui.Class("chip"), nefergui.Class(state), nefergui.Class(fade)).Rect(r.x, r.y, r.w, r.h).Text(text)
70 }
71}
72
73// drawsBackdrop: a screenshot selection shows the frozen screen until F turns
74// it off; a recording selects on the live screen.
75func drawsBackdrop(m *model) bool {
76 return m.backdrop != nil && !m.live && m.mode == ports.Screenshot && m.w > 0 && m.h > 0
77}
78
79type tagged struct {
80 node nefergui.Node
81 ok bool
82}
83
84func box(st nefergui.Node, key, class string, r frect) {
85 if r.w > 0 && r.h > 0 {
86 st.Box(nefergui.Key(key), nefergui.Class(class)).Rect(r.x, r.y, r.w, r.h)
87 }
88}
89
90// tag draws a text tag; fade is its opacity class (o0 is opaque).
91func tag(st nefergui.Node, key, fade string, r frect) tagged {
92 if r.w <= 0 || r.h <= 0 {
93 return tagged{}
94 }
95 return tagged{st.Box(nefergui.Key(key), nefergui.Class("tag"), nefergui.Class(fade)).Rect(r.x, r.y, r.w, r.h), true}
96}
97
98// Static keys avoid building identity strings per frame.
99var (
100 dimKeys = [4]string{"dim0", "dim1", "dim2", "dim3"}
101 gridKeys = [6]string{"grid0", "grid1", "grid2", "grid3", "grid4", "grid5"}
102 edgeKeys = [4]string{"edge0", "edge1", "edge2", "edge3"}
103 modeKeys = [2]string{"mode-shot", "mode-rec"}
104 kindKeys = [3]string{"kind-region", "kind-monitor", "kind-workspace"}
105)