Add Apple-style window corners with isolated shell rendering
This commit is contained in:
1 parent
b45917c41d
commit
73da6c68d2
10 files changed
+890
-45
No files matched your search
@@ -24,10 +24,16 @@ void main() {
|
||||
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, clip_rect, radius);
|
||||
float outer_coverage = edge_coverage(signed_distance);
|
||||
|
||||
#ifdef CONTINUOUS_WINDOW_CORNERS
|
||||
// Inset the outer distance field along its normals, rather than shrinking
|
||||
// the quartic's bounding box/radius and changing border thickness.
|
||||
float inner_coverage = edge_coverage(signed_distance + border_width);
|
||||
#else
|
||||
vec2 inner_size = max(clip_rect.zw - vec2(2.0 * border_width), vec2(0.0));
|
||||
vec4 inner_rect = vec4(clip_rect.xy + (clip_rect.zw - inner_size) * 0.5, inner_size);
|
||||
float inner_radius = max(radius - border_width, 0.0);
|
||||
float inner_coverage = edge_coverage(rounded_rect_distance(gl_FragCoord.xy, inner_rect, inner_radius));
|
||||
#endif
|
||||
float coverage = max(outer_coverage - inner_coverage, 0.0);
|
||||
float progress = clamp(dot(gl_FragCoord.xy - gradient_line.xy, gradient_line.zw), 0.0, 1.0);
|
||||
// Interpolate premultiplied endpoints: a transparent endpoint must not
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Apple-style corner approximation: three cubic Beziers per corner, using
|
||||
// measured UIKit path coordinates (radius units), rather than a superellipse.
|
||||
// Source and limits: https://liamrosenfeld.com/posts/apple_icon_quest/
|
||||
// CoreGraphics' private implementation is not available on Linux. These
|
||||
// published cubics have small tangent/curvature jumps at their internal joins;
|
||||
// they must not be described as an exact globally G1/G2-continuous curve.
|
||||
const float CORNER_EXTENT = 1.52866498;
|
||||
|
||||
void corner_controls(int segment, float blend, out vec2 a, out vec2 b, out vec2 c, out vec2 d) {
|
||||
if (segment == 0) {
|
||||
a = mix(vec2(0.0, 1.0), vec2(0.0, CORNER_EXTENT), blend);
|
||||
b = mix(vec2(0.0, 0.8686781289), vec2(0.0, 1.08849296), blend);
|
||||
c = mix(vec2(0.0258657631, 0.7386421566), vec2(0.0, 0.86840694), blend);
|
||||
d = mix(vec2(0.0761204675, 0.6173165676), vec2(0.07491139, 0.63149379), blend);
|
||||
} else if (segment == 1) {
|
||||
a = mix(vec2(0.0761204675, 0.6173165676), vec2(0.07491139, 0.63149379), blend);
|
||||
b = mix(vec2(0.1776144241, 0.3722884810), vec2(0.16905956, 0.37282383), blend);
|
||||
c = b.yx;
|
||||
d = a.yx;
|
||||
} else {
|
||||
a = mix(vec2(0.6173165676, 0.0761204675), vec2(0.63149379, 0.07491139), blend);
|
||||
b = mix(vec2(0.7386421566, 0.0258657631), vec2(0.86840694, 0.0), blend);
|
||||
c = mix(vec2(0.8686781289, 0.0), vec2(1.08849296, 0.0), blend);
|
||||
d = mix(vec2(1.0, 0.0), vec2(CORNER_EXTENT, 0.0), blend);
|
||||
}
|
||||
}
|
||||
|
||||
void corner_sample(float t, vec2 a, vec2 b, vec2 c, vec2 d,
|
||||
out vec2 point, out vec2 tangent, out vec2 acceleration) {
|
||||
vec2 first = 3.0 * (b - a);
|
||||
vec2 second = 3.0 * (c - 2.0 * b + a);
|
||||
vec2 third = d - 3.0 * c + 3.0 * b - a;
|
||||
point = a + t * (first + t * (second + t * third));
|
||||
tangent = first + t * (2.0 * second + 3.0 * t * third);
|
||||
acceleration = 2.0 * second + 6.0 * t * third;
|
||||
}
|
||||
|
||||
float corner_nearest(vec2 query, float t, vec2 a, vec2 b, vec2 c, vec2 d) {
|
||||
// Bounded safeguarded Newton steps. Both endpoint seeds are needed for
|
||||
// interior points with two minima; the midpoint may be a local maximum.
|
||||
for (int iteration = 0; iteration < 8; iteration++) {
|
||||
vec2 point, tangent, acceleration;
|
||||
corner_sample(t, a, b, c, d, point, tangent, acceleration);
|
||||
vec2 error = point - query;
|
||||
float speed_squared = dot(tangent, tangent);
|
||||
float denominator = max(speed_squared + dot(error, acceleration), speed_squared * 0.25);
|
||||
t = clamp(t - clamp(dot(error, tangent) / denominator, -0.25, 0.25), 0.0, 1.0);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
float rounded_rect_distance(vec2 point, vec4 rect, float radius) {
|
||||
vec2 half_size = rect.zw * 0.5;
|
||||
vec2 inset = half_size - abs(point - rect.xy - half_size);
|
||||
float limit = min(half_size.x, half_size.y);
|
||||
|
||||
// Preserve exact circular pills/circles and square/fullscreen outlines.
|
||||
if (radius <= 0.0 || radius >= limit) {
|
||||
vec2 q = vec2(radius) - inset;
|
||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||
}
|
||||
|
||||
// The measured profile extends about 1.53 radii along the straight edge.
|
||||
// When that would overlap its opposite corner, smoothly converge to the
|
||||
// circular profile. This size adaptation is Ferese policy, not UIKit code.
|
||||
float blend = clamp((limit / radius - 1.0) / (CORNER_EXTENT - 1.0), 0.0, 1.0);
|
||||
float extent = radius * mix(1.0, CORNER_EXTENT, blend);
|
||||
|
||||
if (max(inset.x, inset.y) >= extent) {
|
||||
return -min(inset.x, inset.y);
|
||||
}
|
||||
|
||||
vec2 query = inset / radius;
|
||||
float best_squared = 1.0e20;
|
||||
float side = 1.0;
|
||||
|
||||
for (int segment = 0; segment < 3; segment++) {
|
||||
vec2 a, b, c, d;
|
||||
corner_controls(segment, blend, a, b, c, d);
|
||||
|
||||
for (int seed = 0; seed < 2; seed++) {
|
||||
float t = corner_nearest(query, float(seed), a, b, c, d);
|
||||
vec2 closest, tangent, acceleration;
|
||||
corner_sample(t, a, b, c, d, closest, tangent, acceleration);
|
||||
vec2 error = query - closest;
|
||||
float squared = dot(error, error);
|
||||
|
||||
if (squared < best_squared) {
|
||||
best_squared = squared;
|
||||
side = dot(error, vec2(tangent.y, -tangent.x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sqrt(best_squared) * radius * (side < 0.0 ? -1.0 : 1.0);
|
||||
}
|
||||
|
||||
float edge_coverage(float distance) {
|
||||
return clamp(0.5 - distance, 0.0, 1.0);
|
||||
}
|
||||
Reference in new issue
Block a user