pix_engine/gui/
widgets.rs1use crate::{gui::Direction, ops::clamp_size, prelude::*};
29
30pub mod field;
31pub mod select;
32pub mod slider;
33pub mod text;
34pub mod tooltip;
35
36impl PixState {
37 pub fn button<L>(&mut self, label: L) -> PixResult<bool>
58 where
59 L: AsRef<str>,
60 {
61 let label = label.as_ref();
62
63 let s = self;
64 let id = s.ui.get_id(&label);
65 let label = s.ui.get_label(label);
66 let pos = s.cursor_pos();
67 let fpad = s.theme.spacing.frame_pad;
68
69 let (label_width, label_height) = s.text_size(label)?;
71 let width = s.ui.next_width.take().unwrap_or(label_width);
72 let button = rect![pos, width, label_height].offset_size(2 * fpad);
73
74 let hovered = s.focused() && s.ui.try_hover(id, &button);
76 if s.focused() {
77 s.ui.try_focus(id);
78 }
79 let disabled = s.ui.disabled;
80 let active = s.ui.is_active(id);
81
82 s.push();
83 s.ui.push_cursor();
84
85 s.rect_mode(RectMode::Corner);
87 if hovered {
88 s.frame_cursor(&Cursor::hand())?;
89 }
90 let [stroke, bg, fg] = s.widget_colors(id, ColorType::Primary);
91 s.stroke(stroke);
92 s.fill(bg);
93 if active {
94 s.rect(button.offset([1, 1]))?;
95 } else {
96 s.rect(button)?;
97 }
98
99 s.rect_mode(RectMode::Center);
101 s.clip(button)?;
102 s.set_cursor_pos(button.center());
103 s.stroke(None);
104 s.fill(fg);
105 s.text(label)?;
106 s.clip(None)?;
107
108 s.ui.pop_cursor();
109 s.pop();
110
111 s.ui.handle_focus(id);
113 s.advance_cursor(button.size());
114 Ok(!disabled && s.ui.was_clicked(id))
115 }
116
117 pub fn link<S>(&mut self, text: S) -> PixResult<bool>
138 where
139 S: AsRef<str>,
140 {
141 let text = text.as_ref();
142
143 let s = self;
144 let id = s.ui.get_id(&text);
145 let text = s.ui.get_label(text);
146 let pos = s.cursor_pos();
147 let pad = s.theme.spacing.item_pad;
148
149 let (width, height) = s.text_size(text)?;
151 let bounding_box = rect![pos, width, height].grow(pad / 2);
152
153 let hovered = s.focused() && s.ui.try_hover(id, &bounding_box);
155 let focused = s.focused() && s.ui.try_focus(id);
156 let disabled = s.ui.disabled;
157 let active = s.ui.is_active(id);
158
159 s.push();
160
161 if hovered {
163 s.frame_cursor(&Cursor::hand())?;
164 }
165 let [stroke, bg, fg] = s.widget_colors(id, ColorType::Primary);
166 if focused {
167 s.stroke(stroke);
168 s.fill(None);
169 s.rect(bounding_box)?;
170 }
171
172 s.stroke(None);
174 if active {
175 s.fill(fg.blended(bg, 0.04));
176 } else {
177 s.fill(bg);
178 }
179 s.text(text)?;
180
181 s.pop();
182
183 s.ui.handle_focus(id);
185 Ok(!disabled && s.ui.was_clicked(id))
186 }
187
188 pub fn checkbox<S>(&mut self, label: S, checked: &mut bool) -> PixResult<bool>
207 where
208 S: AsRef<str>,
209 {
210 let label = label.as_ref();
211
212 let s = self;
213 let id = s.ui.get_id(&label);
214 let label = s.ui.get_label(label);
215 let pos = s.cursor_pos();
216 let (_, checkbox_size) = s.text_size(label)?;
217
218 let checkbox = square![pos, checkbox_size];
220
221 let hovered = s.focused() && s.ui.try_hover(id, &checkbox);
223 if s.focused() {
224 s.ui.try_focus(id);
225 }
226 let disabled = s.ui.disabled;
227
228 s.push();
229
230 s.rect_mode(RectMode::Corner);
232 if hovered {
233 s.frame_cursor(&Cursor::hand())?;
234 }
235 let [stroke, bg, fg] = if *checked {
236 s.widget_colors(id, ColorType::Primary)
237 } else {
238 s.widget_colors(id, ColorType::Background)
239 };
240 s.stroke(stroke);
241 s.fill(bg);
242 s.rect(checkbox)?;
243
244 if *checked {
245 s.stroke(fg);
246 s.stroke_weight(2);
247 let half = checkbox_size / 2;
248 let third = checkbox_size / 3;
249 let x = checkbox.left() + half - 1;
250 let y = checkbox.bottom() - third + 1;
251 let start = point![x - third + 2, y - third + 2];
252 let mid = point![x, y];
253 let end = point![x + third + 1, y - half + 2];
254 s.line([start, mid])?;
255 s.line([mid, end])?;
256 }
257 s.advance_cursor(checkbox.size());
258 s.pop();
259
260 s.same_line(None);
262 s.text(label)?;
263
264 s.ui.handle_focus(id);
266 if disabled {
267 Ok(false)
268 } else {
269 let clicked = s.ui.was_clicked(id);
270 if clicked {
271 *checked = !(*checked);
272 }
273 Ok(clicked)
274 }
275 }
276
277 pub fn radio<S>(&mut self, label: S, selected: &mut usize, index: usize) -> PixResult<bool>
298 where
299 S: AsRef<str>,
300 {
301 let label = label.as_ref();
302
303 let s = self;
304 let id = s.ui.get_id(&label);
305 let label = s.ui.get_label(label);
306 let pos = s.cursor_pos();
307 let (_, label_height) = s.text_size(label)?;
308 let radio_size = label_height / 2;
309
310 let radio = circle![pos + radio_size, radio_size];
312
313 let hovered = s.focused() && s.ui.try_hover(id, &radio);
315 if s.focused() {
316 s.ui.try_focus(id);
317 }
318 let disabled = s.ui.disabled;
319
320 s.push();
321
322 s.rect_mode(RectMode::Corner);
324 s.ellipse_mode(EllipseMode::Center);
325 if hovered {
326 s.frame_cursor(&Cursor::hand())?;
327 }
328 let is_selected = *selected == index;
329 let [stroke, bg, _] = if is_selected {
330 s.widget_colors(id, ColorType::Primary)
331 } else {
332 s.widget_colors(id, ColorType::Background)
333 };
334 if is_selected {
335 s.stroke(bg);
336 s.fill(None);
337 } else {
338 s.stroke(stroke);
339 s.fill(bg);
340 }
341 s.circle(radio)?;
342
343 if is_selected {
344 s.stroke(bg);
345 s.fill(bg);
346 s.circle([radio.x(), radio.y(), radio.radius() - 3])?;
347 }
348 s.advance_cursor(radio.bounding_rect().size());
349 s.pop();
350
351 s.same_line(None);
353 s.text(label)?;
354
355 s.ui.handle_focus(id);
357 if disabled {
358 Ok(false)
359 } else {
360 let clicked = s.ui.was_clicked(id);
361 if clicked {
362 *selected = index;
363 }
364 Ok(clicked)
365 }
366 }
367
368 pub fn arrow<P, S>(&mut self, pos: P, direction: Direction, scale: S) -> PixResult<()>
374 where
375 P: Into<Point<i32>>,
376 S: Into<f64>,
377 {
378 let pos: Point<f64> = pos.into().as_();
379 let scale = scale.into();
380
381 let s = self;
382 let font_size = clamp_size(s.theme.font_size);
383
384 let height = f64::from(font_size);
385 let mut ratio = height * 0.4 * scale;
386 let center = pos + point![height * 0.5, height * 0.5 * scale];
387
388 if let Direction::Up | Direction::Left = direction {
389 ratio = -ratio;
390 }
391 let (p1, p2, p3) = match direction {
392 Direction::Up | Direction::Down => (
393 point![0.0, 0.75],
394 point![-0.866, -0.75],
395 point![0.866, -0.75],
396 ),
397 Direction::Left | Direction::Right => (
398 point![0.75, 0.0],
399 point![-0.75, 0.866],
400 point![-0.75, -0.866],
401 ),
402 };
403
404 s.triangle([
405 (center + p1 * ratio).round().as_(),
406 (center + p2 * ratio).round().as_(),
407 (center + p3 * ratio).round().as_(),
408 ])?;
409
410 Ok(())
411 }
412}