feat: add builders (#73)

This commit is contained in:
Amr Bashir 2023-07-18 04:59:47 +03:00 committed by GitHub
parent dded1938d9
commit ee30bf8d29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 562 additions and 83 deletions

View file

@ -0,0 +1,5 @@
---
"muda": "patch"
---
Add `AboutMetadataBuilder`.

5
.changes/builders.md Normal file
View file

@ -0,0 +1,5 @@
---
"muda": "patch"
---
Add `builders` module with `MenuItemBuilder`, `SubmenuBuilder`, `CheckMenuItemBuilder` and `IconMenuItemBuilder`.

5
.changes/try-from.md Normal file
View file

@ -0,0 +1,5 @@
---
"muda": "patch"
---
Impl `TryFrom<&str>` and `TryFrom<String>` for `Accelerator`.

176
src/about_metadata.rs Normal file
View file

@ -0,0 +1,176 @@
use crate::icon::Icon;
/// Application metadata for the [`PredefinedMenuItem::about`].
#[derive(Debug, Clone, Default)]
pub struct AboutMetadata {
/// Sets the application name.
pub name: Option<String>,
/// The application version.
pub version: Option<String>,
/// The short version, e.g. "1.0".
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Appended to the end of `version` in parentheses.
pub short_version: Option<String>,
/// The authors of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub authors: Option<Vec<String>>,
/// Application comments.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub comments: Option<String>,
/// The copyright of the application.
pub copyright: Option<String>,
/// The license of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub license: Option<String>,
/// The application website.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub website: Option<String>,
/// The website label.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub website_label: Option<String>,
/// The credits.
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Unsupported.
pub credits: Option<String>,
/// The application icon.
///
/// ## Platform-specific
///
/// - **Windows:** Unsupported.
pub icon: Option<Icon>,
}
impl AboutMetadata {
#[allow(unused)]
pub(crate) fn full_version(&self) -> Option<String> {
Some(format!(
"{}{}",
(self.version.as_ref())?,
(self.short_version.as_ref())
.map(|v| format!(" ({v})"))
.unwrap_or_default()
))
}
}
/// A builder type for [`AboutMetadata`].
#[derive(Clone, Debug, Default)]
pub struct AboutMetadataBuilder(AboutMetadata);
impl AboutMetadataBuilder {
pub fn new() -> Self {
Default::default()
}
/// Sets the application name.
pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {
self.0.name = name.map(|s| s.into());
self
}
/// Sets the application version.
pub fn version<S: Into<String>>(mut self, version: Option<S>) -> Self {
self.0.version = version.map(|s| s.into());
self
}
/// Sets the short version, e.g. "1.0".
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Appended to the end of `version` in parentheses.
pub fn short_version<S: Into<String>>(mut self, short_version: Option<S>) -> Self {
self.0.short_version = short_version.map(|s| s.into());
self
}
/// Sets the authors of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub fn authors(mut self, authors: Option<Vec<String>>) -> Self {
self.0.authors = authors;
self
}
/// Application comments.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub fn comments<S: Into<String>>(mut self, comments: Option<S>) -> Self {
self.0.comments = comments.map(|s| s.into());
self
}
/// Sets the copyright of the application.
pub fn copyright<S: Into<String>>(mut self, copyright: Option<S>) -> Self {
self.0.copyright = copyright.map(|s| s.into());
self
}
/// Sets the license of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub fn license<S: Into<String>>(mut self, license: Option<S>) -> Self {
self.0.license = license.map(|s| s.into());
self
}
/// Sets the application website.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub fn website<S: Into<String>>(mut self, website: Option<S>) -> Self {
self.0.website = website.map(|s| s.into());
self
}
/// Sets the website label.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub fn website_label<S: Into<String>>(mut self, website_label: Option<S>) -> Self {
self.0.website_label = website_label.map(|s| s.into());
self
}
/// Sets the credits.
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Unsupported.
pub fn credits<S: Into<String>>(mut self, credits: Option<S>) -> Self {
self.0.credits = credits.map(|s| s.into());
self
}
/// Sets the application icon.
///
/// ## Platform-specific
///
/// - **Windows:** Unsupported.
pub fn icon(mut self, icon: Option<Icon>) -> Self {
self.0.icon = icon;
self
}
/// Construct the final [`AboutMetadata`]
pub fn build(self) -> AboutMetadata {
self.0
}
}

View file

