GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/display/list_item.rs (5.9K)
1 //! `TextItem` (retained cosmic-text buffer holder, unchanged) and the narrow-trait
2 //! `InteractiveListItem` (Phase 5j): Button-style press/release with themed
3 //! selected/hover/press overlays and title/subtitle text.
4
5 use crate::colors;
6 use crate::scene::layout::Rect;
7 use crate::scene::paint::PaintCtx;
8 use crate::widget::{Adapted, ElementState, Event, EventCtx, Input, Layout, MouseButton, Paint};
9
10 #[derive(Debug, Clone)]
11 pub struct TextItem {
12 pub buffer: cosmic_text::Buffer,
13 pub x: f32,
14 pub y: f32,
15 pub color: cosmic_text::Color,
16 pub bounds: Option<[f32; 4]>,
17 /// Optional circular clip `[cx, cy, r]` in logical pixels (a display-list item's
18 /// `clip_circle` carried through to the glyph pass). `None` for ordinary labels.
19 pub clip_circle: Option<[f32; 3]>,
20 /// Optional rounded-rect clip `[cx, cy, bx, by, r]` in logical pixels (a display-list
21 /// item's `clip_rrect` carried through to the glyph pass). `None` for ordinary labels.
22 pub clip_rrect: Option<[f32; 5]>,
23 }
24
25 impl TextItem {
26 pub fn new(
27 fs: &mut cosmic_text::FontSystem,
28 text: &str,
29 size: f32,
30 x: f32,
31 y: f32,
32 color: cosmic_text::Color,
33 font: Option<&str>,
34 bounds: Option<[f32; 4]>,
35 ) -> Self {
36 let buffer = crate::backend::window_runner::get_text_buffer(fs, text, size, font);
37 Self { buffer, x, y, color, bounds, clip_circle: None, clip_rrect: None }
38 }
39 }
40
41 #[derive(Debug, Clone)]
42 pub struct InteractiveListItem {
43 pub title: String,
44 pub subtitle: Option<String>,
45 pub selected: bool,
46 pub pressed: bool,
47 pub just_clicked: bool,
48 hovered: bool,
49 }
50
51 impl InteractiveListItem {
52 pub fn new(title: &str) -> Adapted<InteractiveListItem> {
53 Adapted::new(InteractiveListItem {
54 title: title.to_string(),
55 subtitle: None,
56 selected: false,
57 pressed: false,
58 just_clicked: false,
59 hovered: false,
60 })
61 }
62
63 pub fn set_selected(&mut self, selected: bool) {
64 self.selected = selected;
65 }
66 }
67
68 impl Adapted<InteractiveListItem> {
69 pub fn with_subtitle(mut self, subtitle: &str) -> Self {
70 self.subtitle = Some(subtitle.to_string());
71 self
72 }
73 }
74
75 impl Layout for InteractiveListItem {
76 fn inline_label(&self) -> bool {
77 true
78 }
79 }
80
81 impl Paint for InteractiveListItem {
82 /// A list row's text is in the list font — the rows of a TreeList, a menu's items.
83 fn widget_font(&self) -> Option<String> {
84 Some(crate::layout::list_font())
85 }
86
87 fn color(&self) -> [f32; 4] {
88 let theme = colors::active_theme();
89 if self.selected {
90 let mut base_color = colors::highlight_primary_color();
91 if self.pressed {
92 base_color[3] = (base_color[3] + theme.press_overlay[3]).min(1.0);
93 } else if self.hovered {
94 base_color[3] = (base_color[3] + theme.hover_overlay[3]).min(1.0);
95 }
96 base_color
97 } else if self.pressed {
98 let mut base_color = theme.surface_bg;
99 base_color[3] = (base_color[3] + theme.press_overlay[3]).min(1.0);
100 base_color
101 } else if self.hovered {
102 let mut base_color = theme.surface_bg;
103 base_color[3] = (base_color[3] + theme.hover_overlay[3]).min(1.0);
104 base_color
105 } else {
106 [0.0, 0.0, 0.0, 0.0]
107 }
108 }
109
110 fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
111 let col = self.color();
112 if col[3] > 0.0 {
113 // The state wash, rounded like a list-row Button's.
114 let r = crate::layout::button_corner_radius();
115 ctx.rounded_rect(rect, r, (true, true, true, true), col);
116 }
117
118 let (x, y, h) = (rect.x, rect.y, rect.height);
119 let title_y = if self.subtitle.is_some() {
120 crate::layout::align_text_y(y, h, 22.0, 0.0)
121 } else {
122 crate::layout::align_text_y(y, h, 12.0, 0.0)
123 };
124 let fc = colors::list_font_color();
125 let title_col = [(fc[0] * 255.0) as u8, (fc[1] * 255.0) as u8, (fc[2] * 255.0) as u8];
126 // Clipped to the row. A row's width comes from the LIST, not from its
127 // own text, so a long title or path is routine here — and unbounded it
128 // simply kept drawing past the row's right edge, over the scrollbar and
129 // out of the list.
130 let clip = Some([rect.x, rect.y, rect.x + rect.width, rect.y + rect.height]);
131 ctx.text_with(self.title.clone(), x + 8.0, title_y, 12.0, title_col, None, clip);
132 if let Some(ref sub) = self.subtitle {
133 ctx.text_with(sub.clone(), x + 8.0, title_y + 13.0, 10.0, [140, 140, 153], None, clip);
134 }
135 }
136 }
137
138 impl Input for InteractiveListItem {
139 fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
140 match event {
141 Event::MouseButton { button: MouseButton::Left, state: ElementState::Pressed, .. } => {
142 self.pressed = true;
143 true
144 }
145 Event::MouseButton { button: MouseButton::Left, state: ElementState::Released, x, y, .. } => {
146 if self.pressed && self.hit(ectx.rect, *x, *y) {
147 self.just_clicked = true;
148 }
149 std::mem::take(&mut self.pressed)
150 }
151 Event::MouseEnter => {
152 // Hover flips are visual changes: report them handled so the
153 // demand-driven frame loop repaints now — returning false left
154 // the highlight waiting for the next unrelated rebuild.
155 self.hovered = true;
156 true
157 }
158 Event::MouseLeave => {
159 self.hovered = false;
160 true
161 }
162 _ => false,
163 }
164 }
165
166 fn take_click(&mut self) -> bool {
167 std::mem::take(&mut self.just_clicked)
168 }
169
170 fn set_selected(&mut self, selected: bool) {
171 self.selected = selected;
172 }
173 }