diff --git a/examples/button.rs b/examples/button.rs index 6e00657..df75743 100644 --- a/examples/button.rs +++ b/examples/button.rs @@ -1,7 +1,6 @@ use gooey::dynamic::Dynamic; -use gooey::widget::Widget; use gooey::widgets::Button; -use gooey::EventLoopError; +use gooey::{EventLoopError, Run}; fn main() -> Result<(), EventLoopError> { let count = Dynamic::new(0_usize); diff --git a/examples/canvas.rs b/examples/canvas.rs index b9b4eb4..945999a 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -1,5 +1,5 @@ -use gooey::widget::Widget; use gooey::widgets::Canvas; +use gooey::Run; use kludgine::figures::units::Px; use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size}; use kludgine::shapes::Shape; diff --git a/examples/counter.rs b/examples/counter.rs index 2700d73..5477702 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -2,10 +2,9 @@ use std::string::ToString; use gooey::children::Children; use gooey::dynamic::Dynamic; -use gooey::widget::Widget; use gooey::widgets::array::Array; use gooey::widgets::{Button, Label}; -use gooey::EventLoopError; +use gooey::{EventLoopError, Run}; fn main() -> Result<(), EventLoopError> { let counter = Dynamic::new(0i32); diff --git a/examples/input.rs b/examples/input.rs index e22400d..8111de2 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -1,6 +1,5 @@ -use gooey::widget::Widget; use gooey::widgets::Input; -use gooey::EventLoopError; +use gooey::{EventLoopError, Run}; fn main() -> Result<(), EventLoopError> { Input::new("Hello").run() diff --git a/examples/style.rs b/examples/style.rs index 30052bc..abb2eb3 100644 --- a/examples/style.rs +++ b/examples/style.rs @@ -4,19 +4,22 @@ use gooey::widget::Widget; use gooey::widgets::array::Array; use gooey::widgets::{Button, Style}; use gooey::window::Window; -use gooey::EventLoopError; +use gooey::{styles, EventLoopError, Run}; use kludgine::Color; fn main() -> Result<(), EventLoopError> { - Window::for_widget(Array::rows( - Children::new() - .with_widget(Button::new("Default")) - .with_widget(styled(Button::new("Styled"))), - )) - .styles(Styles::new().with(&TextColor, Color::GREEN)) + Window::for_widget( + Array::rows( + Children::new() + .with_widget(Button::new("Default")) + .with_widget(red_text(Button::new("Styled"))), + ) + .with_styles(Styles::new().with(&TextColor, Color::GREEN)), + ) .run() } -fn styled(w: impl Widget) -> Style { - Style::new(Styles::new().with(&TextColor, Color::RED), w) +/// Creating reusable style helpers that work with any Widget is straightfoward +fn red_text(w: impl Widget) -> Style { + Style::new(styles!(TextColor => Color::RED), w) } diff --git a/src/children.rs b/src/children.rs index addb602..59330fc 100644 --- a/src/children.rs +++ b/src/children.rs @@ -2,7 +2,7 @@ use std::ops::{Index, IndexMut}; use alot::OrderedLots; -use crate::widget::{BoxedWidget, Widget}; +use crate::widget::{BoxedWidget, MakeWidget}; #[derive(Debug, Default)] #[must_use] @@ -19,9 +19,9 @@ impl Children { pub fn with_widget(mut self, widget: W) -> Self where - W: Widget, + W: MakeWidget, { - self.ordered.push(BoxedWidget::new(widget)); + self.ordered.push(widget.make_widget()); self } diff --git a/src/lib.rs b/src/lib.rs index 5cce41d..478370e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub mod widget; pub mod widgets; pub mod window; +pub use kludgine; pub use kludgine::app::winit::error::EventLoopError; pub use kludgine::app::winit::event::ElementState; use kludgine::figures::units::UPx; @@ -37,3 +38,7 @@ impl ConstraintLimit { } pub type Result = std::result::Result; + +pub trait Run: Sized { + fn run(self) -> Result<(), EventLoopError>; +} diff --git a/src/styles.rs b/src/styles.rs index 5189fd7..cc97133 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -1,10 +1,36 @@ use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{hash_map, HashMap}; use std::sync::Arc; use crate::names::Name; use crate::utils::Lazy; +#[macro_export] +#[doc(hidden)] +macro_rules! count { + ($value:expr ;) => { + 1 + }; + ($value:expr , $($remaining:expr),+ ;) => { + 1 + count!($($remaining),+ ;) + } +} + +#[macro_export] +macro_rules! styles { + () => {{ + $crate::styles::Styles::new() + }}; + ($($component:expr => $value:expr),*) => {{ + let mut styles = $crate::styles::Styles::with_capacity($crate::count!($($value),* ;)); + $(styles.push(&$component, $value);)* + styles + }}; + ($($component:expr => $value:expr),* ,) => {{ + $crate::styles!($($component => $value),*) + }}; +} + #[derive(Clone, Debug, Default)] pub struct Styles(Arc>>); @@ -14,14 +40,23 @@ impl Styles { Self::default() } - pub fn push(&mut self, name: &impl NamedComponent, component: impl Into) { - let name = name.name().into_owned(); + #[must_use] + pub fn with_capacity(components: usize) -> Self { + Self(Arc::new(HashMap::with_capacity(components))) + } + + pub fn push_component(&mut self, name: ComponentName, component: impl Into) { Arc::make_mut(&mut self.0) .entry(name.group) .or_default() .insert(name.name, component.into()); } + pub fn push(&mut self, name: &impl NamedComponent, component: impl Into) { + let name = name.name().into_owned(); + self.push_component(name, component); + } + #[must_use] pub fn with(mut self, name: &impl NamedComponent, component: impl Into) -> Self { self.push(name, component); @@ -53,6 +88,54 @@ impl Styles { } } +impl FromIterator<(ComponentName, Component)> for Styles { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut styles = Self::with_capacity(iter.size_hint().0); + for (name, component) in iter { + styles.push_component(name, component); + } + styles + } +} + +impl IntoIterator for Styles { + type IntoIter = StylesIntoIter; + type Item = (ComponentName, Component); + + fn into_iter(self) -> Self::IntoIter { + StylesIntoIter { + main: Arc::try_unwrap(self.0) + .unwrap_or_else(|err| err.as_ref().clone()) + .into_iter(), + names: None, + } + } +} + +pub struct StylesIntoIter { + main: hash_map::IntoIter>, + names: Option<(Group, hash_map::IntoIter)>, +} + +impl Iterator for StylesIntoIter { + type Item = (ComponentName, Component); + + fn next(&mut self) -> Option { + loop { + if let Some((group, names)) = &mut self.names { + if let Some((name, component)) = names.next() { + return Some((ComponentName::new(group.clone(), name), component)); + } + self.names = None; + } + + let (group, names) = self.main.next()?; + self.names = Some((group, names.into_iter())); + } + } +} + pub type StyleQuery = Vec; use std::any::Any; use std::fmt::Debug; diff --git a/src/widget.rs b/src/widget.rs index 1ce4ad5..9305648 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -15,17 +15,11 @@ use crate::context::{EventContext, GraphicsContext}; use crate::dynamic::Dynamic; use crate::styles::{Component, Group, Styles}; use crate::tree::{Tree, WidgetId}; +use crate::widgets::Style; use crate::window::{RunningWindow, Window, WindowBehavior}; -use crate::ConstraintLimit; +use crate::{ConstraintLimit, Run}; pub trait Widget: Send + UnwindSafe + Debug + 'static { - fn run(self) -> Result<(), EventLoopError> - where - Self: Sized, - { - Window::::new(BoxedWidget::new(self)).run() - } - fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>); fn measure( @@ -123,6 +117,39 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static { fn query_component(&self, group: Group, name: &str) -> Option { None } + + fn with_styles(self, styles: impl Into) -> Style + where + Self: Sized, + { + Style::new(styles, self) + } +} + +impl Run for T +where + T: Widget, +{ + fn run(self) -> crate::Result<(), EventLoopError> { + BoxedWidget::new(self).run() + } +} + +pub trait MakeWidget: Sized { + fn make_widget(self) -> BoxedWidget; + + fn run(self) -> Result<(), EventLoopError> { + self.make_widget().run() + } +} + +impl MakeWidget for T +where + T: Widget, +{ + fn make_widget(self) -> BoxedWidget { + BoxedWidget::new(self) + } } pub type EventHandling = ControlFlow; @@ -151,6 +178,12 @@ impl BoxedWidget { } } +impl Run for BoxedWidget { + fn run(self) -> crate::Result<(), EventLoopError> { + Window::::new(self).run() + } +} + impl Eq for BoxedWidget {} impl PartialEq for BoxedWidget { @@ -172,25 +205,30 @@ impl WindowBehavior for BoxedWidget { } #[derive(Debug)] -pub enum Value -where - T: 'static, -{ - Static(T), +pub enum Value { + Constant(T), Dynamic(Dynamic), } impl Value { + pub fn dynamic(value: T) -> Self { + Self::Dynamic(Dynamic::new(value)) + } + + pub fn constant(value: T) -> Self { + Self::Constant(value) + } + pub fn map(&mut self, map: impl FnOnce(&T) -> R) -> R { match self { - Value::Static(value) => map(value), + Value::Constant(value) => map(value), Value::Dynamic(dynamic) => dynamic.map_ref(map), } } pub fn map_mut(&mut self, map: impl FnOnce(&mut T) -> R) -> R { match self { - Value::Static(value) => map(value), + Value::Constant(value) => map(value), Value::Dynamic(dynamic) => dynamic.map_mut(map), } } @@ -204,7 +242,7 @@ impl Value { pub fn generation(&self) -> Option { match self { - Value::Static(_) => None, + Value::Constant(_) => None, Value::Dynamic(value) => Some(value.generation()), } } @@ -216,13 +254,13 @@ pub trait IntoValue { impl IntoValue for T { fn into_value(self) -> Value { - Value::Static(self) + Value::Constant(self) } } impl<'a> IntoValue for &'a str { fn into_value(self) -> Value { - Value::Static(self.to_owned()) + Value::Constant(self.to_owned()) } } @@ -232,6 +270,12 @@ impl IntoValue for Dynamic { } } +impl IntoValue for Value { + fn into_value(self) -> Value { + self + } +} + pub struct Callback(Box>); impl Debug for Callback { diff --git a/src/widgets/style.rs b/src/widgets/style.rs index f01367e..7ca708a 100644 --- a/src/widgets/style.rs +++ b/src/widgets/style.rs @@ -14,12 +14,9 @@ pub struct Style { } impl Style { - pub fn new(styles: Styles, child: W) -> Self - where - W: Widget, - { + pub fn new(styles: impl Into, child: impl Widget) -> Self { Self { - styles, + styles: styles.into(), child: BoxedWidget::new(child), mounted_child: None, } diff --git a/src/window.rs b/src/window.rs index 22ead98..c8cce2e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -16,11 +16,11 @@ use kludgine::Kludgine; use crate::context::{EventContext, Exclusive, GraphicsContext, WidgetContext}; use crate::graphics::Graphics; -use crate::styles::Styles; use crate::tree::Tree; use crate::utils::ModifiersExt; use crate::widget::{BoxedWidget, EventHandling, ManagedWidget, Widget, HANDLED, UNHANDLED}; use crate::window::sealed::WindowCommand; +use crate::Run; pub type RunningWindow<'window> = kludgine::app::Window<'window, WindowCommand>; pub type WindowAttributes = kludgine::app::WindowAttributes; @@ -32,7 +32,6 @@ where { context: Behavior::Context, pub attributes: WindowAttributes, - pub styles: Option, } impl Default for Window @@ -66,20 +65,18 @@ where ..WindowAttributes::default() }, context, - styles: None, } } +} - pub fn styles(mut self, styles: Styles) -> Self { - self.styles = Some(styles); - self - } - - pub fn run(self) -> Result<(), EventLoopError> { +impl Run for Window +where + Behavior: WindowBehavior, +{ + fn run(self) -> crate::Result<(), EventLoopError> { GooeyWindow::::run_with(AssertUnwindSafe(( self.context, RefCell::new(WindowSettings { - styles: self.styles, attributes: Some(self.attributes), }), ))) @@ -142,9 +139,7 @@ where ) -> Self { let mut behavior = T::initialize(&mut window, context.0 .0); let root = Tree::default().push_boxed(behavior.make_root(), None); - if let Some(styles) = context.0 .1.borrow_mut().styles.take() { - root.attach_styles(styles); - } + Self { behavior, root, @@ -425,7 +420,6 @@ fn recursively_handle_event( } pub struct WindowSettings { - styles: Option, attributes: Option, }