1use super::theme::FontId;
4use crate::{
5 gui::{keys::KeyState, mouse::MouseState},
6 prelude::*,
7};
8use lru::LruCache;
9use std::{
10 collections::{hash_map::DefaultHasher, HashSet},
11 convert::TryInto,
12 error::Error,
13 fmt,
14 hash::{Hash, Hasher},
15 mem,
16 ops::{Deref, DerefMut},
17 str::FromStr,
18};
19
20#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
22pub struct ElementId(pub u64);
23
24impl ElementId {
25 const NONE: Self = ElementId(0);
26}
27
28impl fmt::Display for ElementId {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "{}", self.0)
31 }
32}
33
34impl Deref for ElementId {
35 type Target = u64;
36 fn deref(&self) -> &Self::Target {
37 &self.0
38 }
39}
40
41impl DerefMut for ElementId {
42 fn deref_mut(&mut self) -> &mut Self::Target {
43 &mut self.0
44 }
45}
46
47impl From<ElementId> for u64 {
48 fn from(id: ElementId) -> Self {
49 *id
50 }
51}
52
53const ELEMENT_CACHE_SIZE: usize = 128;
54
55#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
57pub(crate) struct Texture {
58 pub(crate) id: TextureId,
59 pub(crate) element_id: ElementId,
60 pub(crate) src: Option<Rect<i32>>,
61 pub(crate) dst: Option<Rect<i32>>,
62 pub(crate) visible: bool,
63 pub(crate) font_id: FontId,
64 pub(crate) font_size: u32,
65}
66
67impl Texture {
68 pub(crate) const fn new(
69 id: TextureId,
70 element_id: ElementId,
71 src: Option<Rect<i32>>,
72 dst: Option<Rect<i32>>,
73 font_id: FontId,
74 font_size: u32,
75 ) -> Self {
76 Self {
77 id,
78 element_id,
79 src,
80 dst,
81 visible: true,
82 font_id,
83 font_size,
84 }
85 }
86}
87
88#[derive(Debug)]
90pub(crate) struct UiState {
91 cursor: Point<i32>,
93 pcursor: Point<i32>,
95 column_offset: i32,
97 pub(crate) line_height: i32,
99 pub(crate) pline_height: i32,
101 cursor_stack: Vec<(Point<i32>, Point<i32>, i32, i32)>,
103 offset_stack: Vec<i32>,
105 id_stack: Vec<u64>,
107 pub(crate) next_width: Option<i32>,
109 pub(crate) textures: Vec<Texture>,
111 pub(crate) disabled: bool,
113 pub(crate) mouse: MouseState,
115 pub(crate) mouse_offset: Option<Point<i32>>,
117 pub(crate) pmouse: MouseState,
119 pub(crate) keys: KeyState,
121 pub(crate) elements: LruCache<ElementId, ElementState>,
123 active: Option<ElementId>,
125 hovered: Option<ElementId>,
127 focused: Option<ElementId>,
129 editing: Option<ElementId>,
131 focus_enabled: bool,
133 last_focusable: Option<ElementId>,
135 last_size: Option<Rect<i32>>,
137}
138
139impl Default for UiState {
140 #[allow(clippy::expect_used)]
141 fn default() -> Self {
142 Self {
143 cursor: point![],
144 pcursor: point![],
145 column_offset: 0,
146 line_height: 0,
147 pline_height: 0,
148 cursor_stack: vec![],
149 offset_stack: vec![],
150 id_stack: vec![],
151 next_width: None,
152 textures: vec![],
153 disabled: false,
154 mouse: MouseState::default(),
155 mouse_offset: None,
156 pmouse: MouseState::default(),
157 keys: KeyState::default(),
158 elements: LruCache::new(ELEMENT_CACHE_SIZE.try_into().expect("valid cache size")),
159 active: None,
160 hovered: None,
161 focused: Some(ElementId::NONE),
162 editing: None,
163 focus_enabled: true,
164 last_focusable: None,
165 last_size: None,
166 }
167 }
168}
169
170impl UiState {
171 #[inline]
173 pub(crate) fn pre_update(&mut self, theme: &Theme) {
174 self.clear_hovered();
175
176 self.pcursor = point![];
177 self.cursor = theme.spacing.frame_pad;
178 self.column_offset = 0;
179 }
180
181 #[inline]
183 pub(crate) fn post_update(&mut self) {
184 for texture in &mut self.textures {
185 texture.visible = false;
186 }
187
188 self.pmouse.pos = self.mouse.pos;
189 if !self.mouse.is_down(Mouse::Left) {
190 self.clear_active();
191 } else if !self.has_active() {
192 self.set_active(ElementId(0));
194 }
195 self.clear_entered();
196 }
197
198 #[inline]
200 #[must_use]
201 pub(crate) fn get_id<T: Hash>(&self, t: &T) -> ElementId {
202 let mut hasher = DefaultHasher::new();
203 t.hash(&mut hasher);
204 if let Some(id) = self.id_stack.last() {
205 id.hash(&mut hasher);
206 }
207 ElementId(hasher.finish())
208 }
209
210 #[inline]
212 #[must_use]
213 #[allow(clippy::unused_self)]
215 pub(crate) fn get_label<'a>(&self, label: &'a str) -> &'a str {
216 label.split("##").next().unwrap_or("")
217 }
218
219 #[inline]
221 pub(crate) const fn cursor(&self) -> Point<i32> {
222 self.cursor
223 }
224
225 #[inline]
227 pub(crate) fn set_cursor<P: Into<Point<i32>>>(&mut self, cursor: P) {
228 self.cursor = cursor.into();
229 }
230
231 #[inline]
233 pub(crate) const fn pcursor(&self) -> Point<i32> {
234 self.pcursor
235 }
236
237 #[inline]
239 pub(crate) const fn column_offset(&self) -> i32 {
240 self.column_offset
241 }
242
243 #[inline]
245 pub(crate) fn set_column_offset(&mut self, offset: i32) {
246 self.offset_stack.push(offset);
247 self.cursor.offset_x(offset);
248 self.column_offset += offset;
249 }
250
251 #[inline]
253 pub(crate) fn reset_column_offset(&mut self) {
254 let offset = self.offset_stack.pop().unwrap_or_default();
255 self.cursor.offset_x(-offset);
256 self.column_offset -= offset;
257 }
258
259 #[inline]
261 pub(crate) fn push_cursor(&mut self) {
262 self.cursor_stack.push((
263 self.pcursor,
264 self.cursor,
265 self.pline_height,
266 self.line_height,
267 ));
268 }
269
270 #[inline]
272 pub(crate) fn pop_cursor(&mut self) {
273 let (pcursor, cursor, pline_height, line_height) =
274 self.cursor_stack.pop().unwrap_or_default();
275 self.pcursor = pcursor;
276 self.cursor = cursor;
277 self.pline_height = pline_height;
278 self.line_height = line_height;
279 }
280
281 #[inline]
283 pub(crate) fn mouse_pos(&self) -> Point<i32> {
284 let mut pos = self.mouse.pos;
285 if let Some(offset) = self.mouse_offset {
286 pos.offset(-offset);
287 }
288 pos
289 }
290
291 #[inline]
293 pub(crate) fn pmouse_pos(&self) -> Point<i32> {
294 let mut pos = self.pmouse.pos;
295 if let Some(offset) = self.mouse_offset {
296 pos.offset(-offset);
297 }
298 pos
299 }
300
301 #[inline]
303 #[must_use]
304 pub(crate) fn mouse_pressed(&self) -> bool {
305 self.mouse.is_pressed()
306 }
307
308 #[inline]
310 #[must_use]
311 pub(crate) fn mouse_clicked(&self, btn: Mouse) -> bool {
312 self.mouse.was_clicked(btn)
313 }
314
315 #[inline]
317 #[must_use]
318 pub(crate) fn mouse_dbl_clicked(&self, btn: Mouse) -> bool {
319 self.mouse.was_dbl_clicked(btn)
320 }
321
322 #[inline]
324 #[must_use]
325 pub(crate) fn mouse_down(&self, btn: Mouse) -> bool {
326 self.mouse.is_down(btn)
327 }
328
329 #[inline]
331 #[must_use]
332 pub(crate) const fn mouse_buttons(&self) -> &HashSet<Mouse> {
333 self.mouse.pressed()
334 }
335
336 #[inline]
338 #[must_use]
339 pub(crate) fn key_pressed(&self) -> bool {
340 self.keys.is_pressed()
341 }
342
343 #[inline]
345 #[must_use]
346 pub(crate) fn key_down(&self, key: Key) -> bool {
347 self.keys.is_down(key)
348 }
349
350 #[inline]
352 #[must_use]
353 pub(crate) const fn keys(&self) -> &HashSet<Key> {
354 self.keys.pressed()
355 }
356
357 #[inline]
359 #[must_use]
360 pub(crate) const fn keymod_down(&self, keymod: KeyMod) -> bool {
361 self.keys.mod_down(keymod)
362 }
363
364 #[inline]
366 pub(crate) const fn keymod(&self) -> &KeyMod {
367 self.keys.keymod()
368 }
369
370 #[inline]
372 pub(crate) fn offset_mouse<P: Into<Point<i32>>>(&mut self, offset: P) {
373 self.mouse_offset = Some(offset.into());
374 }
375
376 #[inline]
378 pub(crate) fn clear_mouse_offset(&mut self) {
379 self.mouse_offset = None;
380 }
381
382 #[inline]
386 #[must_use]
387 pub(crate) fn is_active(&self, id: ElementId) -> bool {
388 !self.disabled && matches!(self.active, Some(el) if el == id)
389 }
390
391 #[inline]
393 #[must_use]
394 pub(crate) const fn has_active(&self) -> bool {
395 self.active.is_some()
396 }
397
398 #[inline]
400 pub(crate) fn set_active(&mut self, id: ElementId) {
401 self.active = Some(id);
402 }
403
404 #[inline]
406 pub(crate) fn clear_active(&mut self) {
407 self.active = None;
408 }
409
410 #[inline]
414 #[must_use]
415 pub(crate) fn is_hovered(&self, id: ElementId) -> bool {
416 matches!(self.hovered, Some(el) if el == id)
417 }
418
419 #[inline]
421 #[must_use]
422 pub(crate) const fn has_hover(&self) -> bool {
423 self.hovered.is_some()
424 }
425
426 #[inline]
429 pub(crate) fn hover(&mut self, id: ElementId) {
430 self.hovered = Some(id);
431 if !self.has_active() && self.mouse.is_down(Mouse::Left) {
434 self.set_active(id);
435 }
436 }
437
438 #[inline]
440 pub(crate) fn clear_hovered(&mut self) {
441 self.hovered = None;
442 }
443
444 #[inline]
446 pub(crate) fn try_hover<S: Contains<Point<i32>>>(&mut self, id: ElementId, shape: &S) -> bool {
447 if !self.has_hover() && !self.disabled && shape.contains(self.mouse_pos()) {
448 self.hover(id);
449 }
450 self.is_hovered(id)
451 }
452
453 #[inline]
456 #[must_use]
457 pub(crate) fn is_focused(&self, id: ElementId) -> bool {
458 !self.disabled && matches!(self.focused, Some(el) if el == id)
459 }
460
461 #[inline]
463 #[must_use]
464 pub(crate) const fn has_focused(&self) -> bool {
465 self.focused.is_some()
466 }
467
468 #[inline]
470 pub(crate) fn focus(&mut self, id: ElementId) {
471 self.focused = Some(id);
472 }
473
474 #[inline]
477 pub(crate) fn try_focus(&mut self, id: ElementId) -> bool {
478 if !self.disabled && !self.has_focused() {
479 self.focus(id);
480 }
481 self.is_focused(id)
482 }
483
484 #[inline]
486 pub(crate) fn blur(&mut self) {
487 self.focused = Some(ElementId::NONE);
488 }
489
490 #[inline]
492 #[must_use]
493 pub(crate) fn is_editing(&self, id: ElementId) -> bool {
494 !self.disabled && matches!(self.editing, Some(el) if el == id)
495 }
496
497 #[inline]
499 pub(crate) fn begin_edit(&mut self, id: ElementId) {
500 self.editing = Some(id);
501 }
502
503 #[inline]
505 pub(crate) fn end_edit(&mut self) {
506 self.editing = None;
507 }
508
509 #[inline]
511 pub(crate) fn disable_focus(&mut self) {
512 self.focus_enabled = false;
513 }
514
515 #[inline]
517 pub(crate) fn enable_focus(&mut self) {
518 self.focus_enabled = true;
519 }
520
521 #[inline]
523 pub(crate) fn handle_focus(&mut self, id: ElementId) {
524 if !self.focus_enabled {
525 return;
526 }
527 let active = self.is_active(id);
528 let hovered = self.is_hovered(id);
529 let focused = self.is_focused(id);
530 if self.keys.was_entered(Key::Tab) {
531 let none_focused = self.focused == Some(ElementId::NONE);
536 if none_focused || focused {
537 if self.keys.mod_down(KeyMod::SHIFT) {
538 self.focused = self.last_focusable;
539 self.clear_entered();
540 } else if focused {
541 self.focused = None;
542 self.clear_entered();
543 } else if none_focused {
544 self.focused = Some(id);
545 self.clear_entered();
546 }
547 }
548 } else if !self.mouse.is_down(Mouse::Left) && active && hovered {
549 self.focus(id);
551 } else if focused && self.mouse.is_down(Mouse::Left) && !active && !hovered {
552 self.blur();
554 }
555 self.last_focusable = Some(id);
556 }
557
558 #[inline]
565 #[must_use]
566 pub(crate) fn was_clicked(&mut self, id: ElementId) -> bool {
567 if self.is_focused(id) && self.keys.was_entered(Key::Return) {
569 self.clear_entered();
570 true
571 } else {
572 !self.mouse.is_down(Mouse::Left) && self.is_hovered(id) && self.is_active(id)
574 }
575 }
576
577 #[inline]
580 #[must_use]
581 pub(crate) const fn key_entered(&self) -> Option<Key> {
582 self.keys.entered
583 }
584
585 #[inline]
587 pub(crate) fn clear_entered(&mut self) {
588 self.keys.typed = None;
589 self.keys.entered = None;
590 self.mouse.clicked.clear();
591 self.mouse.xrel = 0;
592 self.mouse.yrel = 0;
593 }
594
595 #[inline]
597 pub(crate) fn scroll(&self, id: ElementId) -> Vector<i32> {
598 self.elements
599 .peek(&id)
600 .map_or_else(Vector::default, |state| state.scroll)
601 }
602
603 #[inline]
605 pub(crate) fn set_scroll(&mut self, id: ElementId, scroll: Vector<i32>) {
606 if let Some(state) = self.elements.get_mut(&id) {
607 state.scroll = scroll;
608 } else {
609 self.elements.put(
610 id,
611 ElementState {
612 scroll,
613 ..ElementState::default()
614 },
615 );
616 }
617 }
618
619 #[inline]
621 #[must_use]
622 pub(crate) fn text_edit<S>(&mut self, id: ElementId, initial_text: S) -> String
623 where
624 S: Into<String>,
625 {
626 self.elements.get_mut(&id).map_or_else(
627 || initial_text.into(),
628 |state| mem::take(&mut state.text_edit),
629 )
630 }
631
632 #[inline]
634 pub(crate) fn set_text_edit(&mut self, id: ElementId, text_edit: String) {
635 if let Some(state) = self.elements.get_mut(&id) {
636 state.text_edit = text_edit;
637 } else {
638 self.elements.put(
639 id,
640 ElementState {
641 text_edit,
642 ..ElementState::default()
643 },
644 );
645 }
646 }
647
648 #[inline]
650 #[must_use]
651 pub(crate) fn parse_text_edit<T>(&mut self, id: ElementId, default: T) -> T
652 where
653 T: FromStr + Copy,
654 <T as FromStr>::Err: Error + Sync + Send + 'static,
655 {
656 self.elements
657 .pop(&id)
658 .map_or(default, |state| state.text_edit.parse().unwrap_or(default))
659 }
660
661 #[inline]
663 #[must_use]
664 pub(crate) fn expanded(&mut self, id: ElementId) -> bool {
665 self.elements
666 .get_mut(&id)
667 .map_or(false, |state| state.expanded)
668 }
669
670 #[inline]
672 pub(crate) fn set_expanded(&mut self, id: ElementId, expanded: bool) {
673 if let Some(state) = self.elements.get_mut(&id) {
674 state.expanded = expanded;
675 } else {
676 self.elements.put(
677 id,
678 ElementState {
679 expanded,
680 ..ElementState::default()
681 },
682 );
683 }
684 }
685
686 #[inline]
689 #[must_use]
690 pub(crate) fn last_width(&self) -> i32 {
691 self.last_size.map(|s| s.width()).unwrap_or_default()
692 }
693}
694
695impl PixState {
696 #[inline]
699 pub fn push_id<I>(&mut self, id: I)
700 where
701 I: TryInto<u64>,
702 {
703 self.ui.id_stack.push(id.try_into().unwrap_or(1));
704 }
705
706 #[inline]
708 pub fn pop_id(&mut self) {
709 self.ui.id_stack.pop();
710 }
711
712 #[inline]
730 pub const fn cursor_pos(&self) -> Point<i32> {
731 self.ui.cursor()
732 }
733
734 #[inline]
751 pub fn set_cursor_pos<P: Into<Point<i32>>>(&mut self, cursor: P) {
752 self.ui.set_cursor(cursor.into());
753 }
754
755 #[inline]
758 pub fn set_column_offset(&mut self, offset: i32) {
759 self.ui.set_column_offset(offset);
760 }
761
762 #[inline]
765 pub fn reset_column_offset(&mut self) {
766 self.ui.reset_column_offset();
767 }
768
769 #[inline]
787 #[must_use]
788 pub fn hovered(&self) -> bool {
789 self.ui.last_size.map_or(false, |rect| {
790 !self.ui.disabled && rect.contains(self.mouse_pos())
791 })
792 }
793
794 #[inline]
812 #[must_use]
813 pub fn clicked(&self) -> bool {
814 self.ui.last_size.map_or(false, |rect| {
815 !self.ui.disabled && self.mouse_clicked(Mouse::Left) && rect.contains(self.mouse_pos())
816 })
817 }
818
819 #[inline]
837 #[must_use]
838 pub fn dbl_clicked(&self) -> bool {
839 self.ui.last_size.map_or(false, |rect| {
840 !self.ui.disabled
841 && self.mouse_clicked(Mouse::Left)
842 && self.mouse_dbl_clicked(Mouse::Left)
843 && rect.contains(self.mouse_pos())
844 })
845 }
846}
847
848impl PixState {
849 #[inline]
851 pub(crate) fn advance_cursor<S: Into<Point<i32>>>(&mut self, size: S) {
852 let size = size.into();
853 let pos = self.ui.cursor;
854 let padx = self.theme.spacing.frame_pad.x();
855 let pady = self.theme.spacing.item_pad.y();
856 let offset_x = self.ui.column_offset;
857
858 self.ui.pcursor = point![pos.x() + size.x(), pos.y()];
860 if self.settings.rect_mode == RectMode::Center {
861 self.ui.pcursor.offset(-size / 2);
862 }
863
864 if cfg!(feature = "debug_ui") {
865 self.push();
866 self.fill(None);
867 self.stroke(Color::RED);
868 let _result = self.rect(rect![pos, size.x(), size.y()]);
869 self.fill(Color::BLUE);
870 let _result = self.circle(circle![self.ui.pcursor(), 3]);
871 self.pop();
872 }
873
874 let line_height = self.ui.line_height.max(size.y());
877 self.ui.cursor = point![padx + offset_x, pos.y() + line_height + pady];
878 self.ui.pline_height = line_height;
879 self.ui.line_height = 0;
880 self.ui.last_size = Some(rect![pos, size.x(), size.y()]);
881 }
882
883 #[inline]
885 pub(crate) fn get_or_create_texture<R>(
886 &mut self,
887 id: ElementId,
888 src: R,
889 dst: Rect<i32>,
890 ) -> PixResult<TextureId>
891 where
892 R: Into<Option<Rect<i32>>>,
893 {
894 let font_id = self.theme.fonts.body.id();
895 let font_size = self.theme.font_size;
896 if let Some(texture) = self
897 .ui
898 .textures
899 .iter_mut()
900 .find(|t| t.element_id == id && t.font_id == font_id && t.font_size == font_size)
901 {
902 texture.visible = true;
903 texture.dst = Some(dst);
904 Ok(texture.id)
905 } else {
906 let texture_id =
907 self.create_texture(dst.width() as u32, dst.height() as u32, PixelFormat::Rgba)?;
908 self.ui.textures.push(Texture::new(
909 texture_id,
910 id,
911 src.into(),
912 Some(dst),
913 font_id,
914 font_size,
915 ));
916 Ok(texture_id)
917 }
918 }
919}
920
921#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
923pub(crate) struct ElementState {
924 scroll: Vector<i32>,
925 text_edit: String,
926 current_tab: usize,
927 expanded: bool,
928}