gardesk/garshot / 6f01cd2

Browse files

overlay: integrate color picker with optimized redraw

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6f01cd2d99643c37b362a0b319bfe2a0c94fa4e8
Parents
f669e50
Tree
42da6a9

1 changed file

StatusFile+-
M garshot/src/annotate/overlay.rs 151 4
garshot/src/annotate/overlay.rsmodified
@@ -6,7 +6,10 @@ use crate::annotate::canvas::AnnotationCanvas;
66
 use crate::annotate::history::{History, Snapshot};
77
 use crate::annotate::state::{AnnotationResult, AnnotationState, ToolType};
88
 use crate::annotate::tools::{self, Tool};
9
-use crate::annotate::ui::{Toolbar, TOOLBAR_HEIGHT};
9
+use crate::annotate::ui::{
10
+    color_picker::{ColorPicker, ColorPickerResult},
11
+    Toolbar, ToolbarClickResult, TOOLBAR_HEIGHT,
12
+};
1013
 
1114
 use gartk_core::{InputEvent, Key, KeyEvent, Modifiers, MouseButton, MouseEvent, Point};
1215
 use gartk_render::Surface;
@@ -44,6 +47,10 @@ pub struct AnnotationOverlay {
4447
     needs_redraw: bool,
4548
     /// Image offset from top (for toolbar).
4649
     image_offset_y: i32,
50
+    /// Color picker dialog (when open).
51
+    color_picker: Option<ColorPicker>,
52
+    /// Color picker surface (reused to avoid allocation).
53
+    color_picker_surface: Option<Surface>,
4754
 }
4855
 
4956
 impl AnnotationOverlay {
@@ -175,6 +182,8 @@ impl AnnotationOverlay {
175182
             cursor_manager,
176183
             needs_redraw: true,
177184
             image_offset_y: TOOLBAR_HEIGHT as i32,
185
+            color_picker: None,
186
+            color_picker_surface: None,
178187
         })
179188
     }
180189
 
