mod lazy;
mod widgets;
pub use crate::boundary::LaunchBuilder;
pub use crate::brush::{Brush, Stop, brush};
pub use crate::drawing::{DrawCommand, DrawList, DrawListBuilder};
pub use crate::message::{Message, show_message};
pub use crate::runtime::{application, launch};
pub use crate::schema::{
Alignment, Arrangement, ButtonKind, ButtonVariant, Color, ColorRole, ColorScheme, DesignSystem,
IconRole, Key, MaterialRole, MessageDuration, MotionRole, Paint, ShapeRole, SpaceRole,
TextAlign, TextOverflow, Theme, TypeRole, WindowHeightClass, WindowSizeClass,
};
pub use crate::spans::TextSpans;
pub use crate::theme::{ThemeHandle, current_theme};
pub fn theme() -> ThemeHandle {
ThemeHandle::default()
}
pub use crate::window::{NodeSize, WindowSafeArea, WindowSize};
pub use crate::{
Cursor, FileDrop, KeyEvent, PointerEvent, PointerKind, PointerKinds, RangeRequest, UserSelect,
asset, pointer_button,
};
pub use lazy::{LazyColumn, LazyGrid, LazyList, LazyListScope, LazyRow};
pub use widgets::*;
use crate::runtime::composer::{EventCallback, OwnedAttr};
use crate::runtime::{group_guard, with_composer, with_composer_if_idle};
use crate::schema::WidgetKind;
pub const MAX_SHADOWS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Shadow {
pub x: f32,
pub y: f32,
pub blur: f32,
pub spread: f32,
pub paint: Paint,
}
impl Shadow {
pub const fn new(x: f32, y: f32, blur: f32, spread: f32, paint: Paint) -> Self {
Self {
x,
y,
blur,
spread,
paint,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Modifier {
observe_size: Option<i64>,
motion: Option<MotionRole>,
material: Option<MaterialRole>,
weight: Option<f32>,
width: Option<f32>,
height: Option<f32>,
padding: Option<f32>,
padding_role: Option<SpaceRole>,
background: Option<Paint>,
shape_role: Option<ShapeRole>,
corner_radius: Option<f32>,
corner_radii: Option<[f32; 4]>,
border_width: Option<f32>,
border_color: Option<Paint>,
elevation: Option<f32>,
alpha: Option<f32>,
press_feedback: Option<(f32, f32)>,
transform: Option<[f32; 8]>,
animated_alpha: Option<crate::Animated<f32>>,
animated_background: Option<crate::Animated<Paint>>,
animated_offset: Option<crate::Animated<(f32, f32)>>,
cursor: Option<crate::Cursor>,
user_select: Option<crate::UserSelect>,
clip: bool,
window_drag: Option<bool>,
offset: Option<(f32, f32)>,
shadows: [Option<Shadow>; MAX_SHADOWS],
fill_max_width: bool,
match_parent_size: bool,
fill_max_height: bool,
}
#[allow(non_upper_case_globals)]
pub const Modifier: Modifier = Modifier::new();
impl Modifier {
pub const fn new() -> Self {
Self {
observe_size: None,
motion: None,
material: None,
weight: None,
width: None,
height: None,
padding: None,
padding_role: None,
background: None,
shape_role: None,
corner_radius: None,
corner_radii: None,
border_width: None,
border_color: None,
elevation: None,
alpha: None,
press_feedback: None,
transform: None,
animated_alpha: None,
animated_background: None,
animated_offset: None,
cursor: None,
user_select: None,
clip: false,
window_drag: None,
offset: None,
shadows: [None; MAX_SHADOWS],
fill_max_width: false,
match_parent_size: false,
fill_max_height: false,
}
}
pub const fn weight(mut self, weight: f32) -> Self {
self.weight = Some(weight);
self
}
pub const fn width(mut self, dp: f32) -> Self {
self.width = Some(dp);
self
}
pub const fn height(mut self, dp: f32) -> Self {
self.height = Some(dp);
self
}
pub const fn size(self, width: f32, height: f32) -> Self {
self.width(width).height(height)
}
pub const fn width_if(mut self, dp: Option<f32>) -> Self {
self.width = dp;
self
}
pub const fn match_parent_size(mut self) -> Self {
self.match_parent_size = true;
self
}
pub const fn fill_max_width(mut self) -> Self {
self.fill_max_width = true;
self
}
pub const fn fill_max_height(mut self) -> Self {
self.fill_max_height = true;
self
}
pub const fn fill_max_size(self) -> Self {
self.fill_max_width().fill_max_height()
}
pub const fn fill_max_width_if(mut self, fill: bool) -> Self {
self.fill_max_width = fill;
self
}
pub const fn padding(mut self, dp: f32) -> Self {
self.padding = Some(dp);
self
}
pub const fn padding_role(mut self, role: SpaceRole) -> Self {
self.padding_role = Some(role);
self
}
pub const fn padding_role_if(mut self, role: Option<SpaceRole>) -> Self {
self.padding_role = role;
self
}
pub const fn background(mut self, paint: Paint) -> Self {
self.background = Some(paint);
self
}
pub const fn background_if(mut self, paint: Option<Paint>) -> Self {
self.background = paint;
self
}
pub const fn shape_role(mut self, role: ShapeRole) -> Self {
self.shape_role = Some(role);
self
}
pub const fn corner_radius(mut self, dp: f32) -> Self {
self.corner_radius = Some(dp);
self.corner_radii = None;
self
}
pub const fn corner_radii(mut self, radii: [f32; 4]) -> Self {
self.corner_radii = Some(radii);
self.corner_radius = None;
self
}
pub const fn clip(mut self, clip: bool) -> Self {
self.clip = clip;
self
}
pub const fn window_drag(mut self, enabled: bool) -> Self {
self.window_drag = Some(enabled);
self
}
pub const fn border(mut self, width: f32, paint: Paint) -> Self {
self.border_width = Some(width);
self.border_color = Some(paint);
self
}
pub const fn elevation(mut self, dp: f32) -> Self {
self.elevation = Some(dp);
self
}
pub const fn observe_size(mut self, token: i64) -> Self {
self.observe_size = Some(token);
self
}
pub const fn motion(mut self, role: MotionRole) -> Self {
self.motion = Some(role);
self
}
pub const fn material(mut self, role: MaterialRole) -> Self {
self.material = Some(role);
self
}
pub const fn offset(mut self, x: f32, y: f32) -> Self {
self.offset = Some((x, y));
self
}
pub const fn shadow(mut self, shadow: Shadow) -> Self {
let mut index = 0;
while index < MAX_SHADOWS {
if self.shadows[index].is_none() {
self.shadows[index] = Some(shadow);
break;
}
index += 1;
}
self
}
pub fn shadows(mut self, shadows: impl IntoIterator<Item = Shadow>) -> Self {
for shadow in shadows {
self = self.shadow(shadow);
}
self
}
pub const fn alpha(mut self, alpha: f32) -> Self {
self.alpha = Some(alpha);
self
}
pub const fn press_feedback(mut self, scale: f32, tint_alpha: f32) -> Self {
self.press_feedback = Some((scale, tint_alpha));
self
}
pub fn graphics_layer(mut self, layer: crate::GraphicsLayer) -> Self {
if let crate::schema::Modifier::Transform {
a,
b,
c,
d,
e,
f,
origin_x,
origin_y,
} = layer.transform()
{
self.transform = Some([a, b, c, d, e, f, origin_x, origin_y]);
}
self.alpha = Some(layer.alpha);
self
}
pub const fn animated_alpha(mut self, value: crate::Animated<f32>) -> Self {
self.animated_alpha = Some(value);
self
}
pub const fn animated_background(mut self, value: crate::Animated<Paint>) -> Self {
self.animated_background = Some(value);
self
}
pub const fn animated_offset(mut self, value: crate::Animated<(f32, f32)>) -> Self {
self.animated_offset = Some(value);
self
}
pub const fn cursor(mut self, cursor: crate::Cursor) -> Self {
self.cursor = Some(cursor);
self
}
pub const fn user_select(mut self, user_select: crate::UserSelect) -> Self {
self.user_select = Some(user_select);
self
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mods {
Container,
Standard,
Chip,
Scaffold,
None,
}
pub(crate) type Attrs = Vec<(&'static str, OwnedAttr)>;
fn float(value: Option<f32>) -> OwnedAttr {
value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
}
fn role(value: Option<impl Into<u16>>) -> OwnedAttr {
value.map_or(OwnedAttr::None, |value| {
OwnedAttr::Int(i64::from(value.into()))
})
}
fn paint(value: Option<Paint>) -> OwnedAttr {
value.map_or(OwnedAttr::None, |paint| {
OwnedAttr::Int(paint.to_bits() as i64)
})
}
pub(crate) fn role_or_zero(value: Option<impl Into<u16>>) -> OwnedAttr {
OwnedAttr::Int(value.map_or(0, |value| i64::from(value.into())))
}
pub(crate) fn dp_or_zero(value: Option<f32>) -> OwnedAttr {
OwnedAttr::Float(f64::from(value.unwrap_or(0.0)))
}
pub(crate) fn paint_or_zero(value: Option<Paint>) -> OwnedAttr {
OwnedAttr::Int(value.map_or(0, |paint| paint.to_bits() as i64))
}
pub(crate) fn optional_text(value: Option<String>) -> OwnedAttr {
value.map_or(OwnedAttr::None, OwnedAttr::Text)
}
pub(crate) fn push_modifiers(modifier: &Modifier, mods: Mods, out: &mut Attrs) {
let container = mods == Mods::Container;
let standard = container || mods == Mods::Standard;
let chip = standard || mods == Mods::Chip;
let scaffold = mods == Mods::Scaffold;
let mut push = |always: bool, name: &'static str, value: OwnedAttr| {
let set = !matches!(value, OwnedAttr::None | OwnedAttr::Bool(false));
if always || set {
out.push((name, value));
}
};
push(
container,
"observe_size",
modifier
.observe_size
.map_or(OwnedAttr::None, OwnedAttr::Int),
);
push(container, "motion", role(modifier.motion));
push(container || scaffold, "material", role(modifier.material));
push(chip, "weight", float(modifier.weight));
push(chip, "width", float(modifier.width));
push(chip, "height", float(modifier.height));
push(chip, "padding", float(modifier.padding));
push(chip, "padding_role", role(modifier.padding_role));
push(
standard || scaffold,
"background",
paint(if modifier.animated_background.is_some() {
None
} else {
modifier.background
}),
);
push(standard, "shape_role", role(modifier.shape_role));
push(standard, "corner_radius", float(modifier.corner_radius));
push(
false,
"corner_radii",
modifier.corner_radii.map_or(OwnedAttr::None, |values| {
OwnedAttr::Bytes(
values
.iter()
.flat_map(|value| value.to_le_bytes())
.collect(),
)
}),
);
push(false, "clip", OwnedAttr::Bool(modifier.clip));
push(
true,
"window_drag",
modifier
.window_drag
.map_or(OwnedAttr::None, OwnedAttr::Bool),
);
push(standard, "border_width", float(modifier.border_width));
push(standard, "border_color", paint(modifier.border_color));
push(standard, "elevation", float(modifier.elevation));
push(
false,
"match_parent_size",
OwnedAttr::Bool(modifier.match_parent_size),
);
push(
chip,
"fill_max_width",
OwnedAttr::Bool(modifier.fill_max_width),
);
push(
standard,
"fill_max_height",
OwnedAttr::Bool(modifier.fill_max_height),
);
push(
false,
"press_feedback",
modifier
.press_feedback
.map_or(OwnedAttr::None, |(scale, alpha)| {
OwnedAttr::Bytes([scale.to_le_bytes(), alpha.to_le_bytes()].concat())
}),
);
push(
false,
"alpha",
float(if modifier.animated_alpha.is_some() {
None
} else {
modifier.alpha
}),
);
push(
false,
"transform",
match (modifier.animated_offset, modifier.transform) {
(None, Some(values)) => OwnedAttr::Bytes(
values
.iter()
.flat_map(|value| value.to_le_bytes())
.collect(),
),
_ => OwnedAttr::None,
},
);
push(
false,
"cursor",
match modifier.cursor {
Some(crate::Cursor::Auto) | None => OwnedAttr::None,
Some(cursor) => OwnedAttr::Int(i64::from(u16::from(cursor))),
},
);
push(
false,
"user_select",
match modifier.user_select {
Some(crate::UserSelect::Auto) | None => OwnedAttr::None,
Some(select) => OwnedAttr::Int(i64::from(u16::from(select))),
},
);
push(
false,
"offset",
modifier.offset.map_or(OwnedAttr::None, |(x, y)| {
let mut bytes = Vec::with_capacity(8);
bytes.extend_from_slice(&x.to_le_bytes());
bytes.extend_from_slice(&y.to_le_bytes());
OwnedAttr::Bytes(bytes)
}),
);
push(
false,
"shadows",
if modifier.shadows[0].is_none() {
OwnedAttr::None
} else {
let mut bytes = Vec::new();
for shadow in modifier.shadows.iter().flatten() {
for value in [shadow.x, shadow.y, shadow.blur, shadow.spread] {
bytes.extend_from_slice(&value.to_le_bytes());
}
bytes.extend_from_slice(&shadow.paint.to_bits().to_le_bytes());
}
OwnedAttr::Bytes(bytes)
},
);
push(
false,
"animated_alpha",
modifier.animated_alpha.map_or(OwnedAttr::None, |value| {
OwnedAttr::Bytes(crate::animation::animated_bytes(
&[value.target.to_bits()],
&value.spec,
))
}),
);
push(
false,
"animated_background",
modifier
.animated_background
.map_or(OwnedAttr::None, |value| {
let bits = value.target.to_bits();
OwnedAttr::Bytes(crate::animation::animated_bytes(
&[bits as u32, (bits >> 32) as u32],
&value.spec,
))
}),
);
push(
false,
"animated_offset",
modifier.animated_offset.map_or(OwnedAttr::None, |value| {
OwnedAttr::Bytes(crate::animation::animated_bytes(
&[value.target.0.to_bits(), value.target.1.to_bits()],
&value.spec,
))
}),
);
}
pub(crate) fn emit(
key: u64,
widget: WidgetKind,
fill: impl FnOnce(&mut Attrs),
listeners: Vec<(&'static str, EventCallback)>,
content: impl FnOnce(),
) {
let _group = group_guard(key);
emit_node(widget, fill, listeners, content);
}
pub(crate) fn emit_node(
widget: WidgetKind,
fill: impl FnOnce(&mut Attrs),
listeners: Vec<(&'static str, EventCallback)>,
content: impl FnOnce(),
) {
let node = with_composer_if_idle(|composer| {
if !composer.composing() {
return None;
}
let mut attrs = composer.take_attrs();
fill(&mut attrs);
Some(composer.update_node(widget, attrs, listeners))
})
.flatten();
let Some(node) = node else {
return;
};
with_composer(|composer| composer.push_node_context(node));
let _context = NodeContextGuard;
content();
}
struct NodeContextGuard;
impl Drop for NodeContextGuard {
fn drop(&mut self) {
with_composer_if_idle(|composer| composer.pop_node_context());
}
}
pub(crate) const fn widget_key(name: &str) -> u64 {
crate::runtime::composer::call_site(name, 0)
}
#[cfg(test)]
mod fr19_9_tests {
use super::*;
#[test]
fn fr19_9_window_drag_builder_preserves_false() {
for enabled in [false, true] {
let mut attrs = Vec::new();
push_modifiers(
&Modifier::new().window_drag(enabled),
Mods::Standard,
&mut attrs,
);
assert!(attrs.iter().any(|(name, value)| *name == "window_drag"
&& matches!(value, OwnedAttr::Bool(value) if *value == enabled)));
}
}
}