use leptos::prelude::*;
use lodviz_core::core::selection::Selection;
#[component]
pub fn Brush(
on_brush: Callback<Selection>,
#[prop(into)]
x_min: Signal<f64>,
#[prop(into)]
x_max: Signal<f64>,
#[prop(into)]
y_min: Signal<f64>,
#[prop(into)]
y_max: Signal<f64>,
#[prop(into)]
inner_width: Signal<f64>,
#[prop(into)]
inner_height: Signal<f64>,
#[prop(into, optional)]
fill: Option<Signal<String>>,
#[prop(into, optional)]
stroke: Option<Signal<String>>,
) -> impl IntoView {
let brush_fill = move || {
fill.map(|s| s.get())
.unwrap_or_else(|| "rgba(100,150,255,0.25)".to_string())
};
let brush_stroke = move || {
stroke
.map(|s| s.get())
.unwrap_or_else(|| "rgba(100,150,255,0.6)".to_string())
};
let (is_brushing, set_is_brushing) = signal(false);
let (start_px, set_start_px) = signal((0.0_f64, 0.0_f64));
let (current_px, set_current_px) = signal((0.0_f64, 0.0_f64));
let brush_rect = Memo::new(move |_| {
let (sx, sy) = start_px.get();
let (cx, cy) = current_px.get();
let rx = sx.min(cx);
let ry = sy.min(cy);
let rw = (cx - sx).abs();
let rh = (cy - sy).abs();
(rx, ry, rw, rh)
});
let on_mousedown = move |ev: web_sys::MouseEvent| {
ev.prevent_default();
let x = ev.offset_x() as f64;
let y = ev.offset_y() as f64;
set_start_px.set((x, y));
set_current_px.set((x, y));
set_is_brushing.set(true);
};
let on_mousemove = move |ev: web_sys::MouseEvent| {
if !is_brushing.get() {
return;
}
let x = (ev.offset_x() as f64).clamp(0.0, inner_width.get());
let y = (ev.offset_y() as f64).clamp(0.0, inner_height.get());
set_current_px.set((x, y));
};
let on_mouseup = move |_: web_sys::MouseEvent| {
if !is_brushing.get() {
return;
}
set_is_brushing.set(false);
let w = inner_width.get();
let h = inner_height.get();
if w <= 0.0 || h <= 0.0 {
return;
}
let (rx, ry, rw, rh) = brush_rect.get();
if rw < 4.0 && rh < 4.0 {
return;
}
let x_domain_min = x_min.get();
let x_domain_max = x_max.get();
let y_domain_min = y_min.get();
let y_domain_max = y_max.get();
let x_range = x_domain_max - x_domain_min;
let y_range = y_domain_max - y_domain_min;
let sel_x_min = x_domain_min + (rx / w) * x_range;
let sel_x_max = x_domain_min + ((rx + rw) / w) * x_range;
let sel_y_max = y_domain_max - (ry / h) * y_range;
let sel_y_min = y_domain_max - ((ry + rh) / h) * y_range;
on_brush.run(Selection::interval_xy(
sel_x_min, sel_x_max, sel_y_min, sel_y_max,
));
};
let on_mouseleave = move |_: web_sys::MouseEvent| {
set_is_brushing.set(false);
};
view! {
<g class="brush-overlay">
<rect
width=move || format!("{:.2}", inner_width.get())
height=move || format!("{:.2}", inner_height.get())
fill="transparent"
style="cursor: crosshair;"
on:mousedown=on_mousedown
on:mousemove=on_mousemove
on:mouseup=on_mouseup
on:mouseleave=on_mouseleave
/>
{move || {
if is_brushing.get() {
let (rx, ry, rw, rh) = brush_rect.get();
view! {
<rect
x=format!("{rx:.2}")
y=format!("{ry:.2}")
width=format!("{rw:.2}")
height=format!("{rh:.2}")
fill=brush_fill()
stroke=brush_stroke()
stroke-width="1"
pointer-events="none"
/>
}
.into_any()
} else {
().into_any()
}
}}
</g>
}
}