Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion backend/src/glass/LiquidField.zig
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,12 @@ pub fn pack(self: *const LiquidField, covered: dvui.Rect.Physical, has_sharp: bo
/// `liquid_glass`'s rim line width, in points.
const rim_line_width: f32 = 0.8;

/// How many pixels the glass's edge coverage ramps over at `scale`: wider at 1x, where a one-pixel
/// ramp shows as stairs. The program's `aa`.
fn edgeRamp(scale: f32) f32 {
return if (scale <= 1.25) 1.4 else 1.0;
}

// ── Drawing ─────────────────────────────────────────────────────────────────────────────────────

/// The program, written once in `shaders/liquid_glass.fragment.hlsl` and compiled from it for every
Expand Down Expand Up @@ -619,7 +625,7 @@ pub fn sample(self: *const LiquidField, p: dvui.Point.Physical) Sample {
const soft = @max(0, -f);
return .{
.d = @max(d, in_place),
.coverage = std.math.clamp(0.5 - @max(d, in_place), 0, 1),
.coverage = std.math.clamp(0.5 - @max(d, in_place) / edgeRamp(self.scale), 0, 1),
.out = .{ .x = out[0], .y = out[1] },
.steep = @exp(-soft / liquid_glass.depthPx(self.look(), rim)),
.blur = std.math.lerp(m1[0], m2[0], wm),
Expand Down Expand Up @@ -674,3 +680,17 @@ pub fn smin(a: f32, b: f32, k: f32, m: *f32) f32 {
if (b < a) m.* = 1 - m.*;
return @min(a, b) - h * h * k * 0.25;
}

test "the edge coverage ramps over 1.4 px at 1x and 1 px from 1.5x" {
var f: LiquidField = .{};
f.add(.{ .rect = .{ .x = 0, .y = 0, .w = 100, .h = 100 }, .radii = @splat(10) });
// Half a pixel outside the top edge, and half a pixel inside it.
const out: dvui.Point.Physical = .{ .x = 50, .y = -0.5 };
const in: dvui.Point.Physical = .{ .x = 50, .y = 0.5 };
f.scale = 1;
try std.testing.expectApproxEqAbs(0.5 - 0.5 / 1.4, f.sample(out).coverage, 1e-4);
try std.testing.expectApproxEqAbs(0.5 + 0.5 / 1.4, f.sample(in).coverage, 1e-4);
f.scale = 2;
try std.testing.expectEqual(@as(f32, 0), f.sample(out).coverage);
try std.testing.expectEqual(@as(f32, 1), f.sample(in).coverage);
}
13 changes: 9 additions & 4 deletions backend/src/glass/shaders/liquid_glass.fragment.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ float4 main(PSInput input) : SV_Target0
float F = smin(f1, f2, k, wf);
// Cut: the glass's outline, and so its rim line, runs along the place's where that is nearer.
if (cutEdge) D = max(D, inPlace);
float cov = clamp(0.5 - max(D, inPlace), 0.0, 1.0);
// At scale 1 a one-pixel ramp reads as stairs, so the edge is spread over `aa` pixels (the
// rim below widens with it); from 1.5 the ramp is one pixel (`LiquidField.edgeRamp`).
float px = g2.z * 1.25;
bool lowRes = px <= 1.25;
float aa = lowRes ? 1.4 : 1.0;
float cov = clamp(0.5 - max(D, inPlace) / aa, 0.0, 1.0);
if (cov <= 0.0) discard;
if (input.color.r < 0.5) return float4(0.0, 0.0, 0.0, cov);

Expand Down Expand Up @@ -273,11 +278,10 @@ float4 main(PSInput input) : SV_Target0
// Nothing across the band in either. Shade is the glass's own, not the picture's: a dark layer
// over it, as whole where the picture is see-through as where it is not. `px` is a point, in
// pixels (the rim line's width is 0.8 of one, `LiquidField.rim_line_width`).
float px = g2.z * 1.25;
float af = abs(facing);
bool diagonal = inward && dg > 0.0;
float edge_dark = diagonal ? dg * (1.0 - smoothstep(0.2, 0.7, af)) * exp(-max(0.0, -D) / (2.0 * px))
: g8.x * (1.0 - smoothstep(0.0, 0.5 * px, -D));
: g8.x * (1.0 - smoothstep(0.0, lowRes ? 0.7 : 0.5 * px, -D));
// As much as the edge has formed (`lens`), as its bend, clearing and light are: glass that is
// barely a lens yet — a drop sunk into its place's edge — draws no dark line round itself.
float shade = inward ? max(g5.w * t * t, edge_dark) * min(lens, 1.0) : 0.0;
Expand All @@ -286,7 +290,8 @@ float4 main(PSInput input) : SV_Target0
float spec = inward ? (diagonal ? dg * smoothstep(0.75, 1.0, af) : 0.25)
: 0.45 * (toward * sqrt(toward) + 0.75 * away * sqrt(away)) + 0.06;
float line_ = diagonal ? exp(-max(0.0, -D) / px)
: inward ? smoothstep(0.25 * px, 0.5 * px, -D) * (1.0 - smoothstep(1.2 * px, 1.6 * px, -D))
: inward ? (lowRes ? smoothstep(0.0, 0.9, -D) * (1.0 - smoothstep(1.5, 2.4, -D))
: smoothstep(0.25 * px, 0.5 * px, -D) * (1.0 - smoothstep(1.2 * px, 1.6 * px, -D)))
: exp(-max(0.0, -D) / g2.z);
float lit = line_ * spec + (inward ? 0.0 : 0.04 * steepL * toward);
// Down the shape (`v`, 0 at its top): the wash falls from `wash_top` to `wash_bottom`, and the
Expand Down
23 changes: 17 additions & 6 deletions core/dialogs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub fn frostPaneKept(id: dvui.Id, rect: dvui.Rect.Physical, corners: dvui.Corner
pub fn carriedGlass(id: dvui.Id, r: dvui.Rect.Physical, scale: f32) void {
const corners = rounding.round(rounding.card);
if (!frostPane(id, r, corners, scale)) {
r.fill(corners.scale(scale, dvui.CornerRect.Physical), .{ .color = .{ .color = dialogFill() }, .fade = 1 });
r.fill(corners.scale(scale, dvui.CornerRect.Physical), .{ .color = .{ .color = dialogFill() }, .fade = edgeFade(scale) });
}
glassShadow(r, corners, scale, surfaceShadow(), 1);
}
Expand Down Expand Up @@ -387,11 +387,11 @@ pub fn glassBubbles(id: dvui.Id, bubbles: []const Bubble, scale: f32, opts: Bubb
const physical = round.scale(scale, dvui.CornerRect.Physical);
const piece = id.update(std.mem.asBytes(&i));
const frosted = if (opts.witness) |w| frostPaneKept(piece, b.rect, round, scale, w) else frostPane(piece, b.rect, round, scale);
if (!frosted) b.rect.fill(physical, .{ .color = .{ .color = if (opts.tint) |t| t.opacity(opts.mix) else dialogFill() }, .fade = 1 });
if (!frosted) b.rect.fill(physical, .{ .color = .{ .color = if (opts.tint) |t| t.opacity(opts.mix) else dialogFill() }, .fade = edgeFade(scale) });
if (b.light >= bubble_press) {
b.rect.fill(physical, .{ .color = .{ .color = rowPress() }, .fade = 1 });
b.rect.fill(physical, .{ .color = .{ .color = rowPress() }, .fade = edgeFade(scale) });
} else if (b.light > 0) {
b.rect.fill(physical, .{ .color = .{ .color = rowHover() }, .fade = 1 });
b.rect.fill(physical, .{ .color = .{ .color = rowHover() }, .fade = edgeFade(scale) });
}
}
}
Expand All @@ -417,7 +417,7 @@ pub fn dropSlot(r: dvui.Rect.Physical, scale: f32) void {
const slot = r.insetAll(2 * scale);
if (slot.w < 1 or slot.h < 1) return;
const radius = @min(rounding.scaled(rounding.small) * scale, @min(slot.w, slot.h) / 2);
slot.fill(.round(radius), .{ .color = .{ .color = dvui.themeGet().color(.highlight, .fill).opacity(drop_slot_alpha) }, .fade = 1 });
slot.fill(.round(radius), .{ .color = .{ .color = dvui.themeGet().color(.highlight, .fill).opacity(drop_slot_alpha) }, .fade = edgeFade(scale) });
}
/// How much of the highlight a drop slot is: there without shouting over the tabs beside it.
const drop_slot_alpha: f32 = 0.55;
Expand Down Expand Up @@ -560,7 +560,7 @@ fn tooltipGlass(wd: *dvui.WidgetData, r: dvui.Rect.Physical, scale: f32, t: f32,
const f = dialogFrost() orelse {
const prect = r.insetAll(scale * bs.shrink).offsetPoint(bs.offset.scale(scale, dvui.Point.Physical));
prect.fill(phys_corners, .{ .color = .{ .color = bs.color.opacity(bs.alpha * p) }, .fade = scale * bs.fade });
r.fill(phys_corners, .{ .color = .{ .color = dialogFill().opacity(p) }, .fade = 1 });
r.fill(phys_corners, .{ .color = .{ .color = dialogFill().opacity(p) }, .fade = edgeFade(scale) });
return;
};
// The shadow as a ring round the glass, after it (`glassShadow`), so the glass does not blur
Expand Down Expand Up @@ -699,6 +699,17 @@ pub fn rowPress() dvui.Color {
/// The style a row inside a floating surface takes its colours from.
pub const row_style: dvui.Theme.Style.Name = .control;

/// The fade ring, in pixels, of a rounded fill at `scale`: wider at 1x, where one pixel steps.
fn edgeFade(scale: f32) f32 {
return if (scale <= 1.25) 1.5 else 1;
}

test "a rounded fill's fade ring widens only on a 1x display" {
try std.testing.expectEqual(@as(f32, 1.5), edgeFade(1));
try std.testing.expectEqual(@as(f32, 1), edgeFade(1.5));
try std.testing.expectEqual(@as(f32, 1), edgeFade(2));
}

/// The fill a dialog paints when there is no frost to composite with: the chrome at the
/// dialog's opacity, lifted the same amount.
pub fn dialogFill() dvui.Color {
Expand Down
Loading