use std::fmt::Display;
use crate::icons::ArrowIcon;
use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_elements::events::keyboard::Key;
use freya_elements::events::{KeyboardEvent, MouseEvent};
use freya_hooks::{
theme_with, use_applied_theme, use_focus, use_platform, ArrowIconThemeWith,
DropdownItemThemeWith, DropdownTheme, DropdownThemeWith,
};
use winit::window::CursorIcon;
#[derive(Props)]
pub struct DropdownItemProps<'a, T: 'static> {
pub theme: Option<DropdownItemThemeWith>,
pub children: Element<'a>,
pub value: T,
#[props(optional)]
pub onclick: Option<EventHandler<'a, ()>>,
}
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum DropdownItemStatus {
#[default]
Idle,
Hovering,
}
#[allow(non_snake_case)]
pub fn DropdownItem<'a, T>(cx: Scope<'a, DropdownItemProps<'a, T>>) -> Element<'a>
where
T: PartialEq + 'static,
{
let selected = use_shared_state::<T>(cx).unwrap();
let theme = use_applied_theme!(cx, &cx.props.theme, dropdown_item);
let focus = use_focus(cx);
let status = use_state(cx, DropdownItemStatus::default);
let platform = use_platform(cx);
let focus_id = focus.attribute(cx);
let is_focused = focus.is_focused();
let is_selected = *selected.read() == cx.props.value;
let background = match *status.get() {
_ if is_selected => theme.select_background,
_ if is_focused => theme.hover_background,
DropdownItemStatus::Hovering => theme.hover_background,
DropdownItemStatus::Idle => theme.background,
};
let color = theme.font_theme.color;
use_on_destroy(cx, {
to_owned![status, platform];
move || {
if *status.current() == DropdownItemStatus::Hovering {
platform.set_cursor(CursorIcon::default());
}
}
});
let onclick = move |_: MouseEvent| {
if let Some(onclick) = &cx.props.onclick {
onclick.call(())
}
};
let onmouseenter = {
to_owned![platform];
move |_| {
platform.set_cursor(CursorIcon::Pointer);
status.set(DropdownItemStatus::Hovering);
}
};
let onmouseleave = move |_| {
platform.set_cursor(CursorIcon::default());
status.set(DropdownItemStatus::default());
};
let onkeydown = move |ev: KeyboardEvent| {
if ev.key == Key::Enter && is_focused {
if let Some(onclick) = &cx.props.onclick {
onclick.call(())
}
}
};
render!(
rect {
color: "{color}",
focus_id: focus_id,
role: "button",
background: "{background}",
padding: "6 22 6 16",
corner_radius: "6",
main_align: "center",
cross_align: "center",
onmouseenter: onmouseenter,
onmouseleave: onmouseleave,
onclick: onclick,
onkeydown: onkeydown,
&cx.props.children
}
)
}
#[derive(Props)]
pub struct DropdownProps<'a, T: 'static> {
pub theme: Option<DropdownThemeWith>,
pub children: Element<'a>,
pub value: T,
}
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum DropdownStatus {
#[default]
Idle,
Hovering,
}
#[allow(non_snake_case)]
pub fn Dropdown<'a, T>(cx: Scope<'a, DropdownProps<'a, T>>) -> Element<'a>
where
T: PartialEq + Clone + Display + 'static,
{
use_shared_state_provider(cx, || cx.props.value.clone());
let selected = use_shared_state::<T>(cx).unwrap();
let theme = use_applied_theme!(cx, &cx.props.theme, dropdown);
let focus = use_focus(cx);
let status = use_state(cx, DropdownStatus::default);
let opened = use_state(cx, || false);
let platform = use_platform(cx);
let is_opened = *opened.get();
let is_focused = focus.is_focused();
let focus_id = focus.attribute(cx);
let _ = use_memo(cx, &cx.props.value, move |value| {
*selected.write() = value;
});
use_on_destroy(cx, {
to_owned![status, platform];
move || {
if *status.current() == DropdownStatus::Hovering {
platform.set_cursor(CursorIcon::default());
}
}
});
let onglobalclick = move |_: MouseEvent| {
opened.set(false);
};
let onclick = move |_| {
focus.focus();
opened.set(true)
};
let onkeydown = move |e: KeyboardEvent| {
match e.key {
Key::Escape => {
opened.set(false);
}
Key::Enter if is_focused && !is_opened => {
opened.set(true);
}
_ => {}
}
};
let onmouseenter = {
to_owned![status, platform];
move |_| {
platform.set_cursor(CursorIcon::Pointer);
status.set(DropdownStatus::Hovering);
}
};
let onmouseleave = move |_| {
platform.set_cursor(CursorIcon::default());
status.set(DropdownStatus::default());
};
let DropdownTheme {
font_theme,
dropdown_background,
background_button,
hover_background,
border_fill,
arrow_fill,
} = &theme;
let button_background = match *status.get() {
DropdownStatus::Hovering => hover_background,
DropdownStatus::Idle => background_button,
};
let selected = selected.read().to_string();
render!(
rect {
onmouseenter: onmouseenter,
onmouseleave: onmouseleave,
onclick: onclick,
onkeydown: onkeydown,
margin: "4",
focus_id: focus_id,
background: "{button_background}",
color: "{font_theme.color}",
corner_radius: "8",
padding: "8 16",
border: "1 solid {border_fill}",
shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)",
direction: "horizontal",
main_align: "center",
cross_align: "center",
label {
text_align: "center",
"{selected}"
}
ArrowIcon {
rotate: "0",
fill: "{arrow_fill}",
theme: theme_with!(ArrowIconTheme {
margin : "0 0 0 8".into(),
})
}
}
if *opened.get() {
rsx!(
rect {
height: "0",
rect {
onglobalclick: onglobalclick,
onkeydown: onkeydown,
layer: "-99",
margin: "4",
border: "1 solid {border_fill}",
overflow: "clip",
corner_radius: "8",
background: "{dropdown_background}",
shadow: "0 4 5 0 rgb(0, 0, 0, 0.3)",
padding: "6",
&cx.props.children
}
}
)
}
)
}