Skip to main content

cranpose_ui_graphics/
alpha_mask.rs

1//! Alpha-mask helpers for graphics-layer effects.
2//!
3//! These utilities provide a dev-facing API for common Jetpack Compose style
4//! masking workflows, such as revealing/cutting content with a rounded shape
5//! and a feathered opacity transition.
6
7use crate::{CornerRadii, RenderEffect, RuntimeShader};
8
9/// Direction of the gradient cut mask.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
11pub enum CutDirection {
12    /// Keep content from the left edge up to the cut progress.
13    #[default]
14    LeftToRight,
15    /// Keep content from the right edge up to the cut progress.
16    RightToLeft,
17    /// Keep content from the top edge down to the cut progress.
18    TopToBottom,
19    /// Keep content from the bottom edge up to the cut progress.
20    BottomToTop,
21}
22
23impl CutDirection {
24    fn uniform_code(self) -> f32 {
25        match self {
26            CutDirection::LeftToRight => 0.0,
27            CutDirection::RightToLeft => 1.0,
28            CutDirection::TopToBottom => 2.0,
29            CutDirection::BottomToTop => 3.0,
30        }
31    }
32}
33
34/// Configuration for a directional gradient cut mask.
35#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct GradientCutMaskSpec {
37    /// Reveal progress in [0, 1].
38    pub progress: f32,
39    /// Width of the edge feather in dp/px.
40    pub feather: f32,
41    /// Rounded-corner radius of the masked area in dp/px.
42    pub corner_radius: f32,
43    /// Direction from which content is revealed.
44    pub direction: CutDirection,
45}
46
47impl Default for GradientCutMaskSpec {
48    fn default() -> Self {
49        Self {
50            progress: 0.5,
51            feather: 24.0,
52            corner_radius: 16.0,
53            direction: CutDirection::LeftToRight,
54        }
55    }
56}
57
58/// Configuration for a directional gradient fade mask that matches
59/// `drawWithContent { drawRect(..., blendMode = DstOut) }` behavior.
60#[derive(Clone, Copy, Debug, PartialEq)]
61pub struct GradientFadeMaskSpec {
62    /// Axis coordinate where fade starts (fully cut) in dp/px.
63    pub start: f32,
64    /// Axis coordinate where fade ends (fully visible) in dp/px.
65    pub end: f32,
66    /// Direction that defines the gradient axis.
67    pub direction: CutDirection,
68}
69
70impl Default for GradientFadeMaskSpec {
71    fn default() -> Self {
72        Self {
73            start: 0.0,
74            end: 64.0,
75            direction: CutDirection::TopToBottom,
76        }
77    }
78}
79
80/// WGSL shader for directional cut + rounded rect alpha mask.
81///
82/// Uniform layout:
83/// - 0,1: container size in dp
84/// - 2: progress `0,1`
85/// - 3: feather in dp
86/// - 4: corner radius in dp
87/// - 5: direction code (0=L->R, 1=R->L, 2=T->B, 3=B->T)
88pub const GRADIENT_CUT_MASK_WGSL: &str = runtime_shader_wgsl!("gradient_cut_mask.wgsl");
89
90/// WGSL shader for rounded-rectangle alpha masking with feathered edges.
91///
92/// Uniform layout:
93/// - 0,1: container size in dp
94/// - 2: edge feather in dp
95/// - 3,4,5,6: corner radii in dp (top-left, top-right, bottom-right, bottom-left)
96pub const ROUNDED_ALPHA_MASK_WGSL: &str = runtime_shader_wgsl!("rounded_alpha_mask.wgsl");
97
98/// WGSL shader for directional fade-out alpha masking (DstOut-style).
99///
100/// Uniform layout:
101/// - 0,1: container size in dp
102/// - 2: fade start in dp
103/// - 3: fade end in dp
104/// - 4: direction code (0=L->R, 1=R->L, 2=T->B, 3=B->T)
105pub const GRADIENT_FADE_DST_OUT_WGSL: &str = runtime_shader_wgsl!("gradient_fade_dst_out.wgsl");
106
107/// Builds a directional cut mask effect.
108///
109/// The resulting `RenderEffect` keeps content from one side up to `progress`
110/// with a feathered transition and rounded-rect outer masking.
111pub fn gradient_cut_mask_effect(
112    spec: &GradientCutMaskSpec,
113    area_width: f32,
114    area_height: f32,
115) -> RenderEffect {
116    let mut shader = RuntimeShader::new(GRADIENT_CUT_MASK_WGSL);
117    shader.set_position_independent(true);
118    shader.set_float2(0, area_width.max(1.0), area_height.max(1.0));
119    shader.set_float(2, spec.progress.clamp(0.0, 1.0));
120    shader.set_float(3, spec.feather.max(0.0));
121    shader.set_float(4, spec.corner_radius.max(0.0));
122    shader.set_float(5, spec.direction.uniform_code());
123    RenderEffect::runtime_shader(shader)
124}
125
126/// Builds a rounded alpha mask effect without directional cutting.
127///
128/// Useful for masking other effects (for example blur) to a rounded rectangle
129/// with an explicit feathered edge width.
130pub fn rounded_alpha_mask_effect(
131    area_width: f32,
132    area_height: f32,
133    corner_radius: f32,
134    edge_feather: f32,
135) -> RenderEffect {
136    rounded_corner_alpha_mask_effect(
137        area_width,
138        area_height,
139        CornerRadii::uniform(corner_radius),
140        edge_feather,
141    )
142}
143
144/// Builds a rounded alpha mask effect with independent corner radii.
145pub fn rounded_corner_alpha_mask_effect(
146    area_width: f32,
147    area_height: f32,
148    corner_radii: CornerRadii,
149    edge_feather: f32,
150) -> RenderEffect {
151    let mut shader = RuntimeShader::new(ROUNDED_ALPHA_MASK_WGSL);
152    shader.set_position_independent(true);
153    shader.set_float2(0, area_width.max(1.0), area_height.max(1.0));
154    shader.set_float(2, edge_feather.max(0.0));
155    shader.set_float4(
156        3,
157        corner_radii.top_left.max(0.0),
158        corner_radii.top_right.max(0.0),
159        corner_radii.bottom_right.max(0.0),
160        corner_radii.bottom_left.max(0.0),
161    );
162    RenderEffect::runtime_shader(shader)
163}
164
165/// Builds a directional gradient fade mask with DstOut semantics.
166///
167/// Equivalent to drawing an opaque->transparent gradient on top of content
168/// using destination-out blending: the fade start is fully cut, and the end
169/// is fully preserved.
170pub fn gradient_fade_dst_out_effect(
171    spec: &GradientFadeMaskSpec,
172    area_width: f32,
173    area_height: f32,
174) -> RenderEffect {
175    let mut shader = RuntimeShader::new(GRADIENT_FADE_DST_OUT_WGSL);
176    shader.set_position_independent(true);
177    shader.set_float2(0, area_width.max(1.0), area_height.max(1.0));
178    shader.set_float(2, spec.start);
179    shader.set_float(3, spec.end);
180    shader.set_float(4, spec.direction.uniform_code());
181    RenderEffect::runtime_shader(shader)
182}
183
184#[cfg(test)]
185#[path = "tests/alpha_mask_tests.rs"]
186mod tests;