use button_css::input_style;
use gloo_timers::future::TimeoutFuture;
use web_sys::HtmlElement;
use yew::platform::spawn_local;
use yew::prelude::*;
use wasm_bindgen::JsCast;
use crate::{GButtonStyle, GIconStyle, icons::GIcon};
mod button_css;
#[derive(Default, PartialEq)]
pub enum DependsOn {
GTextInput,
#[default]
None,
}
pub enum Msg {
OnPointerDown(PointerEvent),
OnKeyPress(KeyboardEvent),
OnPointerUp(PointerEvent),
}
#[derive(Properties, PartialEq)]
pub struct GButtonProps {
pub id: AttrValue,
#[prop_or_default]
pub label: AttrValue,
#[prop_or_else(|| AttrValue::from("submit"))]
pub button_type: AttrValue,
#[prop_or_default]
pub style: GButtonStyle,
#[prop_or_default]
pub outlined_border_color: Option<AttrValue>,
#[prop_or_else(|| AttrValue::from("14px"))]
pub font_size: AttrValue,
#[prop_or_default]
pub onclick: Option<Callback<PointerEvent>>,
#[prop_or_default]
pub class: AttrValue,
#[prop_or_else(|| AttrValue::from("2.85em"))]
pub height: AttrValue,
#[prop_or_default]
pub width: Option<AttrValue>,
#[prop_or_default]
pub children: Html,
#[prop_or_default]
pub parent: DependsOn,
#[prop_or_else(|| AttrValue::from("#6750A4"))]
pub background_color: AttrValue,
#[prop_or_else(|| AttrValue::from("#FFFFFF"))]
pub label_color: AttrValue,
#[prop_or_else(|| AttrValue::from("20px"))]
pub border_radius: AttrValue,
#[prop_or_default]
pub has_icon: Option<AttrValue>,
#[prop_or_default]
pub trailing_icon: bool,
#[prop_or_default]
pub icon_style: Option<GIconStyle>,
#[prop_or_default]
pub autofocus: bool,
#[prop_or_else(|| false )]
pub fill: bool,
#[prop_or_else(|| AttrValue::from("300"))]
pub wght: AttrValue,
#[prop_or_else(|| AttrValue::from("100"))]
pub grade: AttrValue,
#[prop_or_else(|| AttrValue::from("24"))]
pub opsz: AttrValue,
#[prop_or_default]
pub dark_theame: bool,
#[prop_or_default]
pub disabled: bool,
}
pub struct GButton {
button: NodeRef,
only_icon: bool,
leading_icon: bool,
pointer_id: Option<i32>,
button_node: NodeRef,
}
impl Component for GButton {
type Message = Msg;
type Properties = GButtonProps;
fn create(ctx: &Context<Self>) -> Self {
let only_icon: bool = if ctx.props().label == AttrValue::default() {true} else {false};
let leading_icon: bool = if ctx.props().has_icon.is_none() {
false
} else if ctx.props().trailing_icon {
false
} else {
true
};
Self {
button: NodeRef::default(),
only_icon,
leading_icon,
pointer_id: None,
button_node: NodeRef::default(),
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::OnPointerDown(event) => {
let onmouse = true;
let button = self.button.cast::<HtmlElement>().unwrap();
self.pointer_id = Some(event.pointer_id());
button.set_pointer_capture(self.pointer_id.unwrap()).unwrap();
let button_rect = button.get_bounding_client_rect();
let x = {
let x = event.client_x() - button_rect.left().round() as i32;
format!("{x}px")
};
let y = {
let y = event.client_y() - button_rect.top().round() as i32;
format!("{y}px")
};
if !ctx.props().disabled {
ripple_effect(onmouse, &x, &y, button, &ctx.props().id);
}
},
Msg::OnKeyPress(event) => {
if event.key() == "Enter" {
let onmouse = false;
let button = self.button.cast::<HtmlElement>().unwrap();
let x = {
let x = button.offset_width() / 2;
format!("{x}px")
};
let y = {
let y = button.offset_height() / 2;
format!("{y}px")
};
if !ctx.props().disabled {
ripple_effect(onmouse, &x, &y, button, &ctx.props().id);
}
if let Some(onclick) = ctx.props().onclick.as_ref() {
onclick.emit(PointerEvent::new("pointerup").expect("Key to Pointer fail"));
}
}
},
Msg::OnPointerUp(event) => {
if self.pointer_id.is_some() {
let g_span_ripple_selector = AttrValue::from(format!("span#g_init_span{}", ctx.props().id));
if let Some(span) = self.button.cast::<HtmlElement>().unwrap().query_selector(&g_span_ripple_selector).unwrap() {
span.remove()
}
self.button.cast::<HtmlElement>().unwrap().release_pointer_capture(self.pointer_id.expect("No button pointer id")).unwrap();
if let Some(value) = ctx.props().onclick.as_ref() {
value.emit(event)
}
self.pointer_id = None;
}
},
}
false
}
fn view(&self, ctx: &Context<Self>) -> Html {
let g_init = AttrValue::from(format!("g_init_{}", ctx.props().id));
let has_icon = if ctx.props().has_icon.is_some() { true } else { false };
let stylesheet = input_style(
&ctx.props().style,
&ctx.props().id,
self.only_icon,
&g_init,
ctx.props().font_size.clone(),
ctx.props().height.clone(),
&ctx.props().width,
&ctx.props().background_color,
ctx.props().label_color.clone(),
&ctx.props().outlined_border_color,
ctx.props().border_radius.clone(),
ctx.props().disabled,
has_icon,
ctx.props().trailing_icon,
ctx.props().dark_theame,
&ctx.props().parent,
);
let onpointerdown = ctx.link().callback(|event: PointerEvent| Msg::OnPointerDown(event));
let onkeydown = ctx.link().callback(|event: KeyboardEvent| Msg::OnKeyPress(event));
let onpointerup = ctx.link().callback(|event: PointerEvent| Msg::OnPointerUp(event));
html! {
<gbutton ref={&self.button_node} style="line-height: 0">
<stl class={stylesheet}>
<div id={g_init}>
<button
id={ctx.props().id.clone()}
type={ctx.props().button_type.clone()}
ref={&self.button}
class={&ctx.props().class}
{onpointerdown}
{onkeydown}
{onpointerup}
aria-label={ctx.props().id.clone()}
disabled={ctx.props().disabled}
autofocus={ctx.props().autofocus}
>
{&ctx.props().label}
</button>
if ctx.props().has_icon.is_some() {
<GIcon
icon={ctx.props().has_icon.clone().unwrap()}
icon_style={ctx.props().icon_style.clone().unwrap()}
fill={ctx.props().fill}
wght={&ctx.props().wght}
grade={&ctx.props().grade}
opsz={&ctx.props().opsz}
leading_icon={self.leading_icon}
trailing_icon={ctx.props().trailing_icon}
/>
}
{ctx.props().children.clone()}
</div>
</stl>
</gbutton>
}
}
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
if first_render {
match ctx.props().parent {
DependsOn::GTextInput => {
let button = self.button_node.cast::<HtmlElement>().unwrap();
let input_height = button.parent_element().unwrap().first_element_child().unwrap().client_height() as f64;
let button_height = button.query_selector("button").unwrap().unwrap().client_height() as f64;
let icon_margin_top_and_side = (input_height - button_height) / 2.0 + 1.0;
let button_align = if self.leading_icon {
"left"
} else {
"right"
};
let css = format!(r#"
display: block;
position: absolute;
top: {icon_margin_top_and_side}px;
{button_align}: 0.25em;
"#);
button.style().set_css_text(&css);
},
DependsOn::None => (),
}
}
}
}
fn ripple_effect(onmouse: bool, x: &str, y: &str, button: HtmlElement, id: &AttrValue) {
let span = button
.owner_document()
.unwrap()
.create_element("span")
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap();
let g_span_ripple = AttrValue::from(format!("g_init_span{}", id));
span.set_id(&g_span_ripple);
span.style().set_property("left", &x).unwrap();
span.style().set_property("top", &y).unwrap();
button.append_child(&span).unwrap();
if !onmouse {
spawn_local(async move {
TimeoutFuture::new(300).await;
span.remove()
})
}
}