From 735cfb790813c44852612400e31c0190b9c641a6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 21:44:01 +0200 Subject: Move `menu` module from `iced_native` to `iced_core` --- core/src/menu.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 core/src/menu.rs (limited to 'core/src/menu.rs') diff --git a/core/src/menu.rs b/core/src/menu.rs new file mode 100644 index 00000000..fe770216 --- /dev/null +++ b/core/src/menu.rs @@ -0,0 +1,84 @@ +//! Build menus for your application. +use crate::keyboard::Hotkey; + +/// Menu representation. +/// +/// This can be used by `shell` implementations to create a menu. +#[derive(Debug, Clone, PartialEq)] +pub struct Menu { + entries: Vec>, +} + +impl Menu { + /// Creates an empty [`Menu`]. + pub fn new() -> Self { + Self::with_entries(Vec::new()) + } + + /// Creates a new [`Menu`] with the given entries. + pub fn with_entries(entries: Vec>) -> Self { + Self { entries } + } + + /// Adds an [`Entry`] to the [`Menu`]. + pub fn push(mut self, entry: Entry) -> Self { + self.entries.push(entry); + self + } + + /// Returns a [`MenuEntry`] iterator. + pub fn iter(self) -> impl Iterator> { + self.entries.into_iter() + } +} + +/// Represents one of the possible entries used to build a [`Menu`]. +#[derive(Debug, Clone, PartialEq)] +pub enum Entry { + /// Item for a [`Menu`] + Item { + /// The title of the item + content: String, + /// The [`Hotkey`] to activate the item, if any + hotkey: Option, + /// The message generated when the item is activated + on_activation: Message, + }, + /// Dropdown for a [`Menu`] + Dropdown { + /// Title of the dropdown + content: String, + /// The submenu of the dropdown + submenu: Menu, + }, + /// Separator for a [`Menu`] + Separator, +} + +impl Entry { + /// Creates an [`Entry::Item`]. + pub fn item>( + content: S, + hotkey: impl Into>, + on_activation: Message, + ) -> Self { + let content = content.into(); + let hotkey = hotkey.into(); + + Entry::Item { + content, + hotkey, + on_activation, + } + } + + /// Creates an [`Entry::Dropdown`]. + pub fn dropdown>( + content: S, + submenu: Menu, + ) -> Self { + let content = content.into(); + + Entry::Dropdown { content, submenu } + } +} -- cgit From 31997d255f263a0f47f5af0d8560f3d5bd37f077 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 12 Jul 2021 22:28:18 +0200 Subject: Store and synchronize `Menu` in `application::State` --- core/src/menu.rs | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) (limited to 'core/src/menu.rs') diff --git a/core/src/menu.rs b/core/src/menu.rs index fe770216..52186140 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -4,11 +4,17 @@ use crate::keyboard::Hotkey; /// Menu representation. /// /// This can be used by `shell` implementations to create a menu. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Menu { entries: Vec>, } +impl PartialEq for Menu { + fn eq(&self, other: &Self) -> bool { + self.entries == other.entries + } +} + impl Menu { /// Creates an empty [`Menu`]. pub fn new() -> Self { @@ -27,13 +33,13 @@ impl Menu { } /// Returns a [`MenuEntry`] iterator. - pub fn iter(self) -> impl Iterator> { - self.entries.into_iter() + pub fn iter(&self) -> impl Iterator> { + self.entries.iter() } } /// Represents one of the possible entries used to build a [`Menu`]. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Entry { /// Item for a [`Menu`] Item { @@ -82,3 +88,29 @@ impl Entry { Entry::Dropdown { content, submenu } } } + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Entry::Item { + content, hotkey, .. + }, + Entry::Item { + content: other_content, + hotkey: other_hotkey, + .. + }, + ) => content == other_content && hotkey == other_hotkey, + ( + Entry::Dropdown { content, submenu }, + Entry::Dropdown { + content: other_content, + submenu: other_submenu, + }, + ) => content == other_content && submenu == other_submenu, + (Entry::Separator, Entry::Separator) => true, + _ => false, + } + } +} -- cgit From 3099f3610003f513a386125d3cb81bfbf0ffe887 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 20:59:09 +0700 Subject: Implement `Menu::map` naively --- core/src/menu.rs | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'core/src/menu.rs') diff --git a/core/src/menu.rs b/core/src/menu.rs index 52186140..e9d3d13a 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -26,15 +26,30 @@ impl Menu { Self { entries } } + /// Returns a [`MenuEntry`] iterator. + pub fn iter(&self) -> impl Iterator> { + self.entries.iter() + } + /// Adds an [`Entry`] to the [`Menu`]. pub fn push(mut self, entry: Entry) -> Self { self.entries.push(entry); self } - /// Returns a [`MenuEntry`] iterator. - pub fn iter(&self) -> impl Iterator> { - self.entries.iter() + /// Maps the `Message` of the [`Menu`] using the provided function. + /// + /// This is useful to compose menus and split them into different + /// abstraction levels. + pub fn map(self, f: &impl Fn(Message) -> B) -> Menu { + // TODO: Use a boxed trait to avoid reallocation of entries + Menu { + entries: self + .entries + .into_iter() + .map(|entry| entry.map(f)) + .collect(), + } } } @@ -71,7 +86,7 @@ impl Entry { let content = content.into(); let hotkey = hotkey.into(); - Entry::Item { + Self::Item { content, hotkey, on_activation, @@ -85,7 +100,26 @@ impl Entry { ) -> Self { let content = content.into(); - Entry::Dropdown { content, submenu } + Self::Dropdown { content, submenu } + } + + fn map(self, f: &impl Fn(Message) -> B) -> Entry { + match self { + Self::Item { + content, + hotkey, + on_activation, + } => Entry::Item { + content, + hotkey, + on_activation: f(on_activation), + }, + Self::Dropdown { content, submenu } => Entry::Dropdown { + content, + submenu: submenu.map(f), + }, + Self::Separator => Entry::Separator, + } } } -- cgit From a2f49a74d08a0cff2e892932b484a88a4034f627 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 21:01:24 +0700 Subject: Replace `content` with `title` in `menu` module --- core/src/menu.rs | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) (limited to 'core/src/menu.rs') diff --git a/core/src/menu.rs b/core/src/menu.rs index e9d3d13a..3ad7b7a2 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -59,7 +59,7 @@ pub enum Entry { /// Item for a [`Menu`] Item { /// The title of the item - content: String, + title: String, /// The [`Hotkey`] to activate the item, if any hotkey: Option, /// The message generated when the item is activated @@ -68,7 +68,7 @@ pub enum Entry { /// Dropdown for a [`Menu`] Dropdown { /// Title of the dropdown - content: String, + title: String, /// The submenu of the dropdown submenu: Menu, }, @@ -79,43 +79,40 @@ pub enum Entry { impl Entry { /// Creates an [`Entry::Item`]. pub fn item>( - content: S, + title: S, hotkey: impl Into>, on_activation: Message, ) -> Self { - let content = content.into(); + let title = title.into(); let hotkey = hotkey.into(); Self::Item { - content, + title, hotkey, on_activation, } } /// Creates an [`Entry::Dropdown`]. - pub fn dropdown>( - content: S, - submenu: Menu, - ) -> Self { - let content = content.into(); + pub fn dropdown>(title: S, submenu: Menu) -> Self { + let title = title.into(); - Self::Dropdown { content, submenu } + Self::Dropdown { title, submenu } } fn map(self, f: &impl Fn(Message) -> B) -> Entry { match self { Self::Item { - content, + title, hotkey, on_activation, } => Entry::Item { - content, + title, hotkey, on_activation: f(on_activation), }, - Self::Dropdown { content, submenu } => Entry::Dropdown { - content, + Self::Dropdown { title, submenu } => Entry::Dropdown { + title, submenu: submenu.map(f), }, Self::Separator => Entry::Separator, @@ -127,22 +124,20 @@ impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { match (self, other) { ( + Entry::Item { title, hotkey, .. }, Entry::Item { - content, hotkey, .. - }, - Entry::Item { - content: other_content, + title: other_title, hotkey: other_hotkey, .. }, - ) => content == other_content && hotkey == other_hotkey, + ) => title == other_title && hotkey == other_hotkey, ( - Entry::Dropdown { content, submenu }, + Entry::Dropdown { title, submenu }, Entry::Dropdown { - content: other_content, + title: other_title, submenu: other_submenu, }, - ) => content == other_content && submenu == other_submenu, + ) => title == other_title && submenu == other_submenu, (Entry::Separator, Entry::Separator) => true, _ => false, } -- cgit From b97954a1ee3ec7bc85d1d41b397e994752ff1831 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 19 Jul 2021 21:18:54 +0700 Subject: Add a presets `Menu` to the `game_of_life` example --- core/src/menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core/src/menu.rs') diff --git a/core/src/menu.rs b/core/src/menu.rs index 3ad7b7a2..8a679085 100644 --- a/core/src/menu.rs +++ b/core/src/menu.rs @@ -41,7 +41,7 @@ impl Menu { /// /// This is useful to compose menus and split them into different /// abstraction levels. - pub fn map(self, f: &impl Fn(Message) -> B) -> Menu { + pub fn map(self, f: impl Fn(Message) -> B + Copy) -> Menu { // TODO: Use a boxed trait to avoid reallocation of entries Menu { entries: self @@ -100,7 +100,7 @@ impl Entry { Self::Dropdown { title, submenu } } - fn map(self, f: &impl Fn(Message) -> B) -> Entry { + fn map(self, f: impl Fn(Message) -> B + Copy) -> Entry { match self { Self::Item { title, -- cgit