@@ -367,15 +376,66 @@ impl AnnotationOverlay {
367376
     fn handle_event(&mut self, event: InputEvent) -> Result<bool> {
368377
         let mut needs_redraw = false;
369378
 
379
+        // If color picker is open, route events to it first
380
+        if let Some(ref mut picker) = self.color_picker {
381
+            match picker.handle_event(&event) {
382
+                ColorPickerResult::Confirm(color) => {
383
+                    self.state.properties.color = color;
384
+                    self.color_picker = None;
385
+                    self.color_picker_surface = None;
386
+                    return Ok(true);
387
+                }
388
+                ColorPickerResult::Cancel => {
389
+                    self.color_picker = None;
390
+                    self.color_picker_surface = None;
391
+                    return Ok(true);
392
+                }
393
+                ColorPickerResult::StartEyedropper => {
394
+                    // Close color picker and start eyedropper
395
+                    let original_color = picker.current_color();
396
+                    self.color_picker = None;
397
+                    self.color_picker_surface = None;
398
+
399
+                    // Run eyedropper (this blocks until user picks or cancels)
400
+                    match self.run_eyedropper()? {
401
+                        Some(color) => {
402
+                            self.state.properties.color = color;
403
+                        }
404
+                        None => {
405
+                            // Cancelled - reopen picker with original color
406
+                            self.open_color_picker(original_color)?;
407
+                        }
408
+                    }
409
+                    return Ok(true);
410
+                }
411
+                ColorPickerResult::Changed => {
412
+                    // Color changed, redraw picker only
413
+                    self.redraw_color_picker()?;
414
+                    return Ok(false); // Don't redraw the whole overlay
415
+                }
416
+                ColorPickerResult::None => {
417
+                    // No change, no redraw needed
418
+                    return Ok(false);
419
+                }
420
+            }
421
+        }
422
+
370423
         match &event {
371424
             // Handle toolbar clicks
372425
             InputEvent::MousePress(e) if e.position.y < 0 => {
373426
                 // Click is in toolbar area (y < 0 because of offset)
374427
                 let toolbar_pos = Point::new(e.position.x, e.position.y + self.image_offset_y);
375
-                if let Some(tool_type) = self.toolbar.handle_click(toolbar_pos) {
428
+                match self.toolbar.handle_click(toolbar_pos) {
429
+                    ToolbarClickResult::Tool(tool_type) => {
376430
                         self.select_tool(tool_type)?;
377431
                         needs_redraw = true;
378432
                     }
433
+                    ToolbarClickResult::ColorPreview => {
434
+                        self.open_color_picker(self.state.properties.color)?;
435
+                        needs_redraw = true;
436
+                    }
437
+                    ToolbarClickResult::None => {}
438
+                }
379439
                 return Ok(needs_redraw);
380440
             }
381441
 
@@ -535,6 +595,55 @@ impl AnnotationOverlay {
535595
         Ok(())
536596
     }
537597
 
598
+    /// Open the color picker dialog.
599
+    fn open_color_picker(&mut self, initial_color: gartk_core::Color) -> Result<()> {
600
+        use crate::annotate::ui::{PICKER_HEIGHT, PICKER_WIDTH};
601
+
602
+        let screen_width = self.canvas.width();
603
+        let screen_height = self.canvas.height() + TOOLBAR_HEIGHT;
604
+
605
+        let picker = ColorPicker::new(initial_color, screen_width, screen_height);
606
+
607
+        // Create surface for color picker if needed
608
+        if self.color_picker_surface.is_none() {
609
+            self.color_picker_surface = Some(
610
+                Surface::new(PICKER_WIDTH, PICKER_HEIGHT)
611
+                    .context("Failed to create color picker surface")?,
612
+            );
613
+        }
614
+
615
+        self.color_picker = Some(picker);
616
+        Ok(())
617
+    }
618
+
619
+    /// Redraw just the color picker (not the whole overlay).
620
+    fn redraw_color_picker(&mut self) -> Result<()> {
621
+        if let (Some(picker), Some(surface)) = (&self.color_picker, &self.color_picker_surface) {
622
+            picker.draw(surface)?;
623
+            self.blit_color_picker()?;
624
+        }
625
+        Ok(())
626
+    }
627
+
628
+    /// Run the eyedropper to sample a color.
629
+    fn run_eyedropper(&mut self) -> Result<Option<gartk_core::Color>> {
630
+        use crate::annotate::ui::color_picker::eyedropper::{Eyedropper, EyedropperResult};
631
+
632
+        // Temporarily release our keyboard grab so eyedropper can grab
633
+        self.window.ungrab_keyboard()?;
634
+
635
+        let eyedropper = Eyedropper::new()?;
636
+        let result = eyedropper.run()?;
637
+
638
+        // Regrab keyboard for our window
639
+        self.window.grab_keyboard_with_retry(10, 50)?;
640
+
641
+        match result {
642
+            EyedropperResult::Color(color) => Ok(Some(color)),
643
+            EyedropperResult::Cancel => Ok(None),
644
+        }
645
+    }
646
+
538647
     /// Redraw the overlay.
539648
     fn redraw(&mut self) -> Result<()> {
540649
         // Draw current tool preview
@@ -552,6 +661,12 @@ impl AnnotationOverlay {
552661
         // Blit to window (toolbar + image)
553662
         self.blit_to_window()?;
554663
 
664
+        // Draw color picker if open
665
+        if let (Some(picker), Some(surface)) = (&self.color_picker, &self.color_picker_surface) {
666
+            picker.draw(surface)?;
667
+            self.blit_color_picker()?;
668
+        }
669
+
555670
         self.needs_redraw = false;
556671
 
557672
         Ok(())
@@ -605,6 +720,38 @@ impl AnnotationOverlay {
605720
         Ok(())
606721
     }
607722
 
723
+    /// Blit color picker to window.
724
+    fn blit_color_picker(&mut self) -> Result<()> {
725
+        use crate::annotate::ui::{PICKER_HEIGHT, PICKER_WIDTH};
726
+
727
+        let picker = self.color_picker.as_ref().ok_or_else(|| anyhow::anyhow!("No color picker"))?;
728
+        let surface = self.color_picker_surface.as_mut().ok_or_else(|| anyhow::anyhow!("No color picker surface"))?;
729
+
730
+        let rect = picker.rect();
731
+
732
+        // Get picker data and convert to BGRA
733
+        let data = surface.to_rgba()?;
734
+        let bgra = rgba_to_bgra(&data);
735
+
736
+        // Blit at picker position (adjusted for toolbar offset)
737
+        self.conn.inner().put_image(
738
+            ImageFormat::Z_PIXMAP,
739
+            self.window.id(),
740
+            self.gc,
741
+            PICKER_WIDTH as u16,
742
+            PICKER_HEIGHT as u16,
743
+            rect.x as i16,
744
+            (rect.y + self.image_offset_y) as i16,
745
+            0,
746
+            24,
747
+            &bgra,
748
+        )?;
749
+
750
+        self.conn.inner().flush()?;
751
+
752
+        Ok(())
753
+    }
754
+
608755
     /// Set window to full opacity to prevent compositor transparency.
609756
     fn set_full_opacity(&self) -> Result<()> {
610757
         // Intern the _NET_WM_WINDOW_OPACITY atom