use freya_core::prelude::*;
use torin::{
content::Content,
prelude::{
Alignment,
Position,
},
size::Size,
};
use crate::{
get_theme,
theming::component_themes::{
MenuContainerThemePartial,
MenuItemThemePartial,
},
};
#[derive(Default, Clone, PartialEq)]
pub struct Menu {
children: Vec<Element>,
on_close: Option<EventHandler<()>>,
key: DiffKey,
}
impl ChildrenExt for Menu {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
}
impl KeyExt for Menu {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl Menu {
pub fn new() -> Self {
Self::default()
}
pub fn on_close<F>(mut self, f: F) -> Self
where
F: Into<EventHandler<()>>,
{
self.on_close = Some(f.into());
self
}
}
impl RenderOwned for Menu {
fn render(self) -> impl IntoElement {
use_provide_context(|| State::create(ROOT_MENU.0));
use_provide_context::<State<Vec<MenuId>>>(|| State::create(vec![ROOT_MENU]));
use_provide_context(|| ROOT_MENU);
rect()
.corner_radius(8.0)
.on_press(move |ev: Event<PressEventData>| {
ev.stop_propagation();
})
.on_global_mouse_up(move |_| {
if let Some(on_close) = &self.on_close {
on_close.call(());
}
})
.child(MenuContainer::new().children(self.children))
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(Default, Clone, PartialEq)]
pub struct MenuContainer {
pub(crate) theme: Option<MenuContainerThemePartial>,
children: Vec<Element>,
key: DiffKey,
}
impl KeyExt for MenuContainer {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl ChildrenExt for MenuContainer {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
}
impl MenuContainer {
pub fn new() -> Self {
Self::default()
}
}
impl RenderOwned for MenuContainer {
fn render(self) -> impl IntoElement {
let focus = use_focus();
let theme = get_theme!(self.theme, menu_container);
use_provide_context(move || focus.a11y_id());
rect()
.a11y_id(focus.a11y_id())
.a11y_member_of(focus.a11y_id())
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Menu)
.position(Position::new_absolute())
.shadow((0.0, 4.0, 10.0, 0., theme.shadow))
.background(theme.background)
.corner_radius(theme.corner_radius)
.padding(theme.padding)
.border(Border::new().width(1.).fill(theme.border_fill))
.content(Content::fit())
.children(self.children)
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(Default, Clone, PartialEq)]
pub struct MenuItem {
pub(crate) theme: Option<MenuItemThemePartial>,
children: Vec<Element>,
on_press: Option<EventHandler<Event<PressEventData>>>,
on_pointer_enter: Option<EventHandler<Event<PointerEventData>>>,
key: DiffKey,
}
impl KeyExt for MenuItem {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl MenuItem {
pub fn new() -> Self {
Self::default()
}
pub fn on_press<F>(mut self, f: F) -> Self
where
F: Into<EventHandler<Event<PressEventData>>>,
{
self.on_press = Some(f.into());
self
}
pub fn on_pointer_enter<F>(mut self, f: F) -> Self
where
F: Into<EventHandler<Event<PointerEventData>>>,
{
self.on_pointer_enter = Some(f.into());
self
}
}
impl ChildrenExt for MenuItem {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
}
impl RenderOwned for MenuItem {
fn render(self) -> impl IntoElement {
let theme = get_theme!(self.theme, menu_item);
let mut hovering = use_state(|| false);
let focus = use_focus();
let focus_status = use_focus_status(focus);
let menu_group = use_consume::<AccessibilityId>();
let background = if focus_status() == FocusStatus::Keyboard || *hovering.read() {
theme.hover_background
} else {
Color::TRANSPARENT
};
let on_pointer_enter = move |e| {
hovering.set(true);
if let Some(on_pointer_enter) = &self.on_pointer_enter {
on_pointer_enter.call(e);
}
};
let on_pointer_leave = move |_| {
hovering.set(false);
};
let on_press = move |e: Event<PressEventData>| {
e.stop_propagation();
e.prevent_default();
focus.request_focus();
if let Some(on_press) = &self.on_press {
on_press.call(e);
}
};
rect()
.a11y_role(AccessibilityRole::MenuItem)
.a11y_id(focus.a11y_id())
.a11y_focusable(true)
.a11y_member_of(menu_group)
.min_width(Size::px(105.))
.width(Size::fill_minimum())
.padding((4.0, 10.0))
.corner_radius(theme.corner_radius)
.background(background)
.color(theme.color)
.text_align(TextAlign::Start)
.main_align(Alignment::Center)
.on_pointer_enter(on_pointer_enter)
.on_pointer_leave(on_pointer_leave)
.on_press(on_press)
.children(self.children)
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(Default, Clone, PartialEq)]
pub struct MenuButton {
children: Vec<Element>,
on_press: Option<EventHandler<()>>,
key: DiffKey,
}
impl ChildrenExt for MenuButton {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
}
impl KeyExt for MenuButton {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl MenuButton {
pub fn new() -> Self {
Self::default()
}
pub fn on_press(mut self, on_press: impl Into<EventHandler<()>>) -> Self {
self.on_press = Some(on_press.into());
self
}
}
impl RenderOwned for MenuButton {
fn render(self) -> impl IntoElement {
let mut menus = use_consume::<State<Vec<MenuId>>>();
let parent_menu_id = use_consume::<MenuId>();
MenuItem::new()
.on_pointer_enter(move |_| close_menus_until(&mut menus, parent_menu_id))
.on_press(move |_| {
if let Some(on_press) = &self.on_press {
on_press.call(());
}
})
.children(self.children)
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(Default, Clone, PartialEq)]
pub struct SubMenu {
label: Option<Element>,
items: Vec<Element>,
key: DiffKey,
}
impl KeyExt for SubMenu {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl SubMenu {
pub fn new() -> Self {
Self::default()
}
pub fn label(mut self, label: impl IntoElement) -> Self {
self.label = Some(label.into_element());
self
}
}
impl ChildrenExt for SubMenu {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.items
}
}
impl RenderOwned for SubMenu {
fn render(self) -> impl IntoElement {
let parent_menu_id = use_consume::<MenuId>();
let mut menus = use_consume::<State<Vec<MenuId>>>();
let mut menus_ids_generator = use_consume::<State<usize>>();
let submenu_id = use_hook(|| {
*menus_ids_generator.write() += 1;
let menu_id = MenuId(*menus_ids_generator.peek());
provide_context(menu_id);
menu_id
});
let show_submenu = menus.read().contains(&submenu_id);
let onmouseenter = move |_| {
close_menus_until(&mut menus, parent_menu_id);
push_menu(&mut menus, submenu_id);
};
let onpress = move |_| {
close_menus_until(&mut menus, parent_menu_id);
push_menu(&mut menus, submenu_id);
};
MenuItem::new()
.on_pointer_enter(onmouseenter)
.on_press(onpress)
.child(rect().horizontal().maybe_child(self.label.clone()))
.maybe_child(show_submenu.then(|| {
rect()
.position(Position::new_absolute().top(-8.).right(-10.))
.width(Size::px(0.))
.height(Size::px(0.))
.child(
rect()
.width(Size::window_percent(100.))
.child(MenuContainer::new().children(self.items)),
)
}))
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
static ROOT_MENU: MenuId = MenuId(0);
#[derive(Clone, Copy, PartialEq, Eq)]
struct MenuId(usize);
fn close_menus_until(menus: &mut State<Vec<MenuId>>, until: MenuId) {
menus.write().retain(|&id| id.0 <= until.0);
}
fn push_menu(menus: &mut State<Vec<MenuId>>, id: MenuId) {
if !menus.read().contains(&id) {
menus.write().push(id);
}
}