use std::collections::HashMap;
use std::sync::Arc;
use llimphi_layout::taffy::NodeId;
use llimphi_layout::{ComputedLayout, LayoutTree, Style};
use vello::kurbo::{
Affine, Ellipse, Point, Rect as KurboRect, RoundedRect, RoundedRectRadii, Stroke,
};
use vello::peniko::{BlendMode, Color, Fill, Gradient, ImageBrush as Image, Mix};
mod anim;
mod hero;
mod layout_builder;
mod render;
mod ripple;
mod semantics;
mod view;
pub use anim::{
ease_out_cubic, reconcile_size_anim, Anim, AnimRegistry, SizeAnim, SizeAnimRegistry,
};
pub use hero::{Hero, HeroRegistry};
pub use layout_builder::{collect_builder_constraints, expand_layout_builders, has_layout_builder};
pub use render::*;
pub use ripple::{Ripple, RippleRegistry};
pub use semantics::{Role, SemanticsFlags, SemanticsSpec};
#[derive(Clone)]
pub struct TextSpec {
pub content: String,
pub size_px: f32,
pub color: Color,
pub alignment: llimphi_text::Alignment,
pub italic: bool,
pub weight: f32,
pub max_lines: Option<usize>,
pub ellipsis: bool,
pub font_family: Option<String>,
pub line_height: f32,
pub runs: Option<Vec<(usize, usize, Color)>>,
pub underline: bool,
pub strikethrough: bool,
pub spans: Option<Vec<llimphi_text::TextSpan>>,
pub letter_spacing: f32,
pub word_spacing: f32,
pub no_wrap: bool,
pub overflow_wrap: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DragPhase {
Move,
End,
}
pub type DragFn<Msg> = Arc<dyn Fn(DragPhase, f32, f32) -> Option<Msg> + Send + Sync>;
pub type DropFn<Msg> = Arc<dyn Fn(u64) -> Option<Msg> + Send + Sync>;
pub type ClickAtFn<Msg> = Arc<dyn Fn(f32, f32, f32, f32) -> Option<Msg> + Send + Sync>;
pub type ScrollFn<Msg> = Arc<dyn Fn(f32, f32) -> Option<Msg> + Send + Sync>;
pub type DragAtFn<Msg> = Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>;
pub type DragVelocityFn<Msg> =
Arc<dyn Fn(DragPhase, f32, f32, f32, f32) -> Option<Msg> + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GesturePhase {
Begin,
Update,
End,
}
pub type ScaleFn<Msg> = Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>;
pub type RotateFn<Msg> = Arc<dyn Fn(GesturePhase, f32, f32, f32) -> Option<Msg> + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Constraints {
pub max_width: f32,
pub max_height: f32,
}
pub type LayoutBuilderFn<Msg> = Arc<dyn Fn(Constraints) -> View<Msg> + Send + Sync>;
#[derive(Debug, Clone, Copy, Default)]
pub struct PaintRect {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
pub type PaintFn = Arc<
dyn Fn(&mut vello::Scene, &mut llimphi_text::Typesetter, PaintRect) + Send + Sync,
>;
pub type GpuPaintFn = Arc<
dyn Fn(
&wgpu::Device,
&wgpu::Queue,
&mut wgpu::CommandEncoder,
&wgpu::TextureView,
PaintRect,
(u32, u32),
) + Send
+ Sync,
>;
pub type OverPaintFn = PaintFn;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Shadow {
pub color: Color,
pub blur: f64,
pub dx: f64,
pub dy: f64,
pub spread: f64,
}
impl Shadow {
pub fn new(color: Color, blur: f64) -> Self {
Self { color, blur, dx: 0.0, dy: 0.0, spread: 0.0 }
}
pub fn soft(alpha: u8, blur: f64) -> Self {
Self {
color: Color::from_rgba8(0, 0, 0, alpha),
blur,
dx: 0.0,
dy: blur * 0.4,
spread: 0.0,
}
}
pub fn offset(mut self, dx: f64, dy: f64) -> Self {
self.dx = dx;
self.dy = dy;
self
}
pub fn spread(mut self, spread: f64) -> Self {
self.spread = spread;
self
}
}
#[derive(Clone, Copy, Debug)]
pub struct Border {
pub width: f64,
pub color: Color,
}
impl Border {
pub fn new(width: f64, color: Color) -> Self {
Self { width, color }
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FilterOp {
Blur(f32),
ColorMatrix([f32; 20]),
DropShadow(Shadow),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransformPivot {
pub px: (f64, f64),
pub frac: (f64, f64),
}
impl Default for TransformPivot {
fn default() -> Self {
Self { px: (0.0, 0.0), frac: (0.5, 0.5) }
}
}
pub struct View<Msg> {
pub style: Style,
pub fill: Option<Color>,
pub hover_fill: Option<Color>,
pub radius: f64,
pub corner_radii: Option<RoundedRectRadii>,
pub shadow: Option<Shadow>,
pub fill_gradient: Option<Gradient>,
pub border: Option<Border>,
pub text: Option<TextSpec>,
pub image: Option<Image>,
pub image_fit: Option<ImageFit>,
pub mask_image: Option<Image>,
pub mask_placement: Option<MaskPlacement>,
pub mask_extra: Vec<(Image, MaskCompose)>,
pub painter: Option<PaintFn>,
pub gpu_painter: Option<GpuPaintFn>,
pub over_painter: Option<PaintFn>,
pub on_click: Option<Msg>,
pub on_click_at: Option<ClickAtFn<Msg>>,
pub on_right_click: Option<Msg>,
pub on_right_click_at: Option<ClickAtFn<Msg>>,
pub on_middle_click: Option<Msg>,
pub drag: Option<DragFn<Msg>>,
pub drag_at: Option<DragAtFn<Msg>>,
pub drag_velocity: Option<DragVelocityFn<Msg>>,
pub drag_payload: Option<u64>,
pub on_drop: Option<DropFn<Msg>>,
pub drop_hover_fill: Option<Color>,
pub clip: bool,
pub clip_inset: Option<[f32; 4]>,
pub clip_ellipse: Option<[f32; 14]>,
pub clip_polygon: Option<(bool, Vec<[f32; 4]>)>,
pub clip_path_svg: Option<(bool, String)>,
pub clip_ref_inset: Option<[f32; 4]>,
pub on_pointer_enter: Option<Msg>,
pub on_pointer_leave: Option<Msg>,
pub on_pointer_move_at: Option<ClickAtFn<Msg>>,
pub on_scroll: Option<ScrollFn<Msg>>,
pub on_scale: Option<ScaleFn<Msg>>,
pub on_rotate: Option<RotateFn<Msg>>,
pub on_double_tap: Option<Msg>,
pub on_double_tap_at: Option<ClickAtFn<Msg>>,
pub on_long_press: Option<Msg>,
pub on_long_press_at: Option<ClickAtFn<Msg>>,
pub focusable: Option<u64>,
pub text_select_key: Option<u64>,
pub alpha: Option<f32>,
pub anim: Option<Anim>,
pub animated_size: Option<SizeAnim>,
pub semantics: Option<SemanticsSpec>,
pub hero: Option<Hero>,
pub transform: Option<Affine>,
pub transform_rel: Option<(f64, f64)>,
pub transform_origin: Option<TransformPivot>,
pub tooltip: Option<String>,
pub cursor: Option<Cursor>,
pub ripple: Option<Ripple>,
pub layout_builder: Option<LayoutBuilderFn<Msg>>,
pub backdrop_blur: Option<f32>,
pub filter: Vec<FilterOp>,
pub blend: Option<BlendMode>,
pub children: Vec<View<Msg>>,
}
impl<Msg: 'static> View<Msg> {
pub fn map<Msg2, F>(self, f: F) -> View<Msg2>
where
Msg2: 'static,
F: Fn(Msg) -> Msg2 + Send + Sync + 'static,
{
self.map_shared(Arc::new(f))
}
fn map_shared<Msg2: 'static>(
self,
f: Arc<dyn Fn(Msg) -> Msg2 + Send + Sync>,
) -> View<Msg2> {
let View {
style,
fill,
hover_fill,
radius,
corner_radii,
shadow,
fill_gradient,
border,
text,
image,
image_fit,
mask_image,
mask_placement,
mask_extra,
painter,
gpu_painter,
over_painter,
on_click,
on_click_at,
on_right_click,
on_right_click_at,
on_middle_click,
drag,
drag_at,
drag_velocity,
drag_payload,
on_drop,
drop_hover_fill,
clip,
clip_inset,
clip_ellipse,
clip_polygon,
clip_path_svg,
clip_ref_inset,
on_pointer_enter,
on_pointer_leave,
on_pointer_move_at,
on_scroll,
on_scale,
on_rotate,
on_double_tap,
on_double_tap_at,
on_long_press,
on_long_press_at,
focusable,
text_select_key,
alpha,
anim,
animated_size,
semantics,
hero,
transform,
transform_rel,
transform_origin,
tooltip,
cursor,
ripple,
layout_builder,
backdrop_blur,
filter,
blend,
children,
} = self;
View {
style,
fill,
hover_fill,
radius,
corner_radii,
shadow,
fill_gradient,
border,
text,
image,
image_fit,
mask_image,
mask_placement,
mask_extra,
painter,
gpu_painter,
over_painter,
drag_payload,
drop_hover_fill,
clip,
clip_inset,
clip_ellipse,
clip_polygon,
clip_path_svg,
clip_ref_inset,
focusable,
text_select_key,
alpha,
anim,
animated_size,
semantics,
hero,
transform,
transform_rel,
transform_origin,
tooltip,
cursor,
ripple,
backdrop_blur,
filter,
blend,
on_click: on_click.map(|m| f(m)),
on_right_click: on_right_click.map(|m| f(m)),
on_middle_click: on_middle_click.map(|m| f(m)),
on_pointer_enter: on_pointer_enter.map(|m| f(m)),
on_pointer_leave: on_pointer_leave.map(|m| f(m)),
on_double_tap: on_double_tap.map(|m| f(m)),
on_long_press: on_long_press.map(|m| f(m)),
on_click_at: on_click_at.map(|h| {
let f = f.clone();
Arc::new(move |a, b, c, d| h(a, b, c, d).map(|m| f(m))) as ClickAtFn<Msg2>
}),
on_right_click_at: on_right_click_at.map(|h| {
let f = f.clone();
Arc::new(move |a, b, c, d| h(a, b, c, d).map(|m| f(m))) as ClickAtFn<Msg2>
}),
on_pointer_move_at: on_pointer_move_at.map(|h| {
let f = f.clone();
Arc::new(move |a, b, c, d| h(a, b, c, d).map(|m| f(m))) as ClickAtFn<Msg2>
}),
on_double_tap_at: on_double_tap_at.map(|h| {
let f = f.clone();
Arc::new(move |a, b, c, d| h(a, b, c, d).map(|m| f(m))) as ClickAtFn<Msg2>
}),
on_long_press_at: on_long_press_at.map(|h| {
let f = f.clone();
Arc::new(move |a, b, c, d| h(a, b, c, d).map(|m| f(m))) as ClickAtFn<Msg2>
}),
drag: drag.map(|h| {
let f = f.clone();
Arc::new(move |p, dx, dy| h(p, dx, dy).map(|m| f(m))) as DragFn<Msg2>
}),
drag_at: drag_at.map(|h| {
let f = f.clone();
Arc::new(move |p, dx, dy, lx, ly| h(p, dx, dy, lx, ly).map(|m| f(m)))
as DragAtFn<Msg2>
}),
drag_velocity: drag_velocity.map(|h| {
let f = f.clone();
Arc::new(move |p, dx, dy, vx, vy| h(p, dx, dy, vx, vy).map(|m| f(m)))
as DragVelocityFn<Msg2>
}),
on_drop: on_drop.map(|h| {
let f = f.clone();
Arc::new(move |payload| h(payload).map(|m| f(m))) as DropFn<Msg2>
}),
on_scroll: on_scroll.map(|h| {
let f = f.clone();
Arc::new(move |dx, dy| h(dx, dy).map(|m| f(m))) as ScrollFn<Msg2>
}),
on_scale: on_scale.map(|h| {
let f = f.clone();
Arc::new(move |ph, s, cx, cy| h(ph, s, cx, cy).map(|m| f(m))) as ScaleFn<Msg2>
}),
on_rotate: on_rotate.map(|h| {
let f = f.clone();
Arc::new(move |ph, r, cx, cy| h(ph, r, cx, cy).map(|m| f(m))) as RotateFn<Msg2>
}),
layout_builder: layout_builder.map(|h| {
let f = f.clone();
Arc::new(move |c| h(c).map_shared(f.clone())) as LayoutBuilderFn<Msg2>
}),
children: children
.into_iter()
.map(|c| c.map_shared(f.clone()))
.collect(),
}
}
}
pub struct Mounted<Msg> {
pub root: NodeId,
pub nodes: Vec<MountedNode<Msg>>,
pub text_measures: HashMap<NodeId, TextMeasure>,
}
#[derive(Clone)]
pub struct TextMeasure {
pub content: String,
pub size_px: f32,
pub alignment: llimphi_text::Alignment,
pub italic: bool,
pub font_family: Option<String>,
pub line_height: f32,
pub weight: f32,
pub max_lines: Option<usize>,
pub ellipsis: bool,
pub underline: bool,
pub strikethrough: bool,
pub spans: Option<Vec<llimphi_text::TextSpan>>,
pub letter_spacing: f32,
pub word_spacing: f32,
pub no_wrap: bool,
pub overflow_wrap: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageFit {
Contain,
Cover,
Fill,
None,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MaskLen {
Auto,
Px(f32),
Pct(f32),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MaskSize {
Auto,
Cover,
Contain,
Explicit { x: MaskLen, y: MaskLen },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MaskMode {
#[default]
Luminance,
Alpha,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MaskCompose {
#[default]
Add,
Subtract,
Intersect,
Exclude,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MaskPlacement {
pub size: MaskSize,
pub pos_x: MaskLen,
pub pos_y: MaskLen,
pub repeat_x: bool,
pub repeat_y: bool,
pub mode: MaskMode,
pub clip_inset: Option<[f32; 4]>,
pub origin_inset: Option<[f32; 4]>,
}
impl Default for ImageFit {
fn default() -> Self {
ImageFit::Contain
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cursor {
Default,
Pointer,
Text,
Crosshair,
Move,
Grab,
Grabbing,
NotAllowed,
Wait,
Progress,
Help,
ColResize,
RowResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ZoomIn,
ZoomOut,
}
pub struct MountedNode<Msg> {
pub id: NodeId,
pub fill: Option<Color>,
pub hover_fill: Option<Color>,
pub radius: f64,
pub corner_radii: Option<RoundedRectRadii>,
pub shadow: Option<Shadow>,
pub fill_gradient: Option<Gradient>,
pub border: Option<Border>,
pub text: Option<TextSpec>,
pub image: Option<Image>,
pub image_fit: Option<ImageFit>,
pub mask_image: Option<Image>,
pub mask_placement: Option<MaskPlacement>,
pub mask_extra: Vec<(Image, MaskCompose)>,
pub painter: Option<PaintFn>,
pub gpu_painter: Option<GpuPaintFn>,
pub over_painter: Option<PaintFn>,
pub on_click: Option<Msg>,
pub on_click_at: Option<ClickAtFn<Msg>>,
pub on_right_click: Option<Msg>,
pub on_right_click_at: Option<ClickAtFn<Msg>>,
pub on_middle_click: Option<Msg>,
pub drag: Option<DragFn<Msg>>,
pub drag_at: Option<DragAtFn<Msg>>,
pub drag_velocity: Option<DragVelocityFn<Msg>>,
pub drag_payload: Option<u64>,
pub on_drop: Option<DropFn<Msg>>,
pub drop_hover_fill: Option<Color>,
pub clip: bool,
pub clip_inset: Option<[f32; 4]>,
pub clip_ellipse: Option<[f32; 14]>,
pub clip_polygon: Option<(bool, Vec<[f32; 4]>)>,
pub clip_path_svg: Option<(bool, String)>,
pub clip_ref_inset: Option<[f32; 4]>,
pub on_pointer_enter: Option<Msg>,
pub on_pointer_leave: Option<Msg>,
pub on_pointer_move_at: Option<ClickAtFn<Msg>>,
pub on_scroll: Option<ScrollFn<Msg>>,
pub on_scale: Option<ScaleFn<Msg>>,
pub on_rotate: Option<RotateFn<Msg>>,
pub on_double_tap: Option<Msg>,
pub on_double_tap_at: Option<ClickAtFn<Msg>>,
pub on_long_press: Option<Msg>,
pub on_long_press_at: Option<ClickAtFn<Msg>>,
pub focusable: Option<u64>,
pub text_select_key: Option<u64>,
pub alpha: Option<f32>,
pub anim: Option<Anim>,
pub animated_size: Option<SizeAnim>,
pub semantics: Option<SemanticsSpec>,
pub hero: Option<Hero>,
pub transform: Option<Affine>,
pub transform_rel: Option<(f64, f64)>,
pub transform_origin: Option<TransformPivot>,
pub tooltip: Option<String>,
pub cursor: Option<Cursor>,
pub ripple: Option<Ripple>,
pub is_layout_builder: bool,
pub backdrop_blur: Option<f32>,
pub filter: Vec<FilterOp>,
pub blend: Option<BlendMode>,
pub subtree_end: usize,
}