@ -101,6 +101,22 @@ impl FromStr for Accelerator {
}
}
impl TryFrom<&str> for Accelerator {
type Error = crate::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
parse_accelerator(value)
}
}
impl TryFrom<String> for Accelerator {
type Error = crate::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
parse_accelerator(&value)
}
}
fn parse_accelerator(accelerator: &str) -> crate::Result<Accelerator> {
let tokens = accelerator.split('+').collect::<Vec<&str>>();

57
src/builders/check.rs Normal file
View file

@ -0,0 +1,57 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{accelerator::Accelerator, CheckMenuItem};
/// A builder type for [`CheckMenuItem`]
#[derive(Clone, Debug, Default)]
pub struct CheckMenuItemBuilder {
text: String,
enabled: bool,
checked: bool,
acccelerator: Option<Accelerator>,
}
impl CheckMenuItemBuilder {
pub fn new() -> Self {
Default::default()
}
/// Set the text for this check menu item.
///
/// See [`CheckMenuItem::set_text`] for more info.
pub fn text<S: Into<String>>(mut self, text: S) -> Self {
self.text = text.into();
self
}
/// Enable or disable this menu item.
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// Check or uncheck this menu item.
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
/// Set this check menu item accelerator.
pub fn acccelerator<A: TryInto<Accelerator>>(
mut self,
acccelerator: Option<A>,
) -> crate::Result<Self>
where
crate::Error: From<<A as TryInto<Accelerator>>::Error>,
{
self.acccelerator = acccelerator.map(|a| a.try_into()).transpose()?;
Ok(self)
}
/// Build this check menu item.
pub fn build(self) -> CheckMenuItem {
CheckMenuItem::new(self.text, self.enabled, self.checked, self.acccelerator)
}
}

79
src/builders/icon.rs Normal file
View file

@ -0,0 +1,79 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
accelerator::Accelerator,
icon::{Icon, NativeIcon},
IconMenuItem,
};
/// A builder type for [`IconMenuItem`]
#[derive(Clone, Debug, Default)]
pub struct IconMenuItemBuilder {
text: String,
enabled: bool,
acccelerator: Option<Accelerator>,
icon: Option<Icon>,
native_icon: Option<NativeIcon>,
}
impl IconMenuItemBuilder {
pub fn new() -> Self {
Default::default()
}
/// Set the text for this icon menu item.
///
/// See [`IconMenuItem::set_text`] for more info.
pub fn text<S: Into<String>>(mut self, text: S) -> Self {
self.text = text.into();
self
}
/// Enable or disable this menu item.
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// Set this icon menu item icon.
pub fn icon(mut self, icon: Option<Icon>) -> Self {
self.icon = icon;
self.native_icon = None;
self
}
/// Set this icon menu item native icon.
pub fn native_icon(mut self, icon: Option<NativeIcon>) -> Self {
self.native_icon = icon;
self.icon = None;
self
}
/// Set this icon menu item accelerator.
pub fn acccelerator<A: TryInto<Accelerator>>(
mut self,
acccelerator: Option<A>,
) -> crate::Result<Self>
where
crate::Error: From<<A as TryInto<Accelerator>>::Error>,
{
self.acccelerator = acccelerator.map(|a| a.try_into()).transpose()?;
Ok(self)
}
/// Build this icon menu item.
pub fn build(self) -> IconMenuItem {
if self.icon.is_some() {
IconMenuItem::new(self.text, self.enabled, self.icon, self.acccelerator)
} else {
IconMenuItem::with_native_icon(
self.text,
self.enabled,
self.native_icon,
self.acccelerator,
)
}
}
}

16
src/builders/mod.rs Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! A module containting builder types
mod check;
mod icon;
mod normal;
mod submenu;
pub use crate::about_metadata::AboutMetadataBuilder;
pub use check::*;
pub use icon::*;
pub use normal::*;
pub use submenu::*;

50
src/builders/normal.rs Normal file
View file

