433 lines
11 KiB
Rust
433 lines
11 KiB
Rust
use std::rc::Rc;
|
|
|
|
use super::layout_message::LayoutTarget;
|
|
use crate::message_prelude::*;
|
|
|
|
use derivative::*;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub trait PropertyHolder {
|
|
fn properties(&self) -> WidgetLayout {
|
|
WidgetLayout::default()
|
|
}
|
|
|
|
fn register_properties(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
|
responses.push_back(
|
|
LayoutMessage::SendLayout {
|
|
layout: self.properties(),
|
|
layout_target,
|
|
}
|
|
.into(),
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct WidgetLayout {
|
|
pub layout: SubLayout,
|
|
}
|
|
|
|
impl WidgetLayout {
|
|
pub fn new(layout: SubLayout) -> Self {
|
|
Self { layout }
|
|
}
|
|
|
|
pub fn iter(&self) -> WidgetIter<'_> {
|
|
WidgetIter {
|
|
stack: self.layout.iter().collect(),
|
|
current_slice: None,
|
|
}
|
|
}
|
|
|
|
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
|
|
WidgetIterMut {
|
|
stack: self.layout.iter_mut().collect(),
|
|
current_slice: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type SubLayout = Vec<LayoutRow>;
|
|
|
|
// TODO: Rename LayoutRow to something more generic
|
|
#[remain::sorted]
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum LayoutRow {
|
|
Column {
|
|
#[serde(rename = "columnWidgets")]
|
|
widgets: Vec<WidgetHolder>,
|
|
},
|
|
Row {
|
|
#[serde(rename = "rowWidgets")]
|
|
widgets: Vec<WidgetHolder>,
|
|
},
|
|
Section {
|
|
name: String,
|
|
layout: SubLayout,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct WidgetIter<'a> {
|
|
pub stack: Vec<&'a LayoutRow>,
|
|
pub current_slice: Option<&'a [WidgetHolder]>,
|
|
}
|
|
|
|
impl<'a> Iterator for WidgetIter<'a> {
|
|
type Item = &'a WidgetHolder;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if let Some(item) = self.current_slice.and_then(|slice| slice.first()) {
|
|
self.current_slice = Some(&self.current_slice.unwrap()[1..]);
|
|
return Some(item);
|
|
}
|
|
|
|
match self.stack.pop() {
|
|
Some(LayoutRow::Column { widgets }) => {
|
|
self.current_slice = Some(widgets);
|
|
self.next()
|
|
}
|
|
Some(LayoutRow::Row { widgets }) => {
|
|
self.current_slice = Some(widgets);
|
|
self.next()
|
|
}
|
|
Some(LayoutRow::Section { name: _, layout }) => {
|
|
for layout_row in layout {
|
|
self.stack.push(layout_row);
|
|
}
|
|
self.next()
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct WidgetIterMut<'a> {
|
|
pub stack: Vec<&'a mut LayoutRow>,
|
|
pub current_slice: Option<&'a mut [WidgetHolder]>,
|
|
}
|
|
|
|
impl<'a> Iterator for WidgetIterMut<'a> {
|
|
type Item = &'a mut WidgetHolder;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) {
|
|
self.current_slice = Some(rest);
|
|
return Some(first);
|
|
};
|
|
|
|
match self.stack.pop() {
|
|
Some(LayoutRow::Column { widgets }) => {
|
|
self.current_slice = Some(widgets);
|
|
self.next()
|
|
}
|
|
Some(LayoutRow::Row { widgets }) => {
|
|
self.current_slice = Some(widgets);
|
|
self.next()
|
|
}
|
|
Some(LayoutRow::Section { name: _, layout }) => {
|
|
for layout_row in layout {
|
|
self.stack.push(layout_row);
|
|
}
|
|
self.next()
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct WidgetHolder {
|
|
pub widget_id: u64,
|
|
pub widget: Widget,
|
|
}
|
|
|
|
impl WidgetHolder {
|
|
pub fn new(widget: Widget) -> Self {
|
|
Self { widget_id: generate_uuid(), widget }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct WidgetCallback<T> {
|
|
pub callback: Rc<dyn Fn(&T) -> Message + 'static>,
|
|
}
|
|
|
|
impl<T> WidgetCallback<T> {
|
|
pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self {
|
|
Self { callback: Rc::new(callback) }
|
|
}
|
|
}
|
|
|
|
impl<T> Default for WidgetCallback<T> {
|
|
fn default() -> Self {
|
|
Self::new(|_| Message::NoOp)
|
|
}
|
|
}
|
|
|
|
#[remain::sorted]
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Widget {
|
|
CheckboxInput(CheckboxInput),
|
|
ColorInput(ColorInput),
|
|
DropdownInput(DropdownInput),
|
|
FontInput(FontInput),
|
|
IconButton(IconButton),
|
|
IconLabel(IconLabel),
|
|
NumberInput(NumberInput),
|
|
OptionalInput(OptionalInput),
|
|
PopoverButton(PopoverButton),
|
|
RadioInput(RadioInput),
|
|
Separator(Separator),
|
|
TextAreaInput(TextAreaInput),
|
|
TextButton(TextButton),
|
|
TextInput(TextInput),
|
|
TextLabel(TextLabel),
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct NumberInput {
|
|
pub value: Option<f64>,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<NumberInput>,
|
|
pub min: Option<f64>,
|
|
pub max: Option<f64>,
|
|
#[serde(rename = "isInteger")]
|
|
pub is_integer: bool,
|
|
#[serde(rename = "incrementBehavior")]
|
|
pub increment_behavior: NumberInputIncrementBehavior,
|
|
#[serde(rename = "incrementFactor")]
|
|
#[derivative(Default(value = "1."))]
|
|
pub increment_factor: f64,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub increment_callback_increase: WidgetCallback<NumberInput>,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub increment_callback_decrease: WidgetCallback<NumberInput>,
|
|
pub label: String,
|
|
pub unit: String,
|
|
#[serde(rename = "displayDecimalPlaces")]
|
|
#[derivative(Default(value = "3"))]
|
|
pub display_decimal_places: u32,
|
|
pub disabled: bool,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct TextInput {
|
|
pub value: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<TextInput>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct TextAreaInput {
|
|
pub value: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<TextAreaInput>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct ColorInput {
|
|
pub value: Option<String>,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<ColorInput>,
|
|
#[serde(rename = "canSetTransparent")]
|
|
#[derivative(Default(value = "true"))]
|
|
pub can_set_transparent: bool,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct FontInput {
|
|
#[serde(rename = "isStyle")]
|
|
pub is_style_picker: bool,
|
|
#[serde(rename = "fontFamily")]
|
|
pub font_family: String,
|
|
#[serde(rename = "fontStyle")]
|
|
pub font_style: String,
|
|
#[serde(rename = "fontFileUrl")]
|
|
pub font_file_url: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<FontInput>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
|
pub enum NumberInputIncrementBehavior {
|
|
Add,
|
|
Multiply,
|
|
Callback,
|
|
}
|
|
|
|
impl Default for NumberInputIncrementBehavior {
|
|
fn default() -> Self {
|
|
Self::Add
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct Separator {
|
|
pub direction: SeparatorDirection,
|
|
|
|
#[serde(rename = "type")]
|
|
pub separator_type: SeparatorType,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum SeparatorDirection {
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum SeparatorType {
|
|
Related,
|
|
Unrelated,
|
|
Section,
|
|
List,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct IconButton {
|
|
pub icon: String,
|
|
#[serde(rename = "title")]
|
|
pub tooltip: String,
|
|
pub size: u32,
|
|
pub active: bool,
|
|
#[serde(rename = "gapAfter")]
|
|
pub gap_after: bool,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<IconButton>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
|
pub struct TextButton {
|
|
pub label: String,
|
|
pub emphasized: bool,
|
|
pub min_width: u32,
|
|
pub gap_after: bool,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<TextButton>,
|
|
pub disabled: bool,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct OptionalInput {
|
|
pub checked: bool,
|
|
pub icon: String,
|
|
#[serde(rename = "title")]
|
|
pub tooltip: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<OptionalInput>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct CheckboxInput {
|
|
pub checked: bool,
|
|
pub icon: String,
|
|
#[serde(rename = "outlineStyle")]
|
|
pub outline_style: bool,
|
|
#[serde(rename = "title")]
|
|
pub tooltip: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<CheckboxInput>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct PopoverButton {
|
|
pub title: String,
|
|
pub text: String,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
|
#[derivative(Debug, PartialEq, Default)]
|
|
pub struct DropdownInput {
|
|
pub entries: Vec<Vec<DropdownEntryData>>,
|
|
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace with usize when we switch to a native UI)
|
|
#[serde(rename = "selectedIndex")]
|
|
pub selected_index: Option<u32>,
|
|
#[serde(rename = "drawIcon")]
|
|
pub draw_icon: bool,
|
|
#[derivative(Default(value = "true"))]
|
|
pub interactive: bool,
|
|
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
|
|
pub disabled: bool,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct DropdownEntryData {
|
|
pub value: String,
|
|
pub label: String,
|
|
pub icon: String,
|
|
pub checkbox: bool,
|
|
pub shortcut: Vec<String>,
|
|
#[serde(rename = "shortcutRequiresLock")]
|
|
pub shortcut_requires_lock: bool,
|
|
pub children: Vec<Vec<DropdownEntryData>>,
|
|
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<()>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct RadioInput {
|
|
pub entries: Vec<RadioEntryData>,
|
|
|
|
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number
|
|
// TODO(mfish33): Replace with usize when using native UI
|
|
#[serde(rename = "selectedIndex")]
|
|
pub selected_index: u32,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
|
#[derivative(Debug, PartialEq)]
|
|
pub struct RadioEntryData {
|
|
pub value: String,
|
|
pub label: String,
|
|
pub icon: String,
|
|
pub tooltip: String,
|
|
#[serde(skip)]
|
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
pub on_update: WidgetCallback<()>,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq)]
|
|
pub struct IconLabel {
|
|
pub icon: String,
|
|
#[serde(rename = "gapAfter")]
|
|
pub gap_after: bool,
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Default)]
|
|
pub struct TextLabel {
|
|
pub value: String,
|
|
pub bold: bool,
|
|
pub italic: bool,
|
|
pub multiline: bool,
|
|
#[serde(rename = "tableAlign")]
|
|
pub table_align: bool,
|
|
}
|