trailgen-gui 0.2.1

Native Dwemer Poolrooms workbench for constrained trail-loop generation.
Documentation
use crate::{chrome, witness};
use egui::{CursorIcon, Response, RichText, Ui};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum Term {
    BasePace,
    Fgjw,
    MovingTime,
}

impl Term {
    const ALL: [Self; 3] = [Self::BasePace, Self::Fgjw, Self::MovingTime];

    const fn title(self) -> &'static str {
        match self {
            Self::BasePace => "BASE PACE",
            Self::Fgjw => "FGJW KM",
            Self::MovingTime => "MOVING TIME",
        }
    }

    const fn definition(self) -> &'static str {
        match self {
            Self::BasePace => {
                "Your expected speed on flat, firm ground. Trailgen scales terrain-adjusted moving times from this value."
            }
            Self::Fgjw => {
                "Flat-gravel joint-work-equivalent kilometers: modeled lower-limb load expressed as an equivalent flat gravel walk."
            }
            Self::MovingTime => "Calculated using your Base Pace.",
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Glosses(u8);

impl Glosses {
    pub const NONE: Self = Self(0);
    pub const BASE_PACE: Self = Self(1 << Term::BasePace as u8);
    pub const FGJW: Self = Self(1 << Term::Fgjw as u8);
    pub const MOVING_TIME: Self = Self(1 << Term::MovingTime as u8);
    pub const ROUTE_METRICS: Self = Self(Self::FGJW.0 | Self::MOVING_TIME.0);

    #[must_use]
    pub const fn contains(self, term: Term) -> bool {
        self.0 & (1 << term as u8) != 0
    }

    pub fn explain(self, response: Response) -> Response {
        if self.0 == 0 {
            return response;
        }
        response
            .on_hover_cursor(CursorIcon::Help)
            .on_hover_ui(|ui| self.card(ui))
    }

    pub fn card(self, ui: &mut Ui) {
        let _column = ui.vertical(|ui| {
            for term in Term::ALL.into_iter().filter(|term| self.contains(*term)) {
                let _title = ui.label(chrome::eyebrow(term.title()));
                let _definition = ui.add(
                    egui::Label::new(
                        RichText::new(term.definition())
                            .monospace()
                            .size(11.0)
                            .color(chrome::TEXT),
                    )
                    .wrap(),
                );
            }
            witness::anchor(ui, trailgen_contract::Target::GlossCard, ui.min_rect());
        });
    }
}

#[derive(Clone, Debug)]
pub struct ExplainedText {
    text: String,
    glosses: Glosses,
}

impl ExplainedText {
    #[must_use]
    pub fn forge(text: impl Into<String>, glosses: Glosses) -> Self {
        Self {
            text: text.into(),
            glosses,
        }
    }

    #[must_use]
    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn explain(self, response: Response) -> Response {
        self.glosses.explain(response)
    }
}

impl From<&str> for ExplainedText {
    fn from(text: &str) -> Self {
        Self::forge(text, Glosses::default())
    }
}

impl From<String> for ExplainedText {
    fn from(text: String) -> Self {
        Self::forge(text, Glosses::default())
    }
}