1package ui
2
3import (
4 "fmt"
5 "slices"
6
7 "github.com/bnema/nefergui/internal/platform/wayland"
8)
9
10// LayerLevel is the wlr-layer-shell stacking layer. The zero value is invalid,
11// so a surface never silently lands below every window.
12type LayerLevel uint8
13
14const (
15 LayerBackground LayerLevel = iota + 1
16 LayerBottom
17 LayerTop
18 LayerOverlay
19)
20
21// Anchor is a bit set of output edges a layer surface attaches to.
22type Anchor uint8
23
24const (
25 AnchorTop Anchor = 1 << iota
26 AnchorBottom
27 AnchorLeft
28 AnchorRight
29)
30
31const anchorAll = AnchorTop | AnchorBottom | AnchorLeft | AnchorRight
32
33// KeyboardMode is the layer surface keyboard interactivity. The zero value is
34// KeyboardNone: the surface never takes keyboard focus. KeyboardOnDemand needs
35// layer-shell version 4.
36type KeyboardMode uint8
37
38const (
39 KeyboardNone KeyboardMode = iota
40 KeyboardExclusive
41 KeyboardOnDemand
42)
43
44// Rect is an integer logical-pixel rectangle in surface coordinates.
45type Rect struct{ X, Y, Width, Height int32 }
46
47// LayerConfig requests a wlr-layer-shell surface instead of an xdg toplevel.
48// Size still gives the initial logical size; an axis anchored to both opposite
49// edges is sized by the compositor and reported through OnResize and Frame.Size.
50// ExclusiveZone > 0 asks the compositor to reserve that many logical pixels
51// from an anchored edge (only for one edge, or one edge plus both perpendicular
52// edges); 0 reserves nothing and asks to avoid other surfaces' positive zones;
53// -1 ignores other exclusive zones and extends to the anchored output edges.
54type LayerConfig struct {
55 Output string // output name; empty lets the compositor choose
56 // AllOutputs puts one surface on every output, including outputs added
57 // while Run is running; removed outputs lose theirs. Every surface shows
58 // the same view and model, laid out at its own size and scale. Run then
59 // returns only on cancellation or failure. It excludes Output, keyboard
60 // interactivity, OnSurface, OnResize and RunFrames.
61 AllOutputs bool
62 Namespace string // defaults to "nefergui"
63 Level LayerLevel
64 Anchors Anchor
65 Keyboard KeyboardMode
66 ExclusiveZone int32
67 Margin [4]int32 // top, right, bottom, left
68 // InputRects, when non-nil, restricts pointer input to these rectangles.
69 // An empty non-nil slice makes the surface fully click-through. Rects need
70 // positive width and height.
71 InputRects []Rect
72}
73
74// surfaceOptions lowers the config to platform options and validates it.
75func (c LayerConfig) surfaceOptions() (wayland.SurfaceOptions, error) {
76 if c.AllOutputs && (c.Output != "" || c.Keyboard != KeyboardNone) {
77 return wayland.SurfaceOptions{}, fmt.Errorf("nefergui: AllOutputs excludes Output and keyboard interactivity")
78 }
79 l := wayland.LayerOptions{Output: c.Output, Namespace: c.Namespace, ExclusiveZone: c.ExclusiveZone, Margin: c.Margin}
80 switch c.Level {
81 case LayerBackground:
82 l.Layer = wayland.LayerBackground
83 case LayerBottom:
84 l.Layer = wayland.LayerBottom
85 case LayerTop:
86 l.Layer = wayland.LayerTop
87 case LayerOverlay:
88 l.Layer = wayland.LayerOverlay
89 default:
90 return wayland.SurfaceOptions{}, fmt.Errorf("nefergui: invalid layer level %d", c.Level)
91 }
92 if c.Anchors&^anchorAll != 0 {
93 return wayland.SurfaceOptions{}, fmt.Errorf("nefergui: invalid layer anchors %#x", uint8(c.Anchors))
94 }
95 if c.Anchors&AnchorTop != 0 {
96 l.Anchor |= wayland.AnchorTop
97 }
98 if c.Anchors&AnchorBottom != 0 {
99 l.Anchor |= wayland.AnchorBottom
100 }
101 if c.Anchors&AnchorLeft != 0 {
102 l.Anchor |= wayland.AnchorLeft
103 }
104 if c.Anchors&AnchorRight != 0 {
105 l.Anchor |= wayland.AnchorRight
106 }
107 switch c.Keyboard {
108 case KeyboardNone:
109 l.Keyboard = wayland.KeyboardNone
110 case KeyboardExclusive:
111 l.Keyboard = wayland.KeyboardExclusive
112 case KeyboardOnDemand:
113 l.Keyboard = wayland.KeyboardOnDemand
114 default:
115 return wayland.SurfaceOptions{}, fmt.Errorf("nefergui: invalid keyboard mode %d", c.Keyboard)
116 }
117 o := wayland.SurfaceOptions{Layer: &l}
118 if c.InputRects != nil {
119 o.InputRects = make([]wayland.Rect, len(c.InputRects))
120 for i, r := range c.InputRects {
121 o.InputRects[i] = wayland.Rect{X: r.X, Y: r.Y, W: r.Width, H: r.Height}
122 }
123 }
124 if err := o.Validate(); err != nil {
125 return wayland.SurfaceOptions{}, fmt.Errorf("nefergui: layer: %w", err)
126 }
127 return o, nil
128}
129
130// Layer selects a wlr-layer-shell surface for the window.
131func Layer(c LayerConfig) WindowOption {
132 c.InputRects = slices.Clone(c.InputRects)
133 return windowOption(func(w *windowConfig) { w.layer = &c })
134}