use std::panic::Location;
use crate::cursor::Cursor;
use crate::metrics::MetricsRole;
use crate::style::StyleProfile;
use crate::tokens;
use crate::tree::*;
use crate::widgets::popover::{Anchor, popover};
use crate::widgets::separator::separator;
use crate::widgets::text::{mono, text};
use crate::{IntoIconSource, icon};
#[track_caller]
pub fn dropdown_menu(
key: impl Into<String>,
trigger_key: impl Into<String>,
children: impl IntoIterator<Item = impl Into<El>>,
) -> El {
popover(
key,
Anchor::below_key(trigger_key),
dropdown_menu_content(children),
)
}
#[track_caller]
pub fn dropdown_menu_content<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
El::new(Kind::Custom("dropdown_menu_content"))
.at_loc(Location::caller())
.style_profile(StyleProfile::Surface)
.metrics_role(MetricsRole::Panel)
.surface_role(SurfaceRole::Popover)
.arrow_nav_siblings()
.children(children)
.fill(tokens::POPOVER)
.stroke(tokens::BORDER)
.radius(0.0)
.shadow(tokens::SHADOW_MD)
.padding(Sides::zero())
.gap(0.0)
.width(Size::Hug)
.height(Size::Hug)
.axis(Axis::Column)
.align(Align::Stretch)
}
#[track_caller]
pub fn dropdown_menu_group<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
column(children)
.at_loc(Location::caller())
.width(Size::Fill(1.0))
.height(Size::Hug)
.gap(0.0)
}
#[track_caller]
pub fn dropdown_menu_label(label: impl Into<String>) -> El {
text(label)
.at_loc(Location::caller())
.caption()
.semibold()
.color(tokens::MUTED_FOREGROUND)
.padding(Sides::xy(tokens::SPACE_2, tokens::SPACE_1))
.width(Size::Fill(1.0))
}
#[track_caller]
pub fn dropdown_menu_separator() -> El {
column([separator()])
.at_loc(Location::caller())
.padding(Sides {
left: 0.0,
right: 0.0,
top: tokens::SPACE_1,
bottom: tokens::SPACE_1,
})
.width(Size::Fill(1.0))
.height(Size::Hug)
}
#[track_caller]
pub fn dropdown_menu_item<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
El::new(Kind::Custom("dropdown_menu_item"))
.at_loc(Location::caller())
.style_profile(StyleProfile::Solid)
.metrics_role(MetricsRole::MenuItem)
.focusable()
.cursor(Cursor::Pointer)
.children(children)
.fill(tokens::POPOVER)
.default_padding(Sides::xy(tokens::SPACE_3, 0.0))
.default_gap(tokens::SPACE_2)
.width(Size::Fill(1.0))
.default_height(Size::Fixed(30.0))
.axis(Axis::Row)
.align(Align::Center)
.justify(Justify::Start)
}
#[track_caller]
pub fn dropdown_menu_item_label(label: impl Into<String>) -> El {
text(label)
.at_loc(Location::caller())
.label()
.font_weight(FontWeight::Regular)
.ellipsis()
.width(Size::Fill(1.0))
}
#[track_caller]
pub fn dropdown_menu_icon(source: impl IntoIconSource) -> El {
icon(source)
.at_loc(Location::caller())
.icon_size(tokens::ICON_SM)
.color(tokens::MUTED_FOREGROUND)
}
#[track_caller]
pub fn dropdown_menu_shortcut(shortcut: impl Into<String>) -> El {
mono(shortcut)
.at_loc(Location::caller())
.caption()
.color(tokens::MUTED_FOREGROUND)
.width(Size::Hug)
}
#[track_caller]
pub fn dropdown_menu_item_with_shortcut(
label: impl Into<String>,
shortcut: impl Into<String>,
) -> El {
dropdown_menu_item([
dropdown_menu_item_label(label),
dropdown_menu_shortcut(shortcut),
])
.at_loc(Location::caller())
}
#[track_caller]
pub fn dropdown_menu_item_with_icon(source: impl IntoIconSource, label: impl Into<String>) -> El {
dropdown_menu_item([dropdown_menu_icon(source), dropdown_menu_item_label(label)])
.at_loc(Location::caller())
}
#[track_caller]
pub fn dropdown_menu_item_with_icon_and_shortcut(
source: impl IntoIconSource,
label: impl Into<String>,
shortcut: impl Into<String>,
) -> El {
dropdown_menu_item([
dropdown_menu_icon(source),
dropdown_menu_item_label(label),
dropdown_menu_shortcut(shortcut),
])
.at_loc(Location::caller())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dropdown_menu_wraps_content_in_popover() {
let menu = dropdown_menu(
"actions",
"actions-trigger",
[dropdown_menu_item_with_shortcut("Copy", "Ctrl+C")],
);
assert_eq!(menu.kind, Kind::Overlay);
assert_eq!(menu.children[0].key.as_deref(), Some("actions:dismiss"));
assert_eq!(
menu.children[1].children[0].kind,
Kind::Custom("dropdown_menu_content")
);
}
#[test]
fn dropdown_menu_item_uses_menu_density_and_row_alignment() {
let item = dropdown_menu_item_with_icon_and_shortcut("copy", "Copy", "Ctrl+C");
assert_eq!(item.kind, Kind::Custom("dropdown_menu_item"));
assert_eq!(item.metrics_role, Some(MetricsRole::MenuItem));
assert_eq!(item.axis, Axis::Row);
assert_eq!(item.align, Align::Center);
assert_eq!(item.children.len(), 3);
assert_eq!(item.children[0].width, Size::Fixed(tokens::ICON_SM));
assert_eq!(item.children[1].text.as_deref(), Some("Copy"));
assert_eq!(item.children[1].width, Size::Fill(1.0));
assert_eq!(item.children[2].text.as_deref(), Some("Ctrl+C"));
}
#[test]
fn dropdown_menu_label_and_separator_are_structural_items() {
let label = dropdown_menu_label("Actions");
let sep = dropdown_menu_separator();
assert_eq!(label.text.as_deref(), Some("Actions"));
assert_eq!(label.text_role, TextRole::Caption);
assert_eq!(sep.kind, Kind::Group);
assert_eq!(sep.children[0].kind, Kind::Divider);
assert_eq!(sep.padding.top, tokens::SPACE_1);
}
}