lerni 0.0.6

Lerni content framework
Documentation
use leptos::prelude::*;
use web_sys::MouseEvent;

use crate::{Align, Color, Frame, Label, Size, VAlign, provide_frame, use_frame};

const WIDTH: i32 = 400;
const HEIGHT: i32 = 150;

#[component]
pub fn Button<F>(
    #[prop(optional)] on_click: Option<F>,
    #[prop(optional, into)] on_mousedown: Option<Callback<MouseEvent>>,
    #[prop(optional, into)] on_mouseup: Option<Callback<MouseEvent>>,
    #[prop(optional)] text_bold: bool,
    #[prop(optional, into)] width: Option<Size>,
    #[prop(optional, into)] height: Option<Size>,
    #[prop(optional, into)] radius: Option<i32>,
    #[prop(default = false)] rounded: bool,
    #[prop(optional, into)] font: String,
    #[prop(default = 48.into(), into)] font_size: Size,
    #[prop(default = Color::AliceBlue.into(), into)] color: Signal<Color>,
    #[prop(default = Color::Black.into(), into)] text_color: Signal<Color>,
    #[prop(default = 12.into(), into)] border_width: Signal<i32>,
    #[prop(default = Color::RoyalBlue.into(), into)] border_color: Signal<Color>,
    #[prop(default = Align::Center)] align: Align,
    #[prop(default = VAlign::Middle)] valign: VAlign,
    #[prop(optional)] margin: i32,
    #[prop(optional, into)] image: Signal<String>,
    #[prop(default = true.into(), into)] visible: Signal<bool>,
    #[prop(default = "all .3s".to_string(), into)] transition: String,
    children: Children,
) -> impl IntoView
where
    F: Fn(MouseEvent) + 'static,
{
    let f = use_frame();

    let width = width.map(|s| s.into_pixels(f.width));
    let height = height.map(|s| s.into_pixels(f.height));

    let size = if rounded {
        width.or(height).or(radius.map(|r| r * 2))
    } else {
        None
    };

    let (width, height, radius) = if rounded {
        let size = size.unwrap_or(WIDTH);
        (size, size, size / 2)
    } else {
        let width = if align == Align::Fill {
            f.width
        } else {
            width.unwrap_or(WIDTH)
        };
        let height = if valign == VAlign::Fill {
            f.height
        } else {
            height.unwrap_or(HEIGHT)
        };
        (width, height, radius.unwrap_or(24))
    };

    let x = match align {
        Align::Left | Align::Fill => f.x,
        Align::Center => f.x + (f.width - width) / 2,
        Align::Right => f.x + f.width - width,
    };
    let y = match valign {
        VAlign::Top | VAlign::Fill => f.y,
        VAlign::Middle => f.y + (f.height - height) / 2,
        VAlign::Bottom => f.y + f.height - height,
    };

    let frame = Frame {
        x: x + margin,
        y: y + margin,
        width: width - 2 * margin,
        height: height - 2 * margin,
    };
    provide_frame(frame);

    let x = x + border_width.get() / 2;
    let y = y + border_width.get() / 2;
    let width = width - border_width.get();
    let height = height - border_width.get();

    let (border, set_border) = signal(border_width.get());
    let on_mousedown = move |e| {
        set_border.set(border_width.get() + 6);
        if let Some(cb) = on_mousedown {
            cb.run(e);
        }
    };
    let on_mouseup = move |e| {
        set_border.set(border_width.get());
        if let Some(cb) = on_mouseup {
            cb.run(e);
        }
    };

    view! {
        <g
            style:opacity=move || if visible.get() { "1" } else { "0" }
            style:visibility=move || { if visible.get() { "visible" } else { "hidden" } }
            style:transition=transition
        >
            <rect
                on:click=move |e| {
                    if let Some(cb) = &on_click {
                        cb(e);
                    }
                }

                on:mousedown=on_mousedown
                on:mouseup=on_mouseup
                on:mouseleave=on_mouseup
                x=x
                y=y
                width=width
                height=height
                rx=radius
                ry=radius
                fill=color
                stroke=border_color
                stroke-width=move || border.get()
                style="cursor: pointer;"
            ></rect>
            <image
                stroke=border_color
                stroke-width=4
                href=image
                x=x + margin
                y=y + margin
                width=width - 2 * margin
                height=height - 2 * margin
                preserveAspectRatio="xMidYMid meet"
                pointer-events="none"
                style="user-select: none; -webkit-user-select: none;"
            ></image>
            <Label bold=text_bold color=text_color font=font font_size=font_size>
                {children()}
            </Label>
        </g>
    }
}