diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /toolkit/crashreporter/client/app/src/ui/model | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
10 files changed, 579 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/app/src/ui/model/button.rs b/toolkit/crashreporter/client/app/src/ui/model/button.rs new file mode 100644 index 0000000000..d522fad6fc --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/button.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::{Element, ElementBuilder}; +use crate::data::Event; + +/// A clickable button. +#[derive(Default, Debug)] +pub struct Button { + pub content: Option<Box<Element>>, + pub click: Event<()>, +} + +impl ElementBuilder<Button> { + pub fn on_click<F>(&mut self, f: F) + where + F: Fn() + 'static, + { + self.element_type.click.subscribe(move |_| f()); + } + + pub fn add_child(&mut self, child: Element) { + Self::single_child(&mut self.element_type.content, child); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/checkbox.rs b/toolkit/crashreporter/client/app/src/ui/model/checkbox.rs new file mode 100644 index 0000000000..8923e33558 --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/checkbox.rs @@ -0,0 +1,22 @@ +/* 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/. */ + +use crate::data::Property; + +/// A checkbox (with optional label). +#[derive(Default, Debug)] +pub struct Checkbox { + pub checked: Property<bool>, + pub label: Option<String>, +} + +impl super::ElementBuilder<Checkbox> { + pub fn checked(&mut self, value: impl Into<Property<bool>>) { + self.element_type.checked = value.into(); + } + + pub fn label<S: Into<String>>(&mut self, label: S) { + self.element_type.label = Some(label.into()); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/hbox.rs b/toolkit/crashreporter/client/app/src/ui/model/hbox.rs new file mode 100644 index 0000000000..b6c0e27e8c --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/hbox.rs @@ -0,0 +1,34 @@ +/* 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/. */ + +use super::{Element, ElementBuilder}; + +/// A box which lays out contents horizontally. +#[derive(Default, Debug)] +pub struct HBox { + pub items: Vec<Element>, + pub spacing: u32, + pub affirmative_order: bool, +} + +impl ElementBuilder<HBox> { + pub fn spacing(&mut self, value: u32) { + self.element_type.spacing = value; + } + + /// Whether children are in affirmative order (and should be reordered based on platform + /// conventions). + /// + /// The children passed to `add_child` should be in most-affirmative to least-affirmative order + /// (e.g., "OK" then "Cancel" buttons). + /// + /// This is mainly useful for dialog buttons. + pub fn affirmative_order(&mut self, value: bool) { + self.element_type.affirmative_order = value; + } + + pub fn add_child(&mut self, child: Element) { + self.element_type.items.push(child); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/label.rs b/toolkit/crashreporter/client/app/src/ui/model/label.rs new file mode 100644 index 0000000000..096ce022e3 --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/label.rs @@ -0,0 +1,22 @@ +/* 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/. */ + +use crate::data::Property; + +/// A text label. +#[derive(Debug, Default)] +pub struct Label { + pub text: Property<String>, + pub bold: bool, +} + +impl super::ElementBuilder<Label> { + pub fn text(&mut self, s: impl Into<Property<String>>) { + self.element_type.text = s.into(); + } + + pub fn bold(&mut self, value: bool) { + self.element_type.bold = value; + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/mod.rs b/toolkit/crashreporter/client/app/src/ui/model/mod.rs new file mode 100644 index 0000000000..5ea2ddc59a --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/mod.rs @@ -0,0 +1,344 @@ +/* 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<TYPE>` 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<Element>` for `ElementType`, and `TryFrom<ElementType>` +//! 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<ElementBuilder<...>>` 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<ElementBuilder>` 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<ElementType> for $name { + type Error = &'static str; + + fn try_from(et: ElementType) -> Result<Self, Self::Error> { + 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<Self, Self::Error> { + if let ElementType::$name(v) = et { + Ok(v) + } else { + Err(concat!("ElementType was not ", stringify!($name))) + } + } + } + + impl From<ElementBuilder<$name>> 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<u32>, + pub vertical_size_request: Option<u32>, + pub margin: Margin, + pub visible: Property<bool>, + pub enabled: Property<bool>, + #[cfg(test)] + pub id: Option<String>, +} + +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<TYPE>` to add methods to their builder. +#[derive(Debug, Default)] +pub struct ElementBuilder<T> { + pub style: ElementStyle, + pub element_type: T, +} + +impl<T> ElementBuilder<T> { + /// 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<Property<bool>>) { + 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<Property<bool>>) { + self.style.enabled = value.into(); + } + + /// Set the element identifier. + #[cfg(test)] + pub fn id(&mut self, value: impl Into<String>) { + self.style.id = Some(value.into()); + } + + /// Set the element identifier (stub). + #[cfg(not(test))] + pub fn id(&mut self, _value: impl Into<String>) {} + + fn single_child(slot: &mut Option<Box<Element>>, child: Element) { + if slot.replace(Box::new(child)).is_some() { + panic!("{} can only have one child", std::any::type_name::<T>()); + } + } +} + +/// 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<T>`](ElementBuilder) and ends with a `.into()` (and this implements +/// `From<ElementBuilder<T>>`). +#[derive(Debug, Default)] +pub struct TypedElement<T> { + pub style: ElementStyle, + pub element_type: T, +} + +impl<T> From<ElementBuilder<T>> for TypedElement<T> { + fn from(b: ElementBuilder<T>) -> 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<ElementTypeName>`. 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<TypedElement<Window>>, + /// 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<dyn FnOnce() + Send + 'static>; diff --git a/toolkit/crashreporter/client/app/src/ui/model/progress.rs b/toolkit/crashreporter/client/app/src/ui/model/progress.rs new file mode 100644 index 0000000000..f3e4e4bf77 --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/progress.rs @@ -0,0 +1,19 @@ +/* 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/. */ + +use crate::data::Property; + +/// A progress indicator. +#[derive(Debug, Default)] +pub struct Progress { + /// Progress between 0 and 1, or None if indeterminate. + pub amount: Property<Option<f32>>, +} + +impl super::ElementBuilder<Progress> { + #[allow(dead_code)] + pub fn amount(&mut self, value: impl Into<Property<Option<f32>>>) { + self.element_type.amount = value.into(); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/scroll.rs b/toolkit/crashreporter/client/app/src/ui/model/scroll.rs new file mode 100644 index 0000000000..47efa4a81e --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/scroll.rs @@ -0,0 +1,17 @@ +/* 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/. */ + +use super::{Element, ElementBuilder}; + +/// A scrollable region. +#[derive(Debug, Default)] +pub struct Scroll { + pub content: Option<Box<Element>>, +} + +impl ElementBuilder<Scroll> { + pub fn add_child(&mut self, child: Element) { + Self::single_child(&mut self.element_type.content, child); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/textbox.rs b/toolkit/crashreporter/client/app/src/ui/model/textbox.rs new file mode 100644 index 0000000000..08cd9ca1bc --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/textbox.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +use crate::data::Property; + +/// A text box. +#[derive(Debug, Default)] +pub struct TextBox { + pub placeholder: Option<String>, + pub content: Property<String>, + pub editable: bool, +} + +impl super::ElementBuilder<TextBox> { + pub fn placeholder(&mut self, text: impl Into<String>) { + self.element_type.placeholder = Some(text.into()); + } + + pub fn content(&mut self, value: impl Into<Property<String>>) { + self.element_type.content = value.into(); + } + + pub fn editable(&mut self, value: bool) { + self.element_type.editable = value; + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/vbox.rs b/toolkit/crashreporter/client/app/src/ui/model/vbox.rs new file mode 100644 index 0000000000..6f1b09b1e2 --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/vbox.rs @@ -0,0 +1,22 @@ +/* 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/. */ + +use super::{Element, ElementBuilder}; + +/// A box which lays out contents vertically. +#[derive(Debug, Default)] +pub struct VBox { + pub items: Vec<Element>, + pub spacing: u32, +} + +impl ElementBuilder<VBox> { + pub fn spacing(&mut self, value: u32) { + self.element_type.spacing = value; + } + + pub fn add_child(&mut self, child: Element) { + self.element_type.items.push(child); + } +} diff --git a/toolkit/crashreporter/client/app/src/ui/model/window.rs b/toolkit/crashreporter/client/app/src/ui/model/window.rs new file mode 100644 index 0000000000..b56071ca19 --- /dev/null +++ b/toolkit/crashreporter/client/app/src/ui/model/window.rs @@ -0,0 +1,46 @@ +/* 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/. */ + +use super::{Element, ElementBuilder, TypedElement}; +use crate::data::Event; + +/// A window. +#[derive(Debug, Default)] +pub struct Window { + pub title: String, + /// The window content is the first element. + pub content: Option<Box<Element>>, + /// Logical child windows. + pub children: Vec<TypedElement<Self>>, + pub modal: bool, + pub close: Option<Event<()>>, +} + +impl ElementBuilder<Window> { + /// Set the window title. + pub fn title(&mut self, s: impl Into<String>) { + self.element_type.title = s.into(); + } + + /// Set whether the window is modal (blocking interaction with other windows when displayed). + pub fn modal(&mut self, value: bool) { + self.element_type.modal = value; + } + + /// Register an event to close the window. + pub fn close_when(&mut self, event: &Event<()>) { + self.element_type.close = Some(event.clone()); + } + + /// Add a window as a logical child of this one. + /// + /// Logical children are always displayed above their parents. + pub fn child_window(&mut self, window: TypedElement<Window>) { + self.element_type.children.push(window); + } + + pub fn add_child(&mut self, child: Element) { + Self::single_child(&mut self.element_type.content, child); + } +} |