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::{Color, Point, Rect};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AnnotationType {
    Text,
    Highlight,
    Underline,
    StrikeOut,
    Squiggly,
    Link,
    Popup,
    Line,
    Square,
    Circle,
    Polygon,
    PolyLine,
    Ink,
    Stamp,
    Caret,
    FileAttachment,
    Sound,
    Movie,
    Widget,
    Screen,
    PrinterMark,
    TrapNet,
    Watermark,
    ThreeD,
}
#[derive(Debug, Clone)]
pub struct Annotation {
    pub id: String,
    pub page: u32,
    pub annotation_type: AnnotationType,
    pub rect: Rect,
    pub contents: String,
    pub author: String,
    pub creation_date: String,
    pub modification_date: String,
    pub color: Option<Color>,
    pub opacity: f32,
    pub flags: AnnotationFlags,
    pub custom_data: HashMap<String, String>,
}
impl Annotation {
    pub fn new(id: String, page: u32, annotation_type: AnnotationType, rect: Rect) -> Self {
        Self {
            id,
            page,
            annotation_type,
            rect,
            contents: String::new(),
            author: String::new(),
            creation_date: String::new(),
            modification_date: String::new(),
            color: None,
            opacity: 1.0,
            flags: AnnotationFlags::default(),
            custom_data: HashMap::new(),
        }
    }
    pub fn with_contents(mut self, contents: String) -> Self {
        self.contents = contents;
        self
    }
    pub fn with_author(mut self, author: String) -> Self {
        self.author = author;
        self
    }
    pub fn with_color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }
    pub fn with_opacity(mut self, opacity: f32) -> Self {
        self.opacity = opacity.clamp(0.0, 1.0);
        self
    }
    pub fn is_visible(&self) -> bool {
        !self.flags.hidden && !self.flags.invisible
    }
    pub fn is_editable(&self) -> bool {
        !self.flags.locked && !self.flags.read_only
    }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AnnotationFlags {
    pub invisible: bool,
    pub hidden: bool,
    pub print: bool,
    pub no_zoom: bool,
    pub no_rotate: bool,
    pub no_view: bool,
    pub read_only: bool,
    pub locked: bool,
    pub toggle_no_view: bool,
    pub locked_contents: bool,
}
#[derive(Debug, Clone)]
pub struct TextAnnotation {
    pub base: Annotation,
    pub icon: TextIcon,
    pub open: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextIcon {
    #[default]
    Note,
    Comment,
    Key,
    Help,
    NewParagraph,
    Paragraph,
    Insert,
    Cross,
    Circle,
    Star,
    Check,
    RightArrow,
    RightPointer,
    UpArrow,
    UpLeftArrow,
    CrossHairs,
}
#[derive(Debug, Clone)]
pub struct HighlightAnnotation {
    pub base: Annotation,
    pub quad_points: Vec<QuadPoint>,
    pub highlight_type: HighlightType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightType {
    Highlight,
    Underline,
    Squiggly,
    StrikeOut,
}
#[derive(Debug, Clone, Copy)]
pub struct QuadPoint {
    pub x1: f32,
    pub y1: f32,
    pub x2: f32,
    pub y2: f32,
    pub x3: f32,
    pub y3: f32,
    pub x4: f32,
    pub y4: f32,
}
#[derive(Debug, Clone)]
pub struct InkAnnotation {
    pub base: Annotation,
    pub ink_list: Vec<Vec<Point>>,
}
#[derive(Debug, Clone)]
pub struct LineAnnotation {
    pub base: Annotation,
    pub start: Point,
    pub end: Point,
    pub start_style: LineEndingStyle,
    pub end_style: LineEndingStyle,
    pub interior_color: Option<Color>,
    pub leader_line_length: f32,
    pub leader_line_extension: f32,
    pub caption: bool,
    pub caption_offset: (f32, f32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LineEndingStyle {
    #[default]
    None,
    Square,
    Circle,
    Diamond,
    OpenArrow,
    ClosedArrow,
    Butt,
    ReverseOpenArrow,
    ReverseClosedArrow,
    Slash,
}
pub struct AnnotationManager {
    annotations: HashMap<String, Annotation>,
    page_annotations: HashMap<u32, Vec<String>>,
}
impl AnnotationManager {
    pub fn new() -> Self {
        Self { annotations: HashMap::new(), page_annotations: HashMap::new() }
    }
    pub fn add_annotation(&mut self, annotation: Annotation) {
        let id = annotation.id.clone();
        let page = annotation.page;
        self.annotations.insert(id.clone(), annotation);
        self.page_annotations.entry(page).or_default().push(id);
    }
    pub fn remove_annotation(&mut self, id: &str) -> Option<Annotation> {
        if let Some(annotation) = self.annotations.remove(id) {
            if let Some(page_annotations) = self.page_annotations.get_mut(&annotation.page) {
                page_annotations.retain(|a| a != id);
            }
            Some(annotation)
        } else {
            None
        }
    }
    pub fn get_annotation(&self, id: &str) -> Option<&Annotation> {
        self.annotations.get(id)
    }
    pub fn get_annotation_mut(&mut self, id: &str) -> Option<&mut Annotation> {
        self.annotations.get_mut(id)
    }
    pub fn get_page_annotations(&self, page: u32) -> Vec<&Annotation> {
        self.page_annotations
            .get(&page)
            .map(|ids| ids.iter().filter_map(|id| self.annotations.get(id)).collect())
            .unwrap_or_default()
    }
    pub fn get_annotations_in_rect(&self, page: u32, rect: &Rect) -> Vec<&Annotation> {
        self.get_page_annotations(page).into_iter().filter(|a| a.rect.intersects(rect)).collect()
    }
    pub fn clear(&mut self) {
        self.annotations.clear();
        self.page_annotations.clear();
    }
    pub fn annotation_count(&self) -> usize {
        self.annotations.len()
    }
    pub fn page_count(&self) -> usize {
        self.page_annotations.len()
    }
}
impl Default for AnnotationManager {
    fn default() -> Self {
        Self::new()
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_annotation_creation() {
        let annotation = Annotation::new(
            "test-1".to_string(),
            1,
            AnnotationType::Text,
            Rect::new(100, 100, 200, 50),
        )
        .with_contents("Test annotation".to_string())
        .with_author("Test User".to_string())
        .with_color(Color::YELLOW);
        assert_eq!(annotation.id, "test-1");
        assert_eq!(annotation.page, 1);
        assert_eq!(annotation.annotation_type, AnnotationType::Text);
        assert_eq!(annotation.contents, "Test annotation");
        assert!(annotation.color.is_some());
    }
    #[test]
    fn test_annotation_manager() {
        let mut manager = AnnotationManager::new();
        let annotation = Annotation::new(
            "test-1".to_string(),
            1,
            AnnotationType::Highlight,
            Rect::new(100, 100, 200, 50),
        );
        manager.add_annotation(annotation);
        assert_eq!(manager.annotation_count(), 1);
        assert!(manager.get_annotation("test-1").is_some());
        let page_annotations = manager.get_page_annotations(1);
        assert_eq!(page_annotations.len(), 1);
    }
}