diff --git a/src/view/adhoc.rs b/src/view/adhoc.rs deleted file mode 100644 index fb18ac3..0000000 --- a/src/view/adhoc.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::{Builder, Response, Ui, View, ViewExt}; - -/// Adhoc views are views built out of other views -/// -/// You can create views out of other views, e.g. those created by [`ui.show()`](Ui::show) and [`ui.show_children()`](Ui::show_children) -/// -/// These can be 'shown' with [`ui.adhoc()`](Ui::adhoc). All views that do not -/// show children can be shown using this method. But an adhoc view can only be -/// shown with this method, rather than [`ui.show()`](Ui::show) -/// -/// ## Example -/// ```rust,no_run -/// // We can create a style for our adhoc view -/// #[derive(Copy, Clone, Debug)] -/// struct ButtonWithLabelStyle { -/// attribute: Option, -/// } -/// -/// impl ButtonWithLabelStyle { -/// fn default(_palette: &Palette) -> Self { -/// Self { attribute: None } -/// } -/// -/// fn italic(_palette: &Palette) -> Self { -/// Self { -/// attribute: Some(Attribute::ITALIC), -/// } -/// } -/// } -/// -/// type ButtonWithLabelClass = fn(&Palette) -> ButtonWithLabelStyle; -/// -/// // We can create a builder for our adhoc view -/// struct ButtonWithLabel<'a> { -/// label: &'a str, -/// button: &'a str, -/// class: StyleKind, -/// } -/// -/// impl<'a> ButtonWithLabel<'a> { -/// const fn class(mut self, class: ButtonWithLabelClass) -> Self { -/// self.class = StyleKind::Deferred(class); -/// self -/// } -/// -/// const fn style(mut self, style: ButtonWithLabelStyle) -> Self { -/// self.class = StyleKind::Direct(style); -/// self -/// } -/// } -/// -/// impl<'v> Adhoc<'v> for ButtonWithLabel<'v> { -/// type Output = Response; -/// -/// fn show(self, ui: &Ui) -> Self::Output { -/// let style = match self.class { -/// StyleKind::Deferred(style) => (style)(&ui.palette()), -/// StyleKind::Direct(style) => style, -/// }; -/// -/// let attr = style.attribute.unwrap_or(Attribute::RESET); -/// -/// // our adhoc view is just a button next to a label -/// ui.horizontal(|ui| { -/// let resp = ui.button(self.button); -/// ui.show(label(self.label).attribute(attr)); -/// resp -/// }) -/// .into_inner() -/// } -/// } -/// -/// // builder for our adhoc view -/// fn button_with_label<'a>(label: &'a str, button: &'a str) -> ButtonWithLabel<'a> { -/// ButtonWithLabel { -/// label, -/// button, -/// class: StyleKind::Deferred(ButtonWithLabelStyle::default), -/// } -/// } -/// -/// // and then we can show it: -/// ui.adhoc(button_with_label("hello", "world").class(ButtonWithLabelStyle::italic)); -/// ``` -pub trait Adhoc<'v>: Sized { - /// The output of this view - type Output: 'static; - /// Show the view, returning its output - fn show(self, ui: &Ui) -> Self::Output; -} - -impl<'v, T> Adhoc<'v> for T -where - T: Builder<'v>, -{ - type Output = Response<::Response>; - fn show(self, ui: &Ui) -> Self::Output { - ::show(self, ui) - } -} diff --git a/src/view/mod.rs b/src/view/mod.rs index 09c8e85..6a390bd 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -30,9 +30,6 @@ pub use style::{Elements, Palette, StyleKind}; mod internal_views; -mod adhoc; -pub use adhoc::Adhoc; - mod builder; pub use builder::{Builder, View, ViewExt}; diff --git a/src/view/ui.rs b/src/view/ui.rs index 583ccfa..566d887 100644 --- a/src/view/ui.rs +++ b/src/view/ui.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ filter::{Filter, Filterable}, input::InputState, - internal_views, Adhoc, Builder, LayoutNodes, Palette, Response, State, View, ViewId, ViewNodes, + internal_views, Builder, LayoutNodes, Palette, Response, State, View, ViewId, ViewNodes, }; impl<'a> Filterable for Ui<'a> { @@ -48,13 +48,6 @@ impl<'a> Ui<'a> { } impl<'a> Ui<'a> { - pub fn adhoc<'v, A>(&self, view: A) -> A::Output - where - A: Adhoc<'v>, - { - view.show(self) - } - pub fn show<'v, B>(&self, args: B) -> Response<::Response> where B: Builder<'v>, @@ -363,22 +356,22 @@ impl<'a> Ui<'a> { } pub fn checkbox(&self, value: &mut bool, label: impl Into) -> Response { - self.adhoc(views::checkbox(value, label)) + self.show(views::checkbox(value, label)) } pub fn todo_value(&self, value: &mut bool, label: impl Into) -> Response { - self.adhoc(views::todo_value(value, label)) + self.show(views::todo_value(value, label)) } pub fn selected(&self, value: &mut bool, label: impl Into) -> Response { - self.adhoc(views::selected(value, label)) + self.show(views::selected(value, label)) } pub fn radio(&self, value: V, existing: &mut V, label: impl Into) -> Response where - V: PartialEq, + V: PartialEq + 'static, { - self.adhoc(views::radio(value, existing, label)) + self.show(views::radio(value, existing, label)) } pub fn label(&self, data: impl Into) -> Response { diff --git a/src/views/checkbox.rs b/src/views/checkbox.rs index 6cf660b..53352da 100644 --- a/src/views/checkbox.rs +++ b/src/views/checkbox.rs @@ -1,6 +1,6 @@ use crate::{ renderer::Rgba, - view::{Adhoc, Palette, Response, StyleKind, Ui}, + view::{Builder, Palette, StyleKind, Ui, View}, Str, }; @@ -8,7 +8,7 @@ use super::label::LabelStyle; pub type CheckboxClass = fn(&Palette, bool) -> CheckboxStyle; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct CheckboxStyle { pub checked: &'static str, pub unchecked: &'static str, @@ -55,15 +55,32 @@ impl<'a> Checkbox<'a> { } } -impl<'a> Adhoc<'a> for Checkbox<'a> { - // TODO make this an opaque response - type Output = Response; +impl<'v> Builder<'v> for Checkbox<'v> { + type View = CheckboxView; +} + +#[derive(Debug)] +pub struct CheckboxView { + label: Str, + class: StyleKind, +} + +impl View for CheckboxView { + type Args<'v> = Checkbox<'v>; + type Response = bool; + + fn create(args: Self::Args<'_>) -> Self { + Self { + label: args.label, + class: args.class, + } + } - fn show(self, ui: &Ui) -> Self::Output { + fn update(&mut self, args: Self::Args<'_>, ui: &Ui) -> Self::Response { let resp = ui .mouse_area(|ui| { let style = match self.class { - StyleKind::Deferred(style) => (style)(&ui.palette(), *self.value), + StyleKind::Deferred(style) => (style)(&ui.palette(), *args.value), StyleKind::Direct(style) => style, }; @@ -74,19 +91,19 @@ impl<'a> Adhoc<'a> for Checkbox<'a> { }; ui.horizontal(|ui| { - let marker = if *self.value { + let marker = if *args.value { style.checked } else { style.unchecked }; ui.label(marker); - ui.show(super::label(self.label).style(LabelStyle { foreground })); + ui.show(super::label(&self.label).style(LabelStyle { foreground })); }); }) .flatten_left(); - *self.value ^= resp.clicked(); - resp.map(|c| c.clicked()) + *args.value ^= resp.clicked(); + resp.clicked() } } diff --git a/src/views/mod.rs b/src/views/mod.rs index 0fd5abd..d59fd2d 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -17,8 +17,8 @@ pub use button::{button, Button, ButtonClass, ButtonResponse, ButtonStyle}; mod checkbox; pub use checkbox::{checkbox, Checkbox, CheckboxClass, CheckboxStyle}; -mod collapsible; -pub use collapsible::{collapsible, Collapsible, CollapsibleClass, CollapsibleStyle}; +// mod collapsible; +// pub use collapsible::{collapsible, Collapsible, CollapsibleClass, CollapsibleStyle}; mod constrain; pub use constrain::{Constrain, Unconstrained}; diff --git a/src/views/radio.rs b/src/views/radio.rs index 75416d5..b40b8f0 100644 --- a/src/views/radio.rs +++ b/src/views/radio.rs @@ -1,6 +1,8 @@ +use std::marker::PhantomData; + use crate::{ renderer::Rgba, - view::{Adhoc, Palette, Response, StyleKind}, + view::{Builder, Palette, StyleKind, Ui, View}, Str, }; @@ -8,7 +10,7 @@ use super::label::{label, LabelStyle}; pub type RadioClass = fn(&Palette, bool) -> RadioStyle; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct RadioStyle { pub selected: Option<&'static str>, pub unselected: Option<&'static str>, @@ -51,24 +53,55 @@ pub struct Radio<'a, V> { class: StyleKind, } -impl<'v, V> Adhoc<'v> for Radio<'v, V> +impl<'v, V: PartialEq + 'static> Builder<'v> for Radio<'v, V> { + type View = RadioView; +} + +pub struct RadioView where - V: PartialEq, + V: PartialEq + 'static, { - type Output = Response; + label: Str, + class: StyleKind, + _marker: std::marker::PhantomData, +} + +impl std::fmt::Debug for RadioView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RadioView") + .field("label", &self.label) + .field("class", &self.class) + .finish() + } +} + +impl View for RadioView +where + V: PartialEq + 'static, +{ + type Args<'v> = Radio<'v, V>; + type Response = bool; + + fn create(args: Self::Args<'_>) -> Self { + Self { + label: args.label, + class: args.class, + _marker: PhantomData, + } + } - fn show(self, ui: &crate::view::Ui) -> Self::Output { + fn update(&mut self, args: Self::Args<'_>, ui: &Ui) -> Self::Response { let resp = ui .mouse_area(|ui| { let style = match self.class { StyleKind::Deferred(style) => { - (style)(&ui.palette(), self.value == *self.existing) + (style)(&ui.palette(), args.value == *args.existing) } StyleKind::Direct(style) => style, }; let hovered = ui.is_hovered(); - let fill = match (hovered, self.value == *self.existing) { + let fill = match (hovered, args.value == *args.existing) { (false, true) => style.selected_background, (false, false) => style.background, (true, true) => style @@ -84,15 +117,16 @@ where }; ui.background(fill, |ui| { - ui.show(label(self.label).style(LabelStyle { foreground })) + ui.show(label(&self.label).style(LabelStyle { foreground })) }); }) .flatten_left(); - if resp.clicked() { - *self.existing = self.value; + let clicked = resp.clicked(); + if clicked { + *args.existing = args.value; } - resp.map(|c| c.clicked()) + clicked } } diff --git a/src/views/selected.rs b/src/views/selected.rs index 3fe7dc4..f8bb442 100644 --- a/src/views/selected.rs +++ b/src/views/selected.rs @@ -1,12 +1,12 @@ use crate::{ renderer::Rgba, - view::{Adhoc, Palette, Response, StyleKind}, + view::{Builder, Palette, StyleKind, Ui, View}, Str, }; use super::label::{label, LabelStyle}; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct SelectedStyle { pub text_color: Rgba, @@ -59,19 +59,37 @@ impl<'a> Selected<'a> { } } -impl<'v> Adhoc<'v> for Selected<'v> { - type Output = Response; +impl<'v> Builder<'v> for Selected<'v> { + type View = SelectedView; +} + +#[derive(Debug)] +pub struct SelectedView { + label: Str, + class: StyleKind, +} + +impl View for SelectedView { + type Args<'v> = Selected<'v>; + type Response = bool; + + fn create(args: Self::Args<'_>) -> Self { + Self { + label: args.label, + class: args.class, + } + } - fn show(self, ui: &crate::view::Ui) -> Self::Output { + fn update(&mut self, args: Self::Args<'_>, ui: &Ui) -> Self::Response { let resp = ui .mouse_area(|ui| { let style = match self.class { - StyleKind::Deferred(style) => (style)(&ui.palette(), *self.value), + StyleKind::Deferred(style) => (style)(&ui.palette(), *args.value), StyleKind::Direct(style) => style, }; let hovered = ui.is_hovered(); - let fill = match (hovered, *self.value) { + let fill = match (hovered, *args.value) { (false, true) => style.selected_background, (false, false) => style.background, (true, true) => style @@ -87,13 +105,13 @@ impl<'v> Adhoc<'v> for Selected<'v> { }; ui.background(fill, |ui| { - ui.show(label(self.label).style(LabelStyle { foreground: text })) + ui.show(label(&self.label).style(LabelStyle { foreground: text })) }); }) .flatten_left(); - *self.value ^= resp.clicked(); - resp.map(|c| c.clicked()) + *args.value ^= resp.clicked(); + resp.clicked() } } diff --git a/src/views/todo_value.rs b/src/views/todo_value.rs index 8156d43..b3c7ee7 100644 --- a/src/views/todo_value.rs +++ b/src/views/todo_value.rs @@ -1,6 +1,6 @@ use crate::{ renderer::{Attribute, Rgba}, - view::{Adhoc, Palette, Response, StyleKind}, + view::{Builder, Palette, StyleKind, Ui, View}, Str, }; @@ -8,7 +8,7 @@ use super::label::LabelStyle; pub type TodoClass = fn(&Palette, bool) -> TodoStyle; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct TodoStyle { pub selected: Attribute, pub text_color: Rgba, @@ -44,14 +44,32 @@ impl<'a> TodoValue<'a> { } } -impl<'a> Adhoc<'a> for TodoValue<'a> { - type Output = Response; +impl<'v> Builder<'v> for TodoValue<'v> { + type View = TodoValueView; +} + +#[derive(Debug)] +pub struct TodoValueView { + label: Str, + class: StyleKind, +} + +impl View for TodoValueView { + type Args<'v> = TodoValue<'v>; + type Response = bool; + + fn create(args: Self::Args<'_>) -> Self { + Self { + label: args.label, + class: args.class, + } + } - fn show(self, ui: &crate::view::Ui) -> Self::Output { + fn update(&mut self, args: Self::Args<'_>, ui: &Ui) -> Self::Response { let resp = ui .mouse_area(|ui| { let style = match self.class { - StyleKind::Deferred(style) => (style)(&ui.palette(), *self.value), + StyleKind::Deferred(style) => (style)(&ui.palette(), *args.value), StyleKind::Direct(style) => style, }; @@ -61,7 +79,7 @@ impl<'a> Adhoc<'a> for TodoValue<'a> { style.text_color }; - let attr = if *self.value { + let attr = if *args.value { style.selected } else { Attribute::RESET @@ -69,7 +87,7 @@ impl<'a> Adhoc<'a> for TodoValue<'a> { ui.horizontal(|ui| { ui.show( - super::label(self.label) + super::label(&self.label) .style(LabelStyle { foreground }) .attribute(attr), ); @@ -77,8 +95,8 @@ impl<'a> Adhoc<'a> for TodoValue<'a> { }) .flatten_left(); - *self.value ^= resp.clicked(); - resp.map(|c| c.clicked()) + *args.value ^= resp.clicked(); + resp.clicked() } }