mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 11:28:30 +08:00
Refactor the old menu bar plumbing to use standard TextButtons (#3444)
* Refactor the old menu bar plumbing to use standard TextButtons * WIP: Fix Mac native menu bar * WIP: fix desktop menu bar mac * Refactor menu bar definitions to use the builder pattern * WIP: fixup desktop * cleanup * fix linux * Remove dead code that was failing to lint --------- Co-authored-by: Timon Schelling <me@timon.zip>
This commit is contained in:
@@ -279,7 +279,7 @@ pub enum FrontendMessage {
|
||||
UpdateMenuBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: Vec<MenuBarEntry>,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateMouseCursor {
|
||||
cursor: MouseCursorIcon,
|
||||
|
||||
@@ -25,7 +25,6 @@ impl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHa
|
||||
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
|
||||
// Find the updated diff based on the specified layout target
|
||||
let Some(diff) = (match &self.layouts[layout_target as usize] {
|
||||
Layout::MenuLayout(_) => return,
|
||||
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| {
|
||||
// Create a widget update diff for the relevant id
|
||||
let new_value = DiffUpdate::Widget(widget.clone());
|
||||
@@ -112,7 +111,10 @@ impl LayoutMessageHandler {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(widget_holder) = layout.iter_mut().find(|widget| widget.widget_id == widget_id) else {
|
||||
let mut layout_iter = match layout {
|
||||
Layout::WidgetLayout(widget_layout) => widget_layout.iter_mut(),
|
||||
};
|
||||
let Some(widget_holder) = layout_iter.find(|widget| widget.widget_id == widget_id) else {
|
||||
warn!("handle_widget_callback was called referencing an invalid widget ID, although the layout target was valid. `widget_id: {widget_id}`, `layout_target: {layout_target:?}`",);
|
||||
return;
|
||||
};
|
||||
@@ -309,14 +311,6 @@ impl LayoutMessageHandler {
|
||||
}
|
||||
Widget::ImageLabel(_) => {}
|
||||
Widget::IconLabel(_) => {}
|
||||
Widget::InvisibleStandinInput(invisible) => {
|
||||
let callback_message = match action {
|
||||
WidgetValueAction::Commit => (invisible.on_commit.callback)(&()),
|
||||
WidgetValueAction::Update => (invisible.on_update.callback)(&()),
|
||||
};
|
||||
|
||||
responses.add(callback_message);
|
||||
}
|
||||
Widget::NodeCatalog(node_type_input) => match action {
|
||||
WidgetValueAction::Commit => {
|
||||
let callback_message = (node_type_input.on_commit.callback)(&());
|
||||
@@ -411,7 +405,34 @@ impl LayoutMessageHandler {
|
||||
Widget::TextButton(text_button) => {
|
||||
let callback_message = match action {
|
||||
WidgetValueAction::Commit => (text_button.on_commit.callback)(&()),
|
||||
WidgetValueAction::Update => (text_button.on_update.callback)(text_button),
|
||||
WidgetValueAction::Update => {
|
||||
let Some(value_path) = value.as_array() else {
|
||||
error!("TextButton update was not of type: array");
|
||||
return;
|
||||
};
|
||||
|
||||
// Process the text button click, since no menu is involved if we're given an empty array.
|
||||
if value_path.is_empty() {
|
||||
(text_button.on_update.callback)(text_button)
|
||||
}
|
||||
// Process the text button's menu list entry click, since we have a path to the value of the contained menu entry.
|
||||
else {
|
||||
let mut current_submenu = &text_button.menu_list_children;
|
||||
let mut final_entry: Option<&MenuListEntry> = None;
|
||||
|
||||
// Loop through all menu entry value strings in the path until we reach the final entry (which we store).
|
||||
// Otherwise we exit early if we can't traverse the full path.
|
||||
for value in value_path.iter().filter_map(|v| v.as_str().map(|s| s.to_string())) {
|
||||
let Some(next_entry) = current_submenu.iter().flatten().find(|e| e.value == value) else { return };
|
||||
|
||||
current_submenu = &next_entry.children;
|
||||
final_entry = Some(next_entry);
|
||||
}
|
||||
|
||||
// If we've reached here without returning early, we have a final entry in the path and we should now execute its callback.
|
||||
(final_entry.unwrap().on_commit.callback)(&())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
responses.add(callback_message);
|
||||
@@ -447,31 +468,26 @@ impl LayoutMessageHandler {
|
||||
match new_layout {
|
||||
Layout::WidgetLayout(_) => {
|
||||
let mut widget_diffs = Vec::new();
|
||||
self.layouts[layout_target as usize].diff(new_layout, &mut Vec::new(), &mut widget_diffs);
|
||||
|
||||
// Skip sending if no diff.
|
||||
let Layout::WidgetLayout(current) = &mut self.layouts[layout_target as usize];
|
||||
let Layout::WidgetLayout(new) = new_layout;
|
||||
current.diff(new, &mut Vec::new(), &mut widget_diffs);
|
||||
|
||||
// Skip sending if no diff
|
||||
if widget_diffs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_diff(widget_diffs, layout_target, responses, action_input_mapping);
|
||||
}
|
||||
// We don't diff the menu bar layout yet.
|
||||
Layout::MenuLayout(_) => {
|
||||
// Skip update if the same
|
||||
if self.layouts[layout_target as usize] == new_layout {
|
||||
return;
|
||||
// On Mac we need the full MenuBar layout to construct the native menu
|
||||
#[cfg(target_os = "macos")]
|
||||
if layout_target == LayoutTarget::MenuBar {
|
||||
widget_diffs = vec![WidgetDiff {
|
||||
widget_path: Vec::new(),
|
||||
new_value: DiffUpdate::SubLayout(current.layout.clone()),
|
||||
}];
|
||||
}
|
||||
|
||||
// Update the backend storage
|
||||
self.layouts[layout_target as usize] = new_layout;
|
||||
|
||||
// Update the UI
|
||||
let Some(layout) = self.layouts[layout_target as usize].clone().as_menu_layout(action_input_mapping).map(|x| x.layout) else {
|
||||
error!("Called unwrap_menu_layout on a widget layout");
|
||||
return;
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateMenuBarLayout { layout_target, layout });
|
||||
self.send_diff(widget_diffs, layout_target, responses, action_input_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -481,24 +497,25 @@ impl LayoutMessageHandler {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
let message = match layout_target {
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::DataPanel => FrontendMessage::UpdateDataPanelLayout { layout_target, diff },
|
||||
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
||||
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
||||
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||
LayoutTarget::DataPanel => FrontendMessage::UpdateDataPanelLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff },
|
||||
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { layout_target, diff },
|
||||
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff },
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
||||
|
||||
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
|
||||
};
|
||||
|
||||
responses.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use super::widgets::button_widgets::*;
|
||||
use super::widgets::input_widgets::*;
|
||||
use super::widgets::label_widgets::*;
|
||||
use super::widgets::menu_widgets::MenuLayout;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
||||
@@ -101,56 +100,11 @@ pub trait DialogLayoutHolder: LayoutHolder {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Unwrap this enum
|
||||
/// Wraps a choice of layout type. The chosen layout contains an arrangement of widgets mounted somewhere specific in the frontend.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum Layout {
|
||||
WidgetLayout(WidgetLayout),
|
||||
MenuLayout(MenuLayout),
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn as_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> Option<MenuLayout> {
|
||||
if let Self::MenuLayout(mut menu) = self {
|
||||
menu.layout
|
||||
.iter_mut()
|
||||
.for_each(|menu_column| menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping));
|
||||
Some(menu)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Box<dyn Iterator<Item = &WidgetHolder> + '_> {
|
||||
match self {
|
||||
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()),
|
||||
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> Box<dyn Iterator<Item = &mut WidgetHolder> + '_> {
|
||||
match self {
|
||||
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()),
|
||||
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Diffing updates self (where self is old) based on new, updating the list of modifications as it does so.
|
||||
pub fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
||||
match (self, new) {
|
||||
// Simply diff the internal layout
|
||||
(Self::WidgetLayout(current), Self::WidgetLayout(new)) => current.diff(new, widget_path, widget_diffs),
|
||||
(current, Self::WidgetLayout(widget_layout)) => {
|
||||
// Update current to the new value
|
||||
*current = Self::WidgetLayout(widget_layout.clone());
|
||||
|
||||
// Push an update sublayout value
|
||||
let new_value = DiffUpdate::SubLayout(widget_layout.layout);
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
}
|
||||
(_, Self::MenuLayout(_)) => panic!("Cannot diff menu layout"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
@@ -159,6 +113,7 @@ impl Default for Layout {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Unwrap this struct
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)]
|
||||
pub struct WidgetLayout {
|
||||
pub layout: SubLayout,
|
||||
@@ -327,7 +282,6 @@ pub enum LayoutGroup {
|
||||
#[serde(rename = "tableWidgets")]
|
||||
rows: Vec<Vec<WidgetHolder>>,
|
||||
},
|
||||
// TODO: Move this from being a child of `enum LayoutGroup` to being a child of `enum Layout`
|
||||
#[serde(rename = "section")]
|
||||
Section {
|
||||
name: String,
|
||||
@@ -378,7 +332,7 @@ impl LayoutGroup {
|
||||
Widget::TextInput(x) => &mut x.tooltip_label,
|
||||
Widget::TextLabel(x) => &mut x.tooltip_label,
|
||||
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_label,
|
||||
Widget::InvisibleStandinInput(_) | Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
|
||||
Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
|
||||
};
|
||||
if val.is_empty() {
|
||||
val.clone_from(&label);
|
||||
@@ -414,7 +368,7 @@ impl LayoutGroup {
|
||||
Widget::TextInput(x) => &mut x.tooltip_description,
|
||||
Widget::TextLabel(x) => &mut x.tooltip_description,
|
||||
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_description,
|
||||
Widget::InvisibleStandinInput(_) | Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
|
||||
Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
|
||||
};
|
||||
if val.is_empty() {
|
||||
val.clone_from(&description);
|
||||
@@ -520,6 +474,7 @@ impl LayoutGroup {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename to WidgetInstance
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WidgetHolder {
|
||||
#[serde(rename = "widgetId")]
|
||||
@@ -609,7 +564,6 @@ pub enum Widget {
|
||||
IconLabel(IconLabel),
|
||||
ImageButton(ImageButton),
|
||||
ImageLabel(ImageLabel),
|
||||
InvisibleStandinInput(InvisibleStandinInput),
|
||||
NodeCatalog(NodeCatalog),
|
||||
NumberInput(NumberInput),
|
||||
ParameterExposeButton(ParameterExposeButton),
|
||||
@@ -680,7 +634,6 @@ impl DiffUpdate {
|
||||
Widget::IconLabel(_)
|
||||
| Widget::ImageLabel(_)
|
||||
| Widget::CurveInput(_)
|
||||
| Widget::InvisibleStandinInput(_)
|
||||
| Widget::NodeCatalog(_)
|
||||
| Widget::ReferencePointInput(_)
|
||||
| Widget::RadioInput(_)
|
||||
@@ -709,10 +662,45 @@ impl DiffUpdate {
|
||||
}
|
||||
};
|
||||
|
||||
// Recursively fill menu list entries with their realized shortcut keys specific to the current bindings and platform
|
||||
let apply_action_keys_to_menu_lists = |entry_sections: &mut MenuListEntrySections| {
|
||||
struct RecursiveWrapper<'a>(&'a dyn Fn(&mut MenuListEntrySections, &RecursiveWrapper));
|
||||
let recursive_wrapper = RecursiveWrapper(&|entry_sections: &mut MenuListEntrySections, recursive_wrapper| {
|
||||
for entries in entry_sections {
|
||||
for entry in entries {
|
||||
// Convert the shortcut actions to keys for this menu entry
|
||||
if let Some(shortcut_keys) = &mut entry.shortcut_keys {
|
||||
shortcut_keys.to_keys(action_input_mapping);
|
||||
}
|
||||
|
||||
// Recursively call this inner closure on the menu's children
|
||||
(recursive_wrapper.0)(&mut entry.children, recursive_wrapper);
|
||||
}
|
||||
}
|
||||
});
|
||||
(recursive_wrapper.0)(entry_sections, &recursive_wrapper)
|
||||
};
|
||||
|
||||
// Apply shortcut conversions to all widgets that have menu lists
|
||||
let convert_menu_lists = |widget_holder: &mut WidgetHolder| match &mut widget_holder.widget {
|
||||
Widget::DropdownInput(dropdown_input) => apply_action_keys_to_menu_lists(&mut dropdown_input.entries),
|
||||
Widget::TextButton(text_button) => apply_action_keys_to_menu_lists(&mut text_button.menu_list_children),
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|layout_group| layout_group.iter_mut()).for_each(convert_tooltip),
|
||||
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(convert_tooltip),
|
||||
Self::Widget(widget_holder) => convert_tooltip(widget_holder),
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|layout_group| layout_group.iter_mut()).for_each(|widget_holder| {
|
||||
convert_tooltip(widget_holder);
|
||||
convert_menu_lists(widget_holder);
|
||||
}),
|
||||
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(|widget_holder| {
|
||||
convert_tooltip(widget_holder);
|
||||
convert_menu_lists(widget_holder);
|
||||
}),
|
||||
Self::Widget(widget_holder) => {
|
||||
convert_tooltip(widget_holder);
|
||||
convert_menu_lists(widget_holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,4 @@ pub mod widget_prelude {
|
||||
pub use super::widgets::button_widgets::*;
|
||||
pub use super::widgets::input_widgets::*;
|
||||
pub use super::widgets::label_widgets::*;
|
||||
pub use super::widgets::menu_widgets::*;
|
||||
}
|
||||
|
||||
@@ -133,15 +133,28 @@ pub struct MenuListEntry {
|
||||
|
||||
pub label: String,
|
||||
|
||||
pub font: String,
|
||||
|
||||
pub icon: String,
|
||||
|
||||
pub shortcut: Vec<String>,
|
||||
pub disabled: bool,
|
||||
|
||||
#[serde(rename = "tooltipLabel")]
|
||||
pub tooltip_label: String,
|
||||
|
||||
#[serde(rename = "tooltipDescription")]
|
||||
pub tooltip_description: String,
|
||||
|
||||
#[serde(rename = "tooltipShortcut")]
|
||||
pub tooltip_shortcut: String,
|
||||
|
||||
// TODO: Make this serde(skip)
|
||||
#[serde(rename = "shortcutKeys")]
|
||||
pub shortcut_keys: Option<ActionKeys>,
|
||||
|
||||
#[serde(rename = "shortcutRequiresLock")]
|
||||
pub shortcut_requires_lock: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
pub children: MenuListEntrySections,
|
||||
|
||||
// Callbacks
|
||||
@@ -192,21 +205,6 @@ pub struct FontInput {
|
||||
pub on_commit: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
/// This widget allows for the flexible use of the layout system.
|
||||
/// In a custom layout, one can define a widget that is just used to trigger code on the backend.
|
||||
/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend.
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct InvisibleStandinInput {
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_commit: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct NumberInput {
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
use super::input_widgets::InvisibleStandinInput;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Default, specta::Type)]
|
||||
pub struct MenuBarEntryChildren(pub Vec<Vec<MenuBarEntry>>);
|
||||
|
||||
impl MenuBarEntryChildren {
|
||||
pub fn empty() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
let entries = self.0.iter_mut().flatten();
|
||||
|
||||
for entry in entries {
|
||||
if let Some(action_keys) = &mut entry.shortcut {
|
||||
action_keys.to_keys(action_input_mapping);
|
||||
}
|
||||
|
||||
// Recursively do this for the children also
|
||||
entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)]
|
||||
pub struct MenuBarEntry {
|
||||
pub label: String,
|
||||
pub icon: Option<String>,
|
||||
pub shortcut: Option<ActionKeys>,
|
||||
pub action: WidgetHolder,
|
||||
pub children: MenuBarEntryChildren,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl MenuBarEntry {
|
||||
pub fn new_root(label: String, disabled: bool, children: MenuBarEntryChildren) -> Self {
|
||||
Self {
|
||||
label,
|
||||
disabled,
|
||||
children,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_action(callback: impl Fn(&()) -> Message + 'static + Send + Sync) -> WidgetHolder {
|
||||
InvisibleStandinInput::new().on_update(callback).widget_holder()
|
||||
}
|
||||
|
||||
pub fn no_action() -> WidgetHolder {
|
||||
MenuBarEntry::create_action(|_| Message::NoOp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MenuBarEntry {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
label: "".into(),
|
||||
icon: None,
|
||||
shortcut: None,
|
||||
action: MenuBarEntry::no_action(),
|
||||
children: MenuBarEntryChildren::empty(),
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct MenuLayout {
|
||||
pub layout: Vec<MenuBarEntry>,
|
||||
}
|
||||
|
||||
impl MenuLayout {
|
||||
pub fn new(layout: Vec<MenuBarEntry>) -> Self {
|
||||
Self { layout }
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
||||
MenuLayoutIter { stack: self.layout.iter().collect() }
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
|
||||
MenuLayoutIterMut {
|
||||
stack: self.layout.iter_mut().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MenuLayoutIter<'a> {
|
||||
pub stack: Vec<&'a MenuBarEntry>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MenuLayoutIter<'a> {
|
||||
type Item = &'a WidgetHolder;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(menu_entry) => {
|
||||
let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter());
|
||||
self.stack.extend(more_entries);
|
||||
|
||||
Some(&menu_entry.action)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MenuLayoutIterMut<'a> {
|
||||
pub stack: Vec<&'a mut MenuBarEntry>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for MenuLayoutIterMut<'a> {
|
||||
type Item = &'a mut WidgetHolder;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(menu_entry) => {
|
||||
let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut());
|
||||
self.stack.extend(more_entries);
|
||||
|
||||
Some(&mut menu_entry.action)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod button_widgets;
|
||||
pub mod input_widgets;
|
||||
pub mod label_widgets;
|
||||
pub mod menu_widgets;
|
||||
|
||||
@@ -157,7 +157,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed(
|
||||
"Improves rendering performance if used in rare circumstances where automatic caching is not yet advanced enough to handle the situation.
|
||||
"Improves rendering performance if used in rare circumstances where automatic caching is not yet advanced enough to handle the situation.\n\
|
||||
\n\
|
||||
Stores the last evaluated data that flowed through this node, and immediately returns that data on subsequent renders if the context has not changed.",
|
||||
),
|
||||
properties: None,
|
||||
@@ -1014,7 +1015,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Loads an image from a given URL"),
|
||||
description: Cow::Borrowed("Loads an image from a given URL."),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(all(feature = "gpu", target_family = "wasm"))]
|
||||
|
||||
@@ -2112,7 +2112,7 @@ pub mod choice {
|
||||
}
|
||||
|
||||
/// Not yet implemented!
|
||||
pub fn into_menu_entries(self, _action: impl Fn(E) -> Message + 'static + Send + Sync) -> Vec<Vec<MenuBarEntry>> {
|
||||
pub fn into_menu_entries(self, _action: impl Fn(E) -> Message + 'static + Send + Sync) -> MenuListEntrySections {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user