1package core
2
3import (
4 "sort"
5
6 "github.com/bnema/neferwl/internal/ports"
7)
8
9// arrangeLayers places exclusive surfaces first, then sorts the resulting scene by layer.
10func arrangeLayers(outW, outH int, layers []ports.LayerSurface) (placed []ports.SceneLayer, usable ports.Rect) {
11 full := ports.Rect{W: max(outW, 0), H: max(outH, 0)}
12 usable = full
13 for pass := 0; pass < 2; pass++ {
14 for _, s := range layers {
15 if (s.ExclusiveZone > 0) != (pass == 0) {
16 continue
17 }
18 bounds := usable
19 if s.ExclusiveZone == -1 {
20 bounds = full
21 }
22 top, right, bottom, left := int(s.Margin[0]), int(s.Margin[1]), int(s.Margin[2]), int(s.Margin[3])
23 w, h := s.Width, s.Height
24 x, y := bounds.X+(bounds.W-w)/2, bounds.Y+(bounds.H-h)/2
25 switch s.Anchor & (ports.AnchorLeft | ports.AnchorRight) {
26 case ports.AnchorLeft | ports.AnchorRight:
27 span := bounds.W - left - right
28 if w == 0 {
29 w = span
30 }
31 x = bounds.X + left + (span-w)/2
32 case ports.AnchorLeft:
33 x = bounds.X + left
34 case ports.AnchorRight:
35 x = bounds.X + bounds.W - w - right
36 }
37 switch s.Anchor & (ports.AnchorTop | ports.AnchorBottom) {
38 case ports.AnchorTop | ports.AnchorBottom:
39 span := bounds.H - top - bottom
40 if h == 0 {
41 h = span
42 }
43 y = bounds.Y + top + (span-h)/2
44 case ports.AnchorTop:
45 y = bounds.Y + top
46 case ports.AnchorBottom:
47 y = bounds.Y + bounds.H - h - bottom
48 }
49 placed = append(placed, ports.SceneLayer{ID: s.ID, Layer: s.Layer, Rect: ports.Rect{X: x, Y: y, W: w, H: h}})
50 if s.ExclusiveZone <= 0 {
51 continue
52 }
53 a := s.Anchor
54 z := int(s.ExclusiveZone)
55 switch {
56 case a&ports.AnchorTop != 0 && a&ports.AnchorBottom == 0 && (a&(ports.AnchorLeft|ports.AnchorRight) == 0 || a&(ports.AnchorLeft|ports.AnchorRight) == ports.AnchorLeft|ports.AnchorRight):
57 z = min(max(z+top, 0), usable.H)
58 usable.Y += z
59 usable.H -= z
60 case a&ports.AnchorBottom != 0 && a&ports.AnchorTop == 0 && (a&(ports.AnchorLeft|ports.AnchorRight) == 0 || a&(ports.AnchorLeft|ports.AnchorRight) == ports.AnchorLeft|ports.AnchorRight):
61 z = min(max(z+bottom, 0), usable.H)
62 usable.H -= z
63 case a&ports.AnchorLeft != 0 && a&ports.AnchorRight == 0 && (a&(ports.AnchorTop|ports.AnchorBottom) == 0 || a&(ports.AnchorTop|ports.AnchorBottom) == ports.AnchorTop|ports.AnchorBottom):
64 z = min(max(z+left, 0), usable.W)
65 usable.X += z
66 usable.W -= z
67 case a&ports.AnchorRight != 0 && a&ports.AnchorLeft == 0 && (a&(ports.AnchorTop|ports.AnchorBottom) == 0 || a&(ports.AnchorTop|ports.AnchorBottom) == ports.AnchorTop|ports.AnchorBottom):
68 z = min(max(z+right, 0), usable.W)
69 usable.W -= z
70 }
71 }
72 }
73 sort.SliceStable(placed, func(i, j int) bool { return placed[i].Layer < placed[j].Layer })
74 return
75}
76
77// layerOf finds a placed layer surface by ID.
78func (c *Core) layerOf(id WindowID) (*screen, ports.SceneLayer, bool) {
79 for _, sc := range c.screens {
80 for _, l := range sc.placed {
81 if l.ID == id {
82 return sc, l, true
83 }
84 }
85 }
86 return nil, ports.SceneLayer{}, false
87}
88
89// hasFullscreen reports whether a fullscreen window covers the screen. In
90// scroll overflow, one scrolled off by the user no longer does; nor does
91// one shown as a preview (the overview or the column switcher).
92func hasFullscreen(sc *screen) bool {
93 return !sc.mon.previewing() && sc.mon.Current().cover() != 0
94}
95
96// shown reports whether a layer surface is drawn. A fullscreen window is
97// exclusive: it hides every layer above the background (bars,
98// notifications), so the output can scan it out. A surface taking the
99// keyboard exclusively (a locker, a launcher) still shows: it has the
100// keyboard and must be seen.
101func shown(sc *screen, l ports.SceneLayer) bool {
102 return l.Layer == ports.LayerBackground || !hasFullscreen(sc) || sc.exclusiveKeyboard(l.ID) || sc.capture[l.ID]
103}
104
105// exclusiveKeyboard reports whether layer surface id takes the keyboard
106// exclusively.
107func (sc *screen) exclusiveKeyboard(id WindowID) bool {
108 for _, l := range sc.layers {
109 if l.ID == id {
110 return l.Keyboard == 1
111 }
112 }
113 return false
114}
115
116// shownLayers are the placed layer surfaces drawn on the screen.
117func shownLayers(sc *screen) []ports.SceneLayer {
118 out := make([]ports.SceneLayer, 0, len(sc.placed))
119 for _, l := range sc.placed {
120 if shown(sc, l) {
121 out = append(out, l)
122 }
123 }
124 return out
125}
126
127// layerAt returns the topmost shown layer surface under the output-local
128// point among those above (or below) the windows.
129func (c *Core) layerAt(sc *screen, lx, ly float64, above bool) (WindowID, float64, float64) {
130 for i := len(sc.placed) - 1; i >= 0; i-- {
131 l := sc.placed[i]
132 r := l.Rect
133 if (l.Layer >= ports.LayerTop) != above || !shown(sc, l) || r.W <= 0 || r.H <= 0 {
134 continue
135 }
136 if lx >= float64(r.X) && lx < float64(r.X+r.W) && ly >= float64(r.Y) && ly < float64(r.Y+r.H) && c.acceptsInput(l.ID, lx-float64(r.X), ly-float64(r.Y)) {
137 return l.ID, lx - float64(r.X), ly - float64(r.Y)
138 }
139 }
140 return 0, 0, 0
141}
142
143// onDemand reports whether id is a mapped layer surface with on-demand
144// keyboard interactivity.
145func (c *Core) onDemand(id WindowID) bool {
146 for _, sc := range c.screens {
147 for _, l := range sc.layers {
148 if l.ID == id {
149 return l.Keyboard == 2
150 }
151 }
152 }
153 return false
154}