git.lucas.co / cce-compositor
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 }