ui: Split up ContextMenu::render into smaller methods (#26489)

This PR refactors the `ContextMenu::render` method to extract a couple
smaller methods from it.

The existing `render` method was suffering from its size, with some of
the `match` arms not being able to be formatted with `rustfmt`.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-03-11 18:26:22 -04:00 committed by GitHub
parent 8d6abf6537
commit 55a90f576a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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)
}),
),
),
),
)
}