Merge pull request #1203 from zed-industries/themes-in-app-menu
Themes in app menu
This commit is contained in:
@@ -133,11 +133,12 @@ pub struct Menu<'a> {
|
||||
}
|
||||
|
||||
pub enum MenuItem<'a> {
|
||||
Separator,
|
||||
Submenu(Menu<'a>),
|
||||
Action {
|
||||
name: &'a str,
|
||||
action: Box<dyn Action>,
|
||||
},
|
||||
Separator,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -40,13 +40,10 @@ impl Element for KeystrokeLabel {
|
||||
let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.action.as_ref()) {
|
||||
Flex::row()
|
||||
.with_children(keystrokes.iter().map(|keystroke| {
|
||||
Label::new(
|
||||
keystroke.to_string().to_uppercase(),
|
||||
self.text_style.clone(),
|
||||
)
|
||||
.contained()
|
||||
.with_style(self.container_style)
|
||||
.boxed()
|
||||
Label::new(keystroke.to_string(), self.text_style.clone())
|
||||
.contained()
|
||||
.with_style(self.container_style)
|
||||
.boxed()
|
||||
}))
|
||||
.boxed()
|
||||
} else {
|
||||
|
||||
+20
-14
@@ -4,7 +4,7 @@ use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
fmt::{Debug, Write},
|
||||
};
|
||||
use tree_sitter::{Language, Node, Parser};
|
||||
|
||||
@@ -318,28 +318,34 @@ impl Keystroke {
|
||||
impl std::fmt::Display for Keystroke {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.ctrl {
|
||||
write!(f, "{}", "^")?;
|
||||
f.write_char('^')?;
|
||||
}
|
||||
if self.alt {
|
||||
write!(f, "{}", "⎇")?;
|
||||
f.write_char('⎇')?;
|
||||
}
|
||||
if self.cmd {
|
||||
write!(f, "{}", "⌘")?;
|
||||
f.write_char('⌘')?;
|
||||
}
|
||||
if self.shift {
|
||||
write!(f, "{}", "⇧")?;
|
||||
f.write_char('⇧')?;
|
||||
}
|
||||
let key = match self.key.as_str() {
|
||||
"backspace" => "⌫",
|
||||
"up" => "↑",
|
||||
"down" => "↓",
|
||||
"left" => "←",
|
||||
"right" => "→",
|
||||
"tab" => "⇥",
|
||||
"escape" => "⎋",
|
||||
key => key,
|
||||
"backspace" => '⌫',
|
||||
"up" => '↑',
|
||||
"down" => '↓',
|
||||
"left" => '←',
|
||||
"right" => '→',
|
||||
"tab" => '⇥',
|
||||
"escape" => '⎋',
|
||||
key => {
|
||||
if key.len() == 1 {
|
||||
key.chars().next().unwrap().to_ascii_uppercase()
|
||||
} else {
|
||||
return f.write_str(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
write!(f, "{}", key)
|
||||
f.write_char(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,89 +127,131 @@ impl MacForegroundPlatform {
|
||||
&self,
|
||||
menus: Vec<Menu>,
|
||||
delegate: id,
|
||||
actions: &mut Vec<Box<dyn Action>>,
|
||||
keystroke_matcher: &keymap::Matcher,
|
||||
) -> id {
|
||||
let menu_bar = NSMenu::new(nil).autorelease();
|
||||
menu_bar.setDelegate_(delegate);
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
state.menu_actions.clear();
|
||||
let application_menu = NSMenu::new(nil).autorelease();
|
||||
application_menu.setDelegate_(delegate);
|
||||
|
||||
for menu_config in menus {
|
||||
let menu_bar_item = NSMenuItem::new(nil).autorelease();
|
||||
let menu = NSMenu::new(nil).autorelease();
|
||||
let menu_name = menu_config.name;
|
||||
|
||||
menu.setTitle_(ns_string(menu_name));
|
||||
menu.setTitle_(ns_string(menu_config.name));
|
||||
menu.setDelegate_(delegate);
|
||||
|
||||
for item_config in menu_config.items {
|
||||
let item;
|
||||
|
||||
match item_config {
|
||||
MenuItem::Separator => {
|
||||
item = NSMenuItem::separatorItem(nil);
|
||||
}
|
||||
MenuItem::Action { name, action } => {
|
||||
let mut keystroke = None;
|
||||
if let Some(binding) = keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.find(|binding| binding.action().eq(action.as_ref()))
|
||||
{
|
||||
if binding.keystrokes().len() == 1 {
|
||||
keystroke = binding.keystrokes().first()
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(keystroke) = keystroke {
|
||||
let mut mask = NSEventModifierFlags::empty();
|
||||
for (modifier, flag) in &[
|
||||
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
|
||||
(keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
|
||||
(keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
|
||||
] {
|
||||
if *modifier {
|
||||
mask |= *flag;
|
||||
}
|
||||
}
|
||||
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||
)
|
||||
.autorelease();
|
||||
item.setKeyEquivalentModifierMask_(mask);
|
||||
} else {
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
ns_string(""),
|
||||
)
|
||||
.autorelease();
|
||||
}
|
||||
|
||||
let tag = state.menu_actions.len() as NSInteger;
|
||||
let _: () = msg_send![item, setTag: tag];
|
||||
state.menu_actions.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
menu.addItem_(item);
|
||||
menu.addItem_(self.create_menu_item(
|
||||
item_config,
|
||||
delegate,
|
||||
actions,
|
||||
keystroke_matcher,
|
||||
));
|
||||
}
|
||||
|
||||
menu_bar_item.setSubmenu_(menu);
|
||||
menu_bar.addItem_(menu_bar_item);
|
||||
let menu_item = NSMenuItem::new(nil).autorelease();
|
||||
menu_item.setSubmenu_(menu);
|
||||
application_menu.addItem_(menu_item);
|
||||
|
||||
if menu_name == "Window" {
|
||||
if menu_config.name == "Window" {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
app.setWindowsMenu_(menu);
|
||||
}
|
||||
}
|
||||
|
||||
menu_bar
|
||||
application_menu
|
||||
}
|
||||
|
||||
unsafe fn create_menu_item(
|
||||
&self,
|
||||
item: MenuItem,
|
||||
delegate: id,
|
||||
actions: &mut Vec<Box<dyn Action>>,
|
||||
keystroke_matcher: &keymap::Matcher,
|
||||
) -> id {
|
||||
match item {
|
||||
MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
||||
MenuItem::Action { name, action } => {
|
||||
let keystrokes = keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.find(|binding| binding.action().eq(action.as_ref()))
|
||||
.map(|binding| binding.keystrokes());
|
||||
|
||||
let item;
|
||||
if let Some(keystrokes) = keystrokes {
|
||||
if keystrokes.len() == 1 {
|
||||
let keystroke = &keystrokes[0];
|
||||
let mut mask = NSEventModifierFlags::empty();
|
||||
for (modifier, flag) in &[
|
||||
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
|
||||
(keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
|
||||
(keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
|
||||
] {
|
||||
if *modifier {
|
||||
mask |= *flag;
|
||||
}
|
||||
}
|
||||
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||
)
|
||||
.autorelease();
|
||||
item.setKeyEquivalentModifierMask_(mask);
|
||||
}
|
||||
// For multi-keystroke bindings, render the keystroke as part of the title.
|
||||
else {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut name = format!("{name} [");
|
||||
for (i, keystroke) in keystrokes.iter().enumerate() {
|
||||
if i > 0 {
|
||||
name.push(' ');
|
||||
}
|
||||
write!(&mut name, "{}", keystroke).unwrap();
|
||||
}
|
||||
name.push(']');
|
||||
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(&name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
ns_string(""),
|
||||
)
|
||||
.autorelease();
|
||||
}
|
||||
} else {
|
||||
item = NSMenuItem::alloc(nil)
|
||||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector("handleGPUIMenuItem:"),
|
||||
ns_string(""),
|
||||
)
|
||||
.autorelease();
|
||||
}
|
||||
|
||||
let tag = actions.len() as NSInteger;
|
||||
let _: () = msg_send![item, setTag: tag];
|
||||
actions.push(action);
|
||||
item
|
||||
}
|
||||
MenuItem::Submenu(Menu { name, items }) => {
|
||||
let item = NSMenuItem::new(nil).autorelease();
|
||||
let submenu = NSMenu::new(nil).autorelease();
|
||||
submenu.setDelegate_(delegate);
|
||||
for item in items {
|
||||
submenu.addItem_(self.create_menu_item(
|
||||
item,
|
||||
delegate,
|
||||
actions,
|
||||
keystroke_matcher,
|
||||
));
|
||||
}
|
||||
item.setSubmenu_(submenu);
|
||||
item.setTitle_(ns_string(name));
|
||||
item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +312,14 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
|
||||
fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &keymap::Matcher) {
|
||||
unsafe {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), keystroke_matcher));
|
||||
let mut state = self.0.borrow_mut();
|
||||
let actions = &mut state.menu_actions;
|
||||
app.setMainMenu_(self.create_menu_bar(
|
||||
menus,
|
||||
app.delegate(),
|
||||
actions,
|
||||
keystroke_matcher,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-8
@@ -15,14 +15,23 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||
action: Box::new(auto_update::Check),
|
||||
},
|
||||
MenuItem::Separator,
|
||||
MenuItem::Action {
|
||||
name: "Open Settings",
|
||||
action: Box::new(super::OpenSettings),
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Open Key Bindings",
|
||||
action: Box::new(super::OpenKeymap),
|
||||
},
|
||||
MenuItem::Submenu(Menu {
|
||||
name: "Preferences",
|
||||
items: vec![
|
||||
MenuItem::Action {
|
||||
name: "Open Settings",
|
||||
action: Box::new(super::OpenSettings),
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Open Key Bindings",
|
||||
action: Box::new(super::OpenKeymap),
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Select Theme",
|
||||
action: Box::new(theme_selector::Toggle),
|
||||
},
|
||||
],
|
||||
}),
|
||||
MenuItem::Action {
|
||||
name: "Install CLI",
|
||||
action: Box::new(super::InstallCommandLineInterface),
|
||||
|
||||
Reference in New Issue
Block a user