flux_tui/components/
button.rs1use super::*;
2use crate::canvas::*;
3
4use crossterm::event::{Event, KeyCode};
5use std::num::NonZero;
6use std::rc::Rc;
7use unicode_width::UnicodeWidthStr;
8
9pub type ExecuteCallback = dyn Fn(&mut Button);
10pub type ButtonKeyHandler = fn(&mut Button, event: &mut WindowEvent);
11
12pub struct Button {
22 text: String,
23 text_alignment: HorizontalAlignment,
24 callback: Rc<ExecuteCallback>,
25 key_handler: ButtonKeyHandler,
26 has_focus: bool,
27 base: WidgetBase,
28}
29
30impl Default for Button {
31 fn default() -> Self {
32 let mut s = Self {
33 text: String::default(),
34 text_alignment: HorizontalAlignment::default(),
35 callback: Rc::new(Self::default_callback),
36 key_handler: Self::default_key_handler,
37 has_focus: false,
38 base: WidgetBase::default(),
39 };
40 s.base.constraints.y.min = NonZero::<TSize>::MIN;
41 s.base.constraints.y.max = Size::Fixed(NonZero::<TSize>::MIN);
42 s
43 }
44}
45
46impl Button {
47 pub fn default_callback(_: &mut Self) {}
48
49 pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
50 if let Event::Key(k) = event.raw() {
51 if self.base.enabled && k.code == KeyCode::Enter {
52 self.execute();
53 event.handled = true;
54 }
55 }
56 }
57
58 pub fn set_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
60 self.callback = Rc::new(callback);
61 }
62
63 pub fn set_key_handler(&mut self, f: ButtonKeyHandler) {
64 self.key_handler = f;
65 }
66
67 pub fn set_text_alignment(&mut self, alignment: HorizontalAlignment) {
69 self.text_alignment = alignment;
70 }
71
72 pub fn get_text(&self) -> &str {
74 &self.text
75 }
76
77 pub fn set_text(&mut self, text: &str) {
79 self.text.clear();
80 self.text.push_str(text);
81 }
82
83 pub fn execute(&mut self) {
85 let cb = self.callback.clone();
86 cb(self);
87 }
88}
89
90impl Window for Button {
91 fn render(&self, canvas: &mut Canvas) {
92 let mut row = canvas.get_row_variable_width(TSize::MIN).unwrap();
93 let width = self.text.width() as TSize;
94 let mut skip = TSize::MIN;
95
96 match self.text_alignment {
97 HorizontalAlignment::Center => {
98 if row.width() > width {
99 skip = (row.width() - width) / 2;
100 }
101 }
102 HorizontalAlignment::Left => {}
104 HorizontalAlignment::Right => {
105 if row.width() > width {
106 skip = row.width() - width;
107 }
108 }
109 }
110
111 row.skip(skip);
112 row.add_string(
113 &self.text,
114 self.base.colors.base_fg,
115 self.base.colors.base_bg,
116 Style::Reverse.when(self.has_focus),
117 Some(VariableWidthGlyphRow::DOT3_REPLACEMENT),
118 )
119 .ok();
120 if let Some(color) = self.base.colors.disabled
121 && !self.base.enabled
122 {
123 row.fill_foreground(color);
124 }
125 }
126
127 fn handle_event(&mut self, event: &mut WindowEvent) {
128 match event.raw() {
129 Event::FocusGained => self.has_focus = true,
130 Event::FocusLost => self.has_focus = false,
131 _ => (self.key_handler)(self, event),
132 }
133 }
134
135 fn is_enabled(&self) -> bool {
136 self.base.enabled
137 }
138}
139
140impl WindowLayout for Button {
141 fn desired_size(&self, available_size: TPoint) -> TPoint {
142 self.base.desired_size(available_size)
143 }
144
145 fn border(&self) -> BorderStyle {
146 self.base.border
147 }
148
149 fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
150 self.base.alignment
151 }
152
153 fn is_visible(&self) -> bool {
154 self.base.visibility
155 }
156
157 fn margin(&self) -> Thickness {
158 self.base.margin
159 }
160}
161
162impl HasWindowUID for Button {
163 fn uid(&self) -> WindowUID {
164 self.base.uid
165 }
166}
167
168impl Widget for Button {
169 fn set_visibility(&mut self, visibility: bool) {
170 self.base.visibility = visibility;
171 self.provoke_changed_property(WindowProperty::IsVisible);
172 }
173
174 fn set_width(&mut self, width: Size) {
175 self.base.size.x = width;
176 self.provoke_changed_property(WindowProperty::Size);
177 }
178
179 fn set_height(&mut self, _: Size) {}
180
181 fn set_margin(&mut self, margin: Thickness) {
182 self.base.margin = margin;
183 self.provoke_changed_property(WindowProperty::Margin);
184 }
185
186 fn set_border(&mut self, border: BorderStyle) {
187 self.base.border = border;
188 self.provoke_changed_property(WindowProperty::Border);
189 }
190
191 fn set_alignment(&mut self, horizontal: HorizontalAlignment, vertical: VerticalAlignment) {
192 self.base.alignment = (horizontal, vertical);
193 self.provoke_changed_property(WindowProperty::Alignment);
194 }
195
196 fn set_enabled_state(&mut self, is_enabled: bool) {
197 self.base.enabled = is_enabled;
198 }
199
200 fn set_width_constraint(&mut self, width: SizeConstraint) {
201 self.base.constraints.x = width;
202 self.provoke_changed_property(WindowProperty::Size);
203 }
204
205 fn set_height_constraint(&mut self, _: SizeConstraint) {}
206}
207
208impl WidgetColors for Button {
209 fn set_disabled_color(&mut self, color: Option<Color>) {
210 self.base.colors.disabled = color;
211 }
212
213 fn set_base_fg_color(&mut self, color: Option<Color>) {
214 self.base.colors.base_fg = color;
215 }
216
217 fn set_base_bg_color(&mut self, color: Option<Color>) {
218 self.base.colors.base_bg = color;
219 }
220}