Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
scenefx/render/fx_renderer/shaders/frame.frag (7K)
1 // Window resize handles: EIGHT DISCS inside the window's rounded
2 // silhouette — one on each side and one on each corner — each disc a zone
3 // of its own, plus (`buttons` > 0) three BUTTON discs in the top row, left
4 // of the top-right corner disc: minimize, maximize, float/tile toggle.
5 // `band` is the disc diameter. Every disc sits the same distance in from
6 // the edges it touches, so the three discs along an edge are inline: a
7 // corner disc sits on the corner's 45° diagonal, tangent to the rounded
8 // corner arc when that arc is wider than the disc and tucked into the two
9 // straight edges otherwise, and the side discs take that same inset.
10 //
11 // The compositor's `window::handle_disc_layout` lays out the same discs
12 // from the same inputs (size, corner_radius, band, buttons) for the
13 // pointer hit test and the catcher rects; the two MUST stay in step, or a
14 // handle is drawn where it does not grab, or grabs where it is not drawn.
15 //
16 // Still a shader rather than eight scene rects: a rounded scene rect takes
17 // the renderer's global corner shape, a squircle, so a "rect with radius
18 // half its size" would draw as a squircle, not a circle.
19
20 #ifdef GL_FRAGMENT_PRECISION_HIGH
21 precision highp float;
22 #else
23 precision mediump float;
24 #endif
25
26 varying vec4 v_color;
27 varying vec2 v_texcoord;
28
29 uniform vec2 position;
30 uniform vec2 size;
31 uniform float corner_radius;
32 // Disc diameter.
33 uniform float band;
34 // Retired by the disc handles and ignored; kept so the node API and its
35 // callers need not change.
36 uniform float band_min;
37 uniform float corner_len;
38 uniform float gap;
39 uniform float bulge;
40 uniform float swell_curve;
41 // Node-local rect (x, y, w, h) the handles must not draw over — the client
42 // is drawing an in-surface popover there and the menu must read as in
43 // FRONT of the chrome. w or h <= 0 disables.
44 uniform vec4 exclusion;
45 // Zone under the pointer (see ZONE_* below), or < 0 for none.
46 uniform float hovered;
47 uniform vec4 hover_color;
48 // Window buttons: 0 none, 1 for a Floating window, 2 for a Tiled one (the
49 // toggle's glyph shows the mode a click goes to).
50 uniform float buttons;
51
52 // Defined in corner_alpha.frag, concatenated after this source — the same
53 // shared routine bevel.frag uses, so the discs are clipped to the same
54 // superellipse silhouette every other corner cut traces.
55 float corner_dist(vec2 size, vec2 position,
56 float radius_tl, float radius_tr, float radius_bl, float radius_br);
57
58 // Zone numbering MUST match the compositor's BorderElement::index():
59 // 0 Top, 1 Bottom, 2 Left, 3 Right, 4 TopLeft, 5 TopRight, 6 BottomLeft,
60 // 7 BottomRight, 8 Minimize, 9 Maximize, 10 ToggleTile.
61 const float SQRT2 = 1.41421356237;
62 // Centre-to-centre spacing of the top row's discs, in diameters
63 // (`window::HANDLE_BUTTON_STEP`).
64 const float STEP = 1.25;
65
66 float box_sdf(vec2 q, vec2 half_size) {
67 vec2 d = abs(q) - half_size;
68 return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
69 }
70
71 // Coverage of a button's glyph at `q` (px from the disc centre, y down)
72 // for a disc of radius `r`.
73 float glyph(float zone, vec2 q, float r) {
74 float stroke = max(0.11 * r, 1.0);
75 float sd;
76 if (zone < 8.5) {
77 // Minimize: a bar across the lower middle.
78 sd = box_sdf(q - vec2(0.0, 0.18 * r), vec2(0.45 * r, 0.5 * stroke));
79 } else if (zone < 9.5) {
80 // Maximize: a square outline.
81 sd = abs(box_sdf(q, vec2(0.4 * r))) - 0.5 * stroke;
82 } else if (buttons < 1.5) {
83 // Floating -> tile: a 2x2 grid of cells.
84 vec2 cell = vec2(0.2 * r);
85 vec2 g = abs(q) - vec2(0.22 * r);
86 sd = box_sdf(g, cell * 0.85);
87 } else {
88 // Tiled -> float: two overlapping window outlines.
89 float a = abs(box_sdf(q - vec2(0.12 * r, -0.12 * r), vec2(0.28 * r))) - 0.5 * stroke;
90 float b = abs(box_sdf(q + vec2(0.12 * r, -0.12 * r), vec2(0.28 * r))) - 0.5 * stroke;
91 // The back outline is hidden under the front one.
92 if (box_sdf(q - vec2(0.12 * r, -0.12 * r), vec2(0.28 * r)) < 0.0) {
93 b = 1e9;
94 }
95 sd = min(a, b);
96 }
97 return clamp(0.5 - sd, 0.0, 1.0);
98 }
99
100 void main() {
101 // Rect-local position, TOP-DOWN: gl_FragCoord minus the box position is
102 // already y-down box-local here (the pass renders under a FLIPPED_180
103 // projection, so fragment row 0 is the top of the buffer — see the same
104 // note in droplet.frag). corner_dist flips its own copy; this must not
105 // be flipped too, or the zone labels swap vertically.
106 vec2 p = gl_FragCoord.xy - position;
107
108 // A client popover owns this rect; the handles yield to it wholesale.
109 // The rect arrives top-left-origin (y down), the same space as p.
110 if (exclusion.z > 0.0 && exclusion.w > 0.0
111 && p.x >= exclusion.x && p.x < exclusion.x + exclusion.z
112 && p.y >= exclusion.y && p.y < exclusion.y + exclusion.w) {
113 discard;
114 }
115
116 float r = 0.5 * band;
117 float t = corner_radius > r
118 ? corner_radius - (corner_radius - r) / SQRT2
119 : r;
120 float s = STEP * band;
121 // The buttons only when the top row has room for all six discs; the
122 // Top disc then leaves the midpoint only if it would crowd them.
123 bool with_buttons = buttons > 0.5 && size.x >= 2.0 * t + 5.0 * s;
124 float top_x = with_buttons ? min(0.5 * size.x, size.x - t - 4.0 * s) : 0.5 * size.x;
125 vec2 c[11];
126 c[0] = vec2(top_x, t); // Top
127 c[1] = vec2(0.5 * size.x, size.y - t); // Bottom
128 c[2] = vec2(t, 0.5 * size.y); // Left
129 c[3] = vec2(size.x - t, 0.5 * size.y); // Right
130 c[4] = vec2(t, t); // TopLeft
131 c[5] = vec2(size.x - t, t); // TopRight
132 c[6] = vec2(t, size.y - t); // BottomLeft
133 c[7] = vec2(size.x - t, size.y - t); // BottomRight
134 c[8] = vec2(size.x - t - 3.0 * s, t); // Minimize
135 c[9] = vec2(size.x - t - 2.0 * s, t); // Maximize
136 c[10] = vec2(size.x - t - s, t); // ToggleTile
137 int n = with_buttons ? 11 : 8;
138
139 // The nearest disc owns the fragment: signed distance to its rim,
140 // negative inside.
141 float d = 1e9;
142 float zone = -1.0;
143 for (int i = 0; i < 11; i++) {
144 if (i >= n) {
145 break;
146 }
147 float di = length(p - c[i]) - r;
148 if (di < d) {
149 d = di;
150 zone = float(i);
151 }
152 }
153 // Feather the rim by a pixel, and clip to the window's own rounded
154 // silhouette (a disc never crosses it, but the feather may).
155 float sil = corner_dist(size, position,
156 corner_radius, corner_radius, corner_radius, corner_radius);
157 float aa = clamp(0.5 - d, 0.0, 1.0) * clamp(-sil, 0.0, 1.0);
158 if (aa <= 0.0) {
159 discard;
160 }
161
162 vec4 base = (hovered >= 0.0 && abs(hovered - zone) < 0.5) ? hover_color : v_color;
163 // A button's glyph is the disc darkened, so it reads on any disc color.
164 if (zone > 7.5) {
165 int zi = int(zone + 0.5);
166 vec2 cz = zi == 8 ? c[8] : (zi == 9 ? c[9] : c[10]);
167 float g = glyph(zone, p - cz, r);
168 base = vec4(base.rgb * (1.0 - 0.65 * g), base.a);
169 }
170 // Premultiplied, like every other rect this renderer draws.
171 gl_FragColor = base * aa;
172 }