rust_widgets 0.9.6

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 60+ widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
use crate::core::Rect;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum LinkAction {
    GoToPage { page: u32, x: f32, y: f32 },
    GoToNamedDestination(String),
    Uri(String),
    LaunchFile(String),
    JavaScript(String),
    NamedAction(NamedAction),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NamedAction {
    NextPage,
    PrevPage,
    FirstPage,
    LastPage,
    Print,
    SaveAs,
}
#[derive(Debug, Clone)]
pub struct Hyperlink {
    pub id: String,
    pub page: u32,
    pub rect: Rect,
    pub action: LinkAction,
    pub border: LinkBorder,
    pub highlight_mode: HighlightMode,
    pub uri: Option<String>,
    pub tooltip: String,
}
impl Hyperlink {
    pub fn new(id: String, page: u32, rect: Rect, action: LinkAction) -> Self {
        Self {
            id,
            page,
            rect,
            action,
            border: LinkBorder::default(),
            highlight_mode: HighlightMode::Invert,
            uri: None,
            tooltip: String::new(),
        }
    }
    pub fn with_uri(mut self, uri: String) -> Self {
        self.uri = Some(uri.clone());
        self.action = LinkAction::Uri(uri);
        self
    }
    pub fn with_page_target(mut self, page: u32, x: f32, y: f32) -> Self {
        self.action = LinkAction::GoToPage { page, x, y };
        self
    }
    pub fn with_tooltip(mut self, tooltip: String) -> Self {
        self.tooltip = tooltip;
        self
    }
    pub fn with_border(mut self, border: LinkBorder) -> Self {
        self.border = border;
        self
    }
    pub fn with_highlight_mode(mut self, mode: HighlightMode) -> Self {
        self.highlight_mode = mode;
        self
    }
    pub fn contains_point(&self, x: i32, y: i32) -> bool {
        self.rect.contains_point(crate::core::Point::from_f32(x as f32, y as f32))
    }
}
#[derive(Debug, Clone)]
pub struct LinkBorder {
    pub horizontal_corner_radius: f32,
    pub vertical_corner_radius: f32,
    pub border_width: f32,
    pub dash_pattern: Option<Vec<f32>>,
}
impl Default for LinkBorder {
    fn default() -> Self {
        Self {
            horizontal_corner_radius: 0.0,
            vertical_corner_radius: 0.0,
            border_width: 0.0,
            dash_pattern: None,
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HighlightMode {
    None,
    #[default]
    Invert,
    Outline,
    Push,
}
#[derive(Debug, Clone)]
pub struct NamedDestination {
    pub name: String,
    pub page: u32,
    pub x: f32,
    pub y: f32,
    pub zoom: f32,
}
impl NamedDestination {
    pub fn new(name: String, page: u32, x: f32, y: f32) -> Self {
        Self { name, page, x, y, zoom: 0.0 }
    }
    pub fn with_zoom(mut self, zoom: f32) -> Self {
        self.zoom = zoom;
        self
    }
}
pub struct HyperlinkManager {
    links: HashMap<String, Hyperlink>,
    page_links: HashMap<u32, Vec<String>>,
    named_destinations: HashMap<String, NamedDestination>,
}
impl HyperlinkManager {
    pub fn new() -> Self {
        Self {
            links: HashMap::new(),
            page_links: HashMap::new(),
            named_destinations: HashMap::new(),
        }
    }
    pub fn add_link(&mut self, link: Hyperlink) {
        let id = link.id.clone();
        let page = link.page;
        self.links.insert(id.clone(), link);
        self.page_links.entry(page).or_default().push(id);
    }
    pub fn remove_link(&mut self, id: &str) -> Option<Hyperlink> {
        if let Some(link) = self.links.remove(id) {
            if let Some(page_links) = self.page_links.get_mut(&link.page) {
                page_links.retain(|l| l != id);
            }
            Some(link)
        } else {
            None
        }
    }
    pub fn get_link(&self, id: &str) -> Option<&Hyperlink> {
        self.links.get(id)
    }
    pub fn get_link_at_point(&self, page: u32, x: i32, y: i32) -> Option<&Hyperlink> {
        self.page_links.get(&page).and_then(|ids| {
            ids.iter().filter_map(|id| self.links.get(id)).find(|link| link.contains_point(x, y))
        })
    }
    pub fn get_page_links(&self, page: u32) -> Vec<&Hyperlink> {
        self.page_links
            .get(&page)
            .map(|ids| ids.iter().filter_map(|id| self.links.get(id)).collect())
            .unwrap_or_default()
    }
    pub fn add_named_destination(&mut self, destination: NamedDestination) {
        self.named_destinations.insert(destination.name.clone(), destination);
    }
    pub fn get_named_destination(&self, name: &str) -> Option<&NamedDestination> {
        self.named_destinations.get(name)
    }
    pub fn remove_named_destination(&mut self, name: &str) -> Option<NamedDestination> {
        self.named_destinations.remove(name)
    }
    pub fn clear(&mut self) {
        self.links.clear();
        self.page_links.clear();
        self.named_destinations.clear();
    }
    pub fn link_count(&self) -> usize {
        self.links.len()
    }
    pub fn destination_count(&self) -> usize {
        self.named_destinations.len()
    }
}
impl Default for HyperlinkManager {
    fn default() -> Self {
        Self::new()
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_hyperlink_creation() {
        let link = Hyperlink::new(
            "link-1".to_string(),
            1,
            Rect::new(100, 100, 200, 50),
            LinkAction::Uri("https://example.com".to_string()),
        )
        .with_tooltip("Click to visit".to_string());
        assert_eq!(link.id, "link-1");
        assert!(matches!(link.action, LinkAction::Uri(_)));
        assert_eq!(link.tooltip, "Click to visit");
    }
    #[test]
    fn test_hyperlink_manager() {
        let mut manager = HyperlinkManager::new();
        let link = Hyperlink::new(
            "link-1".to_string(),
            1,
            Rect::new(100, 100, 200, 50),
            LinkAction::GoToPage { page: 2, x: 0.0, y: 0.0 },
        );
        manager.add_link(link);
        assert_eq!(manager.link_count(), 1);
        let found = manager.get_link_at_point(1, 150, 125);
        assert!(found.is_some());
        let not_found = manager.get_link_at_point(1, 50, 50);
        assert!(not_found.is_none());
    }
    #[test]
    fn test_named_destination() {
        let mut manager = HyperlinkManager::new();
        let dest = NamedDestination::new("intro".to_string(), 1, 0.0, 0.0).with_zoom(1.0);
        manager.add_named_destination(dest);
        let found = manager.get_named_destination("intro");
        assert!(found.is_some());
        assert_eq!(found.unwrap().page, 1);
    }
}