Skip to content

Commit 51cf38f

Browse files
committed
wip: native home-screen
1 parent bb1d7a2 commit 51cf38f

File tree

7 files changed

+523
-21
lines changed

7 files changed

+523
-21
lines changed

application/apps/indexer/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
**/*.rs.bk
33
**/proptest-regressions
44
*.out
5-
lineMetadata.json
65
vim-markdown-preview.html
76
.DS_Store
87
Cargo.lock
8+
*.json

application/apps/indexer/gui/application/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ anyhow.workspace = true
2929
thiserror.workspace = true
3030
clap.workspace = true
3131
serde.workspace = true
32+
serde_json.workspace = true
3233
log.workspace = true
3334
tokio.workspace = true
3435
rfd.workspace = true
Lines changed: 311 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,318 @@
1-
use egui::Ui;
1+
use crate::common::phosphor::icons;
2+
use crate::host::command::HostCommand;
3+
use crate::host::ui::APP_SETTINGS;
4+
use crate::host::ui::UiActions;
5+
use crate::host::ui::home::settings::FavoriteFolder;
6+
use crate::host::ui::home::settings::HomeSettings;
7+
use egui::{Align, Button, CentralPanel, CollapsingHeader, Layout, RichText, SidePanel, Ui};
8+
use tokio::sync::mpsc::Sender;
9+
10+
use std::env;
11+
use std::mem::take;
12+
use std::path::PathBuf;
13+
14+
pub mod settings;
15+
16+
const FAVORITES_DIALOG_ID: &str = "favorites";
217

