use pelican_ui::events::{MouseState, MouseEvent, OnEvent, Event};
use pelican_ui::drawable::{Drawable, Component, Image, Color};
use pelican_ui::layout::{Area, SizeRequest, Layout};
use pelican_ui::{Context, Component};
use crate::utils::Callback;
use crate::components::{Icon, Rectangle};
use crate::layout::{Offset, Padding, Size, Stack, Opt};
use super::{ButtonSize, ButtonState, ButtonStyle};
#[derive(Debug, Component)]
pub struct IconButton(Stack, IconButtonContent, Option<Opt<Image>>, #[skip] bool);
impl IconButton {
pub fn new(
ctx: &mut Context,
icon: &'static str,
size: ButtonSize,
style: ButtonStyle,
state: ButtonState,
on_click: Box<dyn FnMut(&mut Context)>,
flair: Option<(&'static str, Color, bool)>, ) -> Self {
let content = IconButtonContent::new(ctx, icon, size, style, state, on_click);
let s = if size == ButtonSize::Large {52.0} else {36.0};
let icon = flair.map(|(i, c, h)| Opt::new(Icon::new(ctx, i, c, s / 1.8), h));
IconButton(Stack(Offset::End, Offset::Start, Size::Fit, Size::Fit, Padding::default()), content, icon, true)
}
pub fn color(&mut self, ctx: &mut Context, state: ButtonState) {
let colors = state.color(ctx, self.1.3);
*self.1.1.background() = colors.background;
self.1.2.color = Some(colors.label);
if let Some(c) = self.1.1.outline() { *c = colors.outline; }
}
pub fn show_flair(&mut self, hide: bool) {if let Some(i) = self.2.as_mut() {i.display(hide);}}
pub fn status(&mut self) -> &mut ButtonState {&mut self.1.4}
pub fn set_trigger_on_press(&mut self, on_press: bool) {self.3 = on_press;}
}
impl OnEvent for IconButton {
fn on_event(&mut self, ctx: &mut Context, event: &mut dyn Event) -> bool {
if let Some(event) = event.downcast_ref::<MouseEvent>() {
if let Some(state) = self.1.4.handle(ctx, *event) {
self.color(ctx, state);
}
if (matches!(event, MouseEvent { state: MouseState::Pressed, position: Some(_) }) && self.3
|| matches!(event, MouseEvent { state: MouseState::Released, position: Some(_)}) && !self.3)
&& matches!(self.1.4, ButtonState::Default | ButtonState::Hover | ButtonState::Pressed) {
ctx.hardware.haptic();
(self.1.5)(ctx);
}
false
} else {true}
}
}
#[derive(Component)]
pub struct IconButtonContent(Stack, Rectangle, Image, #[skip] ButtonStyle, #[skip] ButtonState, #[skip] pub Box<dyn FnMut(&mut Context)>);
impl OnEvent for IconButtonContent {}
impl IconButtonContent {
pub fn new(
ctx: &mut Context,
icon: &'static str,
size: ButtonSize,
style: ButtonStyle,
state: ButtonState,
on_click: Box<dyn FnMut(&mut Context)>,
) -> Self {
let colors = state.color(ctx, style);
let (size, icon_size, radius) = match (style, size) {
(ButtonStyle::Secondary, ButtonSize::Large) => (52.0, 32.0, 12.0),
(ButtonStyle::Secondary, ButtonSize::Medium) => (36.0, 20.0, 8.0),
(ButtonStyle::Ghost, ButtonSize::Large) => (52.0, 48.0, 12.0),
(ButtonStyle::Ghost, ButtonSize::Medium) => (36.0, 32.0, 8.0),
_ => panic!("{style:?} is not a valid IconButton style")
};
let icon = Icon::new(ctx, icon, colors.label, icon_size);
let background = Rectangle::new(colors.background, radius, Some((1.0, colors.outline)));
IconButtonContent(
Stack(Offset::Center, Offset::Center, Size::Static(size), Size::Static(size), Padding::default()),
background, icon, style, state, on_click
)
}
}
impl std::fmt::Debug for IconButtonContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "IconButtonContent(...)")
}
}
impl IconButton {
pub fn secondary(
ctx: &mut Context,
icon: &'static str,
on_click: Callback
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Large,
ButtonStyle::Secondary,
ButtonState::Default,
on_click,
None,
)
}
pub fn secondary_medium(
ctx: &mut Context,
icon: &'static str,
on_click: Callback
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Secondary,
ButtonState::Default,
on_click,
None,
)
}
pub fn ghost(
ctx: &mut Context,
icon: &'static str,
on_click: Callback
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Large,
ButtonStyle::Ghost,
ButtonState::Default,
on_click,
None,
)
}
pub fn ghost_medium(
ctx: &mut Context,
icon: &'static str,
on_click: Callback
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Ghost,
ButtonState::Default,
on_click,
None,
)
}
pub fn input(
ctx: &mut Context,
icon: &'static str,
on_click: impl FnMut(&mut Context) + 'static
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Secondary,
ButtonState::Default,
Box::new(on_click),
None,
)
}
pub fn keyboard(
ctx: &mut Context,
icon: &'static str,
on_click: impl FnMut(&mut Context) + 'static
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Ghost,
ButtonState::Default,
Box::new(on_click),
None,
)
}
pub fn navigation(
ctx: &mut Context,
icon: &'static str,
on_click: impl FnMut(&mut Context) + 'static
) -> Self {
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Ghost,
ButtonState::Default,
Box::new(on_click),
None
)
}
pub fn close(
ctx: &mut Context,
on_click: impl FnMut(&mut Context) + 'static
) -> Self {
IconButton::new(
ctx,
"close",
ButtonSize::Medium,
ButtonStyle::Ghost,
ButtonState::Default,
Box::new(on_click),
None,
)
}
pub fn tab_nav(
ctx: &mut Context,
icon: &'static str,
selected: bool,
on_click: impl FnMut(&mut Context) + 'static,
) -> Self {
let color = ctx.theme.colors.brand;
let state = if selected {ButtonState::Selected} else {ButtonState::UnSelected};
IconButton::new(
ctx,
icon,
ButtonSize::Medium,
ButtonStyle::Ghost,
state,
Box::new(on_click),
Some(("notification", color, false))
)
}
}