use super::{UseLayout, UseVerticalLayout};
use crate::{
bounds::Bounds,
debug::DebugRect,
edge::Edge,
state::{PreState, State},
Tick,
};
use leptos::prelude::*;
use std::str::FromStr;
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Anchor {
Start,
Middle,
End,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct RotatedLabel {
pub text: RwSignal<String>,
pub anchor: RwSignal<Anchor>,
}
impl RotatedLabel {
fn new(anchor: Anchor, text: String) -> Self {
Self {
text: RwSignal::new(text),
anchor: RwSignal::new(anchor),
}
}
pub fn start(text: impl Into<String>) -> Self {
Self::new(Anchor::Start, text.into())
}
pub fn middle(text: impl Into<String>) -> Self {
Self::new(Anchor::Middle, text.into())
}
pub fn end(text: impl Into<String>) -> Self {
Self::new(Anchor::End, text.into())
}
fn size<X: Tick, Y: Tick>(&self, state: &PreState<X, Y>) -> Signal<f64> {
let text = self.text;
let font_height = state.font_height;
let padding = state.padding;
Signal::derive(move || {
if text.with(|t| t.is_empty()) {
0.0
} else {
font_height.get() + padding.get().height()
}
})
}
pub(super) fn fixed_height<X: Tick, Y: Tick>(&self, state: &PreState<X, Y>) -> Signal<f64> {
self.size(state)
}
pub(super) fn to_horizontal_use(&self) -> UseLayout {
UseLayout::RotatedLabel(self.clone())
}
pub(super) fn to_vertical_use<X: Tick, Y: Tick>(
&self,
state: &PreState<X, Y>,
) -> UseVerticalLayout {
UseVerticalLayout {
width: self.size(state),
layout: UseLayout::RotatedLabel(self.clone()),
}
}
}
impl Anchor {
fn to_svg_attr(self) -> String {
self.to_string()
}
fn map_points(&self, left: f64, middle: f64, right: f64) -> f64 {
match self {
Anchor::Start => left,
Anchor::Middle => middle,
Anchor::End => right,
}
}
pub(crate) fn css_justify_content(&self) -> &'static str {
match self {
Anchor::Start => "flex-start",
Anchor::Middle => "center",
Anchor::End => "flex-end",
}
}
}
impl FromStr for Anchor {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"start" => Ok(Anchor::Start),
"middle" => Ok(Anchor::Middle),
"end" => Ok(Anchor::End),
_ => Err(format!("unknown anchor: `{}`", s)),
}
}
}
impl std::fmt::Display for Anchor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Anchor::Start => write!(f, "start"),
Anchor::Middle => write!(f, "middle"),
Anchor::End => write!(f, "end"),
}
}
}
#[component]
pub(super) fn RotatedLabel<X: Tick, Y: Tick>(
label: RotatedLabel,
edge: Edge,
bounds: Memo<Bounds>,
state: State<X, Y>,
) -> impl IntoView {
let RotatedLabel { text, anchor } = label;
let debug = state.pre.debug;
let font_height = state.pre.font_height;
let padding = state.pre.padding;
let content = Signal::derive(move || padding.get().apply(bounds.get()));
let position = Memo::new(move |_| {
let c = content.get();
let (top, right, bottom, left) = (c.top_y(), c.right_x(), c.bottom_y(), c.left_x());
let (centre_x, centre_y) = (c.centre_x(), c.centre_y());
let anchor = anchor.get();
match edge {
Edge::Top | Edge::Bottom => (0, anchor.map_points(left, centre_x, right), centre_y),
Edge::Left => (270, centre_x, anchor.map_points(bottom, centre_y, top)),
Edge::Right => (90, centre_x, anchor.map_points(top, centre_y, bottom)),
}
});
view! {
<g
class="_chartistry_rotated_label"
font-family="monospace">
<DebugRect label="RotatedLabel" debug=debug bounds=vec![bounds.into(), content] />
<text
x=move || position.with(|(_, x, _)| x.to_string())
y=move || position.with(|(_, _, y)| y.to_string())
transform=move || position.with(|(rotate, x, y)| format!("rotate({rotate}, {x}, {y})"))
dominant-baseline="middle"
text-anchor=move || anchor.get().to_svg_attr()
font-size=move || format!("{}px", font_height.get())>
{text}
</text>
</g>
}
}