318
#[derive(Debug)]
4-
pub struct HomeView {}
19+
pub struct HomeScreen {
20+
pub home_settings: HomeSettings,
21+
pub data_path: PathBuf,
22+
cmd_tx: Sender<HostCommand>,
23+
}
24+
25+
impl HomeScreen {
26+
pub fn new(cmd_tx: Sender<HostCommand>) -> Self {
27+
let dir = env::current_dir().expect("Failed to get current working directory");
28+
let path = dir.join(APP_SETTINGS);
29+
let state = HomeSettings::load(&path).unwrap_or_default();
30+
31+
HomeScreen {
32+
home_settings: state,
33+
data_path: path,
34+
cmd_tx,
35+
}
36+
}
537

6-
impl HomeView {
7-
pub fn render_content(ui: &mut Ui) {
38+
pub fn save(&self) {
39+
self.home_settings.save(&self.data_path);
40+
}
41+
42+
pub fn render_content(&mut self, actions: &mut UiActions, ui: &mut Ui) {
843
ui.centered_and_justified(|ui| {
9-
ui.heading("Welcome to Chipmunk");
44+
home_screen(ui, actions, &mut self.home_settings, &self.cmd_tx);
1045
});
1146
}
1247
}
48+
49+
pub fn home_screen(
50+
ui: &mut egui::Ui,
51+
actions: &mut UiActions,
52+
state: &mut HomeSettings,
53+
cmd_tx: &Sender<HostCommand>,
54+
) {
55+
SidePanel::left("quick actions")
56+
.width_range(50.0..=150.0)
57+
.default_width(100.)
58+
.resizable(true)
59+
.show_inside(ui, |ui| {
60+
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
61+
draw_quick_actions(ui, state);
62+
});
63+
});
64+
65+
SidePanel::right("favorite folders")
66+
.width_range(150.0..=450.0)
67+
.default_width(200.)
68+
.resizable(true)
69+
.show_inside(ui, |ui| {
70+
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
71+
ui.add_space(5.0);
72+
ui.heading("Favorite folders");
73+
draw_favorite_folders(ui, actions, state, cmd_tx);
74+
});
75+
});
76+
77+
CentralPanel::default().show_inside(ui, |ui| {
78+
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
79+
ui.heading("Recently opened");
80+
draw_recent_files(ui, state);
81+
});
82+
});
83+
}
84+
85+
pub fn draw_quick_actions(ui: &mut egui::Ui, state: &mut HomeSettings) {
86+
let item_size = egui::vec2(ui.available_width(), 60.0);
87+
88+
egui::ScrollArea::vertical()
89+
.id_salt("quick_actions_scroll")
90+
.show(ui, |ui| {
91+
for action in &state.quick_actions {
92+
ui.push_id(&action.id, |ui| {
93+
let (rect, response) = ui.allocate_exact_size(item_size, egui::Sense::click());
94+
95+
let visuals = ui.style().interact(&response);
96+
if response.hovered() || response.is_pointer_button_down_on() {
97+
ui.painter()
98+
.rect_filled(rect.shrink(2.0), 6.0, visuals.bg_fill);
99+
}
100+
101+
let inner_rect = rect.shrink(6.0);
102+
103+
ui.scope_builder(egui::UiBuilder::new().max_rect(inner_rect), |ui| {
104+
ui.with_layout(
105+
egui::Layout::top_down_justified(egui::Align::Center),
106+
|ui| {
107+
ui.add_space(2.0);
108+
ui.label(egui::RichText::new(&action.icon).size(22.0));
109+
ui.add(
110+
egui::Label::new(egui::RichText::new(&action.label).small())
111+
.wrap_mode(egui::TextWrapMode::Truncate),
112+
);
113+
},
114+
);
115+
});
116+
117+
if response.clicked() {
118+
println!("Quick action clicked: {}", action.id);
119+
}
120+
response.on_hover_text(&action.label);
121+
122+
ui.add_space(4.0);
123+
});
124+
}
125+
});
126+
}
127+
128+
fn draw_recent_files(ui: &mut egui::Ui, state: &mut HomeSettings) {
129+
egui::ScrollArea::vertical()
130+
.id_salt("recent_files_scroll")
131+
.show(ui, |ui| {
132+
ui.label("List of recently opened sources.");
133+
ui.add_space(5.0);
134+
135+
for file in &mut state.recent_files {
136+
ui.collapsing(file.path.display().to_string(), |ui| {
137+
ui.horizontal(|ui| {
138+
if ui
139+
.add(
140+
Button::new(RichText::new(icons::regular::TRASH).size(12.0))
141+
.small(),
142+
)
143+
.on_hover_text("Remove entry")
144+
.clicked()
145+
{
146+
print!("remove recent entry...");
147+
}
148+
149+
if ui
150+
.add(
151+
Button::new(RichText::new(icons::regular::PLUS).size(12.0)).small(),
152+
)
153+
.on_hover_text("New configuration")
154+
.clicked()
155+
{
156+
print!("open new configuration...");
157+
}
158+
});
159+
160+
for cfg in &file.configurations {
161+
ui.horizontal(|ui| {
162+
if ui
163+
.add(
164+
Button::new(
165+
RichText::new(icons::regular::ARROW_SQUARE_OUT).size(12.0),
166+
)
167+
.small(),
168+
)
169+
.on_hover_text("Open configuration")
170+
.clicked()
171+
{
172+
print!("open configuration...");
173+
}
174+
175+
if ui
176+
.add(
177+
Button::new(
178+
RichText::new(icons::regular::FILE_MINUS).size(12.0),
179+
)
180+
.small(),
181+
)
182+
.on_hover_text("Remove configuration")
183+
.clicked()
184+
{
185+
print!("remove configuration...");
186+
}
187+
188+
ui.label(&cfg.name);
189+
ui.label(&cfg.info());
190+
});
191+
}
192+
});
193+
}
194+
});
195+
}
196+
197+
fn draw_favorite_folders(
198+
ui: &mut egui::Ui,
199+
actions: &mut UiActions,
200+
state: &mut HomeSettings,
201+
cmd_tx: &Sender<HostCommand>,
202+
) {
203+
if let Some(paths) = actions.file_dialog.take_output(FAVORITES_DIALOG_ID) {
204+
for path in paths {
205+
if state.favorite_folders.iter().any(|f| f.path == path) {
206+
continue;
207+
}
208+
209+
let mut folder = FavoriteFolder::new(path);
210+
folder.scan_files();
211+
state.favorite_folders.push(folder);
212+
}
213+
}
214+
215+
ui.horizontal(|ui| {
216+
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
217+
ui.label("Search:");
218+
219+
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
220+
if ui
221+
.add(Button::new(RichText::new(icons::regular::PLUS).size(12.0)).small())
222+
.on_hover_text("Add folder")
223+
.clicked()
224+
{
225+
actions.file_dialog.pick_folder(FAVORITES_DIALOG_ID);
226+
}
227+
228+
if ui
229+
.add(
230+
Button::new(RichText::new(icons::regular::ARROWS_CLOCKWISE).size(12.0))
231+
.small(),
232+
)
233+
.on_hover_text("Refresh")
234+
.clicked()
235+
{
236+
state.scan_files();
237+
}
238+
239+
let search_response = ui.add(
240+
egui::TextEdit::singleline(&mut state.favorite_search)
241+
.desired_width(f32::INFINITY)
242+
.hint_text("type to filter..."),
243+
);
244+
245+
if search_response.changed() && state.favorite_search.is_empty() {
246+
state.favorite_collapse = true;
247+
}
248+
});
249+
});
250+
});
251+
252+
ui.add_space(2.0);
253+
254+
egui::ScrollArea::vertical()
255+
.id_salt("favorite_folders_scroll")
256+
.show(ui, |ui| {
257+
let expand = if take(&mut state.favorite_collapse) {
258+
Some(false)
259+
} else if !state.favorite_search.is_empty() {
260+
Some(true)
261+
} else {
262+
None
263+
};
264+
265+
let mut remove_path: Option<std::path::PathBuf> = None;
266+
267+
for folder in &state.favorite_folders {
268+
CollapsingHeader::new(folder.path.display().to_string())
269+
.id_salt(folder.path.to_string_lossy())
270+
.open(expand)
271+
.show(ui, |ui| {
272+
if ui
273+
.add(
274+
Button::new(RichText::new(icons::regular::TRASH).size(12.0))
275+
.small(),
276+
)
277+
.on_hover_text("Remove folder")
278+
.clicked()
279+
{
280+
remove_path = Some(folder.path.clone());
281+
}
282+
283+
for (file_name, size_info) in &folder.files {
284+
if !state.favorite_search.is_empty()
285+
&& !file_name
286+
.to_lowercase()
287+
.contains(&state.favorite_search.to_lowercase())
288+
{
289+
continue;
290+
}
291+
292+
ui.horizontal(|ui| {
293+
if ui
294+
.add(
295+
Button::new(
296+
RichText::new(icons::regular::ARROW_SQUARE_OUT)
297+
.size(12.0),
298+
)
299+
.small(),
300+
)
301+
.on_hover_text(format!("Open file ({})", size_info))
302+
.clicked()
303+
{
304+
let param = [folder.path.join(file_name)].to_vec();
305+
let cmd = HostCommand::OpenFiles(param);
306+
actions.try_send_command(cmd_tx, cmd);
307+
}
308+
ui.label(file_name);
309+
});
310+
}
311+
});
312+
}
313+
314+
if let Some(path) = remove_path {
315+
state.favorite_folders.retain(|f| f.path != path);
316+
}
317+
});
318+
}

0 commit comments

Comments
 (0)