menu: Fix sub-menu render position. (#694)
<img width="385" alt="image" src="https://github.com/user-attachments/assets/2e8527af-08af-4176-b4cf-931f79b6d45c" />
This commit is contained in:
parent
bf1420357b
commit
c5416c4774
3 changed files with 201 additions and 204 deletions
35
crates/story/examples/popup.rs
Normal file
35
crates/story/examples/popup.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use gpui::*;
|
||||||
|
use story::{Assets, PopupStory};
|
||||||
|
|
||||||
|
pub struct Example {
|
||||||
|
story: Entity<PopupStory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Example {
|
||||||
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let story = PopupStory::view(window, cx);
|
||||||
|
|
||||||
|
Self { story }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
|
cx.new(|cx| Self::new(window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Example {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div().p_4().size_full().child(self.story.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = Application::new().with_assets(Assets);
|
||||||
|
|
||||||
|
app.run(move |cx| {
|
||||||
|
story::init(cx);
|
||||||
|
cx.activate(true);
|
||||||
|
|
||||||
|
story::create_new_window("Popup Example", Example::view, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ use gpui_component::{
|
||||||
input::TextInput,
|
input::TextInput,
|
||||||
popover::{Popover, PopoverContent},
|
popover::{Popover, PopoverContent},
|
||||||
popup_menu::PopupMenuExt,
|
popup_menu::PopupMenuExt,
|
||||||
switch::Switch,
|
|
||||||
v_flex, ActiveTheme as _, ContextModal, IconName, Sizable,
|
v_flex, ActiveTheme as _, ContextModal, IconName, Sizable,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
@ -19,10 +18,7 @@ use serde::Deserialize;
|
||||||
#[derive(Clone, PartialEq, Deserialize)]
|
#[derive(Clone, PartialEq, Deserialize)]
|
||||||
struct Info(usize);
|
struct Info(usize);
|
||||||
|
|
||||||
actions!(
|
actions!(popover_story, [Copy, Paste, Cut, SearchAll, ToggleCheck]);
|
||||||
popover_story,
|
|
||||||
[Copy, Paste, Cut, SearchAll, ToggleWindowMode]
|
|
||||||
);
|
|
||||||
impl_internal_actions!(popover_story, [Info]);
|
impl_internal_actions!(popover_story, [Info]);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
|
|
@ -86,8 +82,8 @@ impl Render for Form {
|
||||||
pub struct PopupStory {
|
pub struct PopupStory {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
form: Entity<Form>,
|
form: Entity<Form>,
|
||||||
|
checked: bool,
|
||||||
message: String,
|
message: String,
|
||||||
window_mode: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Story for PopupStory {
|
impl super::Story for PopupStory {
|
||||||
|
|
@ -116,9 +112,9 @@ impl PopupStory {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
form,
|
form,
|
||||||
|
checked: true,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
message: "".to_string(),
|
message: "".to_string(),
|
||||||
window_mode: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,31 +122,32 @@ impl PopupStory {
|
||||||
self.message = "You have clicked copy".to_string();
|
self.message = "You have clicked copy".to_string();
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
|
fn on_cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.message = "You have clicked cut".to_string();
|
self.message = "You have clicked cut".to_string();
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
|
fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.message = "You have clicked paste".to_string();
|
self.message = "You have clicked paste".to_string();
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_search_all(&mut self, _: &SearchAll, _: &mut Window, cx: &mut Context<Self>) {
|
fn on_search_all(&mut self, _: &SearchAll, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.message = "You have clicked search all".to_string();
|
self.message = "You have clicked search all".to_string();
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
fn on_toggle_window_mode(
|
|
||||||
&mut self,
|
|
||||||
_: &ToggleWindowMode,
|
|
||||||
_: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.window_mode = !self.window_mode;
|
|
||||||
cx.notify()
|
|
||||||
}
|
|
||||||
fn on_action_info(&mut self, info: &Info, _: &mut Window, cx: &mut Context<Self>) {
|
fn on_action_info(&mut self, info: &Info, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.message = format!("You have clicked info: {}", info.0);
|
self.message = format!("You have clicked info: {}", info.0);
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_action_toggle_check(&mut self, _: &ToggleCheck, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.checked = !self.checked;
|
||||||
|
self.message = format!("You have clicked toggle check: {}", self.checked);
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focusable for PopupStory {
|
impl Focusable for PopupStory {
|
||||||
|
|
@ -162,7 +159,7 @@ impl Focusable for PopupStory {
|
||||||
impl Render for PopupStory {
|
impl Render for PopupStory {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let form = self.form.clone();
|
let form = self.form.clone();
|
||||||
let window_mode = self.window_mode;
|
let checked = self.checked;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
|
@ -170,8 +167,8 @@ impl Render for PopupStory {
|
||||||
.on_action(cx.listener(Self::on_cut))
|
.on_action(cx.listener(Self::on_cut))
|
||||||
.on_action(cx.listener(Self::on_paste))
|
.on_action(cx.listener(Self::on_paste))
|
||||||
.on_action(cx.listener(Self::on_search_all))
|
.on_action(cx.listener(Self::on_search_all))
|
||||||
.on_action(cx.listener(Self::on_toggle_window_mode))
|
|
||||||
.on_action(cx.listener(Self::on_action_info))
|
.on_action(cx.listener(Self::on_action_info))
|
||||||
|
.on_action(cx.listener(Self::on_action_toggle_check))
|
||||||
.p_4()
|
.p_4()
|
||||||
.mb_5()
|
.mb_5()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
@ -183,17 +180,13 @@ impl Render for PopupStory {
|
||||||
.menu("Copy", Box::new(Copy))
|
.menu("Copy", Box::new(Copy))
|
||||||
.menu("Paste", Box::new(Paste))
|
.menu("Paste", Box::new(Paste))
|
||||||
.separator()
|
.separator()
|
||||||
|
.menu_with_check("Toggle Check", checked, Box::new(ToggleCheck))
|
||||||
.separator()
|
.separator()
|
||||||
.submenu("Settings", window, cx, move |menu, _, _| {
|
.submenu("Settings", window, cx, move |menu, _, _| {
|
||||||
menu.menu_with_check(
|
menu.menu("Info 0", Box::new(Info(0)))
|
||||||
"Toggle Window Mode",
|
.separator()
|
||||||
window_mode,
|
.menu("Item 1", Box::new(Info(1)))
|
||||||
Box::new(ToggleWindowMode),
|
.menu("Item 2", Box::new(Info(2)))
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.menu("Info 0", Box::new(Info(0)))
|
|
||||||
.menu("Item 1", Box::new(Info(1)))
|
|
||||||
.menu("Item 2", Box::new(Info(2)))
|
|
||||||
})
|
})
|
||||||
.separator()
|
.separator()
|
||||||
.menu("Search All", Box::new(SearchAll))
|
.menu("Search All", Box::new(SearchAll))
|
||||||
|
|
@ -201,14 +194,6 @@ impl Render for PopupStory {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(
|
|
||||||
Switch::new("switch-window-mode")
|
|
||||||
.checked(window_mode)
|
|
||||||
.label("Use Window Popover")
|
|
||||||
.on_click(cx.listener(|this, checked, _, _| {
|
|
||||||
this.window_mode = *checked;
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|
@ -273,13 +258,9 @@ impl Render for PopupStory {
|
||||||
.menu("Cut", Box::new(Cut))
|
.menu("Cut", Box::new(Cut))
|
||||||
.menu("Paste", Box::new(Paste))
|
.menu("Paste", Box::new(Paste))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_icon("Search", IconName::Search, Box::new(SearchAll))
|
.menu_with_check("Toggle Check", checked, Box::new(ToggleCheck))
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_check(
|
.menu_with_icon("Search", IconName::Search, Box::new(SearchAll))
|
||||||
"Window Mode",
|
|
||||||
window_mode,
|
|
||||||
Box::new(ToggleWindowMode),
|
|
||||||
)
|
|
||||||
.separator()
|
.separator()
|
||||||
.menu_with_element(
|
.menu_with_element(
|
||||||
|_, cx| {
|
|_, cx| {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ use std::rc::Rc;
|
||||||
|
|
||||||
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
|
actions!(menu, [Confirm, Dismiss, SelectNext, SelectPrev]);
|
||||||
|
|
||||||
|
const ITEM_HEIGHT: Pixels = px(26.);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
let context = Some("PopupMenu");
|
let context = Some("PopupMenu");
|
||||||
cx.bind_keys([
|
cx.bind_keys([
|
||||||
|
|
@ -476,6 +478,134 @@ impl PopupMenu {
|
||||||
|
|
||||||
Some(icon)
|
Some(icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_item(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
item: &PopupMenuItem,
|
||||||
|
state: ItemState,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let max_width = self.max_width;
|
||||||
|
let bounds = self.bounds;
|
||||||
|
let has_icon = state.has_icon;
|
||||||
|
let hovered = self.hovered_menu_ix == Some(ix);
|
||||||
|
const EDGE_PADDING: Pixels = px(8.);
|
||||||
|
const INNER_PADDING: Pixels = px(4.);
|
||||||
|
|
||||||
|
let this = ListItem::new(ix)
|
||||||
|
.relative()
|
||||||
|
.text_sm()
|
||||||
|
.py_0()
|
||||||
|
.px(INNER_PADDING)
|
||||||
|
.rounded(state.radius)
|
||||||
|
.items_center()
|
||||||
|
.on_mouse_enter(cx.listener(move |this, _, _, cx| {
|
||||||
|
this.hovered_menu_ix = Some(ix);
|
||||||
|
cx.notify();
|
||||||
|
}));
|
||||||
|
|
||||||
|
match item {
|
||||||
|
PopupMenuItem::Separator => this.h_auto().p_0().disabled(true).child(
|
||||||
|
div()
|
||||||
|
.rounded_none()
|
||||||
|
.h(px(1.))
|
||||||
|
.mx_neg_1()
|
||||||
|
.my_0p5()
|
||||||
|
.bg(cx.theme().muted),
|
||||||
|
),
|
||||||
|
PopupMenuItem::ElementItem { render, .. } => this
|
||||||
|
.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.min_h(ITEM_HEIGHT)
|
||||||
|
.items_center()
|
||||||
|
.gap_x_1()
|
||||||
|
.children(Self::render_icon(has_icon, None, window, cx))
|
||||||
|
.child((render)(window, cx)),
|
||||||
|
),
|
||||||
|
PopupMenuItem::Item {
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
action,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let action = action.as_ref().map(|action| action.boxed_clone());
|
||||||
|
let key = Self::render_keybinding(action, window, cx);
|
||||||
|
|
||||||
|
this.on_click(cx.listener(move |this, _, window, cx| this.on_click(ix, window, cx)))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h(ITEM_HEIGHT)
|
||||||
|
.items_center()
|
||||||
|
.gap_x_1()
|
||||||
|
.children(Self::render_icon(has_icon, icon.clone(), window, cx))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_1()
|
||||||
|
.gap_2()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.child(label.clone())
|
||||||
|
.children(key),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PopupMenuItem::Submenu { icon, label, menu } => this.selected(hovered).child(
|
||||||
|
h_flex()
|
||||||
|
.when(hovered, |this| {
|
||||||
|
this.rounded(cx.theme().radius)
|
||||||
|
.mx(-INNER_PADDING)
|
||||||
|
.px(INNER_PADDING)
|
||||||
|
.bg(cx.theme().accent)
|
||||||
|
.text_color(cx.theme().accent_foreground)
|
||||||
|
})
|
||||||
|
.items_start()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.items_center()
|
||||||
|
.gap_x_1()
|
||||||
|
.children(Self::render_icon(has_icon, icon.clone(), window, cx))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_1()
|
||||||
|
.gap_2()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.child(label.clone())
|
||||||
|
.child(IconName::ChevronRight),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(hovered, |this| {
|
||||||
|
let (anchor, left) =
|
||||||
|
if window.bounds().size.width - bounds.origin.x < max_width {
|
||||||
|
(Corner::TopRight, -px(12.))
|
||||||
|
} else {
|
||||||
|
(Corner::TopLeft, bounds.size.width + px(4.))
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_bottom_pos =
|
||||||
|
bounds.origin.y + bounds.size.height > window.bounds().size.height;
|
||||||
|
|
||||||
|
this.child(
|
||||||
|
anchored()
|
||||||
|
.anchor(anchor)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.occlude()
|
||||||
|
.when(is_bottom_pos, |this| this.bottom_0())
|
||||||
|
.when(!is_bottom_pos, |this| this.top(-px(4.)))
|
||||||
|
.left(left)
|
||||||
|
.child(menu.clone()),
|
||||||
|
)
|
||||||
|
.snap_to_window_with_margin(Edges::all(EDGE_PADDING)),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FluentBuilder for PopupMenu {}
|
impl FluentBuilder for PopupMenu {}
|
||||||
|
|
@ -486,13 +616,17 @@ impl Focusable for PopupMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ItemState {
|
||||||
|
radius: Pixels,
|
||||||
|
has_icon: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for PopupMenu {
|
impl Render for PopupMenu {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let view = cx.entity().clone();
|
let view = cx.entity().clone();
|
||||||
let has_icon = self.menu_items.iter().any(|item| item.has_icon());
|
|
||||||
let items_count = self.menu_items.len();
|
let items_count = self.menu_items.len();
|
||||||
let max_width = self.max_width;
|
|
||||||
let bounds = self.bounds;
|
|
||||||
let max_height = self.max_height.map_or_else(
|
let max_height = self.max_height.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
let window_half_height = window.window_bounds().get_bounds().size.height * 0.5;
|
let window_half_height = window.window_bounds().get_bounds().size.height * 0.5;
|
||||||
|
|
@ -501,8 +635,10 @@ impl Render for PopupMenu {
|
||||||
|height| height,
|
|height| height,
|
||||||
);
|
);
|
||||||
|
|
||||||
const ITEM_HEIGHT: Pixels = px(26.);
|
let item_state = ItemState {
|
||||||
let item_radius = cx.theme().radius.min(px(8.));
|
radius: cx.theme().radius.min(px(8.)),
|
||||||
|
has_icon: self.menu_items.iter().any(|item| item.has_icon()),
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("popup-menu")
|
.id("popup-menu")
|
||||||
|
|
@ -521,7 +657,7 @@ impl Render for PopupMenu {
|
||||||
.p_1()
|
.p_1()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("popup-menu-items")
|
.id("items")
|
||||||
.when(self.scrollable, |this| {
|
.when(self.scrollable, |this| {
|
||||||
this.max_h(max_height)
|
this.max_h(max_height)
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
|
|
@ -543,169 +679,14 @@ impl Render for PopupMenu {
|
||||||
})
|
})
|
||||||
.children(
|
.children(
|
||||||
self.menu_items
|
self.menu_items
|
||||||
.iter_mut()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
// Skip last separator
|
// Skip last separator
|
||||||
.filter(|(ix, item)| {
|
.filter(|(ix, item)| {
|
||||||
!(*ix == items_count - 1 && item.is_separator())
|
!(*ix == items_count - 1 && item.is_separator())
|
||||||
})
|
})
|
||||||
.map(|(ix, item)| {
|
.map(|(ix, item)| {
|
||||||
let this = ListItem::new(("menu-item", ix))
|
self.render_item(ix, item, item_state, window, cx)
|
||||||
.relative()
|
|
||||||
.text_sm()
|
|
||||||
.py_0()
|
|
||||||
.px_1()
|
|
||||||
.rounded(item_radius)
|
|
||||||
.items_center()
|
|
||||||
.on_mouse_enter(cx.listener(move |this, _, _, cx| {
|
|
||||||
this.hovered_menu_ix = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
}));
|
|
||||||
|
|
||||||
match item {
|
|
||||||
PopupMenuItem::Separator => {
|
|
||||||
this.h_auto().p_0().disabled(true).child(
|
|
||||||
div()
|
|
||||||
.rounded_none()
|
|
||||||
.h(px(1.))
|
|
||||||
.mx_neg_1()
|
|
||||||
.my_0p5()
|
|
||||||
.bg(cx.theme().muted),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
PopupMenuItem::ElementItem { render, .. } => this
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, _, window, cx| {
|
|
||||||
this.on_click(ix, window, cx)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.min_h(ITEM_HEIGHT)
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1()
|
|
||||||
.children(Self::render_icon(
|
|
||||||
has_icon, None, window, cx,
|
|
||||||
))
|
|
||||||
.child((render)(window, cx)),
|
|
||||||
),
|
|
||||||
PopupMenuItem::Item {
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
action,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let action = action
|
|
||||||
.as_ref()
|
|
||||||
.map(|action| action.boxed_clone());
|
|
||||||
let key =
|
|
||||||
Self::render_keybinding(action, window, cx);
|
|
||||||
|
|
||||||
this.on_click(cx.listener(
|
|
||||||
move |this, _, window, cx| {
|
|
||||||
this.on_click(ix, window, cx)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.h(ITEM_HEIGHT)
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1()
|
|
||||||
.children(Self::render_icon(
|
|
||||||
has_icon,
|
|
||||||
icon.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_1()
|
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.child(label.clone())
|
|
||||||
.children(key),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
PopupMenuItem::Submenu { icon, label, menu } => this
|
|
||||||
.when(self.hovered_menu_ix == Some(ix), |this| {
|
|
||||||
this.selected(true)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.items_start()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.size_full()
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1()
|
|
||||||
.children(Self::render_icon(
|
|
||||||
has_icon,
|
|
||||||
icon.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_1()
|
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.child(label.clone())
|
|
||||||
.child(
|
|
||||||
IconName::ChevronRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.when_some(
|
|
||||||
self.hovered_menu_ix,
|
|
||||||
|this, hovered_ix| {
|
|
||||||
let (anchor, left) =
|
|
||||||
if window.bounds().size.width
|
|
||||||
- bounds.origin.x
|
|
||||||
< max_width
|
|
||||||
{
|
|
||||||
(Corner::TopRight, -px(15.))
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
Corner::TopLeft,
|
|
||||||
bounds.size.width
|
|
||||||
- px(10.),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let top = if bounds.origin.y
|
|
||||||
+ bounds.size.height
|
|
||||||
> window.bounds().size.height
|
|
||||||
{
|
|
||||||
px(32.)
|
|
||||||
} else {
|
|
||||||
-px(10.)
|
|
||||||
};
|
|
||||||
|
|
||||||
if hovered_ix == ix {
|
|
||||||
this.child(
|
|
||||||
anchored()
|
|
||||||
.anchor(anchor)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.occlude()
|
|
||||||
.top(top)
|
|
||||||
.left(left)
|
|
||||||
.child(menu.clone()),
|
|
||||||
)
|
|
||||||
.snap_to_window_with_margin(
|
|
||||||
Edges::all(px(8.)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue