Detailed changes
@@ -1,6 +1,6 @@
use gpui::{
- App, Application, Context, Menu, MenuItem, SystemMenuType, Window, WindowOptions, actions, div,
- prelude::*, rgb,
+ App, Application, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window,
+ WindowOptions, actions, div, prelude::*, rgb,
};
struct SetMenus;
@@ -21,29 +21,87 @@ impl Render for SetMenus {
fn main() {
Application::new().run(|cx: &mut App| {
+ cx.set_global(AppState::new());
+
// Bring the menu bar to the foreground (so you can see the menu bar)
cx.activate(true);
// Register the `quit` function so it can be referenced by the `MenuItem::action` in the menu bar
cx.on_action(quit);
+ cx.on_action(toggle_check);
// Add menu items
- cx.set_menus(vec![Menu {
- name: "set_menus".into(),
- items: vec![
- MenuItem::os_submenu("Services", SystemMenuType::Services),
- MenuItem::separator(),
- MenuItem::action("Quit", Quit),
- ],
- }]);
+ set_app_menus(cx);
cx.open_window(WindowOptions::default(), |_, cx| cx.new(|_| SetMenus {}))
.unwrap();
});
}
+#[derive(PartialEq)]
+enum ViewMode {
+ List,
+ Grid,
+}
+
+impl ViewMode {
+ fn toggle(&mut self) {
+ *self = match self {
+ ViewMode::List => ViewMode::Grid,
+ ViewMode::Grid => ViewMode::List,
+ }
+ }
+}
+
+impl Into<SharedString> for ViewMode {
+ fn into(self) -> SharedString {
+ match self {
+ ViewMode::List => "List",
+ ViewMode::Grid => "Grid",
+ }
+ .into()
+ }
+}
+
+struct AppState {
+ view_mode: ViewMode,
+}
+
+impl AppState {
+ fn new() -> Self {
+ Self {
+ view_mode: ViewMode::List,
+ }
+ }
+}
+
+impl Global for AppState {}
+
+fn set_app_menus(cx: &mut App) {
+ let app_state = cx.global::<AppState>();
+ cx.set_menus(vec![Menu {
+ name: "set_menus".into(),
+ items: vec![
+ MenuItem::os_submenu("Services", SystemMenuType::Services),
+ MenuItem::separator(),
+ MenuItem::action(ViewMode::List, ToggleCheck)
+ .checked(app_state.view_mode == ViewMode::List),
+ MenuItem::action(ViewMode::Grid, ToggleCheck)
+ .checked(app_state.view_mode == ViewMode::Grid),
+ MenuItem::separator(),
+ MenuItem::action("Quit", Quit),
+ ],
+ }]);
+}
+
// Associate actions using the `actions!` macro (or `Action` derive macro)
-actions!(set_menus, [Quit]);
+actions!(set_menus, [Quit, ToggleCheck]);
// Define the quit function that is registered with the App
fn quit(_: &Quit, cx: &mut App) {
println!("Gracefully quitting the application . . .");
cx.quit();
}
+
+fn toggle_check(_: &ToggleCheck, cx: &mut App) {
+ let app_state = cx.global_mut::<AppState>();
+ app_state.view_mode.toggle();
+ set_app_menus(cx);
+}
@@ -64,12 +64,15 @@ pub enum MenuItem {
/// The name of this menu item
name: SharedString,
- /// the action to perform when this menu item is selected
+ /// The action to perform when this menu item is selected
action: Box<dyn Action>,
/// The OS Action that corresponds to this action, if any
/// See [`OsAction`] for more information
os_action: Option<OsAction>,
+
+ /// Whether this action is checked
+ checked: bool,
},
}
@@ -98,6 +101,7 @@ impl MenuItem {
name: name.into(),
action: Box::new(action),
os_action: None,
+ checked: false,
}
}
@@ -111,6 +115,7 @@ impl MenuItem {
name: name.into(),
action: Box::new(action),
os_action: Some(os_action),
+ checked: false,
}
}
@@ -123,14 +128,36 @@ impl MenuItem {
name,
action,
os_action,
+ checked,
} => OwnedMenuItem::Action {
name: name.into(),
action,
os_action,
+ checked,
},
MenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.owned()),
}
}
+
+ /// Set whether this menu item is checked
+ ///
+ /// Only for [`MenuItem::Action`], otherwise, will be ignored
+ pub fn checked(mut self, checked: bool) -> Self {
+ match self {
+ MenuItem::Action {
+ action,
+ os_action,
+ name,
+ ..
+ } => MenuItem::Action {
+ name,
+ action,
+ os_action,
+ checked,
+ },
+ _ => self,
+ }
+ }
}
/// OS menus are menus that are recognized by the operating system
@@ -171,12 +198,15 @@ pub enum OwnedMenuItem {
/// The name of this menu item
name: String,
- /// the action to perform when this menu item is selected
+ /// The action to perform when this menu item is selected
action: Box<dyn Action>,
/// The OS Action that corresponds to this action, if any
/// See [`OsAction`] for more information
os_action: Option<OsAction>,
+
+ /// Whether this action is checked
+ checked: bool,
},
}
@@ -189,10 +219,12 @@ impl Clone for OwnedMenuItem {
name,
action,
os_action,
+ checked,
} => OwnedMenuItem::Action {
name: name.clone(),
action: action.boxed_clone(),
os_action: *os_action,
+ checked: *checked,
},
OwnedMenuItem::SystemMenu(os_menu) => OwnedMenuItem::SystemMenu(os_menu.clone()),
}
@@ -19,7 +19,7 @@ use cocoa::{
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeRTFD, NSPasteboardTypeString,
- NSPasteboardTypeTIFF, NSSavePanel, NSWindow,
+ NSPasteboardTypeTIFF, NSSavePanel, NSVisualEffectState, NSVisualEffectView, NSWindow,
},
base::{BOOL, NO, YES, id, nil, selector},
foundation::{
@@ -315,6 +315,7 @@ impl MacPlatform {
name,
action,
os_action,
+ checked,
} => {
// Note that this is intentionally using earlier bindings, whereas typically
// later ones take display precedence. See the discussion on
@@ -409,6 +410,10 @@ impl MacPlatform {
.autorelease();
}
+ if *checked {
+ item.setState_(NSVisualEffectState::Active);
+ }
+
let tag = actions.len() as NSInteger;
let _: () = msg_send![item, setTag: tag];
actions.push(action.boxed_clone());
@@ -41,6 +41,7 @@ fn main() {
name: "Quit".into(),
action: Box::new(Quit),
os_action: None,
+ checked: false,
}],
}]);
@@ -110,16 +110,24 @@ impl ApplicationMenu {
.into_iter()
.fold(menu, |menu, item| match item {
OwnedMenuItem::Separator => menu.separator(),
- OwnedMenuItem::Action { name, action, .. } => menu.action(name, action),
+ OwnedMenuItem::Action {
+ name,
+ action,
+ checked,
+ ..
+ } => menu.action_checked(name, action, checked),
OwnedMenuItem::Submenu(submenu) => {
submenu
.items
.into_iter()
.fold(menu, |menu, item| match item {
OwnedMenuItem::Separator => menu.separator(),
- OwnedMenuItem::Action { name, action, .. } => {
- menu.action(name, action)
- }
+ OwnedMenuItem::Action {
+ name,
+ action,
+ checked,
+ ..
+ } => menu.action_checked(name, action, checked),
OwnedMenuItem::Submenu(_) => menu,
OwnedMenuItem::SystemMenu(_) => {
// A system menu doesn't make sense in this context, so ignore it
@@ -542,9 +542,22 @@ impl ContextMenu {
self
}
- pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
+ pub fn action(self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
+ self.action_checked(label, action, false)
+ }
+
+ pub fn action_checked(
+ mut self,
+ label: impl Into<SharedString>,
+ action: Box<dyn Action>,
+ checked: bool,
+ ) -> Self {
self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
- toggle: None,
+ toggle: if checked {
+ Some((IconPosition::Start, true))
+ } else {
+ None
+ },
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |context, window, cx| {