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

Marshall Bowers created

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

Change summary

crates/ui/src/components/context_menu.rs | 455 ++++++++++++-------------
1 file changed, 218 insertions(+), 237 deletions(-)

Detailed changes

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