use crate::anim::{Easing, Repeat, Transition};
use crate::color::Color;
use crate::cursor::CursorShape;
use crate::enter::Enter;
use crate::geom::Edges;
use crate::input::Buttons;
use crate::keyframes::Keyframe;
use crate::value::Value;
use crate::window::{WindowButton, WindowRole};
pub use crate::access::{Label, Live, Role};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Sizing {
#[default]
Fit,
Grow(f32),
Fixed(f32),
Percent(f32),
Calc(crate::calc::Calc),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Bound {
Px(f32),
Fit,
Calc(crate::calc::Calc),
}
pub fn max_of_calc(c: crate::calc::Calc) -> f32 {
-1.0 - c.id() as f32
}
pub fn px_ceiling(px: f32) -> f32 {
px.max(0.0)
}
pub fn max_calc(v: f32) -> Option<crate::calc::Calc> {
(v < 0.0)
.then(|| crate::calc::Calc::from_id((-v - 1.0) as u32))
.flatten()
}
impl From<f32> for Bound {
fn from(px: f32) -> Self {
Bound::Px(px)
}
}
impl From<Min> for Bound {
fn from(m: Min) -> Self {
if m.is_fit() {
Bound::Fit
} else if let Some(c) = m.as_calc() {
Bound::Calc(c)
} else if m.is_auto() {
Bound::Px(-0.0)
} else {
Bound::Px(m.resolved())
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Min(f32);
impl Default for Min {
fn default() -> Self {
Min::AUTO
}
}
impl Min {
pub const FIT: Min = Min(-1.0);
pub const AUTO: Min = Min(-0.0);
pub fn px(v: f32) -> Min {
Min(if v > 0.0 { v } else { 0.0 })
}
pub fn is_auto(self) -> bool {
self.0 == 0.0 && self.0.is_sign_negative()
}
pub fn is_fit(self) -> bool {
self.0 == Min::FIT.0
}
pub fn calc(c: crate::calc::Calc) -> Min {
Min(-2.0 - c.id() as f32)
}
pub fn as_calc(self) -> Option<crate::calc::Calc> {
(self.0 <= -2.0)
.then(|| crate::calc::Calc::from_id((-self.0 - 2.0) as u32))
.flatten()
}
pub(crate) fn deferred(self) -> bool {
self.0 < 0.0
}
pub fn resolved(self) -> f32 {
if self.0 > 0.0 { self.0 } else { 0.0 }
}
}
impl From<f32> for Min {
fn from(v: f32) -> Self {
Min::px(v)
}
}
impl From<f32> for Sizing {
fn from(px: f32) -> Self {
Sizing::Fixed(px)
}
}
impl Sizing {
pub const GROW: Sizing = Sizing::Grow(1.0);
}
impl Sizing {
pub fn describe(self) -> String {
match self {
Sizing::Fit => "fit".into(),
Sizing::Grow(w) => format!("grow({w})"),
Sizing::Fixed(px) => format!("{px}px"),
Sizing::Percent(p) => format!("{}%", p * 100.0),
Sizing::Calc(c) => c.describe(),
}
}
pub fn amount(self) -> Option<f32> {
match self {
Sizing::Fit | Sizing::Calc(_) => None,
Sizing::Grow(v) | Sizing::Fixed(v) | Sizing::Percent(v) => Some(v),
}
}
pub fn with_amount(self, v: f32) -> Self {
match self {
Sizing::Fit => Sizing::Fit,
Sizing::Calc(c) => Sizing::Calc(c),
Sizing::Grow(_) => Sizing::Grow(v),
Sizing::Fixed(_) => Sizing::Fixed(v),
Sizing::Percent(_) => Sizing::Percent(v),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Dir {
Row,
#[default]
Column,
}
impl Dir {
pub fn name(self) -> &'static str {
match self {
Dir::Row => "row",
Dir::Column => "column",
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Align {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
SpaceEvenly,
Baseline,
}
impl Align {
pub fn name(self) -> &'static str {
crate::schema::ALIGNS[self as usize]
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FloatAnchor {
#[default]
Parent,
Viewport,
Node(crate::key::Key),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FloatConfig {
pub anchor: FloatAnchor,
pub anchor_point: (Align, Align),
pub self_point: (Align, Align),
pub offset: Vec2Offset,
pub fit: bool,
pub clip: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec2Offset {
pub x: f32,
pub y: f32,
}
impl Default for FloatConfig {
fn default() -> Self {
Self {
anchor: FloatAnchor::Parent,
anchor_point: (Align::Start, Align::Start),
self_point: (Align::Start, Align::Start),
offset: Vec2Offset::default(),
fit: false,
clip: false,
}
}
}
impl FloatConfig {
pub fn parent() -> Self {
Self::default()
}
pub fn viewport() -> Self {
Self {
anchor: FloatAnchor::Viewport,
..Self::default()
}
}
pub fn below() -> Self {
Self {
anchor: FloatAnchor::Parent,
anchor_point: (Align::Center, Align::End),
self_point: (Align::Center, Align::Start),
offset: Vec2Offset { x: 0.0, y: 6.0 },
..Self::default()
}
}
pub fn above() -> Self {
Self {
anchor: FloatAnchor::Parent,
anchor_point: (Align::Center, Align::Start),
self_point: (Align::Center, Align::End),
offset: Vec2Offset { x: 0.0, y: -6.0 },
..Self::default()
}
}
pub fn at(mut self, x: Align, y: Align) -> Self {
self.anchor_point = (x, y);
self
}
pub fn self_at(mut self, x: Align, y: Align) -> Self {
self.self_point = (x, y);
self
}
#[inline]
pub fn inside(self, x: Align, y: Align) -> Self {
self.at(x, y).self_at(x, y)
}
pub fn offset(mut self, x: f32, y: f32) -> Self {
self.offset = Vec2Offset { x, y };
self
}
pub fn fit(mut self) -> Self {
self.fit = true;
self
}
pub fn clipped(mut self) -> Self {
self.clip = true;
self
}
pub fn clipped_by_parent(&self) -> bool {
self.clip && self.anchor == FloatAnchor::Parent
}
pub fn preset_at(i: usize) -> Option<Self> {
Some(match i {
0 => Self::parent(),
1 => Self::viewport(),
2 => Self::below(),
3 => Self::above(),
_ => return None,
})
}
pub fn preset(name: &str) -> Option<Self> {
Self::preset_at(FLOAT_PRESETS.iter().position(|p| *p == name)?)
}
pub fn build(
base: FloatConfig,
anchor_at: Option<(Align, Align)>,
self_at: Option<(Align, Align)>,
dx: Option<f32>,
dy: Option<f32>,
fit: bool,
clip: bool,
) -> Self {
let mut cfg = base;
if let Some((x, y)) = anchor_at {
cfg.anchor_point = (x, y);
}
if let Some((x, y)) = self_at {
cfg.self_point = (x, y);
}
if let Some(x) = dx {
cfg.offset.x = x;
}
if let Some(y) = dy {
cfg.offset.y = y;
}
cfg.fit |= fit;
cfg.clip |= clip;
cfg
}
}
pub const FLOAT_PRESETS: &[&str] = &["parent", "viewport", "below", "above"];
pub const OVERFLOW_CLIP: u32 = 1 << 0;
pub const OVERFLOW_SCROLL_X: u32 = 1 << 1;
pub const OVERFLOW_SCROLL_Y: u32 = 1 << 2;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct PadShorthand {
pub all: Option<f32>,
pub x: Option<f32>,
pub y: Option<f32>,
pub l: Option<f32>,
pub r: Option<f32>,
pub t: Option<f32>,
pub b: Option<f32>,
}
impl PadShorthand {
pub fn declared(self) -> bool {
[self.all, self.x, self.y, self.l, self.r, self.t, self.b]
.iter()
.any(Option::is_some)
}
pub fn resolve(self) -> Edges {
let all = self.all.unwrap_or(0.0);
let (x, y) = (self.x.unwrap_or(all), self.y.unwrap_or(all));
Edges {
l: self.l.unwrap_or(x),
r: self.r.unwrap_or(x),
t: self.t.unwrap_or(y),
b: self.b.unwrap_or(y),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutSpec {
pub width: Sizing,
pub height: Sizing,
pub min_w: Min,
pub max_w: f32,
pub min_h: Min,
pub max_h: f32,
pub dir: Dir,
pub padding: Edges,
pub gap: f32,
pub wrap: bool,
pub table: bool,
pub cross_gap: f32,
pub main_align: Align,
pub cross_align: Align,
pub aspect: f32,
pub clip: bool,
pub scroll_x: bool,
pub scroll_y: bool,
pub anchor: bool,
pub float: Option<FloatConfig>,
}
impl Default for LayoutSpec {
fn default() -> Self {
Self {
width: Sizing::Fit,
height: Sizing::Fit,
min_w: Min::AUTO,
max_w: f32::INFINITY,
min_h: Min::AUTO,
max_h: f32::INFINITY,
dir: Dir::Column,
padding: Edges::default(),
gap: 0.0,
wrap: false,
table: false,
cross_gap: 0.0,
main_align: Align::Start,
cross_align: Align::Start,
aspect: 0.0,
clip: false,
scroll_x: false,
scroll_y: false,
anchor: false,
float: None,
}
}
}
impl LayoutSpec {
pub fn clips(&self) -> bool {
self.clip || self.scroll_x || self.scroll_y
}
pub fn is_table(&self) -> bool {
self.table && self.dir == Dir::Column
}
}
impl LayoutSpec {
pub(crate) fn aspect_width(&self) -> Option<f32> {
match (self.width, self.height) {
(Sizing::Fit, Sizing::Fixed(h)) if self.aspect > 0.0 => {
Some(self.clamp_h(h) * self.aspect)
}
_ => None,
}
}
pub(crate) fn aspect_height(&self) -> bool {
self.aspect > 0.0 && self.height == Sizing::Fit
}
pub(crate) fn clamp_w(&self, w: f32) -> f32 {
let min = self.min_w.resolved();
w.clamp(min, self.max_w_px().max(min))
}
pub(crate) fn clamp_h(&self, h: f32) -> f32 {
let min = self.min_h.resolved();
h.clamp(min, self.max_h_px().max(min))
}
pub fn max_w_px(&self) -> f32 {
if self.max_w < 0.0 {
f32::INFINITY
} else {
self.max_w
}
}
pub fn max_h_px(&self) -> f32 {
if self.max_h < 0.0 {
f32::INFINITY
} else {
self.max_h
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VisualStyle {
pub bg: Color,
pub border_color: Color,
pub border_w: f32,
pub radius: [f32; 4],
pub opacity: f32,
pub shadow: Shadow,
pub pixel_snap: bool,
}
impl Default for VisualStyle {
fn default() -> Self {
Self {
bg: Color::TRANSPARENT,
border_color: Color::TRANSPARENT,
border_w: 0.0,
radius: [0.0; 4],
opacity: 1.0,
shadow: Shadow::default(),
pixel_snap: false,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Shadow {
pub color: Color,
pub dx: f32,
pub dy: f32,
pub blur: f32,
pub spread: f32,
}
impl Shadow {
pub fn is_visible(&self) -> bool {
self.color.is_visible()
}
}
pub mod corner {
pub const TL: usize = 0;
pub const TR: usize = 1;
pub const BR: usize = 2;
pub const BL: usize = 3;
}
#[derive(Clone, Debug, Default)]
pub struct NodeSpec {
pub layout: LayoutSpec,
pub style: VisualStyle,
pub hoverable: bool,
pub animate: bool,
pub accent: bool,
pub window: Option<WindowRole>,
pub transition: Option<Transition>,
pub slide: bool,
pub focusable: bool,
pub initial_focus: bool,
pub disabled: bool,
pub cursor: Option<CursorShape>,
pub events: Option<Box<EventSpec>>,
pub anim: Option<Box<AnimSpec>>,
pub access: Option<Box<AccessSpec>>,
pub interact: Option<Box<InteractSpec>>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EventSpec {
pub on_click: Option<Value>,
pub on_drag: Option<Value>,
pub on_key: Option<Value>,
pub key_up: bool,
pub modifier_keys: bool,
pub on_context_menu: Option<Value>,
pub on_force_click: Option<Value>,
pub on_button: Option<Value>,
pub buttons: Buttons,
pub on_scroll: Option<Value>,
pub scroll_axes: ScrollAxes,
pub scroll_mods: crate::input::KeyMods,
pub on_hover: Option<Value>,
pub on_drop: Option<Value>,
pub on_layout: Option<Value>,
pub on_change: Option<Value>,
pub modal: Option<Value>,
pub keep_focus: bool,
pub on_focus: Option<Value>,
}
impl EventSpec {
pub const EMPTY: Self = Self {
on_click: None,
on_drag: None,
on_key: None,
key_up: false,
modifier_keys: false,
on_context_menu: None,
on_force_click: None,
on_button: None,
buttons: Buttons::ALL,
on_scroll: None,
scroll_axes: ScrollAxes::Both,
scroll_mods: crate::input::KeyMods::NONE,
on_hover: None,
on_drop: None,
on_layout: None,
on_change: None,
modal: None,
keep_focus: false,
on_focus: None,
};
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct AnimSpec {
pub keyframes: Vec<Keyframe>,
pub enter: Option<Enter>,
pub exit: Option<Enter>,
}
impl AnimSpec {
pub const EMPTY: Self = Self {
keyframes: Vec::new(),
enter: None,
exit: None,
};
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct AccessSpec {
pub role: Option<Role>,
pub label: Option<Label>,
pub description: Option<Label>,
pub checked: bool,
pub mixed: bool,
pub selected: bool,
pub expanded: Option<bool>,
pub value_now: Option<f32>,
pub value_min: Option<f32>,
pub value_max: Option<f32>,
pub value_text: Option<Label>,
pub value_step: Option<f32>,
pub caret: Option<u32>,
pub selection_anchor: Option<u32>,
pub caret_solid: bool,
pub tooltip: bool,
pub live: Live,
}
impl AccessSpec {
pub const EMPTY: Self = Self {
role: None,
label: None,
description: None,
checked: false,
mixed: false,
selected: false,
expanded: None,
value_now: None,
value_min: None,
value_max: None,
value_text: None,
value_step: None,
caret: None,
selection_anchor: None,
caret_solid: false,
tooltip: false,
live: Live::Off,
};
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct InteractSpec {
pub hover_bg: Option<Color>,
pub pressed_bg: Option<Color>,
pub hover_group: Option<u64>,
pub click_sound: Option<crate::resources::SoundId>,
pub hover_sound: Option<crate::resources::SoundId>,
pub focus_bg: Option<Color>,
pub drop_bg: Option<Color>,
pub selectable: bool,
pub focus_region: bool,
pub scrollbar: Scrollbar,
pub overscroll: Overscroll,
pub rules: Option<Color>,
pub rule_w: f32,
pub gradient: Option<crate::gradient::Gradient>,
pub backdrop_blur: f32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Scrollbar {
pub mode: ScrollbarMode,
pub width: Option<f32>,
pub color: Option<Color>,
pub active_color: Option<Color>,
}
impl Scrollbar {
pub const DEFAULT: Self = Self {
mode: ScrollbarMode::Visible,
width: None,
color: None,
active_color: None,
};
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ScrollbarMode {
#[default]
Visible,
Hidden,
Auto,
}
impl ScrollbarMode {
pub const ALL: [ScrollbarMode; 3] = [
ScrollbarMode::Visible,
ScrollbarMode::Hidden,
ScrollbarMode::Auto,
];
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Overscroll {
#[default]
Auto,
Contain,
}
impl Overscroll {
pub const ALL: [Overscroll; 2] = [Overscroll::Auto, Overscroll::Contain];
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ScrollAxes {
#[default]
Both,
X,
Y,
}
impl ScrollAxes {
pub const ALL: [ScrollAxes; 3] = [ScrollAxes::Both, ScrollAxes::X, ScrollAxes::Y];
pub fn takes(self, x: bool) -> bool {
match self {
ScrollAxes::Both => true,
ScrollAxes::X => x,
ScrollAxes::Y => !x,
}
}
}
impl InteractSpec {
pub const EMPTY: Self = Self {
hover_bg: None,
pressed_bg: None,
hover_group: None,
click_sound: None,
hover_sound: None,
focus_bg: None,
drop_bg: None,
selectable: false,
focus_region: false,
scrollbar: Scrollbar::DEFAULT,
overscroll: Overscroll::Auto,
rules: None,
rule_w: 0.0,
gradient: None,
backdrop_blur: 0.0,
};
}
impl NodeSpec {
#[inline]
pub fn events(&self) -> &EventSpec {
static EMPTY: EventSpec = EventSpec::EMPTY;
self.events.as_deref().unwrap_or(&EMPTY)
}
#[inline]
pub fn events_mut(&mut self) -> &mut EventSpec {
self.events.get_or_insert_with(Box::default)
}
#[inline]
pub fn anim(&self) -> &AnimSpec {
static EMPTY: AnimSpec = AnimSpec::EMPTY;
self.anim.as_deref().unwrap_or(&EMPTY)
}
#[inline]
pub fn anim_mut(&mut self) -> &mut AnimSpec {
self.anim.get_or_insert_with(Box::default)
}
#[inline]
pub fn access(&self) -> &AccessSpec {
static EMPTY: AccessSpec = AccessSpec::EMPTY;
self.access.as_deref().unwrap_or(&EMPTY)
}
#[inline]
pub fn access_mut(&mut self) -> &mut AccessSpec {
self.access.get_or_insert_with(Box::default)
}
#[inline]
pub fn interact(&self) -> &InteractSpec {
static EMPTY: InteractSpec = InteractSpec::EMPTY;
self.interact.as_deref().unwrap_or(&EMPTY)
}
#[inline]
pub fn interact_mut(&mut self) -> &mut InteractSpec {
self.interact.get_or_insert_with(Box::default)
}
pub fn hover_tracked(&self) -> bool {
self.hoverable
|| self.focusable
|| self.window.is_some()
|| self.cursor.is_some()
|| self.events.as_deref().is_some_and(|e| {
e.modal.is_some()
|| e.on_click.is_some()
|| e.on_drag.is_some()
|| e.on_key.is_some()
|| e.on_context_menu.is_some()
|| e.on_force_click.is_some()
|| e.on_button.is_some()
|| e.on_hover.is_some()
|| e.on_drop.is_some()
|| e.on_change.is_some()
})
|| self.interact.as_deref().is_some_and(|i| {
i.hover_bg.is_some()
|| i.pressed_bg.is_some()
|| i.drop_bg.is_some()
|| i.hover_group.is_some()
|| i.click_sound.is_some()
|| i.hover_sound.is_some()
|| i.selectable
|| i.focus_region
})
}
pub fn hover_group_id(name: &str) -> u64 {
crate::key::Key::ROOT.str(name).0
}
pub fn row() -> Self {
Self {
layout: LayoutSpec {
dir: Dir::Row,
..Default::default()
},
..Default::default()
}
}
pub fn column() -> Self {
Self {
layout: LayoutSpec {
dir: Dir::Column,
..Default::default()
},
..Default::default()
}
}
pub fn table() -> Self {
Self {
layout: LayoutSpec {
dir: Dir::Column,
table: true,
..Default::default()
},
..Default::default()
}
}
#[inline]
pub fn width(mut self, s: impl Into<Sizing>) -> Self {
self.layout.width = s.into();
self
}
#[inline]
pub fn height(mut self, s: impl Into<Sizing>) -> Self {
self.layout.height = s.into();
self
}
#[inline]
pub fn size(self, w: impl Into<Sizing>, h: impl Into<Sizing>) -> Self {
self.width(w).height(h)
}
#[inline]
pub fn grow_width(self) -> Self {
self.width(Sizing::GROW)
}
#[inline]
pub fn grow_height(self) -> Self {
self.height(Sizing::GROW)
}
#[inline]
pub fn fill(self) -> Self {
self.grow_width().grow_height()
}
pub fn min_width(mut self, v: impl Into<Bound>) -> Self {
self.layout.min_w = match v.into() {
Bound::Px(px) if px == 0.0 && px.is_sign_negative() => Min::AUTO,
Bound::Px(px) => Min::px(px),
Bound::Fit => Min::FIT,
Bound::Calc(c) => Min::calc(c),
};
self
}
pub fn max_width(mut self, v: impl Into<Bound>) -> Self {
self.layout.max_w = match v.into() {
Bound::Px(px) => px_ceiling(px),
Bound::Fit => f32::INFINITY,
Bound::Calc(c) => max_of_calc(c),
};
self
}
pub fn min_height(mut self, v: impl Into<Bound>) -> Self {
self.layout.min_h = match v.into() {
Bound::Px(px) if px == 0.0 && px.is_sign_negative() => Min::AUTO,
Bound::Px(px) => Min::px(px),
Bound::Fit => Min::FIT,
Bound::Calc(c) => Min::calc(c),
};
self
}
pub fn with_bounds(
self,
min_w: Option<Bound>,
max_w: Option<Bound>,
min_h: Option<Bound>,
max_h: Option<Bound>,
) -> Self {
let mut s = self;
if let Some(b) = min_w {
s = s.min_width(b);
}
if let Some(b) = max_w {
s = s.max_width(b);
}
if let Some(b) = min_h {
s = s.min_height(b);
}
if let Some(b) = max_h {
s = s.max_height(b);
}
s
}
pub fn clip(mut self) -> Self {
self.layout.clip = true;
self
}
pub fn scroll_y(mut self) -> Self {
self.layout.scroll_y = true;
self
}
pub fn scroll_x(mut self) -> Self {
self.layout.scroll_x = true;
self
}
pub fn anchor(mut self) -> Self {
self.layout.anchor = true;
self
}
pub fn float(mut self, cfg: FloatConfig) -> Self {
self.layout.float = Some(cfg);
self
}
pub fn overflow_bits(mut self, bits: u32) -> Self {
if bits & OVERFLOW_CLIP != 0 {
self = self.clip();
}
if bits & OVERFLOW_SCROLL_X != 0 {
self = self.scroll_x();
}
if bits & OVERFLOW_SCROLL_Y != 0 {
self = self.scroll_y();
}
self
}
#[inline]
pub fn tooltip(self, hint: &str) -> Self {
let mut spec = self.apply_tooltip(hint);
spec.access_mut().tooltip = true;
spec
}
pub fn apply_tooltip(self, hint: &str) -> Self {
self.hoverable().description(hint)
}
pub fn max_height(mut self, v: impl Into<Bound>) -> Self {
self.layout.max_h = match v.into() {
Bound::Px(px) => px_ceiling(px),
Bound::Fit => f32::INFINITY,
Bound::Calc(c) => max_of_calc(c),
};
self
}
pub fn pad(mut self, v: f32) -> Self {
self.layout.padding = Edges::all(v);
self
}
pub fn pad_xy(mut self, x: f32, y: f32) -> Self {
self.layout.padding = Edges::xy(x, y);
self
}
pub fn padding(mut self, e: Edges) -> Self {
self.layout.padding = e;
self
}
pub fn gap(mut self, v: f32) -> Self {
self.layout.gap = v;
self
}
pub fn wrap(mut self) -> Self {
self.layout.wrap = true;
self
}
pub fn cross_gap(mut self, v: f32) -> Self {
self.layout.cross_gap = v;
self
}
pub fn main_align(mut self, a: Align) -> Self {
self.layout.main_align = a;
self
}
pub fn cross_align(mut self, a: Align) -> Self {
self.layout.cross_align = a;
self
}
pub fn aspect_ratio(mut self, ratio: f32) -> Self {
self.layout.aspect = if ratio.is_finite() && ratio > 0.0 {
ratio
} else {
0.0
};
self
}
pub fn center(self) -> Self {
self.main_align(Align::Center).cross_align(Align::Center)
}
pub fn bg(mut self, c: Color) -> Self {
self.style.bg = c;
self
}
pub fn radius(mut self, r: f32) -> Self {
self.style.radius = [r; 4];
self
}
pub fn radii(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
self.style.radius = [tl, tr, br, bl];
self
}
pub fn radius_tl(mut self, r: f32) -> Self {
self.style.radius[corner::TL] = r;
self
}
pub fn radius_tr(mut self, r: f32) -> Self {
self.style.radius[corner::TR] = r;
self
}
pub fn radius_br(mut self, r: f32) -> Self {
self.style.radius[corner::BR] = r;
self
}
pub fn radius_bl(mut self, r: f32) -> Self {
self.style.radius[corner::BL] = r;
self
}
pub fn radius_top(self, r: f32) -> Self {
self.radius_tl(r).radius_tr(r)
}
pub fn radius_bottom(self, r: f32) -> Self {
self.radius_br(r).radius_bl(r)
}
pub fn border(mut self, w: f32, c: Color) -> Self {
self.style.border_w = w;
self.style.border_color = c;
self
}
pub fn pixel_snap(mut self) -> Self {
self.style.pixel_snap = true;
self
}
pub fn opacity(mut self, o: f32) -> Self {
self.style.opacity = o.clamp(0.0, 1.0);
self
}
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.style.shadow = shadow;
self
}
pub fn shadow_color(mut self, c: Color) -> Self {
self.style.shadow.color = c;
self
}
pub fn shadow_blur(mut self, blur: f32) -> Self {
self.style.shadow.blur = blur;
self
}
pub fn shadow_offset(mut self, dx: f32, dy: f32) -> Self {
self.style.shadow.dx = dx;
self.style.shadow.dy = dy;
self
}
pub fn shadow_x(mut self, dx: f32) -> Self {
self.style.shadow.dx = dx;
self
}
pub fn shadow_y(mut self, dy: f32) -> Self {
self.style.shadow.dy = dy;
self
}
pub fn shadow_spread(mut self, spread: f32) -> Self {
self.style.shadow.spread = spread;
self
}
pub fn hoverable(mut self) -> Self {
self.hoverable = true;
self
}
pub fn animate(mut self) -> Self {
self.animate = true;
self
}
pub fn accent(mut self) -> Self {
self.accent = true;
self
}
pub fn on_click(mut self, payload: impl Into<Value>) -> Self {
self.events_mut().on_click = Some(payload.into());
self
}
pub fn hover_bg(mut self, c: Color) -> Self {
self.interact_mut().hover_bg = Some(c);
self
}
pub fn drop_bg(mut self, c: Color) -> Self {
self.interact_mut().drop_bg = Some(c);
self
}
pub fn gradient(mut self, gradient: crate::gradient::Gradient) -> Self {
self.interact_mut().gradient = Some(gradient);
self
}
pub fn backdrop_blur(mut self, radius: f32) -> Self {
self.interact_mut().backdrop_blur = radius.max(0.0);
self
}
pub fn rules(mut self, c: Color) -> Self {
self.interact_mut().rules = Some(c);
self
}
pub fn rule_width(mut self, w: f32) -> Self {
self.interact_mut().rule_w = w;
self
}
pub fn selectable(mut self) -> Self {
self.interact_mut().selectable = true;
self
}
pub fn focus_region(mut self) -> Self {
self.interact_mut().focus_region = true;
self
}
pub fn overscroll(mut self, o: Overscroll) -> Self {
self.interact_mut().overscroll = o;
self
}
pub fn scrollbar(mut self, mode: ScrollbarMode) -> Self {
self.interact_mut().scrollbar.mode = mode;
self
}
pub fn scrollbar_width(mut self, w: f32) -> Self {
self.interact_mut().scrollbar.width = Some(w);
self
}
pub fn scrollbar_color(mut self, c: Color) -> Self {
self.interact_mut().scrollbar.color = Some(c);
self
}
pub fn scrollbar_active_color(mut self, c: Color) -> Self {
self.interact_mut().scrollbar.active_color = Some(c);
self
}
pub fn pressed_bg(mut self, c: Color) -> Self {
self.interact_mut().pressed_bg = Some(c);
self
}
pub fn focus_bg(mut self, c: Color) -> Self {
self.interact_mut().focus_bg = Some(c);
self
}
pub fn focusable(mut self) -> Self {
self.focusable = true;
self
}
pub fn on_focus(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_focus = Some(tag.into());
self
}
pub fn keep_focus(mut self) -> Self {
self.events_mut().keep_focus = true;
self
}
pub fn initial_focus(mut self) -> Self {
self.initial_focus = true;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
#[inline]
pub fn modal(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().modal = Some(tag.into());
self
}
pub fn hover_group(mut self, name: &str) -> Self {
self.interact_mut().hover_group = Some(Self::hover_group_id(name));
self
}
pub fn on_hover(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_hover = Some(tag.into());
self
}
pub fn on_drop(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_drop = Some(tag.into());
self
}
pub fn click_sound(mut self, sound: crate::resources::SoundId) -> Self {
self.interact_mut().click_sound = Some(sound);
self
}
pub fn hover_sound(mut self, sound: crate::resources::SoundId) -> Self {
self.interact_mut().hover_sound = Some(sound);
self
}
pub fn on_drag(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_drag = Some(tag.into());
self
}
pub fn on_layout(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_layout = Some(tag.into());
self
}
pub fn role(mut self, role: Role) -> Self {
self.access_mut().role = Some(role);
self
}
pub fn label(mut self, label: impl Into<Label>) -> Self {
self.access_mut().label = Some(label.into());
self
}
pub fn description(mut self, description: impl Into<Label>) -> Self {
self.access_mut().description = Some(description.into());
self
}
pub fn checked(mut self, checked: bool) -> Self {
self.access_mut().checked = checked;
self
}
pub fn mixed(mut self, mixed: bool) -> Self {
self.access_mut().mixed = mixed;
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.access_mut().selected = selected;
self
}
pub fn expanded(mut self, expanded: bool) -> Self {
self.access_mut().expanded = Some(expanded);
self
}
pub fn live(mut self, live: Live) -> Self {
self.access_mut().live = live;
self
}
pub fn value_now(mut self, v: f32) -> Self {
self.access_mut().value_now = Some(v);
self
}
pub fn value_min(mut self, v: f32) -> Self {
self.access_mut().value_min = Some(v);
self
}
pub fn value_max(mut self, v: f32) -> Self {
self.access_mut().value_max = Some(v);
self
}
pub fn value_step(mut self, v: f32) -> Self {
self.access_mut().value_step = (v.is_finite() && v > 0.0).then_some(v);
self
}
pub fn on_change(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_change = Some(tag.into());
self
}
pub fn value_text(mut self, text: impl Into<Label>) -> Self {
self.access_mut().value_text = Some(text.into());
self
}
pub fn caret(mut self, offset: u32) -> Self {
self.access_mut().caret = Some(offset);
self
}
pub fn selection_anchor(mut self, offset: u32) -> Self {
self.access_mut().selection_anchor = Some(offset);
self
}
pub fn caret_solid(mut self) -> Self {
self.access_mut().caret_solid = true;
self
}
pub fn on_key(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_key = Some(tag.into());
self
}
#[inline]
pub fn key_sink(self) -> Self {
self.on_key(Value::Null)
}
pub fn key_up(mut self) -> Self {
self.events_mut().key_up = true;
self
}
pub fn modifier_keys(mut self) -> Self {
self.events_mut().modifier_keys = true;
self
}
pub fn on_force_click(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_force_click = Some(tag.into());
self
}
pub fn on_button(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_button = Some(tag.into());
self
}
pub fn buttons(mut self, buttons: Buttons) -> Self {
self.events_mut().buttons = buttons;
self
}
pub fn on_scroll(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_scroll = Some(tag.into());
self
}
pub fn scroll_axes(mut self, axes: ScrollAxes) -> Self {
self.events_mut().scroll_axes = axes;
self
}
pub fn scroll_mods(mut self, mods: crate::input::KeyMods) -> Self {
self.events_mut().scroll_mods = mods;
self
}
pub fn on_context_menu(mut self, tag: impl Into<Value>) -> Self {
self.events_mut().on_context_menu = Some(tag.into());
self
}
pub fn transition(mut self, duration_ms: f32) -> Self {
let t = self.transition.get_or_insert(Transition::ms(duration_ms));
t.duration_ms = duration_ms;
self
}
pub fn transition_with(mut self, t: Transition) -> Self {
self.transition = Some(t);
self
}
pub fn slide(mut self) -> Self {
self.transition.get_or_insert(Transition::ms(200.0));
self.slide = true;
self
}
pub fn easing(mut self, easing: Easing) -> Self {
let t = self.transition.get_or_insert(Transition::ms(200.0));
t.easing = easing;
self
}
pub fn bounce(mut self, bounce: f32) -> Self {
let t = self.transition.get_or_insert(Transition::ms(200.0));
*t = t.bounce(bounce);
self
}
pub fn repeat(mut self, repeat: Repeat) -> Self {
let t = self.transition.get_or_insert(Transition::ms(200.0));
t.repeat = repeat;
self
}
pub fn delay(mut self, delay_ms: f32) -> Self {
let t = self.transition.get_or_insert(Transition::ms(200.0));
t.delay_ms = delay_ms;
self
}
pub fn keyframes(mut self, stops: Vec<Keyframe>) -> Self {
self.transition.get_or_insert(Transition::ms(200.0));
self.anim_mut().keyframes = stops;
self
}
pub fn enter(mut self, enter: Enter) -> Self {
self.transition.get_or_insert(Transition::ms(200.0));
self.anim_mut().enter = Some(enter);
self
}
pub fn exit(mut self, exit: Enter) -> Self {
self.transition.get_or_insert(Transition::ms(200.0));
self.anim_mut().exit = Some(exit);
self
}
pub fn window_drag(mut self) -> Self {
self.window = Some(WindowRole::Drag);
self
}
pub fn window_button(mut self, button: WindowButton) -> Self {
self.window = Some(WindowRole::Button(button));
self
}
pub fn cursor(mut self, shape: CursorShape) -> Self {
self.cursor = Some(shape);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FontFamily {
#[default]
Sans,
Serif,
Mono,
Custom(crate::resources::FontId),
}
impl FontFamily {
pub const ALL: &'static [FontFamily] = &[FontFamily::Sans, FontFamily::Serif, FontFamily::Mono];
pub fn name(self) -> Option<&'static str> {
match self {
FontFamily::Sans => Some("sans"),
FontFamily::Serif => Some("serif"),
FontFamily::Mono => Some("mono"),
FontFamily::Custom(_) => None,
}
}
pub fn from_index(i: usize) -> FontFamily {
Self::ALL.get(i).copied().unwrap_or_default()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextWrap {
#[default]
Word,
Glyph,
None,
BreakSpaces,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct FontFeatures {
tags: [[u8; 4]; FontFeatures::MAX],
values: [u32; FontFeatures::MAX],
len: u8,
}
impl FontFeatures {
pub const MAX: usize = 8;
pub const fn new() -> Self {
Self {
tags: [[b' '; 4]; Self::MAX],
values: [0; Self::MAX],
len: 0,
}
}
pub fn set(mut self, tag: &str, value: u32) -> Self {
let mut t = [b' '; 4];
for (i, b) in tag.bytes().take(4).enumerate() {
t[i] = b;
}
let n = self.len as usize;
if let Some(i) = self.tags[..n].iter().position(|x| *x == t) {
self.values[i] = value;
} else if n < Self::MAX {
self.tags[n] = t;
self.values[n] = value;
self.len += 1;
}
self
}
pub fn parse(s: &str) -> Self {
let mut out = Self::new();
for item in s.split(|c: char| c.is_whitespace() || c == ',') {
if item.is_empty() {
continue;
}
let (tag, value) = if let Some((t, v)) = item.split_once('=') {
(t, v.trim().parse::<u32>().unwrap_or(0))
} else if let Some(t) = item.strip_prefix('-') {
(t, 0)
} else if let Some(t) = item.strip_prefix('+') {
(t, 1)
} else {
(item, 1)
};
let tag = tag.trim();
if !tag.is_empty() {
out = out.set(tag, value);
}
}
out
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn iter(&self) -> impl Iterator<Item = (&[u8; 4], u32)> + '_ {
let n = self.len as usize;
self.tags[..n].iter().zip(self.values[..n].iter().copied())
}
pub fn to_string_spelling(&self) -> String {
self.iter()
.map(|(t, v)| format!("{}={}", String::from_utf8_lossy(t).trim_end(), v))
.collect::<Vec<_>>()
.join(" ")
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TextStyle {
pub size: f32,
pub line_height: f32,
pub color: Option<Color>,
pub family: FontFamily,
pub wrap: TextWrap,
pub max_lines: u32,
pub ellipsis: bool,
pub features: FontFeatures,
pub underline: bool,
pub underline_color: Option<Color>,
pub underline_style: UnderlineStyle,
pub strikethrough: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum UnderlineStyle {
#[default]
Solid,
Wavy,
Dotted,
}
impl UnderlineStyle {
pub const NAMES: &[&str] = &["solid", "wavy", "dotted"];
pub fn from_index(i: u32) -> Self {
match i {
1 => Self::Wavy,
2 => Self::Dotted,
_ => Self::Solid,
}
}
pub fn name(self) -> &'static str {
Self::NAMES[self as usize]
}
}
impl Default for TextStyle {
fn default() -> Self {
Self::new(16.0)
}
}
impl TextStyle {
pub fn new(size: f32) -> Self {
Self {
size,
line_height: (size * 1.35).round(),
color: None,
family: FontFamily::Sans,
wrap: TextWrap::Word,
max_lines: 0,
ellipsis: false,
features: FontFeatures::new(),
underline: false,
underline_color: None,
underline_style: UnderlineStyle::Solid,
strikethrough: false,
}
}
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
pub fn underline_color(mut self, c: Color) -> Self {
self.underline = true;
self.underline_color = Some(c);
self
}
pub fn underline_style(mut self, s: UnderlineStyle) -> Self {
self.underline = true;
self.underline_style = s;
self
}
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
pub fn features(mut self, f: FontFeatures) -> Self {
self.features = f;
self
}
pub fn family(mut self, f: FontFamily) -> Self {
self.family = f;
self
}
pub fn mono(self) -> Self {
self.family(FontFamily::Mono)
}
pub fn font(self, id: crate::resources::FontId) -> Self {
self.family(FontFamily::Custom(id))
}
pub fn line_height(mut self, lh: f32) -> Self {
self.line_height = lh;
self
}
pub fn wrap(mut self, wrap: TextWrap) -> Self {
self.wrap = wrap;
self
}
pub fn nowrap(self) -> Self {
self.wrap(TextWrap::None)
}
pub fn max_lines(mut self, n: u32) -> Self {
self.max_lines = n;
self
}
pub fn ellipsis(mut self) -> Self {
self.ellipsis = true;
self
}
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
pub fn or_fg(mut self, fg: Color) -> Self {
self.color = Some(self.color.unwrap_or(fg));
self
}
pub fn color_or_default(&self) -> Color {
self.color.unwrap_or(crate::theme::Theme::DEFAULT_FG)
}
}
impl PartialEq for NodeSpec {
fn eq(&self, other: &Self) -> bool {
self.layout == other.layout
&& self.style == other.style
&& self.hoverable == other.hoverable
&& self.animate == other.animate
&& self.accent == other.accent
&& self.window == other.window
&& self.transition == other.transition
&& self.slide == other.slide
&& self.focusable == other.focusable
&& self.initial_focus == other.initial_focus
&& self.disabled == other.disabled
&& self.cursor == other.cursor
&& self.events() == other.events()
&& self.anim() == other.anim()
&& self.access() == other.access()
&& self.interact() == other.interact()
}
}
#[cfg(test)]
mod size_tests {
use super::*;
#[test]
fn node_spec_stays_small() {
const BOUND: usize = 256;
let size = std::mem::size_of::<NodeSpec>();
assert!(
size <= BOUND,
"NodeSpec is {size} bytes, over the {BOUND}-byte bound. It is copied \
per node per frame; put cold fields in EventSpec / AnimSpec / \
AccessSpec / InteractSpec rather than inline. See C15."
);
}
#[test]
fn an_undeclared_group_costs_a_pointer() {
let spec = NodeSpec::default();
assert!(spec.events.is_none() && spec.anim.is_none());
assert!(spec.access.is_none() && spec.interact.is_none());
assert!(spec.events().on_click.is_none());
assert_eq!(spec.access().role, None);
}
#[test]
fn an_empty_group_equals_no_group() {
assert_eq!(NodeSpec::default().checked(false), NodeSpec::default());
}
}