diff --git a/examples/widget-gallery/src/buttons.rs b/examples/widget-gallery/src/buttons.rs index c54073c3..aa41732a 100644 --- a/examples/widget-gallery/src/buttons.rs +++ b/examples/widget-gallery/src/buttons.rs @@ -1,9 +1,4 @@ -use floem::{ - peniko::Color, - style::CursorStyle, - view::View, - views::{label, Decorators}, -}; +use floem::{peniko::Color, style::CursorStyle, view::View, views::Decorators, widgets::button}; use crate::form::{form, form_item}; @@ -11,26 +6,17 @@ pub fn button_view() -> impl View { form({ ( form_item("Basic Button:".to_string(), 120.0, || { - label(|| "Click me") - .on_click(|_| { - println!("Button clicked"); - true - }) - .keyboard_navigatable() - .style(|s| { - s.border(1.0) - .border_radius(10.0) - .padding(10.0) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - }) + button(|| "Click me").on_click(|_| { + println!("Button clicked"); + true + }) }), form_item("Styled Button:".to_string(), 120.0, || { - label(|| "Click me") + button(|| "Click me") .on_click(|_| { println!("Button clicked"); true }) - .keyboard_navigatable() .style(|s| { s.border(1.0) .border_radius(10.0) @@ -44,36 +30,17 @@ pub fn button_view() -> impl View { .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) }) }), - form_item("Distabled Button:".to_string(), 120.0, || { - label(|| "Click me") - .disabled(|| true) - .on_click(|_| { - println!("Button clicked"); - true - }) - .keyboard_navigatable() - .style(|s| { - s.border(1.0) - .border_radius(10.0) - .padding(10.0) - .color(Color::GRAY) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - .hover(|s| s.background(Color::rgb8(224, 224, 224))) - }) + form_item("Disabled Button:".to_string(), 120.0, || { + button(|| "Click me").disabled(|| true).on_click(|_| { + println!("Button clicked"); + true + }) }), form_item("Secondary click button:".to_string(), 120.0, || { - label(|| "Right click me") - .on_secondary_click(|_| { - println!("Secondary mouse button click."); - true - }) - .keyboard_navigatable() - .style(|s| { - s.border(1.0) - .border_radius(10.0) - .padding(10.0) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - }) + button(|| "Right click me").on_secondary_click(|_| { + println!("Secondary mouse button click."); + true + }) }), ) }) diff --git a/examples/widget-gallery/src/checkbox.rs b/examples/widget-gallery/src/checkbox.rs index 95d75954..132be5d9 100644 --- a/examples/widget-gallery/src/checkbox.rs +++ b/examples/widget-gallery/src/checkbox.rs @@ -1,56 +1,52 @@ use floem::{ - peniko::Color, reactive::create_signal, view::View, - views::{checkbox, label, stack, Decorators}, + views::Decorators, + widgets::{checkbox, labeled_checkbox}, }; use crate::form::{form, form_item}; pub fn checkbox_view() -> impl View { + let width = 160.0; let (is_checked, set_is_checked) = create_signal(true); form({ ( - form_item("Basic Checkbox:".to_string(), 120.0, move || { + form_item("Checkbox:".to_string(), width, move || { checkbox(is_checked) - .style(|s| s.focus_visible(|s| s.border(2.).border_color(Color::BLUE))) + .style(|s| s.margin(5.0)) .on_click(move |_| { set_is_checked.update(|checked| *checked = !*checked); true }) }), - form_item("Labelled Checkbox:".to_string(), 120.0, move || { - stack({ - ( - checkbox(is_checked) - .on_click(move |_| { - set_is_checked.update(|checked| *checked = !*checked); - true - }) - .style(|s| s.focus_visible(|s| s.border(2.).border_color(Color::BLUE))), - label(|| "Check me!"), - ) - }) - .on_click(move |_| { - set_is_checked.update(|checked| *checked = !*checked); - true - }) + form_item("Disabled Checkbox:".to_string(), width, move || { + checkbox(is_checked) + .style(|s| s.margin(5.0)) + .on_click(move |_| { + set_is_checked.update(|checked| *checked = !*checked); + true + }) + .disabled(|| true) }), - form_item("Disabled Checkbox:".to_string(), 120.0, move || { - stack({ - ( - checkbox(is_checked) - .disabled(|| true) - .style(|s| s.focus_visible(|s| s.border(2.).border_color(Color::BLUE))), - label(|| "Check me!"), - ) - }) - .style(|s| s.color(Color::GRAY)) - .on_click(move |_| { + form_item("Labelled Checkbox:".to_string(), width, move || { + labeled_checkbox(is_checked, || "Check me!").on_click(move |_| { set_is_checked.update(|checked| *checked = !*checked); true }) }), + form_item( + "Disabled Labelled Checkbox:".to_string(), + width, + move || { + labeled_checkbox(is_checked, || "Check me!") + .on_click(move |_| { + set_is_checked.update(|checked| *checked = !*checked); + true + }) + .disabled(|| true) + }, + ), ) }) } diff --git a/examples/widget-gallery/src/form.rs b/examples/widget-gallery/src/form.rs index 8be57b55..d3efa98d 100644 --- a/examples/widget-gallery/src/form.rs +++ b/examples/widget-gallery/src/form.rs @@ -27,7 +27,7 @@ pub fn form_item( .style(move |s| s.width(label_width).justify_end().margin_right(10.0)), view_fn(), )) - .style(|s| s.flex_row().items_start()), + .style(|s| s.flex_row().items_center()), ) .style(|s| { s.flex_row() diff --git a/examples/widget-gallery/src/images.rs b/examples/widget-gallery/src/images.rs index f1a0fdbb..062cd8cb 100644 --- a/examples/widget-gallery/src/images.rs +++ b/examples/widget-gallery/src/images.rs @@ -1,7 +1,7 @@ use floem::{ unit::UnitExt, view::View, - views::{img, scroll, Decorators}, + views::{img, Decorators}, }; use crate::form::{form, form_item}; @@ -10,7 +10,7 @@ pub fn img_view() -> impl View { let ferris = include_bytes!("./../assets/ferris.png"); let sunflower = include_bytes!("./../assets/sunflower.jpg"); - scroll(form({ + form({ ( form_item("PNG:".to_string(), 120.0, move || { img(move || ferris.to_vec()) @@ -32,6 +32,5 @@ pub fn img_view() -> impl View { // .object_fit(ObjectFit::Contain).object_position(VertPosition::Top, HorizPosition::Left)) // ) - })) - .style(|s| s.flex_col().min_width(1000.px())) + }) } diff --git a/examples/widget-gallery/src/lists.rs b/examples/widget-gallery/src/lists.rs index 548643b8..3ea910e5 100644 --- a/examples/widget-gallery/src/lists.rs +++ b/examples/widget-gallery/src/lists.rs @@ -8,9 +8,10 @@ use floem::{ unit::UnitExt, view::View, views::{ - checkbox, container, label, scroll, stack, virtual_list, Decorators, VirtualListDirection, + container, label, scroll, stack, virtual_list, Decorators, VirtualListDirection, VirtualListItemSize, }, + widgets::checkbox, }; use crate::form::{form, form_item}; diff --git a/examples/widget-gallery/src/main.rs b/examples/widget-gallery/src/main.rs index dd0cdc2a..0617ef3d 100644 --- a/examples/widget-gallery/src/main.rs +++ b/examples/widget-gallery/src/main.rs @@ -13,13 +13,14 @@ use floem::{ keyboard::{Key, NamedKey}, peniko::Color, reactive::create_signal, - style::CursorStyle, + style::{Background, CursorStyle, Transition}, unit::UnitExt, view::View, views::{ - container, container_box, label, scroll, stack, tab, virtual_list, Decorators, - VirtualListDirection, VirtualListItemSize, + container, container_box, h_stack, label, scroll, stack, tab, v_stack, virtual_list, + Decorators, VirtualListDirection, VirtualListItemSize, }, + widgets::button, }; fn app_view() -> impl View { @@ -31,125 +32,131 @@ fn app_view() -> impl View { let (tabs, _set_tabs) = create_signal(tabs); let (active_tab, set_active_tab) = create_signal(0); - let view = stack({ - ( - container({ - scroll({ - virtual_list( - VirtualListDirection::Vertical, - VirtualListItemSize::Fixed(Box::new(|| 32.0)), - move || tabs.get(), - move |item| *item, - move |item| { - let index = tabs + + let list = scroll({ + virtual_list( + VirtualListDirection::Vertical, + VirtualListItemSize::Fixed(Box::new(|| 36.0)), + move || tabs.get(), + move |item| *item, + move |item| { + let index = tabs + .get_untracked() + .iter() + .position(|it| *it == item) + .unwrap(); + stack((label(move || item).style(|s| s.font_size(18.0)),)) + .on_click(move |_| { + set_active_tab.update(|v: &mut usize| { + *v = tabs .get_untracked() .iter() .position(|it| *it == item) .unwrap(); - stack((label(move || item).style(|s| s.font_size(24.0)),)) - .on_click(move |_| { - set_active_tab.update(|v: &mut usize| { - *v = tabs - .get_untracked() - .iter() - .position(|it| *it == item) - .unwrap(); - }); - true - }) - .on_event(EventListener::KeyDown, move |e| { - if let Event::KeyDown(key_event) = e { - let active = active_tab.get(); - if key_event.modifiers.is_empty() { - match key_event.key.logical_key { - Key::Named(NamedKey::ArrowUp) => { - if active > 0 { - set_active_tab.update(|v| *v -= 1) - } - true - } - Key::Named(NamedKey::ArrowDown) => { - if active < tabs.get().len() - 1 { - set_active_tab.update(|v| *v += 1) - } - true - } - _ => false, - } - } else { - false + }); + true + }) + .on_event(EventListener::KeyDown, move |e| { + if let Event::KeyDown(key_event) = e { + let active = active_tab.get(); + if key_event.modifiers.is_empty() { + match key_event.key.logical_key { + Key::Named(NamedKey::ArrowUp) => { + if active > 0 { + set_active_tab.update(|v| *v -= 1) + } + true + } + Key::Named(NamedKey::ArrowDown) => { + if active < tabs.get().len() - 1 { + set_active_tab.update(|v| *v += 1) } - } else { - false + true } - }) - .keyboard_navigatable() - .draggable() - .style(move |s| { - s.flex_row() - .width(100.pct()) - .height(32.0) - .border_bottom(1.0) - .border_color(Color::LIGHT_GRAY) - .apply_if(index == active_tab.get(), |s| { - s.background(Color::GRAY) - }) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - .hover(|s| { - s.background(Color::LIGHT_GRAY) - .cursor(CursorStyle::Pointer) - }) - }) - }, - ) - .style(|s| s.flex_col().width(140.0)) - }) - .style(|s| { - s.flex_col() - .width(140.0) - .height(100.pct()) - .border(1.0) - .border_color(Color::GRAY) - }) - }) - .style(|s| { - s.height(100.pct()) - .width(150.0) - .padding_vert(5.0) - .padding_horiz(5.0) - .flex_col() - .items_center() - }), - container({ - tab( - move || active_tab.get(), - move || tabs.get(), - |it| *it, - |it| match it { - "Label" => container_box(labels::label_view()), - "Button" => container_box(buttons::button_view()), - "Checkbox" => container_box(checkbox::checkbox_view()), - "Input" => container_box(inputs::text_input_view()), - "List" => container_box(lists::virt_list_view()), - "Menu" => container_box(context_menu::menu_view()), - "RichText" => container_box(rich_text::rich_text_view()), - "Image" => container_box(images::img_view()), - _ => container_box(label(|| "Not implemented".to_owned())), - }, - ) - .style(|s| s.size(100.pct(), 100.pct())) - }) - .style(|s| { - s.size(100.pct(), 100.pct()) - .padding_vert(5.0) - .padding_horiz(5.0) - .flex_col() - .items_center() - }), + _ => false, + } + } else { + false + } + } else { + false + } + }) + .keyboard_navigatable() + .draggable() + .style(move |s| { + s.flex_row() + .padding(5.0) + .width(100.pct()) + .height(36.0) + .transition(Background, Transition::linear(0.4)) + .items_center() + .border_bottom(1.0) + .border_color(Color::LIGHT_GRAY) + .apply_if(index == active_tab.get(), |s| { + s.background(Color::GRAY.with_alpha_factor(0.6)) + }) + .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) + .hover(|s| { + s.background(Color::LIGHT_GRAY) + .apply_if(index == active_tab.get(), |s| { + s.background(Color::GRAY) + }) + .cursor(CursorStyle::Pointer) + }) + }) + }, ) + .style(|s| s.flex_col().width(140.0)) }) - .style(|s| s.size(100.pct(), 100.pct())) - .window_title(|| "Widget Gallery".to_owned()); + .style(|s| { + s.flex_col() + .width(140.0) + .flex_grow(1.0) + .min_height(0) + .flex_basis(0) + }); + + let list = container(list).style(|s| { + s.border(1.0) + .border_color(Color::GRAY) + .flex_grow(1.0) + .min_height(0) + }); + + let id = list.id(); + let inspector = button(|| "Open Inspector") + .on_click(move |_| { + id.inspect(); + true + }) + .style(|s| s); + + let left = v_stack((list, inspector)).style(|s| s.height_full().gap(0.0, 5.0)); + + let tab = tab( + move || active_tab.get(), + move || tabs.get(), + |it| *it, + |it| match it { + "Label" => container_box(labels::label_view()), + "Button" => container_box(buttons::button_view()), + "Checkbox" => container_box(checkbox::checkbox_view()), + "Input" => container_box(inputs::text_input_view()), + "List" => container_box(lists::virt_list_view()), + "Menu" => container_box(context_menu::menu_view()), + "RichText" => container_box(rich_text::rich_text_view()), + "Image" => container_box(images::img_view()), + _ => container_box(label(|| "Not implemented".to_owned())), + }, + ) + .style(|s| s.flex_col().items_start()); + + let tab = scroll(tab).style(|s| s.flex_basis(0).min_width(0).flex_grow(1.0)); + + let view = h_stack((left, tab)) + .style(|s| s.padding(5.0).width_full().height_full().gap(5.0, 0.0)) + .window_title(|| "Widget Gallery".to_owned()); let id = view.id(); view.on_event(EventListener::KeyUp, move |e| { diff --git a/src/app_handle.rs b/src/app_handle.rs index 391d5872..e75b614d 100644 --- a/src/app_handle.rs +++ b/src/app_handle.rs @@ -152,7 +152,7 @@ impl ApplicationHandle { window_handle.scale(scale_factor); } WindowEvent::ThemeChanged(theme) => { - window_handle.theme_changed(theme); + window_handle.os_theme_changed(theme); } WindowEvent::Occluded(_) => {} WindowEvent::MenuAction(id) => { @@ -172,7 +172,7 @@ impl ApplicationHandle { ) { let mut window_builder = winit::window::WindowBuilder::new(); let transparent = config.as_ref().and_then(|c| c.transparent).unwrap_or(false); - if let Some(config) = config { + let themed = if let Some(config) = config { if let Some(size) = config.size { let size = if size.width == 0.0 || size.height == 0.0 { Size::new(800.0, 600.0) @@ -213,14 +213,17 @@ impl ApplicationHandle { if let Some(title) = config.title { window_builder = window_builder.with_title(title); } - } + config.themed.unwrap_or(true) + } else { + true + }; let result = window_builder.build(event_loop); let window = match result { Ok(window) => window, Err(_) => return, }; let window_id = window.id(); - let window_handle = WindowHandle::new(window, view_fn, transparent); + let window_handle = WindowHandle::new(window, view_fn, transparent, themed); self.window_handles.insert(window_id, window_handle); } diff --git a/src/inspector.rs b/src/inspector.rs index 6cc7530a..b827c7e0 100644 --- a/src/inspector.rs +++ b/src/inspector.rs @@ -8,6 +8,7 @@ use crate::views::{ dyn_container, empty, h_stack, img_dynamic, scroll, stack, static_label, static_list, text, v_stack, Decorators, Label, }; +use crate::widgets::button; use crate::window::WindowConfig; use crate::{new_window, style}; use floem_reactive::{create_rw_signal, RwSignal, Scope}; @@ -486,19 +487,8 @@ fn selected_view(capture: &Rc, selected: RwSignal>) -> impl ) ), ); - let clear = text("Clear selection") - .style(|s| { - s.background(Color::WHITE_SMOKE) - .border(1.0) - .padding(5.0) - .margin(5.0) - .border_color(Color::BLACK.with_alpha_factor(0.4)) - .border_radius(4.0) - .hover(move |s| { - s.border_color(Color::BLACK.with_alpha_factor(0.2)) - .background(Color::GRAY.with_alpha_factor(0.6)) - }) - }) + let clear = button(|| "Clear selection") + .style(|s| s.margin(5.0)) .on_click(move |_| { selected.set(None); true diff --git a/src/lib.rs b/src/lib.rs index d967def8..af755fc6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,6 +111,7 @@ mod update; pub mod view; pub mod view_tuple; pub mod views; +pub mod widgets; pub mod window; mod window_handle; diff --git a/src/style.rs b/src/style.rs index b1f6ed4b..df4f472f 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1061,7 +1061,7 @@ define_builtin_props!( TextOverflowProp text_overflow: TextOverflow {} = TextOverflow::Wrap, LineHeight line_height nocb: Option { inherited } = None, AspectRatio aspect_ratio: Option {} = None, - Gap gap: Size {} = Size::zero(), + Gap gap nocb: Size {} = Size::zero(), ); prop_extracter! { @@ -1175,6 +1175,18 @@ impl Style { self.height(height.pct()) } + pub fn gap(self, width: impl Into, height: impl Into) -> Self { + let width: PxPct = width.into(); + let height: PxPct = height.into(); + self.set( + Gap, + Size { + width: width.into(), + height: height.into(), + }, + ) + } + pub fn size(self, width: impl Into, height: impl Into) -> Self { self.width(width).height(height) } diff --git a/src/view.rs b/src/view.rs index 66b772f7..458819ef 100644 --- a/src/view.rs +++ b/src/view.rs @@ -387,6 +387,12 @@ pub(crate) fn view_tab_navigation(root_view: &dyn View, app_state: &mut AppState .focus .filter(|id| id.id_path().is_some()) .unwrap_or(root_view.id()); + + assert!( + view_filtered_children(root_view, start.id_path().unwrap().dispatch()).is_some(), + "The focused view is missing from the tree" + ); + let tree_iter = |id: Id| { if backwards { view_tree_previous(root_view, &id) @@ -405,20 +411,20 @@ pub(crate) fn view_tab_navigation(root_view: &dyn View, app_state: &mut AppState app_state.update_focus(new_focus, true); } -fn view_filtered_children<'a>(view: &'a dyn View, id_path: &[Id]) -> Vec<&'a dyn View> { +fn view_filtered_children<'a>(view: &'a dyn View, id_path: &[Id]) -> Option> { let id = id_path[0]; let id_path = &id_path[1..]; if id == view.id() { if id_path.is_empty() { - view_children(view) + Some(view_children(view)) } else if let Some(child) = view.child(id_path[0]) { view_filtered_children(child, id_path) } else { - Vec::new() + None } } else { - Vec::new() + None } } @@ -427,6 +433,7 @@ fn view_tree_next(root_view: &dyn View, id: &Id) -> Option { let id_path = id.id_path().unwrap(); if let Some(child) = view_filtered_children(root_view, id_path.dispatch()) + .unwrap() .into_iter() .next() { @@ -456,7 +463,7 @@ fn view_next_sibling<'a>(root_view: &'a dyn View, id_path: &[Id]) -> Option<&'a return None; } - let children = view_filtered_children(root_view, parent); + let children = view_filtered_children(root_view, parent).unwrap(); let pos = children.iter().position(|v| v.id() == id).unwrap(); if pos + 1 < children.len() { @@ -485,7 +492,7 @@ fn view_previous_sibling<'a>(root_view: &'a dyn View, id_path: &[Id]) -> Option< return None; } - let children = view_filtered_children(root_view, parent); + let children = view_filtered_children(root_view, parent).unwrap(); let pos = children.iter().position(|v| v.id() == id).unwrap(); if pos > 0 { diff --git a/src/views/container_box.rs b/src/views/container_box.rs index 627b0f74..b25734c4 100644 --- a/src/views/container_box.rs +++ b/src/views/container_box.rs @@ -31,6 +31,7 @@ pub struct ContainerBox { /// /// ``` /// use floem::views::*; +/// use floem::widgets::*; /// use floem_reactive::*; /// let check = true; /// diff --git a/src/views/dyn_container.rs b/src/views/dyn_container.rs index 818a00c1..d9f40b9b 100644 --- a/src/views/dyn_container.rs +++ b/src/views/dyn_container.rs @@ -114,6 +114,7 @@ impl View for DynamicContainer { fn update(&mut self, cx: &mut crate::context::UpdateCx, state: Box) { if let Ok(val) = state.downcast::() { let old_child_scope = self.child_scope; + cx.app_state_mut().remove_view(&mut self.child); (self.child, self.child_scope) = (self.child_fn)(*val); old_child_scope.dispose(); self.child.id().set_parent(self.id); diff --git a/src/views/svg.rs b/src/views/svg.rs index f556b450..7ede3f96 100644 --- a/src/views/svg.rs +++ b/src/views/svg.rs @@ -5,10 +5,9 @@ use floem_renderer::{ Renderer, }; use kurbo::Size; -use peniko::Color; use sha2::{Digest, Sha256}; -use crate::{id::Id, view::View, views::Decorators}; +use crate::{id::Id, view::View}; pub struct Svg { id: Id, @@ -29,24 +28,6 @@ pub fn svg(svg_str: impl Fn() -> String + 'static) -> Svg { } } -/// Renders a checkbox using an svg and the provided checked signal. -/// Can be combined with a label and a stack with a click event (as in `examples/widget-gallery`). -pub fn checkbox(checked: crate::reactive::ReadSignal) -> Svg { - const CHECKBOX_SVG: &str = r#""#; - let svg_str = move || if checked.get() { CHECKBOX_SVG } else { "" }.to_string(); - - svg(svg_str) - .base_style(|base| { - base.width(20.) - .height(20.) - .border_color(Color::BLACK) - .border(1.) - .border_radius(5.) - .margin_right(5.) - }) - .keyboard_navigatable() -} - impl View for Svg { fn id(&self) -> Id { self.id diff --git a/src/widgets/button.rs b/src/widgets/button.rs new file mode 100644 index 00000000..eb16f424 --- /dev/null +++ b/src/widgets/button.rs @@ -0,0 +1,14 @@ +use crate::{ + style_class, + view::View, + views::{self, container, Decorators}, +}; +use std::fmt::Display; + +style_class!(pub ButtonClass); + +pub fn button(label: impl Fn() -> S + 'static) -> impl View { + container(views::label(label)) + .keyboard_navigatable() + .class(ButtonClass) +} diff --git a/src/widgets/checkbox.rs b/src/widgets/checkbox.rs new file mode 100644 index 00000000..128fe3e0 --- /dev/null +++ b/src/widgets/checkbox.rs @@ -0,0 +1,36 @@ +use crate::{ + style_class, + view::View, + views::{self, h_stack, svg, Decorators}, +}; +use floem_reactive::ReadSignal; +use std::fmt::Display; + +style_class!(pub FocusClass); + +style_class!(pub CheckboxClass); + +style_class!(pub LabeledCheckboxClass); + +fn checkbox_svg(checked: ReadSignal) -> impl View { + const CHECKBOX_SVG: &str = r#""#; + let svg_str = move || if checked.get() { CHECKBOX_SVG } else { "" }.to_string(); + svg(svg_str).class(CheckboxClass) +} + +/// Renders a checkbox the provided checked signal. +/// Can be combined with a label and a stack with a click event (as in `examples/widget-gallery`). +pub fn checkbox(checked: ReadSignal) -> impl View { + checkbox_svg(checked).keyboard_navigatable() +} + +/// Renders a checkbox using the provided checked signal. +pub fn labeled_checkbox( + checked: ReadSignal, + label: impl Fn() -> S + 'static, +) -> impl View { + h_stack((checkbox_svg(checked), views::label(label))) + .class(LabeledCheckboxClass) + .base_style(|s| s.items_center().justify_center()) + .keyboard_navigatable() +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs new file mode 100644 index 00000000..4d1d8036 --- /dev/null +++ b/src/widgets/mod.rs @@ -0,0 +1,129 @@ +//! # Floem widgets +//! +//! This module contains all of the built-in widgets of Floem. +//! + +mod checkbox; +use std::rc::Rc; + +pub use checkbox::*; + +mod button; +pub use button::*; +use peniko::Color; + +use crate::{ + style::{Background, Style, Transition}, + views::scroll, +}; + +pub(crate) struct Theme { + pub(crate) background: Color, + pub(crate) style: Rc