egui_macview/controls/
input_cell.rs1use crate::theme::MacTheme;
7use egui::{Color32, CornerRadius, Key, Response, Stroke, StrokeKind, Ui, Vec2};
8
9pub struct MacInputCell<'a> {
32 id: egui::Id,
33 value: &'a mut String,
34 theme: Option<&'a MacTheme>,
35 width: Option<f32>,
36 height: Option<f32>,
37 font_size: Option<f32>,
38 corner_radius: Option<f32>,
39 next_focus_id: Option<egui::Id>,
40 prev_focus_id: Option<egui::Id>,
41 placeholder: Option<&'a str>,
42}
43
44impl<'a> MacInputCell<'a> {
45 pub fn new(id_source: impl std::hash::Hash + std::fmt::Debug, value: &'a mut String) -> Self {
47 #[allow(deprecated)]
48 let id = egui::Id::new(id_source);
49 Self {
50 id,
51 value,
52 theme: None,
53 width: None,
54 height: None,
55 font_size: None,
56 corner_radius: None,
57 next_focus_id: None,
58 prev_focus_id: None,
59 placeholder: None,
60 }
61 }
62
63 pub fn with_id(id: egui::Id, value: &'a mut String) -> Self {
65 Self {
66 id,
67 value,
68 theme: None,
69 width: None,
70 height: None,
71 font_size: None,
72 corner_radius: None,
73 next_focus_id: None,
74 prev_focus_id: None,
75 placeholder: None,
76 }
77 }
78
79 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
81 self.theme = Some(theme);
82 self
83 }
84
85 pub fn width(mut self, width: f32) -> Self {
87 self.width = Some(width);
88 self
89 }
90
91 pub fn height(mut self, height: f32) -> Self {
93 self.height = Some(height);
94 self
95 }
96
97 pub fn font_size(mut self, font_size: f32) -> Self {
99 self.font_size = Some(font_size);
100 self
101 }
102
103 pub fn placeholder(mut self, text: &'a str) -> Self {
105 self.placeholder = Some(text);
106 self
107 }
108
109 pub fn next_focus(mut self, id: egui::Id) -> Self {
111 self.next_focus_id = Some(id);
112 self
113 }
114
115 pub fn prev_focus(mut self, id: egui::Id) -> Self {
117 self.prev_focus_id = Some(id);
118 self
119 }
120
121 pub fn show(self, ui: &mut Ui) -> Response {
123 let fallback_theme;
124 let theme = match self.theme {
125 Some(t) => t,
126 None => {
127 fallback_theme = MacTheme::default();
128 &fallback_theme
129 }
130 };
131
132 let metrics = &theme.control_metrics().text_field.compact_infobox;
133 let ring_metrics = theme.focus_ring();
134
135 let height = self.height.unwrap_or_else(|| metrics.height.points());
136 let width = self.width.unwrap_or_else(|| metrics.default_width.points());
137 let font_size = self.font_size.unwrap_or_else(|| metrics.font_size.points());
138 let corner_radius = self
139 .corner_radius
140 .unwrap_or_else(|| metrics.corner_radius.points());
141
142 let desired_size = Vec2::new(width, height);
143 let (rect, mut response) =
144 ui.allocate_exact_size(desired_size, egui::Sense::click_and_drag());
145
146 if response.clicked() {
147 ui.memory_mut(|m| m.request_focus(self.id));
148 }
149
150 let is_focused = ui.memory(|m| m.has_focus(self.id));
151 let painter = ui.painter_at(rect);
152
153 if is_focused {
155 let halo_offset = ring_metrics.offset.points();
156 let halo_stroke_width = ring_metrics.stroke_width.points();
157 let halo_radius = corner_radius + ring_metrics.corner_radius_delta.points();
158 let halo_rect = rect.expand(halo_offset);
159
160 painter.rect(
161 halo_rect,
162 CornerRadius::same(halo_radius as u8),
163 Color32::TRANSPARENT,
164 Stroke::new(halo_stroke_width, theme.keyboard_focus_indicator()),
165 StrokeKind::Outside,
166 );
167 }
168
169 let bg_color = theme.control_background();
171 let border_stroke = if is_focused {
172 Stroke::new(metrics.border_width.points(), theme.control_accent())
173 } else {
174 Stroke::new(metrics.border_width.points(), theme.separator())
175 };
176
177 painter.rect(
178 rect,
179 CornerRadius::same(corner_radius as u8),
180 bg_color,
181 border_stroke,
182 StrokeKind::Inside,
183 );
184
185 if is_focused {
187 let tab_pressed = ui.input(|i| i.key_pressed(Key::Tab));
188 let shift_down = ui.input(|i| i.modifiers.shift);
189
190 if tab_pressed {
191 if shift_down {
192 if let Some(prev) = self.prev_focus_id {
193 ui.memory_mut(|m| m.request_focus(prev));
194 }
195 } else if let Some(next) = self.next_focus_id {
196 ui.memory_mut(|m| m.request_focus(next));
197 }
198 }
199 }
200
201 let shrink_pad = metrics
203 .padding_horizontal
204 .as_ref()
205 .map(|d| d.points())
206 .unwrap_or_else(|| metrics.corner_radius.points());
207 let mut child_ui = ui.new_child(
208 egui::UiBuilder::new()
209 .max_rect(rect.shrink(shrink_pad))
210 .layout(egui::Layout::left_to_right(egui::Align::Center)),
211 );
212
213 let mut text_edit = egui::TextEdit::singleline(self.value)
214 .id(self.id)
215 .font(egui::FontId::proportional(font_size))
216 .desired_width(width - shrink_pad * 2.0)
217 .frame(egui::Frame::NONE);
218
219 if let Some(hint) = self.placeholder {
220 text_edit = text_edit.hint_text(hint);
221 }
222
223 let text_res = child_ui.add(text_edit);
224 if text_res.changed() {
225 response.mark_changed();
226 }
227
228 response
229 }
230}