1use super::common::shift;
2use super::input_field::InputField;
3use super::label::Label;
4use super::{Align, Bus, Event, Hub, Id, RenderQueue, View, ViewId, ID_FEEDER};
5use super::{BORDER_RADIUS_MEDIUM, THICKNESS_LARGE};
6use crate::color::{BLACK, WHITE};
7use crate::context::Context;
8use crate::device::CURRENT_DEVICE;
9use crate::font::{font_from_style, Fonts, NORMAL_STYLE};
10use crate::framebuffer::Framebuffer;
11use crate::geom::{big_half, halves, BorderSpec, CornerSpec, Rectangle};
12use crate::gesture::GestureEvent;
13use crate::unit::scale_by_dpi;
14
15pub struct NamedInput {
16 id: Id,
17 rect: Rectangle,
18 children: Vec<Box<dyn View>>,
19 view_id: ViewId,
20}
21
22impl NamedInput {
23 pub fn new(
24 text: String,
25 view_id: ViewId,
26 input_id: ViewId,
27 input_size: usize,
28 context: &mut Context,
29 ) -> NamedInput {
30 let id = ID_FEEDER.next();
31 let dpi = CURRENT_DEVICE.dpi;
32 let (width, height) = context.display.dims;
33
34 let input_size = input_size.max(3);
35 let mut children = Vec::new();
36 let font = font_from_style(&mut context.fonts, &NORMAL_STYLE, dpi);
37 let x_height = font.x_heights.0 as i32;
38 let padding = font.em() as i32;
39
40 let mut label_width = font.plan(&text, None, None).width;
41 let mut input_width = font.plan(&"0".repeat(input_size), None, None).width;
42 let mut total_width = 5 * padding + label_width + input_width;
43 let delta = width as i32 - total_width;
44
45 if delta < 0 {
46 let label_ratio = label_width as f32 / (label_width + input_width) as f32;
47 let label_delta = (delta as f32 * label_ratio) as i32;
48 let input_delta = delta - label_delta;
49 label_width = (label_width + label_delta).abs();
50 input_width = (input_width + input_delta).abs();
51 total_width += delta;
52 }
53
54 let (small_half_width, big_half_width) = halves(total_width);
55 let big_half_padding = big_half(padding);
56
57 let anchor = pt!(width as i32 / 2, height as i32 / 3);
58 let x_min = anchor.x - small_half_width;
59 let x_max = anchor.x + big_half_width;
60 let y_min = anchor.y - 4 * x_height;
61 let y_max = anchor.y + 4 * x_height;
62
63 let label = Label::new(
64 rect![
65 x_min + big_half_padding,
66 y_min + x_height,
67 x_min + big_half_padding + padding + label_width,
68 y_max - x_height
69 ],
70 text,
71 Align::Center,
72 );
73 children.push(Box::new(label) as Box<dyn View>);
74
75 let input_field = InputField::new(
76 rect![
77 x_max - 3 * padding - input_width,
78 y_min + 2 * x_height,
79 x_max - padding,
80 y_max - 2 * x_height
81 ],
82 input_id,
83 );
84 children.push(Box::new(input_field) as Box<dyn View>);
85
86 let rect = rect![x_min, y_min, x_max, y_max];
87
88 NamedInput {
89 id,
90 rect,
91 children,
92 view_id,
93 }
94 }
95
96 pub fn set_text(&mut self, text: &str, rq: &mut RenderQueue, context: &mut Context) {
97 if let Some(input_field) = self.children[1].downcast_mut::<InputField>() {
98 input_field.set_text(text, true, rq, context);
99 }
100 }
101}
102
103impl View for NamedInput {
104 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, _hub, bus, _rq, context), fields(event = ?evt), ret(level=tracing::Level::TRACE)))]
105 fn handle_event(
106 &mut self,
107 evt: &Event,
108 _hub: &Hub,
109 bus: &mut Bus,
110 _rq: &mut RenderQueue,
111 context: &mut Context,
112 ) -> bool {
113 match *evt {
114 Event::Submit(..) => {
115 bus.push_back(Event::Close(self.view_id));
116 false
117 }
118 Event::Gesture(GestureEvent::Tap(center))
119 | Event::Gesture(GestureEvent::HoldFingerShort(center, _)) => {
120 if !self.rect.includes(center) && !context.kb_rect.includes(center) {
121 if !context.kb_rect.is_empty() {
122 bus.push_back(Event::Close(self.view_id));
123 }
124 true
125 } else {
126 self.rect.includes(center)
127 }
128 }
129 Event::Gesture(..) => true,
130 _ => false,
131 }
132 }
133
134 #[cfg_attr(feature = "otel", tracing::instrument(skip(self, fb, _fonts, _rect), fields(rect = ?_rect)))]
135 fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) {
136 let dpi = CURRENT_DEVICE.dpi;
137 let border_radius = scale_by_dpi(BORDER_RADIUS_MEDIUM, dpi) as i32;
138 let border_thickness = scale_by_dpi(THICKNESS_LARGE, dpi) as u16;
139 fb.draw_rounded_rectangle_with_border(
140 &self.rect,
141 &CornerSpec::Uniform(border_radius),
142 &BorderSpec {
143 thickness: border_thickness,
144 color: BLACK,
145 },
146 &WHITE,
147 );
148 }
149
150 fn resize(
151 &mut self,
152 _rect: Rectangle,
153 _hub: &Hub,
154 _rq: &mut RenderQueue,
155 context: &mut Context,
156 ) {
157 let (width, height) = context.display.dims;
158 let dx = (width as i32 - height as i32) / 2;
159 let dy = (height as i32 - width as i32) / 3;
160 let delta = pt!(dx, dy);
161 shift(self, delta);
162 }
163
164 fn is_background(&self) -> bool {
165 true
166 }
167
168 fn view_id(&self) -> Option<ViewId> {
169 Some(self.view_id)
170 }
171
172 fn rect(&self) -> &Rectangle {
173 &self.rect
174 }
175
176 fn rect_mut(&mut self) -> &mut Rectangle {
177 &mut self.rect
178 }
179
180 fn children(&self) -> &Vec<Box<dyn View>> {
181 &self.children
182 }
183
184 fn children_mut(&mut self) -> &mut Vec<Box<dyn View>> {
185 &mut self.children
186 }
187
188 fn id(&self) -> Id {
189 self.id
190 }
191}