1use eframe::egui::{self, Key, Ui, text::CCursor, text::CCursorRange};
9
10use crate::i18n::{fill, t};
11
12#[derive(Clone, Copy, PartialEq, Eq)]
14pub enum Step {
15 Next,
17 Previous,
19}
20
21#[derive(Default)]
23pub struct FindBar {
24 open: bool,
25 query: String,
26 focus: bool,
29 asked: Option<(String, u64)>,
32 pub(crate) count: usize,
34 pub(crate) current: usize,
36 pub(crate) searching: bool,
38}
39
40impl FindBar {
41 pub fn open(&mut self) {
43 self.open = true;
44 self.focus = true;
45 }
46
47 pub fn close(&mut self) {
49 self.open = false;
50 self.asked = None;
51 }
52
53 pub fn is_open(&self) -> bool {
55 self.open
56 }
57
58 pub fn query(&self) -> &str {
60 &self.query
61 }
62
63 pub fn due(&mut self, revision: u64) -> Option<bool> {
66 let same_query = self.asked.as_ref().is_some_and(|(q, _)| *q == self.query);
67 let same_revision = self.asked.as_ref().is_some_and(|(_, r)| *r == revision);
68 if same_query && same_revision {
69 return None;
70 }
71 self.asked = Some((self.query.clone(), revision));
72 Some(!same_query)
73 }
74
75 pub fn ui(&mut self, ui: &mut Ui) -> Option<Step> {
77 let (count, current) = (self.count, self.current);
78 let mut step = None;
79 ui.horizontal(|ui| {
80 let id = egui::Id::new("odox-find");
81 let mut output = egui::TextEdit::singleline(&mut self.query)
82 .id(id)
83 .hint_text(t("Find"))
84 .desired_width(240.0)
85 .show(ui);
86 if self.focus {
87 self.focus = false;
88 output.response.request_focus();
89 output.state.cursor.set_char_range(Some(CCursorRange::two(
90 CCursor::new(0),
91 CCursor::new(self.query.chars().count()),
92 )));
93 output.state.store(ui.ctx(), id);
94 }
95 if output.response.lost_focus() && ui.input(|input| input.key_pressed(Key::Enter)) {
96 step = Some(if ui.input(|input| input.modifiers.shift) {
97 Step::Previous
98 } else {
99 Step::Next
100 });
101 output.response.request_focus();
102 }
103 if output.response.lost_focus() && ui.input(|input| input.key_pressed(Key::Escape)) {
104 self.close();
105 return;
106 }
107 if ui.button(t("Previous")).clicked() {
108 step = Some(Step::Previous);
109 }
110 if ui.button(t("Next")).clicked() {
111 step = Some(Step::Next);
112 }
113 if self.query.is_empty() {
114 } else if count == 0 {
115 ui.colored_label(ui.visuals().warn_fg_color, t("No matches"));
116 } else {
117 ui.label(fill(
118 t("{current} of {count}"),
119 &[
120 ("current", &(current + 1).to_string()),
121 ("count", &count.to_string()),
122 ],
123 ));
124 }
125 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
126 if ui.button("×").on_hover_text(t("Close")).clicked() {
127 self.close();
128 }
129 });
130 });
131 step
132 }
133}