cronixui 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +71 -71
- package/packages/flutter/.qwen/settings.json +7 -0
- package/packages/flutter/pubspec.yaml +20 -20
- package/packages/go/cronixui/cronixui.go +926 -926
- package/packages/python/README.md +142 -0
- package/packages/python/cronixui/__init__.py +15 -6
- package/packages/python/cronixui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/accordion.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/alert.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/avatar.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/badge.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/button.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/card.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/command_palette.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/core.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/dropdown.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/form.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/layout.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/list.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/loading.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/modal.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/nav.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/pagination.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/progress.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/search.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/table.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tabs.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/toast.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/toggle.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tokens.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tooltip.cpython-314.pyc +0 -0
- package/packages/python/cronixui/alert.py +119 -36
- package/packages/python/cronixui/avatar.py +129 -22
- package/packages/python/cronixui/badge.py +161 -24
- package/packages/python/cronixui/button.py +96 -27
- package/packages/python/cronixui/card.py +206 -33
- package/packages/python/cronixui/core.py +212 -23
- package/packages/python/cronixui/form.py +552 -141
- package/packages/python/cronixui/layout.py +358 -96
- package/packages/python/cronixui/list.py +140 -37
- package/packages/python/cronixui/loading.py +107 -17
- package/packages/python/cronixui/progress.py +189 -47
- package/packages/python/cronixui/table.py +118 -31
- package/packages/python/cronixui/tooltip.py +117 -15
- package/packages/react/src/components/Accordion.tsx +82 -82
- package/packages/react/src/components/Button.tsx +47 -47
- package/packages/react/src/components/Card.tsx +69 -69
- package/packages/react/src/components/CommandPalette.tsx +131 -131
- package/packages/react/src/components/Dropdown.tsx +88 -88
- package/packages/react/src/components/FileInput.tsx +86 -86
- package/packages/react/src/components/FormGroup.tsx +36 -36
- package/packages/react/src/components/List.tsx +55 -55
- package/packages/react/src/components/Pagination.tsx +107 -107
- package/packages/react/src/components/Progress.tsx +49 -49
- package/packages/react/src/components/Search.tsx +95 -95
- package/packages/react/src/components/Sidebar.tsx +64 -64
- package/packages/react/src/components/Stack.tsx +69 -69
- package/packages/react/src/components/Table.tsx +90 -90
- package/packages/react/src/components/Toast.tsx +134 -134
- package/packages/react/src/components/Typography.tsx +66 -66
- package/packages/react/src/index.ts +40 -40
- package/packages/react/src/styles.css +2039 -2039
- package/packages/rust/cronixui/src/components/avatar.rs +85 -85
- package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -58
- package/packages/rust/cronixui/src/components/card.rs +259 -259
- package/packages/rust/cronixui/src/components/command_palette.rs +254 -254
- package/packages/rust/cronixui/src/components/dropdown.rs +179 -179
- package/packages/rust/cronixui/src/components/file_input.rs +74 -74
- package/packages/rust/cronixui/src/components/mod.rs +51 -51
- package/packages/rust/cronixui/src/components/search.rs +185 -185
- package/packages/rust/cronixui/src/components/skeleton.rs +63 -63
- package/packages/rust/cronixui/src/components/table.rs +56 -56
- package/packages/rust/cronixui/src/lib.rs +128 -128
- package/packages/web/dist/cronixui.css +97 -93
- package/packages/web/dist/cronixui.min.css +1 -1
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
//! Command palette component
|
|
2
|
-
|
|
3
|
-
use egui::*;
|
|
4
|
-
use crate::{colors::*, tokens::*};
|
|
5
|
-
|
|
6
|
-
pub struct CommandItem {
|
|
7
|
-
pub title: String,
|
|
8
|
-
pub subtitle: Option<String>,
|
|
9
|
-
pub kbd: Option<String>,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
impl CommandItem {
|
|
13
|
-
pub fn new(title: impl Into<String>) -> Self {
|
|
14
|
-
Self { title: title.into(), subtitle: None, kbd: None }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
|
|
18
|
-
self.subtitle = Some(subtitle.into());
|
|
19
|
-
self
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
pub fn kbd(mut self, kbd: impl Into<String>) -> Self {
|
|
23
|
-
self.kbd = Some(kbd.into());
|
|
24
|
-
self
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
pub struct CommandPalette {
|
|
29
|
-
pub items: Vec<CommandItem>,
|
|
30
|
-
pub query: String,
|
|
31
|
-
pub open: bool,
|
|
32
|
-
pub selected_index: usize,
|
|
33
|
-
id: Id,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
impl CommandPalette {
|
|
37
|
-
pub fn new(id: impl Into<Id>) -> Self {
|
|
38
|
-
Self { items: Vec::new(), query: String::new(), open: false, selected_index: 0, id: id.into() }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
pub fn item(mut self, item: CommandItem) -> Self {
|
|
42
|
-
self.items.push(item);
|
|
43
|
-
self
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
pub fn set_items(&mut self, items: Vec<CommandItem>) {
|
|
47
|
-
self.items = items;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
pub fn open(&mut self) {
|
|
51
|
-
self.open = true;
|
|
52
|
-
self.query.clear();
|
|
53
|
-
self.selected_index = 0;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
pub fn close(&mut self) {
|
|
57
|
-
self.open = false;
|
|
58
|
-
self.query.clear();
|
|
59
|
-
self.selected_index = 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub fn toggle(&mut self) {
|
|
63
|
-
if self.open {
|
|
64
|
-
self.close();
|
|
65
|
-
} else {
|
|
66
|
-
self.open();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
pub fn filter(&self) -> Vec<&CommandItem> {
|
|
71
|
-
if self.query.is_empty() {
|
|
72
|
-
return self.items.iter().collect();
|
|
73
|
-
}
|
|
74
|
-
self.items
|
|
75
|
-
.iter()
|
|
76
|
-
.filter(|item| item.title.to_lowercase().contains(&self.query.to_lowercase()))
|
|
77
|
-
.collect()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/// Render the command palette as a modal dialog
|
|
81
|
-
/// Returns the index of the selected command (into the filtered list)
|
|
82
|
-
pub fn show(&mut self, ctx: &egui::Context) -> Option<usize> {
|
|
83
|
-
if !self.open {
|
|
84
|
-
return None;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let colors = Colors::default();
|
|
88
|
-
let mut result = None;
|
|
89
|
-
|
|
90
|
-
// Modal backdrop
|
|
91
|
-
egui::Area::new(self.id.with("backdrop"))
|
|
92
|
-
.order(Order::Foreground)
|
|
93
|
-
.interactable(true)
|
|
94
|
-
.show(ctx, |ui| {
|
|
95
|
-
let screen_size = ctx.screen_rect().size();
|
|
96
|
-
ui.set_min_size(screen_size);
|
|
97
|
-
|
|
98
|
-
// Semi-transparent backdrop
|
|
99
|
-
let painter = ui.painter_at(ui.max_rect());
|
|
100
|
-
painter.rect_filled(
|
|
101
|
-
ui.max_rect(),
|
|
102
|
-
Rounding::ZERO,
|
|
103
|
-
Color32::from_black_alpha(150),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
// Command palette panel centered on screen
|
|
107
|
-
let panel_width = 500.0;
|
|
108
|
-
let panel_height = 400.0;
|
|
109
|
-
|
|
110
|
-
egui::Frame::none()
|
|
111
|
-
.fill(colors.surface)
|
|
112
|
-
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
113
|
-
.rounding(Rounding::same(tokens::RADIUS_LG))
|
|
114
|
-
.shadow(egui::Shadow {
|
|
115
|
-
offset: [0.0, 8.0].into(),
|
|
116
|
-
blur: 24.0,
|
|
117
|
-
spread: 0.0,
|
|
118
|
-
color: Color32::from_black_alpha(100),
|
|
119
|
-
})
|
|
120
|
-
.show(ui, |ui| {
|
|
121
|
-
ui.set_min_size(vec2(panel_width, panel_height));
|
|
122
|
-
|
|
123
|
-
// Search input at top
|
|
124
|
-
ui.horizontal(|ui| {
|
|
125
|
-
ui.label("⌘");
|
|
126
|
-
let response = ui.add(
|
|
127
|
-
TextEdit::singleline(&mut self.query)
|
|
128
|
-
.hint_text("Type a command...")
|
|
129
|
-
.desired_width(f32::INFINITY)
|
|
130
|
-
.font(FontId::new(tokens::FONT_SIZE_BASE, FontFamily::Proportional)),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
if response.has_focus() {
|
|
134
|
-
// Handle keyboard navigation
|
|
135
|
-
if ui.input(|i| i.key_pressed(Key::ArrowDown)) {
|
|
136
|
-
let filtered = self.filter();
|
|
137
|
-
if !filtered.is_empty() {
|
|
138
|
-
self.selected_index = (self.selected_index + 1) % filtered.len();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if ui.input(|i| i.key_pressed(Key::ArrowUp)) {
|
|
142
|
-
let filtered = self.filter();
|
|
143
|
-
if !filtered.is_empty() {
|
|
144
|
-
self.selected_index = if self.selected_index == 0 {
|
|
145
|
-
filtered.len() - 1
|
|
146
|
-
} else {
|
|
147
|
-
self.selected_index - 1
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if ui.input(|i| i.key_pressed(Key::Enter)) {
|
|
152
|
-
result = Some(self.selected_index);
|
|
153
|
-
}
|
|
154
|
-
if ui.input(|i| i.key_pressed(Key::Escape)) {
|
|
155
|
-
self.close();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
ui.add_space(SPACE_2);
|
|
161
|
-
|
|
162
|
-
// Separator
|
|
163
|
-
ui.separator();
|
|
164
|
-
|
|
165
|
-
ui.add_space(SPACE_2);
|
|
166
|
-
|
|
167
|
-
// Command list
|
|
168
|
-
let filtered = self.filter();
|
|
169
|
-
if !filtered.is_empty() {
|
|
170
|
-
// Ensure selected_index is valid
|
|
171
|
-
if self.selected_index >= filtered.len() {
|
|
172
|
-
self.selected_index = 0;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
egui::ScrollArea::vertical()
|
|
176
|
-
.max_height(280.0)
|
|
177
|
-
.show(ui, |ui| {
|
|
178
|
-
for (idx, item) in filtered.iter().enumerate() {
|
|
179
|
-
let is_selected = idx == self.selected_index;
|
|
180
|
-
|
|
181
|
-
let response = ui.horizontal(|ui| {
|
|
182
|
-
if is_selected {
|
|
183
|
-
ui.label(
|
|
184
|
-
RichText::new("→")
|
|
185
|
-
.size(tokens::FONT_SIZE_BASE)
|
|
186
|
-
.color(colors.accent_text),
|
|
187
|
-
);
|
|
188
|
-
} else {
|
|
189
|
-
ui.add_space(SPACE_4);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
ui.label(
|
|
193
|
-
RichText::new(&item.title)
|
|
194
|
-
.size(tokens::FONT_SIZE_BASE)
|
|
195
|
-
.color(if is_selected { colors.accent_text } else { colors.text }),
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
if let Some(subtitle) = &item.subtitle {
|
|
199
|
-
ui.label(
|
|
200
|
-
RichText::new(subtitle)
|
|
201
|
-
.size(tokens::FONT_SIZE_SM)
|
|
202
|
-
.color(colors.text_muted),
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if let Some(kbd) = &item.kbd {
|
|
207
|
-
ui.with_layout(egui::Layout::right_to_left(Align::Center), |ui| {
|
|
208
|
-
egui::Frame::none()
|
|
209
|
-
.fill(colors.surface_3)
|
|
210
|
-
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
211
|
-
.rounding(Rounding::same(tokens::RADIUS_SM))
|
|
212
|
-
.inner_margin(vec2(SPACE_2, SPACE_1))
|
|
213
|
-
.show(ui, |ui| {
|
|
214
|
-
ui.label(
|
|
215
|
-
RichText::new(kbd)
|
|
216
|
-
.size(tokens::FONT_SIZE_XS)
|
|
217
|
-
.color(colors.text_muted)
|
|
218
|
-
.font(FontFamily::Monospace),
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
if response.response.clicked() {
|
|
226
|
-
result = Some(idx);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
} else if !self.query.is_empty() {
|
|
231
|
-
ui.centered_and_justified(|ui| {
|
|
232
|
-
ui.label(
|
|
233
|
-
RichText::new("No commands found")
|
|
234
|
-
.size(tokens::FONT_SIZE_SM)
|
|
235
|
-
.color(colors.text_muted),
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
if result.is_some() {
|
|
243
|
-
self.close();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
result
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
impl Default for CommandPalette {
|
|
251
|
-
fn default() -> Self {
|
|
252
|
-
Self::new("default_command_palette")
|
|
253
|
-
}
|
|
254
|
-
}
|
|
1
|
+
//! Command palette component
|
|
2
|
+
|
|
3
|
+
use egui::*;
|
|
4
|
+
use crate::{colors::*, tokens::*};
|
|
5
|
+
|
|
6
|
+
pub struct CommandItem {
|
|
7
|
+
pub title: String,
|
|
8
|
+
pub subtitle: Option<String>,
|
|
9
|
+
pub kbd: Option<String>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl CommandItem {
|
|
13
|
+
pub fn new(title: impl Into<String>) -> Self {
|
|
14
|
+
Self { title: title.into(), subtitle: None, kbd: None }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
|
|
18
|
+
self.subtitle = Some(subtitle.into());
|
|
19
|
+
self
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn kbd(mut self, kbd: impl Into<String>) -> Self {
|
|
23
|
+
self.kbd = Some(kbd.into());
|
|
24
|
+
self
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub struct CommandPalette {
|
|
29
|
+
pub items: Vec<CommandItem>,
|
|
30
|
+
pub query: String,
|
|
31
|
+
pub open: bool,
|
|
32
|
+
pub selected_index: usize,
|
|
33
|
+
id: Id,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl CommandPalette {
|
|
37
|
+
pub fn new(id: impl Into<Id>) -> Self {
|
|
38
|
+
Self { items: Vec::new(), query: String::new(), open: false, selected_index: 0, id: id.into() }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn item(mut self, item: CommandItem) -> Self {
|
|
42
|
+
self.items.push(item);
|
|
43
|
+
self
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub fn set_items(&mut self, items: Vec<CommandItem>) {
|
|
47
|
+
self.items = items;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub fn open(&mut self) {
|
|
51
|
+
self.open = true;
|
|
52
|
+
self.query.clear();
|
|
53
|
+
self.selected_index = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn close(&mut self) {
|
|
57
|
+
self.open = false;
|
|
58
|
+
self.query.clear();
|
|
59
|
+
self.selected_index = 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn toggle(&mut self) {
|
|
63
|
+
if self.open {
|
|
64
|
+
self.close();
|
|
65
|
+
} else {
|
|
66
|
+
self.open();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn filter(&self) -> Vec<&CommandItem> {
|
|
71
|
+
if self.query.is_empty() {
|
|
72
|
+
return self.items.iter().collect();
|
|
73
|
+
}
|
|
74
|
+
self.items
|
|
75
|
+
.iter()
|
|
76
|
+
.filter(|item| item.title.to_lowercase().contains(&self.query.to_lowercase()))
|
|
77
|
+
.collect()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Render the command palette as a modal dialog
|
|
81
|
+
/// Returns the index of the selected command (into the filtered list)
|
|
82
|
+
pub fn show(&mut self, ctx: &egui::Context) -> Option<usize> {
|
|
83
|
+
if !self.open {
|
|
84
|
+
return None;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let colors = Colors::default();
|
|
88
|
+
let mut result = None;
|
|
89
|
+
|
|
90
|
+
// Modal backdrop
|
|
91
|
+
egui::Area::new(self.id.with("backdrop"))
|
|
92
|
+
.order(Order::Foreground)
|
|
93
|
+
.interactable(true)
|
|
94
|
+
.show(ctx, |ui| {
|
|
95
|
+
let screen_size = ctx.screen_rect().size();
|
|
96
|
+
ui.set_min_size(screen_size);
|
|
97
|
+
|
|
98
|
+
// Semi-transparent backdrop
|
|
99
|
+
let painter = ui.painter_at(ui.max_rect());
|
|
100
|
+
painter.rect_filled(
|
|
101
|
+
ui.max_rect(),
|
|
102
|
+
Rounding::ZERO,
|
|
103
|
+
Color32::from_black_alpha(150),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Command palette panel centered on screen
|
|
107
|
+
let panel_width = 500.0;
|
|
108
|
+
let panel_height = 400.0;
|
|
109
|
+
|
|
110
|
+
egui::Frame::none()
|
|
111
|
+
.fill(colors.surface)
|
|
112
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
113
|
+
.rounding(Rounding::same(tokens::RADIUS_LG))
|
|
114
|
+
.shadow(egui::Shadow {
|
|
115
|
+
offset: [0.0, 8.0].into(),
|
|
116
|
+
blur: 24.0,
|
|
117
|
+
spread: 0.0,
|
|
118
|
+
color: Color32::from_black_alpha(100),
|
|
119
|
+
})
|
|
120
|
+
.show(ui, |ui| {
|
|
121
|
+
ui.set_min_size(vec2(panel_width, panel_height));
|
|
122
|
+
|
|
123
|
+
// Search input at top
|
|
124
|
+
ui.horizontal(|ui| {
|
|
125
|
+
ui.label("⌘");
|
|
126
|
+
let response = ui.add(
|
|
127
|
+
TextEdit::singleline(&mut self.query)
|
|
128
|
+
.hint_text("Type a command...")
|
|
129
|
+
.desired_width(f32::INFINITY)
|
|
130
|
+
.font(FontId::new(tokens::FONT_SIZE_BASE, FontFamily::Proportional)),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if response.has_focus() {
|
|
134
|
+
// Handle keyboard navigation
|
|
135
|
+
if ui.input(|i| i.key_pressed(Key::ArrowDown)) {
|
|
136
|
+
let filtered = self.filter();
|
|
137
|
+
if !filtered.is_empty() {
|
|
138
|
+
self.selected_index = (self.selected_index + 1) % filtered.len();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if ui.input(|i| i.key_pressed(Key::ArrowUp)) {
|
|
142
|
+
let filtered = self.filter();
|
|
143
|
+
if !filtered.is_empty() {
|
|
144
|
+
self.selected_index = if self.selected_index == 0 {
|
|
145
|
+
filtered.len() - 1
|
|
146
|
+
} else {
|
|
147
|
+
self.selected_index - 1
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if ui.input(|i| i.key_pressed(Key::Enter)) {
|
|
152
|
+
result = Some(self.selected_index);
|
|
153
|
+
}
|
|
154
|
+
if ui.input(|i| i.key_pressed(Key::Escape)) {
|
|
155
|
+
self.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
ui.add_space(SPACE_2);
|
|
161
|
+
|
|
162
|
+
// Separator
|
|
163
|
+
ui.separator();
|
|
164
|
+
|
|
165
|
+
ui.add_space(SPACE_2);
|
|
166
|
+
|
|
167
|
+
// Command list
|
|
168
|
+
let filtered = self.filter();
|
|
169
|
+
if !filtered.is_empty() {
|
|
170
|
+
// Ensure selected_index is valid
|
|
171
|
+
if self.selected_index >= filtered.len() {
|
|
172
|
+
self.selected_index = 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
egui::ScrollArea::vertical()
|
|
176
|
+
.max_height(280.0)
|
|
177
|
+
.show(ui, |ui| {
|
|
178
|
+
for (idx, item) in filtered.iter().enumerate() {
|
|
179
|
+
let is_selected = idx == self.selected_index;
|
|
180
|
+
|
|
181
|
+
let response = ui.horizontal(|ui| {
|
|
182
|
+
if is_selected {
|
|
183
|
+
ui.label(
|
|
184
|
+
RichText::new("→")
|
|
185
|
+
.size(tokens::FONT_SIZE_BASE)
|
|
186
|
+
.color(colors.accent_text),
|
|
187
|
+
);
|
|
188
|
+
} else {
|
|
189
|
+
ui.add_space(SPACE_4);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
ui.label(
|
|
193
|
+
RichText::new(&item.title)
|
|
194
|
+
.size(tokens::FONT_SIZE_BASE)
|
|
195
|
+
.color(if is_selected { colors.accent_text } else { colors.text }),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if let Some(subtitle) = &item.subtitle {
|
|
199
|
+
ui.label(
|
|
200
|
+
RichText::new(subtitle)
|
|
201
|
+
.size(tokens::FONT_SIZE_SM)
|
|
202
|
+
.color(colors.text_muted),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if let Some(kbd) = &item.kbd {
|
|
207
|
+
ui.with_layout(egui::Layout::right_to_left(Align::Center), |ui| {
|
|
208
|
+
egui::Frame::none()
|
|
209
|
+
.fill(colors.surface_3)
|
|
210
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
211
|
+
.rounding(Rounding::same(tokens::RADIUS_SM))
|
|
212
|
+
.inner_margin(vec2(SPACE_2, SPACE_1))
|
|
213
|
+
.show(ui, |ui| {
|
|
214
|
+
ui.label(
|
|
215
|
+
RichText::new(kbd)
|
|
216
|
+
.size(tokens::FONT_SIZE_XS)
|
|
217
|
+
.color(colors.text_muted)
|
|
218
|
+
.font(FontFamily::Monospace),
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if response.response.clicked() {
|
|
226
|
+
result = Some(idx);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
} else if !self.query.is_empty() {
|
|
231
|
+
ui.centered_and_justified(|ui| {
|
|
232
|
+
ui.label(
|
|
233
|
+
RichText::new("No commands found")
|
|
234
|
+
.size(tokens::FONT_SIZE_SM)
|
|
235
|
+
.color(colors.text_muted),
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if result.is_some() {
|
|
243
|
+
self.close();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
result
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
impl Default for CommandPalette {
|
|
251
|
+
fn default() -> Self {
|
|
252
|
+
Self::new("default_command_palette")
|
|
253
|
+
}
|
|
254
|
+
}
|