Put keystrokes in their own column

This requires rendering the menu for measurement in a totally different way, where the top level is a flex row. We don't want to render the menu like this for presentation because of hovers / highlights on individual items needing to include the keystrokes.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2022-05-25 10:20:56 -06:00 committed by Antonio Scandurra
parent a8483ba458
commit c0aafac387
2 changed files with 94 additions and 58 deletions

View file

@ -42,16 +42,14 @@ impl View for ContextMenu {
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
enum Tag {}
if !self.visible { if !self.visible {
return Empty::new().boxed(); return Empty::new().boxed();
} }
// Render the menu once at minimum width. // Render the menu once at minimum width.
let mut collapsed_menu = self.render_menu::<()>(false, cx).boxed(); let mut collapsed_menu = self.render_menu_for_measurement(cx).boxed();
let expanded_menu = self let expanded_menu = self
.render_menu::<Tag>(true, cx) .render_menu(cx)
.constrained() .constrained()
.dynamically(move |constraint, cx| { .dynamically(move |constraint, cx| {
SizeConstraint::strict_along( SizeConstraint::strict_along(
@ -97,64 +95,100 @@ impl ContextMenu {
cx.notify(); cx.notify();
} }
fn render_menu<Tag: 'static>( fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
&mut self,
expanded: bool,
cx: &mut RenderContext<Self>,
) -> impl Element {
let style = cx.global::<Settings>().theme.context_menu.clone(); let style = cx.global::<Settings>().theme.context_menu.clone();
Flex::column() Flex::row()
.with_children( .with_child(
(0..self.items.len()) Flex::column()
.map(|ix| self.render_menu_item::<Tag>(ix, expanded, cx, &style)), .with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { label, .. } => {
let style = style.item.style_for(
&Default::default(),
Some(ix) == self.selected_index,
);
Label::new(label.to_string(), style.label.clone()).boxed()
}
ContextMenuItem::Separator => Empty::new()
.collapsed()
.contained()
.with_style(style.separator)
.constrained()
.with_height(1.)
.boxed(),
}
}))
.boxed(),
)
.with_child(
Flex::column()
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { action, .. } => {
let style = style.item.style_for(
&Default::default(),
Some(ix) == self.selected_index,
);
KeystrokeLabel::new(
action.boxed_clone(),
style.keystroke.container,
style.keystroke.text.clone(),
)
.boxed()
}
ContextMenuItem::Separator => Empty::new()
.collapsed()
.contained()
.with_style(style.separator)
.constrained()
.with_height(1.)
.boxed(),
}
}))
.boxed(),
) )
.contained() .contained()
.with_style(style.container) .with_style(style.container)
} }
fn render_menu_item<T: 'static>( fn render_menu(&self, cx: &mut RenderContext<Self>) -> impl Element {
&self, enum Tag {}
ix: usize, let style = cx.global::<Settings>().theme.context_menu.clone();
expanded: bool, Flex::column()
cx: &mut RenderContext<ContextMenu>, .with_children(self.items.iter().enumerate().map(|(ix, item)| {
style: &theme::ContextMenu, match item {
) -> ElementBox { ContextMenuItem::Item { label, action } => {
match &self.items[ix] { let action = action.boxed_clone();
ContextMenuItem::Item { label, action } => { MouseEventHandler::new::<Tag, _, _>(ix, cx, |state, _| {
let action = action.boxed_clone(); let style =
MouseEventHandler::new::<T, _, _>(ix, cx, |state, _| { style.item.style_for(state, Some(ix) == self.selected_index);
let style = style.item.style_for(state, Some(ix) == self.selected_index); Flex::row()
Flex::row() .with_child(
.with_child(Label::new(label.to_string(), style.label.clone()).boxed()) Label::new(label.to_string(), style.label.clone()).boxed(),
.with_child({ )
let label = KeystrokeLabel::new( .with_child({
action.boxed_clone(), KeystrokeLabel::new(
style.keystroke.container, action.boxed_clone(),
style.keystroke.text.clone(), style.keystroke.container,
); style.keystroke.text.clone(),
if expanded { )
label.flex_float().boxed() .flex_float()
} else { .boxed()
label.boxed() })
} .boxed()
}) })
.on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone()))
.boxed() .boxed()
}) }
.on_click(move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())) ContextMenuItem::Separator => Empty::new()
.boxed() .contained()
} .with_style(style.separator)
ContextMenuItem::Separator => { .constrained()
let mut separator = Empty::new(); .with_height(1.)
if !expanded { .boxed(),
separator = separator.collapsed();
} }
separator }))
.contained() .contained()
.with_style(style.separator) .with_style(style.container)
.constrained()
.with_height(1.)
.boxed()
}
}
} }
} }

View file

@ -1,10 +1,9 @@
import Theme from "../themes/common/theme"; import Theme from "../themes/common/theme";
import { shadow, text } from "./components"; import { backgroundColor, shadow, text } from "./components";
export default function contextMenu(theme: Theme) { export default function contextMenu(theme: Theme) {
return { return {
background: "#ff0000", background: backgroundColor(theme, 300, "base"),
// background: backgroundColor(theme, 300, "base"),
cornerRadius: 6, cornerRadius: 6,
padding: { padding: {
bottom: 2, bottom: 2,
@ -15,7 +14,10 @@ export default function contextMenu(theme: Theme) {
shadow: shadow(theme), shadow: shadow(theme),
item: { item: {
label: text(theme, "sans", "secondary", { size: "sm" }), label: text(theme, "sans", "secondary", { size: "sm" }),
keystroke: text(theme, "sans", "muted", { size: "sm", weight: "bold" }), keystroke: {
margin: { left: 60 },
...text(theme, "sans", "muted", { size: "sm", weight: "bold" })
},
}, },
separator: { separator: {
background: "#00ff00" background: "#00ff00"