Selkie.git | xt/snapshots/ | 50-modal-scrim-styled.raku


use lib 'lib';
use Selkie::Test::Snapshot;
use Selkie::BorderStyle;
use Selkie::Container;
use Selkie::Layout::VBox;
use Selkie::Sizing;
use Selkie::Style;
use Selkie::Widget;
use Selkie::Widget::Modal;
use Selkie::Widget::Text;

# BackdropScrim, composited for real.
#
# A scrim only means anything when there is something underneath it, so
# this scenario builds the two-plane scene an App has at runtime: a
# screen root, and a modal mounted as a sibling above it. notcurses
# composites the whole pile before the harness reads cells back, so the
# styled readback below is the actual blended result, not the scrim
# plane's own colours.
#
# What to look for in the golden:
#
#   * The screen text underneath the scrim SURVIVES — the scrim plane
#     is primed with an empty EGC (gcluster 0), which notcurses's glyph
#     search reads as "keep looking further down the pile". A space
#     would have covered it.
#   * Its colours are exactly halved towards black on both channels:
#     0xE0E0E0 on 0x202040 reads back as 0x707070 on 0x101020. One
#     AlphaBlend layer is one 50/50 average, per channel, integer and
#     truncating — there is no fractional alpha to turn up.
#   * The dialog itself is untouched: the frame and the content sit on
#     opaque planes above the scrim.
#
# The modal's own full-screen plane contributes nothing at all here
# (transparent channels, empty EGC), which is the S4 fix for
# `dim-background => False` showing a flat rectangle.

class Scene does Selkie::Container {
    # children[0] is the screen root, children[1] the modal — both are
    # real children so the role's destroy tears the whole scene down.
    method render() {
        my $screen = self.children[0];
        my $modal  = self.children[1];
        for $screen, $modal -> $w {
            next without $w;
            unless $w.plane {
                $w.init-plane(self.plane, y => 0, x => 0,
                              rows => self.rows, cols => self.cols);
            }
            $w.set-viewport(abs-y => self.abs-y, abs-x => self.abs-x,
                            rows => self.rows, cols => self.cols);
            $w.mark-dirty;
            $w.render;
        }
        self.clear-dirty;
    }
}

my $screen = Selkie::Layout::VBox.new(sizing => Sizing.flex);
my $screen-style = Selkie::Style.new(fg => 0xE0E0E0, bg => 0x202040);
for <alpha beta gamma delta epsilon zeta eta theta> -> $word {
    $screen.add: Selkie::Widget::Text.new(
        text => $word x 4, style => $screen-style, sizing => Sizing.fixed(1),
    );
}

my $modal = Selkie::Widget::Modal.new(
    width-ratio  => 0.5,
    height-ratio => 0.5,
    backdrop     => BackdropScrim,
    framed       => True,
    frame-style  => BorderRounded,
    frame-title  => 'Hi',
    frame-padding => 0,
);
$modal.set-content(Selkie::Widget::Text.new(
    text => 'solid', sizing => Sizing.flex,
));

my $scene = Scene.new(sizing => Sizing.flex);
$scene.add($screen);
$scene.add($modal);

print render-to-string($scene, rows => 8, cols => 30, :capture-styles);