/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! The UI model. //! //! Model elements should generally be declared as types with all fields `pub` (to be accessed by //! UI implementations), though accessor methods are acceptable if needed. An //! `ElementBuilder` impl should be provided to create methods that will be used in the //! [`ui!`] macro. The model types are accessible when being _consumed_ by a UI implementation, //! whereas the `ElementBuilder` types are accessible when the model is being _created_. //! //! All elements should be listed in the `element_types!` macro in this file (note that [`Window`], //! while an element, isn't listed here as it cannot be a child element). This populates the //! `ElementType` enum and generates `From` for `ElementType`, and `TryFrom` //! for the element (as well as reference `TryFrom`). //! //! The model is written to accommodate layout and text direction differences (e.g. for RTL //! languages), and UI implementations are expected to account for this correctly. use crate::data::Property; pub use button::Button; pub use checkbox::Checkbox; pub use hbox::HBox; pub use label::Label; pub use progress::Progress; pub use scroll::Scroll; pub use textbox::TextBox; pub use vbox::VBox; pub use window::Window; mod button; mod checkbox; mod hbox; mod label; mod progress; mod scroll; mod textbox; mod vbox; mod window; /// A GUI element, including general style attributes and a more specific type. /// /// `From>` is implemented for all elements listed in `element_types!`. #[derive(Debug)] pub struct Element { pub style: ElementStyle, pub element_type: ElementType, } // This macro creates the `ElementType` enum and corresponding `From` impls for // Element. The `ElementType` discriminants match the element type names. macro_rules! element_types { ( $($name:ident),* ) => { /// A type of GUI element. #[derive(Debug)] pub enum ElementType { $($name($name)),* } $( impl From<$name> for ElementType { fn from(e: $name) -> ElementType { ElementType::$name(e) } } impl TryFrom for $name { type Error = &'static str; fn try_from(et: ElementType) -> Result { if let ElementType::$name(v) = et { Ok(v) } else { Err(concat!("ElementType was not ", stringify!($name))) } } } impl<'a> TryFrom<&'a ElementType> for &'a $name { type Error = &'static str; fn try_from(et: &'a ElementType) -> Result { if let ElementType::$name(v) = et { Ok(v) } else { Err(concat!("ElementType was not ", stringify!($name))) } } } impl From> for Element { fn from(b: ElementBuilder<$name>) -> Self { Element { style: b.style, element_type: b.element_type.into(), } } } )* } } element_types! { Button, Checkbox, HBox, Label, Progress, Scroll, TextBox, VBox } /// Common element style values. #[derive(Debug)] pub struct ElementStyle { pub horizontal_alignment: Alignment, pub vertical_alignment: Alignment, pub horizontal_size_request: Option, pub vertical_size_request: Option, pub margin: Margin, pub visible: Property, pub enabled: Property, #[cfg(test)] pub id: Option, } impl Default for ElementStyle { fn default() -> Self { ElementStyle { horizontal_alignment: Default::default(), vertical_alignment: Default::default(), horizontal_size_request: Default::default(), vertical_size_request: Default::default(), margin: Default::default(), visible: true.into(), enabled: true.into(), #[cfg(test)] id: Default::default(), } } } /// A builder for `Element`s. /// /// Each element should add an `impl ElementBuilder` to add methods to their builder. #[derive(Debug, Default)] pub struct ElementBuilder { pub style: ElementStyle, pub element_type: T, } impl ElementBuilder { /// Set horizontal alignment. pub fn halign(&mut self, alignment: Alignment) { self.style.horizontal_alignment = alignment; } /// Set vertical alignment. pub fn valign(&mut self, alignment: Alignment) { self.style.vertical_alignment = alignment; } /// Set the horizontal size request. pub fn hsize(&mut self, value: u32) { assert!(value <= i32::MAX as u32); self.style.horizontal_size_request = Some(value); } /// Set the vertical size request. pub fn vsize(&mut self, value: u32) { assert!(value <= i32::MAX as u32); self.style.vertical_size_request = Some(value); } /// Set start margin. pub fn margin_start(&mut self, amount: u32) { self.style.margin.start = amount; } /// Set end margin. pub fn margin_end(&mut self, amount: u32) { self.style.margin.end = amount; } /// Set start and end margins. pub fn margin_horizontal(&mut self, amount: u32) { self.margin_start(amount); self.margin_end(amount) } /// Set top margin. pub fn margin_top(&mut self, amount: u32) { self.style.margin.top = amount; } /// Set bottom margin. pub fn margin_bottom(&mut self, amount: u32) { self.style.margin.bottom = amount; } /// Set top and bottom margins. pub fn margin_vertical(&mut self, amount: u32) { self.margin_top(amount); self.margin_bottom(amount) } /// Set all margins. pub fn margin(&mut self, amount: u32) { self.margin_horizontal(amount); self.margin_vertical(amount) } /// Set visibility. pub fn visible(&mut self, value: impl Into>) { self.style.visible = value.into(); } /// Set whether an element is enabled. /// /// This generally should enable/disable interaction with an element. pub fn enabled(&mut self, value: impl Into>) { self.style.enabled = value.into(); } /// Set the element identifier. #[cfg(test)] pub fn id(&mut self, value: impl Into) { self.style.id = Some(value.into()); } /// Set the element identifier (stub). #[cfg(not(test))] pub fn id(&mut self, _value: impl Into) {} fn single_child(slot: &mut Option>, child: Element) { if slot.replace(Box::new(child)).is_some() { panic!("{} can only have one child", std::any::type_name::()); } } } /// A typed [`Element`]. /// /// This is useful for the [`ui!`] macro when a method should accept a specific element type, since /// the macro always creates [`ElementBuilder`](ElementBuilder) and ends with a `.into()` (and this implements /// `From>`). #[derive(Debug, Default)] pub struct TypedElement { pub style: ElementStyle, pub element_type: T, } impl From> for TypedElement { fn from(b: ElementBuilder) -> Self { TypedElement { style: b.style, element_type: b.element_type, } } } /// The alignment of an element in one direction. /// /// Note that rather than `Left`/`Right`, this class has `Start`/`End` as it is meant to be /// layout-direction-aware. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] pub enum Alignment { /// Align to the start of the direction. #[default] Start, /// Align to the center of the direction. Center, /// Align to the end of the direction. End, /// Fill all available space. Fill, } /// The margins of an element. /// /// These are RTL-aware: for instance, `start` is the left margin in left-to-right languages and /// the right margin in right-to-left languages. #[derive(Default, Debug)] pub struct Margin { pub start: u32, pub end: u32, pub top: u32, pub bottom: u32, } /// A macro to allow a convenient syntax for creating elements. /// /// The macro expects the following syntax: /// ``` /// ElementTypeName some_method(arg1, arg2) other_method() { /// Child ..., /// Child2 ... /// } /// ``` /// /// The type is wrapped in an `ElementBuilder`, and methods are called on this builder with a /// mutable reference. This means that element types must implement Default and must implement /// builder methods on `ElementBuilder`. The children block is optional, and calls /// `add_child(child: Element)` for each provided child (so implement this method if desired). /// /// For testing, a string identifier can be set on any element with a `["my_identifier"]` following /// the element type. macro_rules! ui { ( $el:ident $([ $id:literal ])? $( $method:ident $methodargs:tt )* $({ $($contents:tt)* })? ) => { { #[allow(unused_imports)] use $crate::ui::model::*; let mut el: ElementBuilder<$el> = Default::default(); $( el.id($id); )? $( el.$method $methodargs ; )* $( ui! { @children (el) $($contents)* } )? el.into() } }; ( @children ($parent:expr) ) => {}; ( @children ($parent:expr) $el:ident $([ $id:literal ])? $( $method:ident $methodargs:tt )* $({ $($contents:tt)* })? $(, $($rest:tt)* )? ) => { $parent.add_child(ui!( $el $([$id])? $( $method $methodargs )* $({ $($contents)* })? )); $(ui!( @children ($parent) $($rest)* ))? }; } pub(crate) use ui; /// An application, defined as a set of windows. /// /// When all windows are closed, the application is considered complete (and loops should exit). pub struct Application { pub windows: Vec>, /// Whether the text direction should be right-to-left. pub rtl: bool, } /// A function to be invoked in the UI loop. pub type InvokeFn = Box;