1use crate::common::Size;
7use egui::{vec2, FontId, Response, Sense, Stroke, Ui, Widget};
8use egui_components_theme::{mix, Theme};
9
10pub struct Input<'a> {
11 value: &'a mut String,
12 placeholder: Option<String>,
13 width: Option<f32>,
14 password: bool,
15 disabled: bool,
16 size: Size,
17}
18
19impl<'a> Input<'a> {
20 pub fn new(value: &'a mut String) -> Self {
21 Self {
22 value,
23 placeholder: None,
24 width: None,
25 password: false,
26 disabled: false,
27 size: Size::Medium,
28 }
29 }
30 pub fn placeholder(mut self, p: impl Into<String>) -> Self {
31 self.placeholder = Some(p.into());
32 self
33 }
34 pub fn width(mut self, w: f32) -> Self {
35 self.width = Some(w);
36 self
37 }
38 pub fn password(mut self, p: bool) -> Self {
39 self.password = p;
40 self
41 }
42 pub fn disabled(mut self, d: bool) -> Self {
43 self.disabled = d;
44 self
45 }
46 pub fn size(mut self, s: Size) -> Self {
47 self.size = s;
48 self
49 }
50 pub fn small(self) -> Self {
51 self.size(Size::Small)
52 }
53 pub fn large(self) -> Self {
54 self.size(Size::Large)
55 }
56}
57
58impl<'a> Widget for Input<'a> {
59 fn ui(self, ui: &mut Ui) -> Response {
60 let theme = Theme::get(ui.ctx());
61 let m = theme.metrics;
62 let c = theme.colors;
63 let height = self.size.input_height(&m);
64 let width = self.width.unwrap_or_else(|| ui.available_width().min(240.0));
65 let desired = vec2(width, height);
66
67 let (rect, response) = ui.allocate_exact_size(desired, Sense::hover());
69
70 let painter = ui.painter();
71 let radius = theme.corner();
72
73 let bg = if self.disabled {
74 mix(c.background, c.muted_background, 0.6)
75 } else {
76 c.background
77 };
78 painter.rect_filled(rect, radius, bg);
79
80 let mut border_color = c.input_border;
82 let mut has_focus = false;
83
84 let inner_rect = rect.shrink2(vec2(m.input_padding_x, 4.0));
86 let inner_response = {
87 let mut child = ui.new_child(
88 egui::UiBuilder::new()
89 .max_rect(inner_rect)
90 .layout(egui::Layout::left_to_right(egui::Align::Center)),
91 );
92 if self.disabled {
93 child.disable();
94 }
95 let edit = egui::TextEdit::singleline(self.value)
96 .frame(egui::Frame::NONE)
97 .desired_width(inner_rect.width())
98 .password(self.password)
99 .font(FontId::proportional(m.font_size_md))
100 .text_color(if self.disabled {
101 mix(c.foreground, c.muted_foreground, 0.5)
102 } else {
103 c.foreground
104 });
105 let r = child.add(edit);
106 if r.has_focus() {
107 has_focus = true;
108 }
109 r
110 };
111
112 if has_focus {
113 border_color = c.ring;
114 } else if response.hovered() || inner_response.hovered() {
115 border_color = mix(c.input_border, c.foreground, 0.25);
116 }
117
118 ui.painter().rect_stroke(
119 rect,
120 radius,
121 Stroke::new(m.border_width, border_color),
122 egui::StrokeKind::Inside,
123 );
124
125 if has_focus {
126 ui.painter().rect_stroke(
127 rect.expand(2.0),
128 theme.corner(),
129 theme.focus_ring(),
130 egui::StrokeKind::Outside,
131 );
132 }
133
134 if self.value.is_empty() && !has_focus {
136 if let Some(ph) = &self.placeholder {
137 let font = FontId::proportional(m.font_size_md);
138 ui.painter().text(
139 egui::pos2(inner_rect.left(), inner_rect.center().y),
140 egui::Align2::LEFT_CENTER,
141 ph,
142 font,
143 c.muted_foreground,
144 );
145 }
146 }
147
148 if !self.disabled && (response.hovered() || inner_response.hovered()) {
149 ui.ctx().set_cursor_icon(egui::CursorIcon::Text);
150 }
151
152 inner_response.union(response)
154 }
155}