crates/ui/src/components/context_menu.rs 🔗
@@ -495,6 +495,205 @@ impl ContextMenu {
self._on_blur_subscription = new_subscription;
self
}
+
+ fn render_menu_item(
+ &self,
+ ix: usize,
+ item: &ContextMenuItem,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
+ match item {
+ ContextMenuItem::Separator => ListSeparator.into_any_element(),
+ ContextMenuItem::Header(header) => ListSubHeader::new(header.clone())
+ .inset(true)
+ .into_any_element(),
+ ContextMenuItem::Label(label) => ListItem::new(ix)
+ .inset(true)
+ .disabled(true)
+ .child(Label::new(label.clone()))
+ .into_any_element(),
+ ContextMenuItem::Entry(entry) => self
+ .render_menu_entry(ix, entry, window, cx)
+ .into_any_element(),
+ ContextMenuItem::CustomEntry {
+ entry_render,
+ handler,
+ selectable,
+ } => {
+ let handler = handler.clone();
+ let menu = cx.entity().downgrade();
+ let selectable = *selectable;
+ ListItem::new(ix)
+ .inset(true)
+ .toggle_state(if selectable {
+ Some(ix) == self.selected_index
+ } else {
+ false
+ })
+ .selectable(selectable)
+ .when(selectable, |item| {
+ item.on_click({
+ let context = self.action_context.clone();
+ move |_, window, cx| {
+ handler(context.as_ref(), window, cx);
+ menu.update(cx, |menu, cx| {
+ menu.clicked = true;
+ cx.emit(DismissEvent);
+ })
+ .ok();
+ }
+ })
+ })
+ .child(entry_render(window, cx))
+ .into_any_element()
+ }
+ }
+ }
+
+ fn render_menu_entry(
+ &self,
+ ix: usize,
+ entry: &ContextMenuEntry,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> impl IntoElement {
+ let ContextMenuEntry {
+ toggle,
+ label,
+ handler,
+ icon,
+ icon_position,
+ icon_size,
+ icon_color,
+ action,
+ disabled,
+ documentation_aside,
+ } = entry;
+
+ let handler = handler.clone();
+ let menu = cx.entity().downgrade();
+
+ let icon_color = if *disabled {
+ Color::Muted
+ } else if toggle.is_some() {
+ icon_color.unwrap_or(Color::Accent)
+ } else {
+ icon_color.unwrap_or(Color::Default)
+ };
+
+ let label_color = if *disabled {
+ Color::Disabled
+ } else {
+ Color::Default
+ };
+
+ let label_element = if let Some(icon_name) = icon {
+ h_flex()
+ .gap_1p5()
+ .when(
+ *icon_position == IconPosition::Start && toggle.is_none(),
+ |flex| flex.child(Icon::new(*icon_name).size(*icon_size).color(icon_color)),
+ )
+ .child(Label::new(label.clone()).color(label_color))
+ .when(*icon_position == IconPosition::End, |flex| {
+ flex.child(Icon::new(*icon_name).size(*icon_size).color(icon_color))
+ })
+ .into_any_element()
+ } else {
+ Label::new(label.clone())
+ .color(label_color)
+ .into_any_element()
+ };
+
+ let documentation_aside_callback = documentation_aside.clone();
+
+ div()
+ .id(("context-menu-child", ix))
+ .when_some(
+ documentation_aside_callback.clone(),
+ |this, documentation_aside_callback| {
+ this.occlude()
+ .on_hover(cx.listener(move |menu, hovered, _, cx| {
+ if *hovered {
+ menu.documentation_aside =
+ Some((ix, documentation_aside_callback.clone()));
+ cx.notify();
+ } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix)
+ {
+ menu.documentation_aside = None;
+ cx.notify();
+ }
+ }))
+ },
+ )
+ .child(
+ ListItem::new(ix)
+ .inset(true)
+ .disabled(*disabled)
+ .toggle_state(Some(ix) == self.selected_index)
+ .when_some(*toggle, |list_item, (position, toggled)| {
+ let contents = div()
+ .flex_none()
+ .child(
+ Icon::new(icon.unwrap_or(IconName::Check))
+ .color(icon_color)
+ .size(*icon_size),
+ )
+ .when(!toggled, |contents| contents.invisible());
+
+ match position {
+ IconPosition::Start => list_item.start_slot(contents),
+ IconPosition::End => list_item.end_slot(contents),
+ }
+ })
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(label_element)
+ .debug_selector(|| format!("MENU_ITEM-{}", label))
+ .children(action.as_ref().and_then(|action| {
+ self.action_context
+ .as_ref()
+ .map(|focus| {
+ KeyBinding::for_action_in(&**action, focus, window, cx)
+ })
+ .unwrap_or_else(|| {
+ KeyBinding::for_action(&**action, window, cx)
+ })
+ .map(|binding| {
+ div().ml_4().child(binding).when(
+ *disabled && documentation_aside_callback.is_some(),
+ |parent| parent.invisible(),
+ )
+ })
+ }))
+ .when(
+ *disabled && documentation_aside_callback.is_some(),
+ |parent| {
+ parent.child(
+ Icon::new(IconName::Info)
+ .size(IconSize::XSmall)
+ .color(Color::Muted),
+ )
+ },
+ ),
+ )
+ .on_click({
+ let context = self.action_context.clone();
+ move |_, window, cx| {
+ handler(context.as_ref(), window, cx);
+ menu.update(cx, |menu, cx| {
+ menu.clicked = true;
+ cx.emit(DismissEvent);
+ })
+ .ok();
+ }
+ }),
+ )
+ .into_any_element()
+ }
}
impl ContextMenuItem {
@@ -522,23 +721,21 @@ impl Render for ContextMenu {
.map(|(_, callback)| callback.clone());
h_flex()
- .when(is_wide_window, |this| {this.flex_row()})
- .when(!is_wide_window, |this| {this.flex_col()})
+ .when(is_wide_window, |this| this.flex_row())
+ .when(!is_wide_window, |this| this.flex_col())
.w_full()
.items_start()
.gap_1()
- .child(
- div().children(aside.map(|aside|
- WithRemSize::new(ui_font_size)
- .occlude()
- .elevation_2(cx)
- .p_2()
- .overflow_hidden()
- .when(is_wide_window, |this| {this.max_w_96()})
- .when(!is_wide_window, |this| {this.max_w_48()})
- .child(aside(cx))
- ))
- )
+ .child(div().children(aside.map(|aside| {
+ WithRemSize::new(ui_font_size)
+ .occlude()
+ .elevation_2(cx)
+ .p_2()
+ .overflow_hidden()
+ .when(is_wide_window, |this| this.max_w_96())
+ .when(!is_wide_window, |this| this.max_w_48())
+ .child(aside(cx))
+ })))
.child(
WithRemSize::new(ui_font_size)
.occlude()
@@ -579,229 +776,13 @@ impl Render for ContextMenu {
}
el
})
- .child(List::new().children(self.items.iter_mut().enumerate().map(
- |(ix, item)| {
- match item {
- ContextMenuItem::Separator => {
- ListSeparator.into_any_element()
- }
- ContextMenuItem::Header(header) => {
- ListSubHeader::new(header.clone())
- .inset(true)
- .into_any_element()
- }
- ContextMenuItem::Label(label) => ListItem::new(ix)
- .inset(true)
- .disabled(true)
- .child(Label::new(label.clone()))
- .into_any_element(),
- ContextMenuItem::Entry(ContextMenuEntry {
- toggle,
- label,
- handler,
- icon,
- icon_position,
- icon_size,
- icon_color,
- action,
- disabled,
- documentation_aside,
- }) => {
- let handler = handler.clone();
- let menu = cx.entity().downgrade();
-
- let icon_color = if *disabled {
- Color::Muted
- } else if toggle.is_some() {
- icon_color.unwrap_or(Color::Accent)
- } else {
- icon_color.unwrap_or(Color::Default)
- };
-
- let label_color = if *disabled {
- Color::Disabled
- } else {
- Color::Default
- };
-
- let label_element = if let Some(icon_name) = icon {
- h_flex()
- .gap_1p5()
- .when(
- *icon_position == IconPosition::Start && toggle.is_none(),
- |flex| {
- flex.child(
- Icon::new(*icon_name)
- .size(*icon_size)
- .color(icon_color),
- )
- },
- )
- .child(
- Label::new(label.clone())
- .color(label_color),
- )
- .when(
- *icon_position == IconPosition::End,
- |flex| {
- flex.child(
- Icon::new(*icon_name)
- .size(*icon_size)
- .color(icon_color),
- )
- },
- )
- .into_any_element()
- } else {
- Label::new(label.clone())
- .color(label_color)
- .into_any_element()
- };
-
- let documentation_aside_callback =
- documentation_aside.clone();
-
- div()
- .id(("context-menu-child", ix))
- .when_some(
- documentation_aside_callback.clone(),
- |this, documentation_aside_callback| {
- this.occlude().on_hover(cx.listener(
- move |menu, hovered, _, cx| {
- if *hovered {
- menu.documentation_aside = Some((ix, documentation_aside_callback.clone()));
- cx.notify();
- } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix) {
- menu.documentation_aside = None;
- cx.notify();
- }
- },
- ))
- },
- )
- .child(
- ListItem::new(ix)
- .inset(true)
- .disabled(*disabled)
- .toggle_state(
- Some(ix) == self.selected_index,
- )
- .when_some(
- *toggle,
- |list_item, (position, toggled)| {
- let contents =
- div().flex_none().child(
- Icon::new(icon.unwrap_or(IconName::Check))
- .color(icon_color)
- .size(*icon_size)
- )
- .when(!toggled, |contents|
- contents.invisible()
- );
-
- match position {
- IconPosition::Start => {
- list_item
- .start_slot(contents)
- }
- IconPosition::End => {
- list_item.end_slot(contents)
- }
- }
- },
- )
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .child(label_element)
- .debug_selector(|| {
- format!("MENU_ITEM-{}", label)
- })
- .children(
- action.as_ref().and_then(
- |action| {
- self.action_context
- .as_ref()
- .map(|focus| {
- KeyBinding::for_action_in(
- &**action, focus,
- window,
- cx
- )
- })
- .unwrap_or_else(|| {
- KeyBinding::for_action(
- &**action, window, cx
- )
- })
- .map(|binding| {
- div().ml_4().child(binding)
- .when(*disabled && documentation_aside_callback.is_some(), |parent| {
- parent.invisible()
- })
- })
- },
- ),
- )
- .when(*disabled && documentation_aside_callback.is_some(), |parent| {
- parent.child(Icon::new(IconName::Info).size(IconSize::XSmall).color(Color::Muted))
- }),
- )
- .on_click({
- let context =
- self.action_context.clone();
- move |_, window, cx| {
- handler(
- context.as_ref(),
- window,
- cx,
- );
- menu.update(cx, |menu, cx| {
- menu.clicked = true;
- cx.emit(DismissEvent);
- })
- .ok();
- }
- }),
- )
- .into_any_element()
- }
- ContextMenuItem::CustomEntry {
- entry_render,
- handler,
- selectable,
- } => {
- let handler = handler.clone();
- let menu = cx.entity().downgrade();
- let selectable = *selectable;
- ListItem::new(ix)
- .inset(true)
- .toggle_state(if selectable {
- Some(ix) == self.selected_index
- } else {
- false
- })
- .selectable(selectable)
- .when(selectable, |item| {
- item.on_click({
- let context = self.action_context.clone();
- move |_, window, cx| {
- handler(context.as_ref(), window, cx);
- menu.update(cx, |menu, cx| {
- menu.clicked = true;
- cx.emit(DismissEvent);
- })
- .ok();
- }
- })
- })
- .child(entry_render(window, cx))
- .into_any_element()
- }
- }
- },
- )))
+ .child(
+ List::new().children(
+ self.items.iter().enumerate().map(|(ix, item)| {
+ self.render_menu_item(ix, item, window, cx)
+ }),
+ ),
+ ),
),
)
}