#![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::font_features::FontFeature;
use crate::fonts::FontRefs;
use crate::runtime::composer::{EventCallback, OwnedAttr};
use crate::schema::{
Alignment, Arrangement, ButtonKind, ButtonVariant, ColorRole, CompactNavigation, ContentScale,
HorizontalAlignment, IconRole, OverflowWrap, Paint, PopoverAlign, PopoverPlacement, SlotRole,
Snap, SpaceRole, TextAlign, TextOverflow, TypeRole, VerticalAlignment, WidgetKind, WordBreak,
};
use crate::spans::TextSpans;
use crate::{FileDrop, KeyEvent, MotionFinished, 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
}
}
};
}
#[derive(Default)]
struct FocusAndKeyHandlers {
on_focus_gained: Option<Box<dyn FnMut()>>,
on_focus_lost: Option<Box<dyn FnMut()>>,
on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
on_key_up: Option<Box<dyn FnMut(KeyEvent)>>,
on_motion_finished: Option<Box<dyn FnMut(MotionFinished)>>,
focus_handle: Option<super::FocusHandle>,
}
fn motion_input(
handler: Option<Box<dyn FnMut(MotionFinished)>>,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
if let Some(handler) = handler {
listeners.push((
"onmotionfinished",
EventCallback::Motion(Rc::new(RefCell::new(handler))),
));
}
}
fn focus_and_key_input(
mut handlers: FocusAndKeyHandlers,
listeners: &mut Vec<(&'static str, EventCallback)>,
) {
super::focus::bind_focus_handle(handlers.focus_handle.take());
if let Some(handler) = handlers.on_focus_gained.take() {
listeners.push(("onfocusgained", unit(Some(handler))));
}
if let Some(handler) = handlers.on_focus_lost.take() {
listeners.push(("onfocuslost", unit(Some(handler))));
}
if let Some(handler) = handlers.on_key_down.take() {
listeners.push((
"onkeydown",
EventCallback::Key(Rc::new(RefCell::new(handler))),
));
}
if let Some(handler) = handlers.on_key_up.take() {
listeners.push((
"onkeyup",
EventCallback::Key(Rc::new(RefCell::new(handler))),
));
}
motion_input(handlers.on_motion_finished.take(), listeners);
}
macro_rules! focus_and_key_handlers {
($name:ident) => {
impl $name {
pub fn on_focus_gained(mut self, handler: impl FnMut() + 'static) -> Self {
self.focus_and_keys.on_focus_gained = Some(std::boxed::Box::new(handler));
self
}
pub fn on_focus_lost(mut self, handler: impl FnMut() + 'static) -> Self {
self.focus_and_keys.on_focus_lost = Some(std::boxed::Box::new(handler));
self
}
pub fn on_key_down(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
self.focus_and_keys.on_key_down = Some(std::boxed::Box::new(handler));
self
}
pub fn on_key_up(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
self.focus_and_keys.on_key_up = Some(std::boxed::Box::new(handler));
self
}
pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
self.focus_and_keys.focus_handle = Some(handle.clone());
self
}
pub fn on_motion_finished(
mut self,
handler: impl FnMut(MotionFinished) + 'static,
) -> Self {
self.focus_and_keys.on_motion_finished = 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>,
radio_group: 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)));
}
if self.radio_group {
attrs.push(("radio_group", OwnedAttr::Bool(true)));
}
}
}
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()>>,
focus_and_keys: FocusAndKeyHandlers,
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,
focus_and_keys: FocusAndKeyHandlers::default(),
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
}
pub fn radio_group(mut self, enabled: bool) -> Self {
self.linear.radio_group = 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);
focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &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);
focus_and_key_handlers!($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 FlowRowScope {
modifier: Modifier,
horizontal_spacing: Option<f32>,
vertical_spacing: Option<f32>,
horizontal_alignment: Option<HorizontalAlignment>,
vertical_alignment: Option<VerticalAlignment>,
max_items_in_each_row: Option<u32>,
done: bool,
}
pub fn FlowRow() -> FlowRowScope {
FlowRowScope {
modifier: Modifier::new(),
horizontal_spacing: None,
vertical_spacing: None,
horizontal_alignment: None,
vertical_alignment: None,
max_items_in_each_row: None,
done: false,
}
}
impl FlowRowScope {
pub fn horizontal_spacing(mut self, dp: f32) -> Self {
self.horizontal_spacing = Some(dp);
self
}
pub fn vertical_spacing(mut self, dp: f32) -> Self {
self.vertical_spacing = Some(dp);
self
}
pub fn horizontal_alignment(mut self, alignment: HorizontalAlignment) -> Self {
self.horizontal_alignment = Some(alignment);
self
}
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = Some(alignment);
self
}
pub fn max_items_in_each_row(mut self, count: u32) -> Self {
self.max_items_in_each_row = Some(count);
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (horizontal_spacing, vertical_spacing) =
(self.horizontal_spacing, self.vertical_spacing);
let (horizontal_alignment, vertical_alignment) =
(self.horizontal_alignment, self.vertical_alignment);
let max_items = self.max_items_in_each_row;
emit(
widget_key("compose_rust::ui::FlowRow"),
WidgetKind::FlowRow,
|attrs| {
push_modifiers(&modifier, Mods::Container, attrs);
if let Some(dp) = horizontal_spacing {
attrs.push(("horizontal_spacing", dp_or_zero(Some(dp))));
}
if let Some(dp) = vertical_spacing {
attrs.push(("vertical_spacing", dp_or_zero(Some(dp))));
}
if let Some(alignment) = horizontal_alignment {
attrs.push(("horizontal_alignment", role_or_zero(Some(alignment))));
}
if let Some(alignment) = vertical_alignment {
attrs.push(("vertical_alignment", role_or_zero(Some(alignment))));
}
if let Some(count) = max_items {
attrs.push(("max_items_in_each_row", OwnedAttr::Int(i64::from(count))));
}
},
Vec::new(),
content,
);
}
}
container!(FlowRowScope);
pub struct ComposeBox {
modifier: Modifier,
alignment: Option<Alignment>,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
focus_and_keys: FocusAndKeyHandlers,
tear_off: Option<TearOff>,
done: bool,
}
pub fn Box() -> ComposeBox {
ComposeBox {
modifier: Modifier::new(),
alignment: None,
pointer: None,
on_click: None,
focus_and_keys: FocusAndKeyHandlers::default(),
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);
focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &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);
focus_and_key_handlers!(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()>>,
focus_and_keys: FocusAndKeyHandlers,
done: bool,
}
#[doc = $doc]
pub fn $widget() -> $name {
$name {
modifier: Modifier::new(),
pointer: None,
on_click: None,
focus_and_keys: FocusAndKeyHandlers::default(),
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);
focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &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);
focus_and_key_handlers!($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;
let max_offset = state.max_offset;
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>,
snap: Snap,
pointer: Option<PointerSubscription>,
on_click: Option<Box<dyn FnMut()>>,
focus_and_keys: FocusAndKeyHandlers,
done: bool,
}
#[doc = $doc]
pub fn $widget() -> $name {
$name {
modifier: Modifier::new(),
scroll_state: None,
snap: Snap::None,
pointer: None,
on_click: None,
focus_and_keys: FocusAndKeyHandlers::default(),
done: false,
}
}
impl $name {
pub fn scroll_state(mut self, state: &ScrollState) -> Self {
self.scroll_state = Some(state.clone());
self
}
pub fn snap(mut self, snap: Snap) -> Self {
self.snap = snap;
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let snap = self.snap;
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);
focus_and_key_input(std::mem::take(&mut self.focus_and_keys), &mut listeners);
emit(
widget_key($key),
WidgetKind::$widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
if snap != Snap::None {
attrs.push(("snap", OwnedAttr::Int(i64::from(u16::from(snap)))));
}
attrs.append(&mut subscription);
},
listeners,
content,
);
}
}
container!($name);
pointer_subscription!($name);
click_handler!($name);
focus_and_key_handlers!($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,
font_features: OwnedAttr,
word_break: Option<WordBreak>,
overflow_wrap: Option<OverflowWrap>,
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(),
font_features: OwnedAttr::None,
word_break: None,
overflow_wrap: 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 font_feature_settings(mut self, features: &[FontFeature]) -> Self {
self.font_features = crate::font_features::font_feature_attr(features);
self
}
pub fn word_break(mut self, word_break: WordBreak) -> Self {
self.word_break = Some(word_break);
self
}
pub fn overflow_wrap(mut self, overflow_wrap: OverflowWrap) -> Self {
self.overflow_wrap = Some(overflow_wrap);
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 font_features = std::mem::replace(&mut self.font_features, OwnedAttr::None);
let (word_break, overflow_wrap) = (self.word_break, self.overflow_wrap);
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)));
if font_features != OwnedAttr::None {
attrs.push(("font_feature_settings", font_features));
}
attrs.push(("word_break", super::role(word_break)));
attrs.push(("overflow_wrap", super::role(overflow_wrap)));
attrs.push(("text", OwnedAttr::Text(text)));
attrs.push(("type_role", super::role(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",
OwnedAttr::Float(letter_spacing.map_or(f64::NAN, f64::from)),
));
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_size: Option<f32>,
font_weight: Option<u16>,
line_height: Option<f32>,
letter_spacing: Option<f32>,
color: Option<Paint>,
font: FontRefs,
font_features: OwnedAttr,
word_break: Option<WordBreak>,
overflow_wrap: Option<OverflowWrap>,
on_value_change: Option<Box<dyn FnMut(String)>>,
on_submit: Option<Box<dyn FnMut(String)>>,
on_focus_gained: Option<Box<dyn FnMut()>>,
on_focus_lost: Option<Box<dyn FnMut()>>,
on_key_down: Option<Box<dyn FnMut(KeyEvent)>>,
on_key_up: Option<Box<dyn FnMut(KeyEvent)>>,
on_composition_change: Option<Box<dyn FnMut(bool)>>,
on_motion_finished: Option<Box<dyn FnMut(MotionFinished)>>,
focus_handle: Option<super::FocusHandle>,
bare: bool,
done: bool,
}
pub fn TextField() -> TextFieldScope {
TextFieldScope {
modifier: Modifier::new(),
text: String::new(),
placeholder: String::new(),
enabled: true,
multiline: false,
type_role: None,
font_size: None,
font_weight: None,
line_height: None,
letter_spacing: None,
color: None,
font: FontRefs::default(),
font_features: OwnedAttr::None,
word_break: None,
overflow_wrap: None,
on_value_change: None,
on_submit: None,
on_focus_gained: None,
on_focus_lost: None,
on_key_down: None,
on_key_up: None,
on_composition_change: None,
on_motion_finished: None,
focus_handle: None,
bare: false,
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_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 font(mut self, font: FontRefs) -> Self {
self.font = font;
self
}
pub fn font_feature_settings(mut self, features: &[FontFeature]) -> Self {
self.font_features = crate::font_features::font_feature_attr(features);
self
}
pub fn word_break(mut self, word_break: WordBreak) -> Self {
self.word_break = Some(word_break);
self
}
pub fn overflow_wrap(mut self, overflow_wrap: OverflowWrap) -> Self {
self.overflow_wrap = Some(overflow_wrap);
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_gained(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_focus_gained = 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
}
pub fn on_key_up(mut self, handler: impl FnMut(KeyEvent) + 'static) -> Self {
self.on_key_up = Some(std::boxed::Box::new(handler));
self
}
pub fn on_composition_change(mut self, handler: impl FnMut(bool) + 'static) -> Self {
self.on_composition_change = Some(std::boxed::Box::new(handler));
self
}
pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
self.focus_handle = Some(handle.clone());
self
}
pub fn on_motion_finished(mut self, handler: impl FnMut(MotionFinished) + 'static) -> Self {
self.on_motion_finished = Some(std::boxed::Box::new(handler));
self
}
pub fn bare(mut self) -> Self {
self.bare = true;
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 bare = self.bare;
let (font_size, font_weight, line_height) =
(self.font_size, self.font_weight, self.line_height);
let (letter_spacing, color) = (self.letter_spacing, self.color);
let font = std::mem::take(&mut self.font);
let font_features = std::mem::replace(&mut self.font_features, OwnedAttr::None);
let (word_break, overflow_wrap) = (self.word_break, self.overflow_wrap);
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 mut 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),
];
if let Some(handler) = self.on_focus_gained.take() {
listeners.push(("onfocusgained", unit(Some(handler))));
}
if let Some(handler) = self.on_key_up.take() {
listeners.push((
"onkeyup",
EventCallback::Key(Rc::new(RefCell::new(handler))),
));
}
if let Some(mut handler) = self.on_composition_change.take() {
listeners.push((
"oncompositionchange",
EventCallback::Value(Rc::new(RefCell::new(move |value: f64| {
handler(value != 0.0);
}))),
));
}
motion_input(self.on_motion_finished.take(), &mut listeners);
super::focus::bind_focus_handle(self.focus_handle.take());
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", super::role(type_role)));
if let Some(font_size) = font_size {
attrs.push(("font_size", OwnedAttr::Float(font_size as f64)));
}
if let Some(font_weight) = font_weight {
attrs.push(("font_weight", OwnedAttr::Int(font_weight as i64)));
}
if let Some(line_height) = line_height {
attrs.push(("line_height", OwnedAttr::Float(line_height as f64)));
}
if let Some(letter_spacing) = letter_spacing {
attrs.push(("letter_spacing", OwnedAttr::Float(letter_spacing as f64)));
}
if let Some(color) = color {
attrs.push(("color", super::paint(Some(color))));
}
attrs.push(("font", font_bytes(&font)));
if font_features != OwnedAttr::None {
attrs.push(("font_feature_settings", font_features));
}
attrs.push(("word_break", super::role(word_break)));
attrs.push(("overflow_wrap", super::role(overflow_wrap)));
if bare {
attrs.push(("bare", OwnedAttr::Bool(true)));
}
},
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)));
}
}
#[derive(Clone, Copy, Default)]
struct ControlLook {
track: Option<Paint>,
thumb: Option<Paint>,
active_track: Option<Paint>,
container: Option<Paint>,
check: Option<Paint>,
size: Option<(f32, f32)>,
}
impl ControlLook {
fn push(&self, attrs: &mut Attrs) {
let paints = [
("track_color", self.track),
("thumb_color", self.thumb),
("active_track_color", self.active_track),
("container_color", self.container),
("check_color", self.check),
];
for (name, paint) in paints {
if let Some(paint) = paint {
attrs.push((name, super::paint(Some(paint))));
}
}
if let Some((width, height)) = self.size {
let mut bytes = Vec::with_capacity(8);
bytes.extend_from_slice(&width.to_bits().to_le_bytes());
bytes.extend_from_slice(&height.to_bits().to_le_bytes());
attrs.push(("control_size", OwnedAttr::Bytes(bytes)));
}
}
}
macro_rules! control_colour {
($name:ident, $slot:ident, $setter:ident, $doc:literal) => {
impl $name {
#[doc = $doc]
pub fn $setter(mut self, paint: Paint) -> Self {
self.look.$slot = Some(paint);
self
}
}
};
}
fn push_accessible_name(name: Option<String>, attrs: &mut Attrs) {
if let Some(name) = name.filter(|name| !name.is_empty()) {
attrs.retain(|(key, _)| *key != "accessible_name");
attrs.push(("accessible_name", OwnedAttr::Text(name)));
}
}
macro_rules! accessible_name {
($name:ident) => {
impl $name {
pub fn accessible_name(mut self, name: impl Into<String>) -> Self {
self.accessible_name = Some(name.into());
self
}
}
};
}
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>,
accessible_name: Option<String>,
text_align: Option<TextAlign>,
font_size: Option<f32>,
font_weight: Option<u16>,
type_role: Option<TypeRole>,
on_click: Option<Box<dyn FnMut()>>,
focus_handle: Option<super::FocusHandle>,
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,
accessible_name: None,
text_align: None,
font_size: None,
font_weight: None,
type_role: None,
on_click: None,
focus_handle: None,
done: false,
}
}
accessible_name!(ButtonScope);
impl ButtonScope {
pub fn text_align(mut self, align: TextAlign) -> Self {
self.text_align = Some(align);
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 type_role(mut self, role: TypeRole) -> Self {
self.type_role = Some(role);
self
}
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
}
pub fn focus_handle(mut self, handle: &super::FocusHandle) -> Self {
self.focus_handle = Some(handle.clone());
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 accessible_name = self.accessible_name.take();
let (text_align, font_size) = (self.text_align, self.font_size);
let (font_weight, type_role) = (self.font_weight, self.type_role);
let listeners = vec![("onclick", unit(self.on_click.take()))];
super::focus::bind_focus_handle(self.focus_handle.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);
push_accessible_name(accessible_name, attrs);
if text_align.is_some() {
attrs.push(("text_align", role_or_zero(text_align)));
}
if font_size.is_some() {
attrs.push(("font_size", dp_or_zero(font_size)));
}
if let Some(font_weight) = font_weight {
attrs.push(("font_weight", OwnedAttr::Int(i64::from(font_weight))));
}
if type_role.is_some() {
attrs.push(("type_role", super::role(type_role)));
}
},
listeners,
content,
);
}
}
container!(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,
accessible_name: Option<String>,
look: ControlLook,
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,
accessible_name: None,
look: ControlLook::default(),
on_change: None,
done: false,
}
}
accessible_name!($name);
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 accessible_name = self.accessible_name.take();
let look = self.look;
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)));
push_accessible_name(accessible_name, attrs);
look.push(attrs);
},
vec![("onchange", handler)],
content,
);
}
}
composed_on_drop!($name);
};
}
control_colour!(
SwitchScope,
track,
track_color,
"The track, whichever state it is in. The Host owns the state, so it passes the colour \
that goes with it."
);
control_colour!(
SwitchScope,
thumb,
thumb_color,
"The thumb that travels along the track."
);
impl SwitchScope {
pub fn size(mut self, width: f32, height: f32) -> Self {
self.look.size = Some((width, height));
self
}
}
control_colour!(
CheckboxScope,
container,
container_color,
"The box when it is checked."
);
control_colour!(CheckboxScope, check, check_color, "The check mark.");
impl CheckboxScope {
pub fn size(mut self, side: f32) -> Self {
self.look.size = Some((side, side));
self
}
}
control_colour!(
RadioButtonScope,
container,
ring_color,
"The ring around the button, selected or not. Where the design system fills a selected \
button, the fill takes this colour as well."
);
control_colour!(
RadioButtonScope,
check,
dot_color,
"The dot inside a selected button."
);
impl RadioButtonScope {
pub fn size(mut self, side: f32) -> Self {
self.look.size = Some((side, side));
self
}
}
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,
accessible_name: Option<String>,
look: ControlLook,
thumb_size: Option<f32>,
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,
accessible_name: None,
look: ControlLook::default(),
thumb_size: None,
on_change: None,
done: false,
}
}
accessible_name!(SliderScope);
control_colour!(
SliderScope,
track,
track_color,
"The part of the track that is not filled."
);
control_colour!(SliderScope, thumb, thumb_color, "The thumb.");
control_colour!(
SliderScope,
active_track,
active_track_color,
"The part of the track up to the thumb."
);
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 thumb_size(mut self, dp: f32) -> Self {
self.thumb_size = Some(dp);
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 thumb_size = self.thumb_size;
let (color, value, min, max, steps, enabled) = (
self.color,
self.value,
self.min,
self.max,
self.steps,
self.enabled,
);
let accessible_name = self.accessible_name.take();
let look = self.look;
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)));
push_accessible_name(accessible_name, attrs);
look.push(attrs);
if let Some(dp) = thumb_size {
attrs.push(("thumb_size", OwnedAttr::Float(f64::from(dp))));
}
},
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>,
content_scale: Option<ContentScale>,
done: bool,
}
pub fn Image(asset_id: u32) -> AssetScope {
AssetScope {
modifier: Modifier::new(),
widget: WidgetKind::Image,
asset_id,
color: None,
content_scale: None,
done: false,
}
}
pub fn Icon(asset_id: u32) -> AssetScope {
AssetScope {
modifier: Modifier::new(),
widget: WidgetKind::Icon,
asset_id,
color: None,
content_scale: None,
done: false,
}
}
impl AssetScope {
pub fn color(mut self, paint: Paint) -> Self {
self.color = Some(paint);
self
}
pub fn content_scale(mut self, scale: ContentScale) -> Self {
self.content_scale = Some(scale);
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 content_scale = self.content_scale;
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)));
}
if let (WidgetKind::Image, Some(scale)) = (widget, content_scale) {
attrs.push(("content_scale", OwnedAttr::Int(i64::from(u16::from(scale)))));
}
},
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,
bare: 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,
bare: 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
}
pub fn bare(mut self) -> Self {
self.bare = true;
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 bare = self.bare;
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)));
}
if bare {
attrs.push(("bare", 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,
scrim: Option<Paint>,
max_height_fraction: Option<f32>,
on_dismiss: Option<Box<dyn FnMut()>>,
done: bool,
}
pub fn Dialog(open: bool) -> ModalScope {
ModalScope {
modifier: Modifier::new(),
widget: WidgetKind::Dialog,
open,
scrim: None,
max_height_fraction: None,
on_dismiss: None,
done: false,
}
}
pub fn Sheet(open: bool) -> ModalScope {
ModalScope {
modifier: Modifier::new(),
widget: WidgetKind::Sheet,
open,
scrim: None,
max_height_fraction: None,
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
}
pub fn scrim(mut self, paint: impl Into<Paint>) -> Self {
self.scrim = Some(paint.into());
self
}
pub fn max_height_fraction(mut self, fraction: f32) -> Self {
self.max_height_fraction = (fraction > 0.0).then(|| fraction.min(1.0));
self
}
fn compose(&mut self, content: impl FnOnce()) {
let modifier = self.modifier;
let (widget, open) = (self.widget, self.open);
let max_height_fraction = self
.max_height_fraction
.filter(|_| widget == WidgetKind::Sheet);
let key = if widget == WidgetKind::Dialog {
widget_key("compose_rust::ui::Dialog")
} else {
widget_key("compose_rust::ui::Sheet")
};
let scrim = self.scrim;
emit(
key,
widget,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("open", OwnedAttr::Bool(open)));
if let Some(scrim) = scrim {
attrs.push(("scrim", super::paint(Some(scrim))));
}
if let Some(fraction) = max_height_fraction {
attrs.push(("max_height_fraction", OwnedAttr::Float(f64::from(fraction))));
}
},
vec![("ondismiss", unit(self.on_dismiss.take()))],
content,
);
}
}
container!(ModalScope);
pub struct MenuScope<'a> {
modifier: Modifier,
expanded: bool,
scrim: Option<Paint>,
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,
scrim: None,
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 scrim(mut self, paint: impl Into<Paint>) -> Self {
self.scrim = Some(paint.into());
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();
let scrim = self.scrim;
emit(
widget_key("compose_rust::ui::Menu"),
WidgetKind::Menu,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("open", OwnedAttr::Bool(expanded)));
if let Some(scrim) = scrim {
attrs.push(("scrim", super::paint(Some(scrim))));
}
},
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 PopoverScope<'a> {
modifier: Modifier,
open: bool,
placement: Option<PopoverPlacement>,
align: Option<PopoverAlign>,
offset: Option<f32>,
scrim: bool,
scrim_color: Option<Paint>,
on_dismiss: Option<Box<dyn FnMut()>>,
anchor: Option<Box<dyn FnOnce() + 'a>>,
done: bool,
}
pub fn Popover<'a>(open: bool) -> PopoverScope<'a> {
PopoverScope {
modifier: Modifier::new(),
open,
placement: None,
align: None,
offset: None,
scrim: false,
scrim_color: None,
on_dismiss: None,
anchor: None,
done: false,
}
}
impl<'a> PopoverScope<'a> {
pub fn on_dismiss(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_dismiss = Some(std::boxed::Box::new(handler));
self
}
pub fn placement(mut self, placement: PopoverPlacement) -> Self {
self.placement = Some(placement);
self
}
pub fn align(mut self, align: PopoverAlign) -> Self {
self.align = Some(align);
self
}
pub fn offset(mut self, dp: f32) -> Self {
self.offset = Some(dp);
self
}
pub fn scrim(mut self, scrim: bool) -> Self {
self.scrim = scrim;
self
}
pub fn scrim_color(mut self, paint: impl Into<Paint>) -> Self {
self.scrim_color = Some(paint.into());
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 open = self.open;
let (placement, align, offset) = (self.placement, self.align, self.offset);
let (scrim, scrim_color) = (self.scrim, self.scrim_color);
let anchor = self.anchor.take();
emit(
widget_key("compose_rust::ui::Popover"),
WidgetKind::Popover,
|attrs| {
push_modifiers(&modifier, Mods::Standard, attrs);
attrs.push(("open", OwnedAttr::Bool(open)));
if let Some(placement) = placement {
attrs.push(("placement", role_or_zero(Some(placement))));
}
if let Some(align) = align {
attrs.push(("align", role_or_zero(Some(align))));
}
if let Some(offset) = offset {
attrs.push(("offset_dp", OwnedAttr::Float(f64::from(offset))));
}
if let Some(color) = scrim_color {
attrs.push(("scrim", super::paint(Some(color))));
} else if scrim {
attrs.push(("scrim", OwnedAttr::Bool(true)));
}
},
vec![("ondismiss", unit(self.on_dismiss.take()))],
|| {
if let Some(anchor) = anchor {
let _anchor = crate::runtime::group_guard(widget_key(
"compose_rust::ui::Popover::anchor",
));
anchor();
}
let _content =
crate::runtime::group_guard(widget_key("compose_rust::ui::Popover::content"));
content();
},
);
}
}
container!(PopoverScope<'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,
compact: CompactNavigation,
head: Slot<'a>,
foot: Slot<'a>,
done: bool,
}
pub fn Navigation<'a>(selected_index: usize) -> NavigationScope<'a> {
NavigationScope {
modifier: Modifier::new(),
selected_index,
compact: CompactNavigation::Default,
head: None,
foot: None,
done: false,
}
}
impl<'a> NavigationScope<'a> {
pub fn compact(mut self, compact: CompactNavigation) -> Self {
self.compact = compact;
self
}
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 compact = self.compact;
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)));
if compact != CompactNavigation::Default {
attrs.push((
"compact_navigation",
OwnedAttr::Int(i64::from(u16::from(compact))),
));
}
},
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,
collapse_below: Option<f32>,
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,
collapse_below: None,
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 collapse_below(mut self, dp: f32) -> Self {
self.collapse_below = Some(dp);
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, collapse_below, selected_index) = (
self.value,
self.min,
self.max,
self.collapsible,
self.collapse_below,
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(("collapse_below", as_float(collapse_below)));
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>,
report_size: 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,
report_size: 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 report_size(mut self, report: bool) -> Self {
self.report_size = Some(report);
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(report) = self.report_size {
properties.push(("report_size", OwnedAttr::Bool(report)));
}
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();
},
);
}
}