#![allow(non_snake_case)]
use super::{
Attrs, Modifier, Mods, dp_or_zero, emit, optional_text, paint_or_zero, push_modifiers,
role_or_zero, widget_key,
};
use crate::drawing::DrawList;
use crate::fonts::FontRefs;
use crate::runtime::composer::{EventCallback, OwnedAttr};
use crate::schema::{
Alignment, Arrangement, ButtonKind, ButtonVariant, ColorRole, IconRole, Paint, SlotRole,
SpaceRole, TextAlign, TextOverflow, TypeRole, WidgetKind, WordBreak,
};
use crate::spans::TextSpans;
use crate::{FileDrop, KeyEvent, PointerEvent, PointerKinds};
use std::cell::RefCell;
use std::rc::Rc;
fn unit(handler: Option<Box<dyn FnMut()>>) -> EventCallback {
match handler {
Some(handler) => EventCallback::Unit(Rc::new(RefCell::new(handler))),
None => EventCallback::Unit(Rc::new(RefCell::new(|| {}))),
}
}
fn text_handler(handler: Option<Box<dyn FnMut(String)>>) -> EventCallback {
match handler {
Some(handler) => EventCallback::Text(Rc::new(RefCell::new(handler))),
None => EventCallback::Text(Rc::new(RefCell::new(|_: String| {}))),
}
}
fn value_handler(handler: Option<Box<dyn FnMut(f64)>>) -> EventCallback {
match handler {
Some(handler) => EventCallback::Value(Rc::new(RefCell::new(handler))),
None => EventCallback::Value(Rc::new(RefCell::new(|_: f64| {}))),
}
}
struct PointerSubscription {
kinds: PointerKinds,
handler: Box<dyn FnMut(PointerEvent)>,
}
fn pointer_input(
subscription: Option<PointerSubscription>,
attrs: &mut Attrs,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
let Some(subscription) = subscription else {
return;
};
if subscription.kinds == PointerKinds::NONE {
return;
}
attrs.push((
"pointer_kinds",
OwnedAttr::Int(i64::from(subscription.kinds.bits())),
));
listeners.push((
"onpointer",
EventCallback::Pointer(Rc::new(RefCell::new(subscription.handler))),
));
}
struct TearOff {
key: Option<String>,
drag_over: Option<Box<dyn FnMut(crate::windows::WindowDragOver)>>,
}
fn tear_off_input(
tear_off: Option<TearOff>,
attrs: &mut Attrs,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
let Some(tear_off) = tear_off else {
return;
};
if let Some(key) = tear_off.key {
attrs.push(("tear_off_key", OwnedAttr::Text(key)));
}
if let Some(mut handler) = tear_off.drag_over {
listeners.push((
"onwindowdragover",
EventCallback::Window(Rc::new(RefCell::new(
move |event: crate::windows::WindowEvent| {
if let crate::windows::WindowEvent::DragOver { kind, window, x, y } = event {
handler(crate::windows::WindowDragOver { kind, window, x, y });
}
},
))),
));
}
}
macro_rules! tear_off {
($name:ident) => {
impl $name {
pub fn tear_off_key(mut self, key: impl Into<String>) -> Self {
self.tear_off
.get_or_insert_with(|| TearOff {
key: None,
drag_over: None,
})
.key = Some(key.into());
self
}
pub fn on_window_drag_over(
mut self,
handler: impl FnMut(crate::windows::WindowDragOver) + 'static,
) -> Self {
self.tear_off
.get_or_insert_with(|| TearOff {
key: None,
drag_over: None,
})
.drag_over = Some(std::boxed::Box::new(handler));
self
}
}
};
}
macro_rules! pointer_subscription {
($name:ident) => {
impl $name {
pub fn on_pointer(
mut self,
kinds: PointerKinds,
handler: impl FnMut(PointerEvent) + 'static,
) -> Self {
self.pointer = Some(PointerSubscription {
kinds,
handler: std::boxed::Box::new(handler),
});
self
}
}
};
}
fn click_input(
on_click: Option<Box<dyn FnMut()>>,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
if let Some(handler) = on_click {
listeners.push(("onclick", unit(Some(handler))));
}
}
macro_rules! click_handler {
($name:ident) => {
impl $name {
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
}
};
}
macro_rules! composed_on_drop {
($name:ident $(<$lifetime:lifetime>)?) => {
impl$(<$lifetime>)? Drop for $name$(<$lifetime>)? {
fn drop(&mut self) {
if !self.done && !std::thread::panicking() {
self.done = true;
self.compose(|| {});
}
}
}
impl$(<$lifetime>)? $name$(<$lifetime>)? {
pub fn modifier(mut self, modifier: Modifier) -> Self {
self.modifier = modifier;
self
}
}
};
}
macro_rules! container {
($name:ident $(<$lifetime:lifetime>)?) => {
composed_on_drop!($name $(<$lifetime>)?);
impl$(<$lifetime>)? $name$(<$lifetime>)? {
pub fn content(mut self, content: impl FnOnce()) {
self.done = true;
self.compose(content);
}
}
};
}
#[derive(Default)]
struct Linear {
arrangement: Option<Arrangement>,
spacing: Option<f32>,
space_role: Option<SpaceRole>,
alignment: Option<Alignment>,
intrinsic_size: Option<bool>,
}
impl Linear {
fn push(&self, attrs: &mut Attrs) {
attrs.push(("arrangement", role_or_zero(self.arrangement)));
attrs.push(("spacing", dp_or_zero(self.spacing)));
attrs.push(("space_role", role_or_zero(self.space_role)));
attrs.push(("alignment", role_or_zero(self.alignment)));
if let Some(intrinsic) = self.intrinsic_size {
attrs.push(("collapsible", OwnedAttr::Bool(intrinsic)));
}
}
}
macro_rules! linear {
($name:ident, $widget:ident, $key:literal) => {
#[doc = concat!("A `", stringify!($widget), "` and its builder.")]
pub struct $name {
modifier: Modifier,
linear: Linear,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
tear_off: Option<TearOff>,
done: bool,
}
#[doc = concat!("Lays its children out one after another. Compose's `", stringify!($widget), "`.")]
pub fn $widget() -> $name {
$name {
modifier: Modifier::new(),
linear: Linear::default(),
pointer: None,
on_click: None,
tear_off: None,
done: false,
}
}
impl $name {
pub fn arrangement(mut self, arrangement: Arrangement) -> Self {
self.linear.arrangement = Some(arrangement);
self
}
pub fn spacing(mut self, dp: f32) -> Self {
self.linear.spacing = Some(dp);
self
}
pub fn space_role(mut self, role: SpaceRole) -> Self {
self.linear.space_role = Some(role);
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.linear.alignment = Some(alignment);
self
}
pub fn intrinsic_size(mut self, enabled: bool) -> Self {
self.linear.intrinsic_size = Some(enabled);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let linear = std::mem::take(&mut self.linear);
let pointer = self.pointer.take();
let tear_off = self.tear_off.take();
let mut listeners = Vec::new();
let mut subscription = Attrs::new();
pointer_input(pointer, &mut subscription, &mut listeners);
click_input(self.on_click.take(), &mut listeners);
tear_off_input(tear_off, &mut subscription, &mut listeners);
emit(
widget_key($key),
WidgetKind::$widget,
|attrs| {
push_modifiers(&modifier, Mods::Container, attrs);
linear.push(attrs);
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
container!($name);
pointer_subscription!($name);
click_handler!($name);
tear_off!($name);
};
}
linear!(ColumnScope, Column, "compose_rust::ui::Column");
linear!(RowScope, Row, "compose_rust::ui::Row");
impl RowScope {
pub fn equal_height(self, enabled: bool) -> Self {
self.intrinsic_size(enabled)
}
}
impl ColumnScope {
pub fn equal_width(self, enabled: bool) -> Self {
self.intrinsic_size(enabled)
}
}
pub struct ComposeBox {
modifier: Modifier,
alignment: Option<Alignment>,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
tear_off: Option<TearOff>,
done: bool,
}
pub fn Box() -> ComposeBox {
ComposeBox {
modifier: Modifier::new(),
alignment: None,
pointer: None,
on_click: None,
tear_off: None,
done: false,
}
}
impl ComposeBox {
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = Some(alignment);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let (modifier, alignment) = (self.modifier, self.alignment);
let pointer = self.pointer.take();
let tear_off = self.tear_off.take();
let mut listeners = Vec::new();
let mut subscription = Attrs::new();
pointer_input(pointer, &mut subscription, &mut listeners);
click_input(self.on_click.take(), &mut listeners);
tear_off_input(tear_off, &mut subscription, &mut listeners);
emit(
widget_key("compose_rust::ui::Box"),
WidgetKind::Box,
|attrs| {
push_modifiers(&modifier, Mods::Container, attrs);
attrs.push(("alignment", role_or_zero(alignment)));
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
container!(ComposeBox);
pointer_subscription!(ComposeBox);
click_handler!(ComposeBox);
tear_off!(ComposeBox);
macro_rules! plain_container {
($name:ident, $widget:ident, $mods:expr, $key:literal, $doc:literal) => {
#[doc = concat!("A `", stringify!($widget), "` and its builder.")]
pub struct $name {
modifier: Modifier,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
#[doc = $doc]
pub fn $widget() -> $name {
$name {
modifier: Modifier::new(),
pointer: None,
on_click: None,
done: false,
}
}
impl $name {
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let pointer = self.pointer.take();
let mut listeners = Vec::new();
let mut subscription = Attrs::new();
pointer_input(pointer, &mut subscription, &mut listeners);
click_input(self.on_click.take(), &mut listeners);
emit(
widget_key($key),
WidgetKind::$widget,
|attrs| {
push_modifiers(&modifier, $mods, attrs);
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
container!($name);
pointer_subscription!($name);
click_handler!($name);
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScrollState {
offset: crate::runtime::MutableState<f32>,
max_offset: crate::runtime::MutableState<f32>,
command: crate::runtime::MutableState<(i64, f32, bool)>,
}
impl Default for ScrollState {
fn default() -> Self {
Self::new()
}
}
impl ScrollState {
pub fn new() -> Self {
Self {
offset: crate::runtime::mutable_state_of(0.0),
max_offset: crate::runtime::mutable_state_of(0.0),
command: crate::runtime::mutable_state_of((0, 0.0, false)),
}
}
pub fn offset(&self) -> f32 {
self.offset.get()
}
pub fn max_offset(&self) -> f32 {
self.max_offset.get()
}
pub fn scroll_to(&self, dp: f32) {
let (revision, _, _) = self.command.get_untracked();
self.command
.set((revision.wrapping_add(1).max(1), dp.max(0.0), false));
}
pub fn animate_scroll_to(&self, dp: f32) {
let (revision, _, _) = self.command.get_untracked();
self.command
.set((revision.wrapping_add(1).max(1), dp.max(0.0), true));
}
}
pub fn remember_scroll_state() -> ScrollState {
crate::runtime::remember(ScrollState::new)
}
fn scroll_state_input(
state: Option<ScrollState>,
attrs: &mut Attrs,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
let Some(state) = state else {
return;
};
let (revision, target, animate) = state.command.get();
if revision > 0 {
attrs.push(("value", OwnedAttr::Float(f64::from(target))));
attrs.push(("determinate", OwnedAttr::Bool(animate)));
attrs.push(("count", OwnedAttr::Int(revision)));
}
let offset = state.offset.clone();
let max_offset = state.max_offset.clone();
listeners.push((
"onrangerequest",
EventCallback::Range(Rc::new(RefCell::new(move |range: crate::RangeRequest| {
let next_offset = f32::from_bits(range.start() as u32);
let next_max = f32::from_bits(range.count() as u32);
if next_offset.is_finite() {
offset.set(next_offset);
}
if next_max.is_finite() {
max_offset.set(next_max);
}
}))),
));
}
macro_rules! scroll_container {
($name:ident, $widget:ident, $key:literal, $doc:literal) => {
#[doc = concat!("A `", stringify!($widget), "` and its builder.")]
pub struct $name {
modifier: Modifier,
scroll_state: Option<ScrollState>,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
#[doc = $doc]
pub fn $widget() -> $name {
$name {
modifier: Modifier::new(),
scroll_state: None,
pointer: None,
on_click: None,
done: false,
}
}
impl $name {
pub fn scroll_state(mut self, state: &ScrollState) -> Self {
self.scroll_state = Some(state.clone());
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let scroll_state = self.scroll_state.take();
let pointer = self.pointer.take();
let mut listeners = Vec::new();
let mut subscription = Attrs::new();
scroll_state_input(scroll_state, &mut subscription, &mut listeners);
pointer_input(pointer, &mut subscription, &mut listeners);
click_input(self.on_click.take(), &mut listeners);
emit(
widget_key($key),
WidgetKind::$widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
container!($name);
pointer_subscription!($name);
click_handler!($name);
};
}
scroll_container!(
ScrollColumnScope,
ScrollColumn,
"compose_rust::ui::ScrollColumn",
"All of its content, with a vertical scroll the Renderer owns."
);
scroll_container!(
ScrollRowScope,
ScrollRow,
"compose_rust::ui::ScrollRow",
"All of its content, with a horizontal scroll the Renderer owns."
);
plain_container!(
CardScope,
Card,
Mods::Container,
"compose_rust::ui::Card",
"A grouped container. What it looks like is the design system's."
);
plain_container!(
SurfaceScope,
Surface,
Mods::Container,
"compose_rust::ui::Surface",
"A plain background and elevation, without a card's meaning."
);
plain_container!(
SelectionContainerScope,
SelectionContainer,
Mods::Standard,
"compose_rust::ui::SelectionContainer",
"Every `Text` inside can be selected and copied as one selection."
);
plain_container!(
SpacerScope,
Spacer,
Mods::Standard,
"compose_rust::ui::Spacer",
"Empty space, sized by its Modifier."
);
fn font_bytes(font: &FontRefs) -> OwnedAttr {
if font.is_empty() {
OwnedAttr::None
} else {
OwnedAttr::Bytes(font.as_bytes().to_vec())
}
}
pub struct TextScope {
modifier: Modifier,
text: String,
spans: TextSpans,
type_role: Option<TypeRole>,
font_size: Option<f32>,
font_weight: Option<u16>,
line_height: Option<f32>,
letter_spacing: Option<f32>,
color: Option<Paint>,
text_align: Option<TextAlign>,
max_lines: Option<u32>,
overflow: Option<TextOverflow>,
font: FontRefs,
word_break: Option<WordBreak>,
pointer: Option<PointerSubscription>,
done: bool,
}
pub fn Text(text: impl Into<String>) -> TextScope {
TextScope {
modifier: Modifier::new(),
text: text.into(),
spans: TextSpans::default(),
type_role: None,
font_size: None,
font_weight: None,
line_height: None,
letter_spacing: None,
color: None,
text_align: None,
max_lines: None,
overflow: None,
font: FontRefs::default(),
word_break: None,
pointer: None,
done: false,
}
}
impl TextScope {
pub fn spans(mut self, spans: TextSpans) -> Self {
self.spans = spans;
self
}
pub fn type_role(mut self, role: TypeRole) -> Self {
self.type_role = Some(role);
self
}
pub fn font_size(mut self, sp: f32) -> Self {
self.font_size = Some(sp);
self
}
pub fn font_weight(mut self, weight: u16) -> Self {
self.font_weight = Some(weight);
self
}
pub fn line_height(mut self, sp: f32) -> Self {
self.line_height = Some(sp);
self
}
pub fn letter_spacing(mut self, sp: f32) -> Self {
self.letter_spacing = Some(sp);
self
}
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn text_align(mut self, align: TextAlign) -> Self {
self.text_align = Some(align);
self
}
pub fn max_lines(mut self, lines: u32) -> Self {
self.max_lines = Some(lines);
self
}
pub fn overflow(mut self, overflow: TextOverflow) -> Self {
self.overflow = Some(overflow);
self
}
pub fn font(mut self, font: FontRefs) -> Self {
self.font = font;
self
}
pub fn word_break(mut self, word_break: WordBreak) -> Self {
self.word_break = Some(word_break);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let spans = std::mem::take(&mut self.spans);
let font = std::mem::take(&mut self.font);
let word_break = self.word_break;
let (type_role, font_size, font_weight, line_height, letter_spacing) = (
self.type_role,
self.font_size,
self.font_weight,
self.line_height,
self.letter_spacing,
);
let (color, text_align, max_lines, overflow) =
(self.color, self.text_align, self.max_lines, self.overflow);
let pointer = self.pointer.take();
let mut listeners = Vec::new();
let mut subscription = Attrs::new();
pointer_input(pointer, &mut subscription, &mut listeners);
emit(
widget_key("compose_rust::ui::Text"),
WidgetKind::Text,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
let span_fonts = spans.font_table().to_vec();
attrs.push((
"spans",
if spans.is_empty() {
OwnedAttr::None
} else {
OwnedAttr::Spans(spans)
},
));
attrs.push((
"span_fonts",
if span_fonts.is_empty() {
OwnedAttr::None
} else {
OwnedAttr::Bytes(span_fonts)
},
));
attrs.push(("font", font_bytes(&font)));
attrs.push(("word_break", super::role(word_break)));
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("type_role", role_or_zero(type_role)));
attrs.push(("font_size", dp_or_zero(font_size)));
attrs.push((
"font_weight",
OwnedAttr::Int(i64::from(font_weight.unwrap_or(0))),
));
attrs.push(("line_height", dp_or_zero(line_height)));
attrs.push(("letter_spacing", dp_or_zero(letter_spacing)));
attrs.push(("color", paint_or_zero(color)));
attrs.push(("text_align", role_or_zero(text_align)));
attrs.push((
"max_lines",
OwnedAttr::Int(i64::from(max_lines.unwrap_or(0))),
));
attrs.push(("overflow", role_or_zero(overflow)));
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
composed_on_drop!(TextScope);
pointer_subscription!(TextScope);
pub struct TextFieldScope {
modifier: Modifier,
text: String,
placeholder: String,
enabled: bool,
multiline: bool,
type_role: Option<TypeRole>,
font: FontRefs,
on_value_change: Option<Box<dyn FnMut(String)>>,
on_submit: Option<Box<dyn FnMut(String)>>,
on_focus_lost: Option<Box<dyn FnMut()>>,
on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
done: bool,
}
pub fn TextField() -> TextFieldScope {
TextFieldScope {
modifier: Modifier::new(),
text: String::new(),
placeholder: String::new(),
enabled: true,
multiline: false,
type_role: None,
font: FontRefs::default(),
on_value_change: None,
on_submit: None,
on_focus_lost: None,
on_key_down: None,
done: false,
}
}
impl TextFieldScope {
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = text.into();
self
}
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.placeholder = placeholder.into();
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn multiline(mut self, multiline: bool) -> Self {
self.multiline = multiline;
self
}
pub fn type_role(mut self, role: TypeRole) -> Self {
self.type_role = Some(role);
self
}
pub fn font(mut self, font: FontRefs) -> Self {
self.font = font;
self
}
pub fn on_value_change(mut self, handler: impl FnMut(String) + 'static) -> Self {
self.on_value_change = Some(std::boxed::Box::new(handler));
self
}
pub fn on_submit(mut self, handler: impl FnMut(String) + 'static) -> Self {
self.on_submit = Some(std::boxed::Box::new(handler));
self
}
pub fn on_focus_lost(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_focus_lost = Some(std::boxed::Box::new(handler));
self
}
pub fn on_key_down(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
self.on_key_down = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let placeholder = std::mem::take(&mut self.placeholder);
let (enabled, multiline, type_role) = (self.enabled, self.multiline, self.type_role);
let font = std::mem::take(&mut self.font);
let key_down: EventCallback = match self.on_key_down.take() {
Some(handler) => EventCallback::Key(Rc::new(RefCell::new(handler))),
None => EventCallback::Key(Rc::new(RefCell::new(|_: KeyEvent| {}))),
};
let listeners = vec![
("onvaluechange", text_handler(self.on_value_change.take())),
("onsubmit", text_handler(self.on_submit.take())),
("onfocuslost", unit(self.on_focus_lost.take())),
("onkeydown", key_down),
];
emit(
widget_key("compose_rust::ui::TextField"),
WidgetKind::TextField,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
if !text.is_empty() {
attrs.push(("text", OwnedAttr::Text(text)));
}
attrs.push(("placeholder", OwnedAttr::Text(placeholder)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
attrs.push(("multiline", OwnedAttr::Bool(multiline)));
attrs.push(("type_role", role_or_zero(type_role)));
attrs.push(("font", font_bytes(&font)));
},
listeners,
content,
);
}
}
composed_on_drop!(TextFieldScope);
fn lines_and_overflow(max_lines: Option<u32>, overflow: Option<TextOverflow>, attrs: &mut Attrs) {
if let Some(lines) = max_lines {
attrs.push(("max_lines", OwnedAttr::Int(i64::from(lines))));
}
if overflow.is_some() {
attrs.push(("overflow", role_or_zero(overflow)));
}
}
pub struct ButtonScope {
modifier: Modifier,
text: String,
icon: Option<IconRole>,
enabled: bool,
variant: Option<ButtonVariant>,
kind: Option<ButtonKind>,
color: Option<Paint>,
max_lines: Option<u32>,
overflow: Option<TextOverflow>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn Button(text: impl Into<String>) -> ButtonScope {
ButtonScope {
modifier: Modifier::new(),
text: text.into(),
icon: None,
enabled: true,
variant: None,
kind: None,
color: None,
max_lines: None,
overflow: None,
on_click: None,
done: false,
}
}
impl ButtonScope {
pub fn icon(mut self, icon: IconRole) -> Self {
self.icon = Some(icon);
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = Some(variant);
self
}
pub fn kind(mut self, kind: ButtonKind) -> Self {
self.kind = Some(kind);
self
}
pub fn max_lines(mut self, lines: u32) -> Self {
self.max_lines = Some(lines);
self
}
pub fn overflow(mut self, overflow: TextOverflow) -> Self {
self.overflow = Some(overflow);
self
}
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn color_if(mut self, paint: Option<Paint>) -> Self {
self.color = paint;
self
}
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let (icon, enabled, variant, kind, color) =
(self.icon, self.enabled, self.variant, self.kind, self.color);
let (max_lines, overflow) = (self.max_lines, self.overflow);
let listeners = vec![("onclick", unit(self.on_click.take()))];
emit(
widget_key("compose_rust::ui::Button"),
WidgetKind::Button,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("icon", super::role(icon)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
attrs.push(("variant", role_or_zero(variant)));
attrs.push(("button_kind", role_or_zero(kind)));
attrs.push(("color", super::paint(color)));
lines_and_overflow(max_lines, overflow, attrs);
},
listeners,
content,
);
}
}
composed_on_drop!(ButtonScope);
macro_rules! toggle {
($name:ident, $widget:ident, $setter:ident, $key:literal, $doc:literal) => {
#[doc = concat!("A `", stringify!($widget), "` and its builder.")]
pub struct $name {
modifier: Modifier,
checked: bool,
enabled: bool,
on_change: Option<Box<dyn FnMut(bool)>>,
done: bool,
}
#[doc = $doc]
pub fn $widget($setter: bool) -> $name {
$name {
modifier: Modifier::new(),
checked: $setter,
enabled: true,
on_change: None,
done: false,
}
}
impl $name {
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn on_change(mut self, handler: impl FnMut(bool) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (checked, enabled) = (self.checked, self.enabled);
let handler: EventCallback = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(
move |value: f64| handler(value != 0.0),
))),
None => value_handler(None),
};
emit(
widget_key($key),
WidgetKind::$widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("checked", OwnedAttr::Bool(checked)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
},
vec![("onchange", handler)],
content,
);
}
}
composed_on_drop!($name);
};
}
toggle!(
CheckboxScope,
Checkbox,
checked,
"compose_rust::ui::Checkbox",
"A box that is ticked or not. Compose's `Checkbox(checked, onCheckedChange)`."
);
toggle!(
RadioButtonScope,
RadioButton,
selected,
"compose_rust::ui::RadioButton",
"One choice out of several. Compose's `RadioButton(selected, onClick)`."
);
toggle!(
SwitchScope,
Switch,
checked,
"compose_rust::ui::Switch",
"An on and off control. Compose's `Switch(checked, onCheckedChange)`."
);
pub struct SliderScope {
modifier: Modifier,
color: Option<Paint>,
value: f32,
min: f32,
max: f32,
steps: u32,
enabled: bool,
on_change: Option<Box<dyn FnMut(f32)>>,
done: bool,
}
pub fn Slider(value: f32) -> SliderScope {
SliderScope {
modifier: Modifier::new(),
color: None,
value,
min: 0.0,
max: 1.0,
steps: 0,
enabled: true,
on_change: None,
done: false,
}
}
impl SliderScope {
pub fn range(mut self, min: f32, max: f32) -> Self {
self.min = min;
self.max = max;
self
}
pub fn steps(mut self, steps: u32) -> Self {
self.steps = steps;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (color, value, min, max, steps, enabled) = (
self.color,
self.value,
self.min,
self.max,
self.steps,
self.enabled,
);
let handler = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler(value as f32)
}))),
None => value_handler(None),
};
emit(
widget_key("compose_rust::ui::Slider"),
WidgetKind::Slider,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("color", super::paint(color)));
attrs.push(("value", OwnedAttr::Float(f64::from(value))));
attrs.push(("min", OwnedAttr::Float(f64::from(min))));
attrs.push(("max", OwnedAttr::Float(f64::from(max))));
attrs.push(("steps", OwnedAttr::Int(i64::from(steps))));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
},
vec![("onchange", handler)],
content,
);
}
}
composed_on_drop!(SliderScope);
pub struct ProgressIndicatorScope {
modifier: Modifier,
value: f32,
determinate: bool,
circular: bool,
done: bool,
}
pub fn ProgressIndicator(value: f32) -> ProgressIndicatorScope {
ProgressIndicatorScope {
modifier: Modifier::new(),
value,
determinate: true,
circular: false,
done: false,
}
}
pub fn IndeterminateProgressIndicator() -> ProgressIndicatorScope {
ProgressIndicator(0.0).determinate(false)
}
impl ProgressIndicatorScope {
pub fn determinate(mut self, determinate: bool) -> Self {
self.determinate = determinate;
self
}
pub fn circular(mut self, circular: bool) -> Self {
self.circular = circular;
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (value, determinate, circular) = (self.value, self.determinate, self.circular);
emit(
widget_key("compose_rust::ui::ProgressIndicator"),
WidgetKind::ProgressIndicator,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("value", OwnedAttr::Float(f64::from(value))));
attrs.push(("determinate", OwnedAttr::Bool(determinate)));
attrs.push(("circular", OwnedAttr::Bool(circular)));
},
Vec::new(),
content,
);
}
}
composed_on_drop!(ProgressIndicatorScope);
pub struct DividerScope {
modifier: Modifier,
vertical: bool,
done: bool,
}
pub fn Divider() -> DividerScope {
DividerScope {
modifier: Modifier::new(),
vertical: false,
done: false,
}
}
pub fn Separator(color: Option<Paint>) -> DividerScope {
Divider().modifier(Modifier::new().background_if(color))
}
impl DividerScope {
pub fn vertical(mut self, vertical: bool) -> Self {
self.vertical = vertical;
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let vertical = self.vertical;
emit(
widget_key("compose_rust::ui::Divider"),
WidgetKind::Divider,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("vertical", OwnedAttr::Bool(vertical)));
},
Vec::new(),
content,
);
}
}
composed_on_drop!(DividerScope);
pub struct AssetScope {
modifier: Modifier,
widget: WidgetKind,
asset_id: u32,
color: Option<Paint>,
done: bool,
}
pub fn Image(asset_id: u32) -> AssetScope {
AssetScope {
modifier: Modifier::new(),
widget: WidgetKind::Image,
asset_id,
color: None,
done: false,
}
}
pub fn Icon(asset_id: u32) -> AssetScope {
AssetScope {
modifier: Modifier::new(),
widget: WidgetKind::Icon,
asset_id,
color: None,
done: false,
}
}
impl AssetScope {
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (widget, asset_id, color) = (self.widget, self.asset_id, self.color);
let key = if widget == WidgetKind::Icon {
widget_key("compose_rust::ui::Icon")
} else {
widget_key("compose_rust::ui::Image")
};
emit(
key,
widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("asset", OwnedAttr::Int(i64::from(asset_id))));
if widget == WidgetKind::Icon {
attrs.push(("color", paint_or_zero(color)));
}
},
Vec::new(),
content,
);
}
}
composed_on_drop!(AssetScope);
pub struct CanvasScope {
modifier: Modifier,
commands: DrawList,
view_box: Option<(f32, f32)>,
done: bool,
}
pub fn Canvas(commands: DrawList) -> CanvasScope {
let view_box = commands.view_box();
CanvasScope {
modifier: Modifier::new(),
commands,
view_box,
done: false,
}
}
impl CanvasScope {
pub fn view_box(mut self, width: f32, height: f32) -> Self {
self.view_box = Some((width, height));
self
}
pub fn fractional(self) -> Self {
self.view_box(1.0, 1.0)
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let commands = std::mem::take(&mut self.commands);
let view_box = self.view_box;
emit(
widget_key("compose_rust::ui::Canvas"),
WidgetKind::Canvas,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("commands", OwnedAttr::Draw(commands)));
if let Some((width, height)) = view_box {
attrs.push(("min", OwnedAttr::Float(f64::from(width))));
attrs.push(("max", OwnedAttr::Float(f64::from(height))));
}
},
Vec::new(),
content,
);
}
}
composed_on_drop!(CanvasScope);
pub struct DatePickerScope {
modifier: Modifier,
value: i64,
min: Option<i64>,
max: Option<i64>,
enabled: bool,
on_change: Option<Box<dyn FnMut(i64)>>,
done: bool,
}
pub fn DatePicker(value: i64) -> DatePickerScope {
DatePickerScope {
modifier: Modifier::new(),
value,
min: None,
max: None,
enabled: true,
on_change: None,
done: false,
}
}
impl DatePickerScope {
pub fn min(mut self, days: i64) -> Self {
self.min = Some(days);
self
}
pub fn max(mut self, days: i64) -> Self {
self.max = Some(days);
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn on_change(mut self, handler: impl FnMut(i64) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
let handler = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler(value as i64)
}))),
None => value_handler(None),
};
emit(
widget_key("compose_rust::ui::DatePicker"),
WidgetKind::DatePicker,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("value", OwnedAttr::Int(value)));
attrs.push(("min", OwnedAttr::Int(min.unwrap_or(i64::MIN))));
attrs.push(("max", OwnedAttr::Int(max.unwrap_or(i64::MAX))));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
},
vec![("onchange", handler)],
content,
);
}
}
composed_on_drop!(DatePickerScope);
const MINUTES_IN_A_DAY: u32 = 24 * 60;
pub struct TimePickerScope {
modifier: Modifier,
value: u32,
min: Option<u32>,
max: Option<u32>,
enabled: bool,
on_change: Option<Box<dyn FnMut(u32)>>,
done: bool,
}
pub fn TimePicker(value: u32) -> TimePickerScope {
TimePickerScope {
modifier: Modifier::new(),
value,
min: None,
max: None,
enabled: true,
on_change: None,
done: false,
}
}
impl TimePickerScope {
pub fn min(mut self, minutes: u32) -> Self {
self.min = Some(minutes);
self
}
pub fn max(mut self, minutes: u32) -> Self {
self.max = Some(minutes);
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn on_change(mut self, handler: impl FnMut(u32) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (value, min, max, enabled) = (self.value, self.min, self.max, self.enabled);
let handler = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler((value as i64).clamp(0, i64::from(MINUTES_IN_A_DAY - 1)) as u32)
}))),
None => value_handler(None),
};
emit(
widget_key("compose_rust::ui::TimePicker"),
WidgetKind::TimePicker,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("value", OwnedAttr::Int(i64::from(value))));
attrs.push(("min", OwnedAttr::Int(i64::from(min.unwrap_or(0)))));
attrs.push((
"max",
OwnedAttr::Int(i64::from(max.unwrap_or(MINUTES_IN_A_DAY - 1))),
));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
},
vec![("onchange", handler)],
content,
);
}
}
composed_on_drop!(TimePickerScope);
pub struct DropdownScope {
modifier: Modifier,
selected_index: usize,
enabled: bool,
closed: bool,
on_change: Option<Box<dyn FnMut(usize)>>,
done: bool,
}
pub fn Dropdown(selected_index: usize) -> DropdownScope {
DropdownScope {
modifier: Modifier::new(),
selected_index,
enabled: true,
closed: false,
on_change: None,
done: false,
}
}
pub fn Select(selected_index: usize) -> DropdownScope {
Dropdown(selected_index).closed(true)
}
impl DropdownScope {
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn closed(mut self, closed: bool) -> Self {
self.closed = closed;
self
}
pub fn on_change(mut self, handler: impl FnMut(usize) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (selected_index, enabled, closed) = (self.selected_index, self.enabled, self.closed);
let handler = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler((value as i64).max(0) as usize)
}))),
None => value_handler(None),
};
emit(
widget_key("compose_rust::ui::Dropdown"),
WidgetKind::Dropdown,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
if closed {
attrs.push(("collapsible", OwnedAttr::Bool(true)));
}
},
vec![("onchange", handler)],
content,
);
}
}
container!(DropdownScope);
pub struct MenuItemScope {
modifier: Modifier,
text: String,
icon: Option<IconRole>,
selected: bool,
enabled: bool,
color: Option<Paint>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn MenuItem(text: impl Into<String>) -> MenuItemScope {
MenuItemScope {
modifier: Modifier::new(),
text: text.into(),
icon: None,
selected: false,
enabled: true,
color: None,
on_click: None,
done: false,
}
}
impl MenuItemScope {
pub fn icon(mut self, icon: IconRole) -> Self {
self.icon = Some(icon);
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn color_if(mut self, paint: Option<Paint>) -> Self {
self.color = paint;
self
}
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let (icon, selected, enabled, color) = (self.icon, self.selected, self.enabled, self.color);
let listeners = vec![("onclick", unit(self.on_click.take()))];
emit(
widget_key("compose_rust::ui::MenuItem"),
WidgetKind::Button,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("icon", super::role(icon)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
attrs.push(("variant", role_or_zero(Some(ButtonVariant::Text))));
attrs.push(("text_align", role_or_zero(Some(TextAlign::Start))));
attrs.push(("checked", OwnedAttr::Bool(selected)));
attrs.push(("color", super::paint(color)));
},
listeners,
content,
);
}
}
composed_on_drop!(MenuItemScope);
pub struct ModalScope {
modifier: Modifier,
widget: WidgetKind,
open: bool,
on_dismiss: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn Dialog(open: bool) -> ModalScope {
ModalScope {
modifier: Modifier::new(),
widget: WidgetKind::Dialog,
open,
on_dismiss: None,
done: false,
}
}
pub fn Sheet(open: bool) -> ModalScope {
ModalScope {
modifier: Modifier::new(),
widget: WidgetKind::Sheet,
open,
on_dismiss: None,
done: false,
}
}
impl ModalScope {
pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_dismiss = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (widget, open) = (self.widget, self.open);
let key = if widget == WidgetKind::Dialog {
widget_key("compose_rust::ui::Dialog")
} else {
widget_key("compose_rust::ui::Sheet")
};
emit(
key,
widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("open", OwnedAttr::Bool(open)));
},
vec![("ondismiss", unit(self.on_dismiss.take()))],
content,
);
}
}
container!(ModalScope);
pub struct MenuScope<'a> {
modifier: Modifier,
expanded: bool,
on_dismiss: Option<Box<dyn FnMut()>>,
anchor: Option<Box<dyn FnOnce() + 'a>>,
done: bool,
}
pub fn Menu<'a>(expanded: bool) -> MenuScope<'a> {
MenuScope {
modifier: Modifier::new(),
expanded,
on_dismiss: None,
anchor: None,
done: false,
}
}
impl<'a> MenuScope<'a> {
pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_dismiss = Some(std::boxed::Box::new(handler));
self
}
pub fn anchor(mut self, anchor: impl FnOnce() + 'a) -> Self {
self.anchor = Some(std::boxed::Box::new(anchor));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let expanded = self.expanded;
let anchor = self.anchor.take();
emit(
widget_key("compose_rust::ui::Menu"),
WidgetKind::Menu,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("open", OwnedAttr::Bool(expanded)));
},
vec![("ondismiss", unit(self.on_dismiss.take()))],
|| {
if let Some(anchor) = anchor {
let _anchor =
crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::anchor"));
anchor();
}
let _entries =
crate::runtime::group_guard(widget_key("compose_rust::ui::Menu::entries"));
content();
},
);
}
}
container!(MenuScope<'a>);
pub struct TabsScope {
modifier: Modifier,
color: Option<Paint>,
selected_index: usize,
done: bool,
}
pub fn Tabs(selected_index: usize) -> TabsScope {
TabsScope {
modifier: Modifier::new(),
color: None,
selected_index,
done: false,
}
}
impl TabsScope {
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (color, selected_index) = (self.color, self.selected_index);
emit(
widget_key("compose_rust::ui::Tabs"),
WidgetKind::Tabs,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("color", super::paint(color)));
attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
},
Vec::new(),
content,
);
}
}
container!(TabsScope);
pub struct TopAppBarScope {
modifier: Modifier,
title: String,
done: bool,
}
pub fn TopAppBar() -> TopAppBarScope {
TopAppBarScope {
modifier: Modifier::new(),
title: String::new(),
done: false,
}
}
impl TopAppBarScope {
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let title = std::mem::take(&mut self.title);
emit(
widget_key("compose_rust::ui::TopAppBar"),
WidgetKind::TopAppBar,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("text", OwnedAttr::Text(title)));
},
Vec::new(),
content,
);
}
}
container!(TopAppBarScope);
pub struct TooltipScope {
modifier: Modifier,
text: String,
done: bool,
}
pub fn Tooltip(text: impl Into<String>) -> TooltipScope {
TooltipScope {
modifier: Modifier::new(),
text: text.into(),
done: false,
}
}
impl TooltipScope {
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
emit(
widget_key("compose_rust::ui::Tooltip"),
WidgetKind::Tooltip,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
},
Vec::new(),
content,
);
}
}
container!(TooltipScope);
fn slot(role: SlotRole, key: u64, content: impl FnOnce()) {
emit(
key,
WidgetKind::ScaffoldSlot,
|attrs| attrs.push(("slot", OwnedAttr::Int(i64::from(u16::from(role))))),
Vec::new(),
content,
);
}
type Slot<'a> = Option<Box<dyn FnOnce() + 'a>>;
pub struct ScaffoldScope<'a> {
modifier: Modifier,
top_bar: Slot<'a>,
bottom_bar: Slot<'a>,
floating_action: Slot<'a>,
done: bool,
}
pub fn Scaffold<'a>() -> ScaffoldScope<'a> {
ScaffoldScope {
modifier: Modifier::new(),
top_bar: None,
bottom_bar: None,
floating_action: None,
done: false,
}
}
impl<'a> ScaffoldScope<'a> {
pub fn top_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
self.top_bar = Some(std::boxed::Box::new(bar));
self
}
pub fn bottom_bar(mut self, bar: impl FnOnce() + 'a) -> Self {
self.bottom_bar = Some(std::boxed::Box::new(bar));
self
}
pub fn floating_action(mut self, action: impl FnOnce() + 'a) -> Self {
self.floating_action = Some(std::boxed::Box::new(action));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (top_bar, bottom_bar, floating_action) = (
self.top_bar.take(),
self.bottom_bar.take(),
self.floating_action.take(),
);
emit(
widget_key("compose_rust::ui::Scaffold"),
WidgetKind::Scaffold,
|attrs| push_modifiers(&modifier, Mods::Scaffold, attrs),
Vec::new(),
|| {
if let Some(bar) = top_bar {
slot(
SlotRole::TopBar,
widget_key("compose_rust::ui::Scaffold::top_bar"),
bar,
);
}
if let Some(bar) = bottom_bar {
slot(
SlotRole::BottomBar,
widget_key("compose_rust::ui::Scaffold::bottom_bar"),
bar,
);
}
if let Some(action) = floating_action {
slot(
SlotRole::FloatingAction,
widget_key("compose_rust::ui::Scaffold::floating_action"),
action,
);
}
slot(
SlotRole::Content,
widget_key("compose_rust::ui::Scaffold::content"),
content,
);
},
);
}
}
container!(ScaffoldScope<'a>);
pub struct NavigationScope<'a> {
modifier: Modifier,
selected_index: usize,
head: Slot<'a>,
foot: Slot<'a>,
done: bool,
}
pub fn Navigation<'a>(selected_index: usize) -> NavigationScope<'a> {
NavigationScope {
modifier: Modifier::new(),
selected_index,
head: None,
foot: None,
done: false,
}
}
impl<'a> NavigationScope<'a> {
pub fn head(mut self, head: impl FnOnce() + 'a) -> Self {
self.head = Some(std::boxed::Box::new(head));
self
}
pub fn foot(mut self, foot: impl FnOnce() + 'a) -> Self {
self.foot = Some(std::boxed::Box::new(foot));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let selected_index = self.selected_index;
let (head, foot) = (self.head.take(), self.foot.take());
emit(
widget_key("compose_rust::ui::Navigation"),
WidgetKind::Navigation,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
},
Vec::new(),
|| {
if let Some(head) = head {
slot(
SlotRole::TopBar,
widget_key("compose_rust::ui::Navigation::head"),
head,
);
}
if let Some(foot) = foot {
slot(
SlotRole::BottomBar,
widget_key("compose_rust::ui::Navigation::foot"),
foot,
);
}
let _destinations = crate::runtime::group_guard(widget_key(
"compose_rust::ui::Navigation::content",
));
content();
},
);
}
}
container!(NavigationScope<'a>);
pub struct NavigationItemScope {
modifier: Modifier,
text: String,
icon: Option<IconRole>,
color: Option<Paint>,
enabled: bool,
section: Option<String>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn NavigationItem(text: impl Into<String>) -> NavigationItemScope {
NavigationItemScope {
modifier: Modifier::new(),
text: text.into(),
icon: None,
color: None,
enabled: true,
section: None,
on_click: None,
done: false,
}
}
impl NavigationItemScope {
pub fn icon(mut self, icon: IconRole) -> Self {
self.icon = Some(icon);
self
}
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn section(mut self, section: impl Into<String>) -> Self {
self.section = Some(section.into());
self
}
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let section = self.section.take();
let (icon, color, enabled) = (self.icon, self.color, self.enabled);
emit(
widget_key("compose_rust::ui::NavigationItem"),
WidgetKind::NavigationItem,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("icon", super::role(icon)));
attrs.push(("color", super::paint(color)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
attrs.push(("section", optional_text(section)));
},
vec![("onclick", unit(self.on_click.take()))],
content,
);
}
}
composed_on_drop!(NavigationItemScope);
pub struct FileDropTargetScope {
modifier: Modifier,
alignment: Option<Alignment>,
on_files_entered: Option<Box<dyn FnMut()>>,
on_files_dropped: Option<Box<dyn FnMut(FileDrop)>>,
done: bool,
}
pub fn FileDropTarget() -> FileDropTargetScope {
FileDropTargetScope {
modifier: Modifier::new(),
alignment: None,
on_files_entered: None,
on_files_dropped: None,
done: false,
}
}
impl FileDropTargetScope {
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = Some(alignment);
self
}
pub fn on_files_entered(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_files_entered = Some(std::boxed::Box::new(handler));
self
}
pub fn on_files_dropped(mut self, handler: impl FnMut(FileDrop) + 'static) -> Self {
self.on_files_dropped = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let alignment = self.alignment;
let dropped: EventCallback = match self.on_files_dropped.take() {
Some(handler) => EventCallback::Files(Rc::new(RefCell::new(handler))),
None => EventCallback::Files(Rc::new(RefCell::new(|_: FileDrop| {}))),
};
emit(
widget_key("compose_rust::ui::FileDropTarget"),
WidgetKind::FileDropTarget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("alignment", super::role(alignment)));
},
vec![
("onfilesentered", unit(self.on_files_entered.take())),
("onfilesdropped", dropped),
],
content,
);
}
}
container!(FileDropTargetScope);
pub struct ChipScope {
modifier: Modifier,
text: String,
icon: Option<IconRole>,
selected: bool,
enabled: bool,
max_lines: Option<u32>,
overflow: Option<TextOverflow>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn Chip(text: impl Into<String>) -> ChipScope {
ChipScope {
modifier: Modifier::new(),
text: text.into(),
icon: None,
selected: false,
enabled: true,
max_lines: None,
overflow: None,
on_click: None,
done: false,
}
}
impl ChipScope {
pub fn icon(mut self, icon: IconRole) -> Self {
self.icon = Some(icon);
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn max_lines(mut self, lines: u32) -> Self {
self.max_lines = Some(lines);
self
}
pub fn overflow(mut self, overflow: TextOverflow) -> Self {
self.overflow = Some(overflow);
self
}
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let (max_lines, overflow) = (self.max_lines, self.overflow);
let (icon, selected, enabled) = (self.icon, self.selected, self.enabled);
emit(
widget_key("compose_rust::ui::Chip"),
WidgetKind::Chip,
|attrs| {
push_modifiers(&modifier, Mods::Chip, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("icon", super::role(icon)));
attrs.push(("checked", OwnedAttr::Bool(selected)));
attrs.push(("enabled", OwnedAttr::Bool(enabled)));
lines_and_overflow(max_lines, overflow, attrs);
},
vec![("onclick", unit(self.on_click.take()))],
content,
);
}
}
composed_on_drop!(ChipScope);
pub struct FloatingActionScope {
modifier: Modifier,
text: String,
icon: Option<IconRole>,
on_click: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn FloatingAction(text: impl Into<String>) -> FloatingActionScope {
FloatingActionScope {
modifier: Modifier::new(),
text: text.into(),
icon: None,
on_click: None,
done: false,
}
}
impl FloatingActionScope {
pub fn icon(mut self, icon: IconRole) -> Self {
self.icon = Some(icon);
self
}
pub fn on_click(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = std::mem::take(&mut self.text);
let icon = self.icon;
emit(
widget_key("compose_rust::ui::FloatingAction"),
WidgetKind::FloatingAction,
|attrs| {
push_modifiers(&modifier, Mods::None, attrs);
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("icon", super::role(icon)));
},
vec![("onclick", unit(self.on_click.take()))],
content,
);
}
}
composed_on_drop!(FloatingActionScope);
pub struct BadgeScope {
modifier: Modifier,
value: Option<u32>,
text: Option<String>,
color: Option<ColorRole>,
done: bool,
}
pub fn Badge() -> BadgeScope {
BadgeScope {
modifier: Modifier::new(),
value: None,
text: None,
color: None,
done: false,
}
}
impl BadgeScope {
pub fn value(mut self, value: u32) -> Self {
self.value = Some(value);
self
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn color(mut self, role: ColorRole) -> Self {
self.color = Some(role);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let text = self.text.take();
let (value, color) = (self.value, self.color);
emit(
widget_key("compose_rust::ui::Badge"),
WidgetKind::Badge,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push((
"count",
value.map_or(OwnedAttr::None, |value| OwnedAttr::Int(i64::from(value))),
));
attrs.push(("text", optional_text(text)));
attrs.push(("color", super::paint(color.map(Paint::Role))));
},
Vec::new(),
content,
);
}
}
container!(BadgeScope);
pub struct SplitPaneScope {
modifier: Modifier,
value: Option<f32>,
min: Option<f32>,
max: Option<f32>,
collapsible: bool,
selected_index: usize,
label: Option<String>,
on_change: Option<Box<dyn FnMut(f32)>>,
on_dismiss: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn SplitPane() -> SplitPaneScope {
SplitPaneScope {
modifier: Modifier::new(),
value: None,
min: None,
max: None,
collapsible: false,
selected_index: 0,
label: None,
on_change: None,
on_dismiss: None,
done: false,
}
}
impl SplitPaneScope {
pub fn value(mut self, dp: f32) -> Self {
self.value = Some(dp);
self
}
pub fn min(mut self, dp: f32) -> Self {
self.min = Some(dp);
self
}
pub fn max(mut self, dp: f32) -> Self {
self.max = Some(dp);
self
}
pub fn collapsible(mut self, collapsible: bool) -> Self {
self.collapsible = collapsible;
self
}
pub fn selected_index(mut self, index: usize) -> Self {
self.selected_index = index;
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn on_change(mut self, handler: impl FnMut(f32) + 'static) -> Self {
self.on_change = Some(std::boxed::Box::new(handler));
self
}
pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_dismiss = Some(std::boxed::Box::new(handler));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let label = self.label.take();
let (value, min, max, collapsible, selected_index) = (
self.value,
self.min,
self.max,
self.collapsible,
self.selected_index,
);
let change = match self.on_change.take() {
Some(mut handler) => EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler(value as f32)
}))),
None => value_handler(None),
};
let as_float = |value: Option<f32>| {
value.map_or(OwnedAttr::None, |value| OwnedAttr::Float(f64::from(value)))
};
emit(
widget_key("compose_rust::ui::SplitPane"),
WidgetKind::SplitPane,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("value", as_float(value)));
attrs.push(("min", as_float(min)));
attrs.push(("max", as_float(max)));
attrs.push(("collapsible", OwnedAttr::Bool(collapsible)));
attrs.push(("selected_index", OwnedAttr::Int(selected_index as i64)));
attrs.push(("text", optional_text(label)));
},
vec![
("onchange", change),
("ondismiss", unit(self.on_dismiss.take())),
],
content,
);
}
}
container!(SplitPaneScope);
pub struct LinearProgressIndicatorScope {
modifier: Modifier,
progress: f32,
done: bool,
}
pub fn LinearProgressIndicator(progress: f32) -> LinearProgressIndicatorScope {
LinearProgressIndicatorScope {
modifier: Modifier::new(),
progress,
done: false,
}
}
impl LinearProgressIndicatorScope {
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let progress = self.progress;
emit(
widget_key("compose_rust::ui::LinearProgressIndicator"),
WidgetKind::LinearProgressIndicator,
|attrs| {
push_modifiers(&modifier, Mods::None, attrs);
attrs.push(("progress", OwnedAttr::Float(f64::from(progress))));
},
Vec::new(),
content,
);
}
}
composed_on_drop!(LinearProgressIndicatorScope);
pub struct WindowScope {
on_close_request: Option<Box<dyn FnMut()>>,
title: Option<String>,
size: Option<(f32, f32)>,
min_size: Option<(f32, f32)>,
resizable: Option<bool>,
chrome: Option<crate::schema::Chrome>,
title_bar: Option<crate::schema::TitleBar>,
icon: Option<u32>,
position: Option<(f32, f32)>,
restore_key: Option<String>,
zoom_level: Option<i8>,
drag_from_pointer: Option<crate::windows::DragFromPointer>,
theme: Option<crate::schema::Theme>,
on_focus_changed: Option<Box<dyn FnMut(bool)>>,
on_moved: Option<Box<dyn FnMut(crate::windows::WindowFrame)>>,
on_drag: Option<Box<dyn FnMut(crate::windows::WindowDragEvent)>>,
on_unavailable: Option<Box<dyn FnMut()>>,
on_closed: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn Window(on_close_request: impl FnMut() + 'static) -> WindowScope {
WindowScope {
on_close_request: Some(std::boxed::Box::new(on_close_request)),
title: None,
size: None,
min_size: None,
resizable: None,
chrome: None,
title_bar: None,
icon: None,
position: None,
restore_key: None,
zoom_level: None,
drag_from_pointer: None,
theme: None,
on_focus_changed: None,
on_moved: None,
on_drag: None,
on_unavailable: None,
on_closed: None,
done: false,
}
}
impl Drop for WindowScope {
fn drop(&mut self) {
if !self.done && !std::thread::panicking() {
self.done = true;
self.compose(|| {});
}
}
}
fn window_callback(handler: impl FnMut(crate::windows::WindowEvent) + 'static) -> EventCallback {
EventCallback::Window(Rc::new(RefCell::new(handler)))
}
impl WindowScope {
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn size(mut self, width: f32, height: f32) -> Self {
self.size = Some((width, height));
self
}
pub fn min_size(mut self, width: f32, height: f32) -> Self {
self.min_size = Some((width, height));
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = Some(resizable);
self
}
pub fn chrome(mut self, chrome: crate::schema::Chrome) -> Self {
self.chrome = Some(chrome);
self
}
pub fn title_bar(mut self, title_bar: crate::schema::TitleBar) -> Self {
self.title_bar = Some(title_bar);
self
}
pub fn icon(mut self, asset_id: u32) -> Self {
self.icon = Some(asset_id);
self
}
pub fn position(mut self, x: f32, y: f32) -> Self {
self.position = Some((x, y));
self
}
pub fn restore_key(mut self, key: impl Into<String>) -> Self {
self.restore_key = Some(key.into());
self
}
pub fn zoom_level(mut self, level: i8) -> Self {
self.zoom_level = Some(crate::zoom::clamp_level(level));
self
}
pub fn drag_from_pointer(mut self, drag: crate::windows::DragFromPointer) -> Self {
self.drag_from_pointer = Some(drag);
self
}
pub fn theme(mut self, theme: crate::schema::Theme) -> Self {
self.theme = Some(theme);
self
}
pub fn on_focus_changed(mut self, handler: impl FnMut(bool) + 'static) -> Self {
self.on_focus_changed = Some(std::boxed::Box::new(handler));
self
}
pub fn on_moved(mut self, handler: impl FnMut(crate::windows::WindowFrame) + 'static) -> Self {
self.on_moved = Some(std::boxed::Box::new(handler));
self
}
pub fn on_drag(
mut self,
handler: impl FnMut(crate::windows::WindowDragEvent) + 'static,
) -> Self {
self.on_drag = Some(std::boxed::Box::new(handler));
self
}
pub fn on_unavailable(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_unavailable = Some(std::boxed::Box::new(handler));
self
}
pub fn on_closed(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_closed = Some(std::boxed::Box::new(handler));
self
}
pub fn content(mut self, content: impl FnOnce()) {
self.done = true;
self.compose(content);
}
fn compose(&mut self, content: impl FnOnce()) {
use crate::windows::WindowEvent;
let pair = |value: (f32, f32)| {
let mut bytes = Vec::with_capacity(8);
bytes.extend_from_slice(&value.0.to_bits().to_le_bytes());
bytes.extend_from_slice(&value.1.to_bits().to_le_bytes());
OwnedAttr::Bytes(bytes)
};
let mut properties: Attrs = Vec::new();
if let Some(title) = self.title.take() {
properties.push(("window_title", OwnedAttr::Text(title)));
}
if let Some(size) = self.size {
properties.push(("window_size", pair(size)));
}
if let Some(size) = self.min_size {
properties.push(("window_min_size", pair(size)));
}
if let Some(resizable) = self.resizable {
properties.push(("window_resizable", OwnedAttr::Bool(resizable)));
}
if let Some(chrome) = self.chrome {
properties.push(("window_chrome", OwnedAttr::Int(i64::from(chrome as u16))));
}
if let Some(title_bar) = self.title_bar {
properties.push((
"window_title_bar",
OwnedAttr::Int(i64::from(title_bar as u16)),
));
}
if let Some(icon) = self.icon {
properties.push(("window_icon", OwnedAttr::Int(i64::from(icon))));
}
if let Some(position) = self.position {
properties.push(("window_position", pair(position)));
}
if let Some(key) = self.restore_key.take() {
properties.push(("window_restore_key", OwnedAttr::Text(key)));
}
if let Some(level) = self.zoom_level {
properties.push(("window_zoom_level", OwnedAttr::Int(i64::from(level))));
}
if let Some(drag) = self.drag_from_pointer {
properties.push(("drag_from_pointer", OwnedAttr::Bytes(drag.to_bytes())));
}
let mut listeners: Vec<(&'static str, EventCallback)> = Vec::new();
let mut close = self.on_close_request.take();
listeners.push((
"onwindowclose",
window_callback(move |event| {
if let (WindowEvent::CloseRequested, Some(handler)) = (event, close.as_mut()) {
handler();
}
}),
));
if let Some(mut handler) = self.on_focus_changed.take() {
listeners.push((
"onwindowfocus",
window_callback(move |event| {
if let WindowEvent::FocusChanged { focused } = event {
handler(focused);
}
}),
));
}
if let Some(mut handler) = self.on_moved.take() {
listeners.push((
"onwindowmoved",
window_callback(move |event| {
if let WindowEvent::Moved {
x,
y,
width,
height,
} = event
{
handler(crate::windows::WindowFrame {
x,
y,
width,
height,
});
}
}),
));
}
if let Some(mut handler) = self.on_drag.take() {
listeners.push((
"onwindowdrag",
window_callback(move |event| {
if let WindowEvent::Drag {
phase,
screen_x,
screen_y,
over_window,
} = event
{
handler(crate::windows::WindowDragEvent {
phase,
screen_x,
screen_y,
over_window,
});
}
}),
));
}
if let Some(mut handler) = self.on_unavailable.take() {
listeners.push((
"onwindowunavailable",
window_callback(move |event| {
if event == WindowEvent::Unavailable {
handler();
}
}),
));
}
if let Some(mut handler) = self.on_closed.take() {
listeners.push((
"onwindowclosed",
window_callback(move |event| {
if event == WindowEvent::Closed {
handler();
}
}),
));
}
let theme = self.theme.take();
emit(
widget_key("compose_rust::ui::Window"),
WidgetKind::Window,
|attrs| attrs.append(&mut properties),
listeners,
move || {
if let (Some(theme), Some(window)) = (theme, crate::windows::current_window()) {
window.set_theme(theme);
}
content();
},
);
}
}