@ -0,0 +1,50 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{accelerator::Accelerator, MenuItem};
/// A builder type for [`MenuItem`]
#[derive(Clone, Debug, Default)]
pub struct MenuItemBuilder {
text: String,
enabled: bool,
acccelerator: Option<Accelerator>,
}
impl MenuItemBuilder {
pub fn new() -> Self {
Default::default()
}
/// Set the text for this menu item.
///
/// See [`MenuItem::set_text`] for more info.
pub fn text<S: Into<String>>(mut self, text: S) -> Self {
self.text = text.into();
self
}
/// Enable or disable this menu item.
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// Set this menu item accelerator.
pub fn acccelerator<A: TryInto<Accelerator>>(
mut self,
acccelerator: Option<A>,
) -> crate::Result<Self>
where
crate::Error: From<<A as TryInto<Accelerator>>::Error>,
{
self.acccelerator = acccelerator.map(|a| a.try_into()).transpose()?;
Ok(self)
}
/// Build this menu item.
pub fn build(self) -> MenuItem {
MenuItem::new(self.text, self.enabled, self.acccelerator)
}
}

59
src/builders/submenu.rs Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{IsMenuItem, Submenu};
/// A builder type for [`Submenu`]
#[derive(Clone, Default)]
pub struct SubmenuBuilder<'a> {
text: String,
enabled: bool,
items: Vec<&'a dyn IsMenuItem>,
}
impl std::fmt::Debug for SubmenuBuilder<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubmenuBuilder")
.field("text", &self.text)
.field("enabled", &self.enabled)
.finish()
}
}
impl<'a> SubmenuBuilder<'a> {
pub fn new() -> Self {
Default::default()
}
/// Set the text for this menu item.
///
/// See [`Submenu::set_text`] for more info.
pub fn text<S: Into<String>>(mut self, text: S) -> Self {
self.text = text.into();
self
}
/// Enable or disable this menu item.
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
/// Add an item to this submenu.
pub fn item(mut self, item: &'a dyn IsMenuItem) -> Self {
self.items.push(item);
self
}
/// Add these items to this submenu.
pub fn items(mut self, items: &[&'a dyn IsMenuItem]) -> Self {
self.items.extend_from_slice(items);
self
}
/// Build this menu item.
pub fn build(self) -> crate::Result<Submenu> {
Submenu::with_items(self.text, self.enabled, &self.items)
}
}

View file

@ -60,7 +60,7 @@ impl CheckMenuItem {
self.0.borrow().text()
}
/// Get the text for this check menu item. `text` could optionally contain
/// Set the text for this check menu item. `text` could optionally contain
/// an `&` before a character to assign this character as the mnemonic
/// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`.
pub fn set_text<S: AsRef<str>>(&self, text: S) {

View file

@ -48,6 +48,29 @@ impl IconMenuItem {
)))
}
/// Create a new icon menu item but with a native icon.
///
/// See [`IconMenuItem::new`] for more info.
///
/// ## Platform-specific:
///
/// - **Windows / Linux**: Unsupported.
pub fn with_native_icon<S: AsRef<str>>(
text: S,
enabled: bool,
native_icon: Option<NativeIcon>,
acccelerator: Option<Accelerator>,
) -> Self {
Self(Rc::new(RefCell::new(
crate::platform_impl::MenuChild::new_native_icon(
text.as_ref(),
enabled,
native_icon,
acccelerator,
),
)))
}
/// Returns a unique identifier associated with this submenu.
pub fn id(&self) -> u32 {
self.0.borrow().id()
@ -58,7 +81,7 @@ impl IconMenuItem {
self.0.borrow().text()
}
/// Get the text for this check menu item. `text` could optionally contain
/// Set the text for this check menu item. `text` could optionally contain
/// an `&` before a character to assign this character as the mnemonic
/// for this check menu item. To display a `&` without assigning a mnemenonic, use `&&`.
pub fn set_text<S: AsRef<str>>(&self, text: S) {

View file

@ -41,7 +41,7 @@ impl MenuItem {
self.0.borrow().id()
}
/// Get the text for this menu item.
/// Set the text for this menu item.
pub fn text(&self) -> String {
self.0.borrow().text()
}

View file

@ -4,7 +4,7 @@
use std::{cell::RefCell, rc::Rc};
use crate::{accelerator::Accelerator, icon::Icon, IsMenuItem, MenuItemType};
use crate::{accelerator::Accelerator, AboutMetadata, IsMenuItem, MenuItemType};
use keyboard_types::{Code, Modifiers};
#[cfg(target_os = "macos")]
@ -182,78 +182,6 @@ impl PredefinedMenuItem {
}
}
/// Application metadata for the [`PredefinedMenuItem::about`].
#[derive(Debug, Clone, Default)]
pub struct AboutMetadata {
/// The application name.
pub name: Option<String>,
/// The application version.
pub version: Option<String>,
/// The short version, e.g. "1.0".
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Appended to the end of `version` in parentheses.
pub short_version: Option<String>,
/// The authors of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub authors: Option<Vec<String>>,
/// Application comments.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub comments: Option<String>,
/// The copyright of the application.
pub copyright: Option<String>,
/// The license of the application.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub license: Option<String>,
/// The application website.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub website: Option<String>,
/// The website label.
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
pub website_label: Option<String>,
/// The credits.
///
/// ## Platform-specific
///
/// - **Windows / Linux:** Unsupported.
pub credits: Option<String>,
/// The application icon.
///
/// ## Platform-specific
///
/// - **Windows:** Unsupported.
pub icon: Option<Icon>,
}
impl AboutMetadata {
#[allow(unused)]
pub(crate) fn full_version(&self) -> Option<String> {
Some(format!(
"{}{}",
(self.version.as_ref())?,
(self.short_version.as_ref())
.map(|v| format!(" ({v})"))
.unwrap_or_default()
))
}
}
#[test]
fn test_about_metadata() {
assert_eq!(

View file

@ -128,7 +128,9 @@
use crossbeam_channel::{unbounded, Receiver, Sender};
use once_cell::sync::{Lazy, OnceCell};
mod about_metadata;
pub mod accelerator;
pub mod builders;
mod error;
mod items;
mod menu;
@ -139,7 +141,8 @@ mod util;
#[macro_use]
extern crate objc;
pub use self::error::*;
pub use about_metadata::AboutMetadata;
pub use error::*;
pub use items::*;
pub use menu::Menu;
pub mod icon;

View file

@ -9,7 +9,7 @@ pub(crate) use icon::PlatformIcon;
use crate::{
accelerator::Accelerator,
icon::Icon,
icon::{Icon, NativeIcon},
items::*,
util::{AddOp, Counter},
MenuEvent, MenuItemType,
@ -480,6 +480,24 @@ impl MenuChild {
..Default::default()
}
}
pub fn new_native_icon(
text: &str,
enabled: bool,
_native_icon: Option<NativeIcon>,
accelerator: Option<Accelerator>,
) -> Self {
Self {
text: text.to_string(),
enabled,
accelerator,
id: COUNTER.next(),
type_: MenuItemType::Icon,
gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
is_syncing_checked_state: Rc::new(AtomicBool::new(false)),
..Default::default()
}
}
}
/// Shared methods

View file

@ -311,6 +311,23 @@ impl MenuChild {
..Default::default()
}
}
pub fn new_native_icon(
text: &str,
enabled: bool,
native_icon: Option<NativeIcon>,
accelerator: Option<Accelerator>,
) -> Self {
Self {
type_: MenuItemType::Icon,
text: text.to_string(),
enabled,
id: COUNTER.next(),
native_icon,
accelerator,
..Default::default()
}
}
}
/// Shared methods
@ -695,7 +712,11 @@ impl MenuChild {
let () = msg_send![ns_menu_item, setEnabled: NO];
}
if self.icon.is_some() {
menuitem_set_icon(ns_menu_item, self.icon.as_ref());
} else if self.native_icon.is_some() {
menuitem_set_native_icon(ns_menu_item, self.native_icon);
}
}
self.ns_menu_items

View file

@ -10,11 +10,11 @@ pub(crate) use self::icon::WinIcon as PlatformIcon;
use crate::{
accelerator::Accelerator,
icon::Icon,
items::{AboutMetadata, PredfinedMenuItemType},
icon::{Icon, NativeIcon},
items::PredfinedMenuItemType,
util::{AddOp, Counter},
CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType, PredefinedMenuItem,
Submenu,
AboutMetadata, CheckMenuItem, IconMenuItem, IsMenuItem, MenuEvent, MenuItem, MenuItemType,
PredefinedMenuItem, Submenu,
};
use std::{
cell::{RefCell, RefMut},
@ -474,6 +474,24 @@ impl MenuChild {
..Default::default()
}
}
pub fn new_native_icon(
text: &str,
enabled: bool,
_native_icon: Option<NativeIcon>,
accelerator: Option<Accelerator>,
) -> Self {
Self {
type_: MenuItemType::Icon,
text: text.to_string(),
enabled,
parents_hemnu: Vec::new(),
id: COUNTER.next(),
accelerator,
root_menu_haccel_stores: Some(Vec::new()),
..Default::default()
}
}
}
/// Shared methods