gardesk/garshot / d141b9d

Browse files

annotate: add toolbar widget

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
d141b9ddce65e9b57c1a103de222e23ddd8ff085
Parents
f67b0be
Tree
ba1fb0b

1 changed file

StatusFile+-
A garshot/src/annotate/ui/toolbar.rs 154 0
garshot/src/annotate/ui/toolbar.rsadded
@@ -0,0 +1,154 @@
1
+//! Annotation toolbar UI.
2
+
3
+use crate::annotate::state::{ToolProperties, ToolType};
4
+use gartk_core::{Color, Point, Rect};
5
+use gartk_render::{
6
+    fill_rect, fill_rounded_rect, set_color, stroke_rounded_rect, Surface,
7
+};
8
+
9
+/// Toolbar height in pixels.
10
+pub const TOOLBAR_HEIGHT: u32 = 48;
11
+
12
+/// Toolbar for selecting annotation tools.
13
+pub struct Toolbar {
14
+    /// Toolbar bounds.
15
+    rect: Rect,
16
+    /// Currently selected tool.
17
+    selected_tool: ToolType,
18
+    /// Current color.
19
+    current_color: Color,
20
+    /// Current line width.
21
+    line_width: f64,
22
+}
23
+
24
+impl Toolbar {
25
+    /// Tool button width.
26
+    const BUTTON_WIDTH: u32 = 40;
27
+    /// Button padding.
28
+    const PADDING: u32 = 4;
29
+
30
+    /// Create a new toolbar.
31
+    pub fn new(screen_width: u32) -> Self {
32
+        Self {
33
+            rect: Rect::new(0, 0, screen_width, TOOLBAR_HEIGHT),
34
+            selected_tool: ToolType::Arrow,
35
+            current_color: Color::new(1.0, 0.4, 0.0, 1.0),
36
+            line_width: 3.0,
37
+        }
38
+    }
39
+
40
+    /// Update toolbar state from annotation state.
41
+    pub fn update(&mut self, tool: ToolType, props: &ToolProperties) {
42
+        self.selected_tool = tool;
43
+        self.current_color = props.color;
44
+        self.line_width = props.line_width;
45
+    }
46
+
47
+    /// Get the toolbar bounds.
48
+    pub fn rect(&self) -> Rect {
49
+        self.rect
50
+    }
51
+
52
+    /// Get the height.
53
+    pub fn height(&self) -> u32 {
54
+        TOOLBAR_HEIGHT
55
+    }
56
+
57
+    /// Calculate button rect for a tool at index.
58
+    fn button_rect(&self, index: usize) -> Rect {
59
+        let x = Self::PADDING as i32 + (index as i32 * (Self::BUTTON_WIDTH as i32 + Self::PADDING as i32));
60
+        let y = Self::PADDING as i32;
61
+        Rect::new(x, y, Self::BUTTON_WIDTH, TOOLBAR_HEIGHT - Self::PADDING * 2)
62
+    }
63
+
64
+    /// Draw the toolbar onto a surface.
65
+    pub fn draw(&self, surface: &Surface) -> anyhow::Result<()> {
66
+        let ctx = surface.context()?;
67
+
68
+        // Draw background
69
+        fill_rect(
70
+            &ctx,
71
+            self.rect,
72
+            Color::new(0.15, 0.15, 0.15, 0.95),
73
+        );
74
+
75
+        // Draw tool buttons
76
+        for (i, tool) in ToolType::all().iter().enumerate() {
77
+            let btn_rect = self.button_rect(i);
78
+            let is_selected = *tool == self.selected_tool;
79
+
80
+            // Button background
81
+            if is_selected {
82
+                fill_rounded_rect(
83
+                    &ctx,
84
+                    btn_rect,
85
+                    4.0,
86
+                    Color::new(1.0, 1.0, 1.0, 0.2),
87
+                );
88
+            }
89
+
90
+            // Button label (shortcut key)
91
+            let label = tool.shortcut().to_ascii_uppercase().to_string();
92
+            set_color(&ctx, Color::WHITE);
93
+            ctx.select_font_face("monospace", cairo::FontSlant::Normal, cairo::FontWeight::Bold);
94
+            ctx.set_font_size(16.0);
95
+
96
+            let extents = ctx.text_extents(&label)?;
97
+            let text_x = btn_rect.x as f64 + (btn_rect.width as f64 - extents.width()) / 2.0;
98
+            let text_y = btn_rect.y as f64 + (btn_rect.height as f64 + extents.height()) / 2.0;
99
+
100
+            ctx.move_to(text_x, text_y);
101
+            ctx.show_text(&label)?;
102
+        }
103
+
104
+        // Draw color preview
105
+        let color_rect = Rect::new(
106
+            (ToolType::all().len() as i32 + 1) * (Self::BUTTON_WIDTH as i32 + Self::PADDING as i32),
107
+            Self::PADDING as i32 + 4,
108
+            32,
109
+            32,
110
+        );
111
+        fill_rounded_rect(&ctx, color_rect, 4.0, self.current_color);
112
+        stroke_rounded_rect(
113
+            &ctx,
114
+            color_rect,
115
+            4.0,
116
+            Color::WHITE,
117
+            1.0,
118
+        );
119
+
120
+        // Draw line width indicator
121
+        let width_x = color_rect.x + color_rect.width as i32 + Self::PADDING as i32 * 2;
122
+        set_color(&ctx, Color::WHITE);
123
+        ctx.set_font_size(12.0);
124
+        ctx.move_to(width_x as f64, (TOOLBAR_HEIGHT / 2 + 4) as f64);
125
+        ctx.show_text(&format!("{}px", self.line_width as i32))?;
126
+
127
+        // Draw hint text on right side
128
+        let hint = "Ctrl+Enter: Save | Escape: Cancel";
129
+        let hint_extents = ctx.text_extents(hint)?;
130
+        let hint_x = self.rect.width as f64 - hint_extents.width() - 16.0;
131
+        set_color(&ctx, Color::new(0.7, 0.7, 0.7, 1.0));
132
+        ctx.set_font_size(12.0);
133
+        ctx.move_to(hint_x, (TOOLBAR_HEIGHT / 2 + 4) as f64);
134
+        ctx.show_text(hint)?;
135
+
136
+        Ok(())
137
+    }
138
+
139
+    /// Handle click on toolbar, returns selected tool if a button was clicked.
140
+    pub fn handle_click(&self, pos: Point) -> Option<ToolType> {
141
+        if !self.rect.contains_point(pos) {
142
+            return None;
143
+        }
144
+
145
+        for (i, tool) in ToolType::all().iter().enumerate() {
146
+            let btn_rect = self.button_rect(i);
147
+            if btn_rect.contains_point(pos) {
148
+                return Some(*tool);
149
+            }
150
+        }
151
+
152
+        None
153
+    }
154
+}