mimick/library/lightbox/ocr/
layer.rs1use std::cell::RefCell;
4use std::rc::Rc;
5
6use gtk::prelude::*;
7
8use super::geometry::{contain_rect, quad_in_area};
9use super::state::OcrState;
10use crate::api_client::OcrBox;
11
12pub(crate) struct OcrLayer {
14 pub(crate) area: gtk::DrawingArea,
15 state: Rc<RefCell<OcrState>>,
16}
17
18impl OcrLayer {
19 pub(crate) fn new() -> Self {
20 let area = gtk::DrawingArea::builder()
21 .hexpand(true)
22 .vexpand(true)
23 .can_target(false)
24 .visible(false)
25 .build();
26 let state = Rc::new(RefCell::new(OcrState::default()));
27 let draw_state = state.clone();
28 area.set_draw_func(move |_, cr, width, height| {
29 draw_boxes(
30 cr,
31 &draw_state.borrow(),
32 (f64::from(width), f64::from(height)),
33 );
34 });
35 Self { area, state }
36 }
37
38 pub(super) fn set_boxes(&self, boxes: Vec<OcrBox>, image: (f64, f64)) {
40 *self.state.borrow_mut() = OcrState::new(boxes, image);
41 self.area.queue_draw();
42 }
43
44 pub(crate) fn clear(&self) {
45 *self.state.borrow_mut() = OcrState::default();
46 self.area.queue_draw();
47 }
48
49 pub(super) fn has_boxes(&self) -> bool {
50 !self.state.borrow().boxes().is_empty()
51 }
52
53 pub(super) fn box_at(&self, p: (f64, f64)) -> Option<usize> {
55 let area = (f64::from(self.area.width()), f64::from(self.area.height()));
56 self.state.borrow().box_at(area, p)
57 }
58
59 pub(super) fn set_hovered(&self, idx: Option<usize>) -> bool {
61 let changed = self.state.borrow_mut().set_hovered(idx);
62 if changed {
63 self.area.queue_draw();
64 }
65 changed
66 }
67
68 pub(super) fn press(&self, idx: usize, shift: bool, ctrl: bool) -> bool {
70 let extends = self.state.borrow_mut().press(idx, shift, ctrl);
71 self.area.queue_draw();
72 extends
73 }
74
75 pub(super) fn click(&self, idx: usize, extend: bool) {
76 self.state.borrow_mut().click(idx, extend);
77 self.area.queue_draw();
78 }
79
80 pub(super) fn extend_to(&self, idx: usize) {
81 self.state.borrow_mut().extend_to(idx);
82 self.area.queue_draw();
83 }
84
85 pub(super) fn has_selection(&self) -> bool {
86 self.state.borrow().has_selection()
87 }
88
89 pub(super) fn all_text(&self) -> Option<String> {
90 self.state.borrow().all_text()
91 }
92
93 pub(super) fn select_all(&self) {
94 self.state.borrow_mut().select_all();
95 self.area.queue_draw();
96 }
97
98 pub(super) fn clear_selection(&self) {
99 self.state.borrow_mut().clear_selection();
100 self.area.queue_draw();
101 }
102
103 pub(super) fn selected_text(&self) -> Option<String> {
104 self.state.borrow().selected_text()
105 }
106
107 pub(super) fn text_at(&self, idx: usize) -> Option<String> {
108 self.state
109 .borrow()
110 .boxes()
111 .get(idx)
112 .map(|ocr_box| ocr_box.text.clone())
113 }
114}
115
116fn draw_boxes(cr: >k::cairo::Context, state: &OcrState, area: (f64, f64)) {
118 let Some(rect) = contain_rect(area, state.image()) else {
119 return;
120 };
121 let accent = libadwaita::StyleManager::default().accent_color().to_rgba();
122 let (r, g, b) = (
123 f64::from(accent.red()),
124 f64::from(accent.green()),
125 f64::from(accent.blue()),
126 );
127 for (idx, ocr_box) in state.boxes().iter().enumerate() {
128 let quad = quad_in_area(rect, ocr_box.corners());
129 cr.move_to(quad[0].0, quad[0].1);
130 for &(x, y) in &quad[1..] {
131 cr.line_to(x, y);
132 }
133 cr.close_path();
134 let active = state.is_selected(idx) || state.hovered() == Some(idx);
135 let (fill_alpha, line_width) = if active { (0.35, 2.5) } else { (0.12, 1.5) };
136 cr.set_source_rgba(r, g, b, fill_alpha);
137 let _ = cr.fill_preserve();
138 cr.set_source_rgba(r, g, b, 1.0);
139 cr.set_line_width(line_width);
140 let _ = cr.stroke();
141 }
142}