Krolyxon
Commit 88f3865

Branches

Tags

Refs →

bench51cratesbench51-guisrcpanesproject.rs

137 lines · 4.6 KB · Rust

1//! Project window: the target being built, open files, and the sources in
2//! its folder.
3
4use std::path::{Path, PathBuf};
5
6use eframe::egui::{self, CursorIcon, FontId, Id, RichText, Sense, Ui};
7
8use crate::state::{Action, State};
9use crate::theme::{BODY, palette};
10use crate::widgets;
11
12const EXTENSIONS: [&str; 6] = ["asm", "a51", "s", "src", "hex", "ihx"];
13
14/// Source files in `dir`, re-read at most every two seconds.
15fn folder_files(ui: &Ui, dir: &Path) -> Vec<PathBuf> {
16 let id = Id::unique(("project_dir", dir));
17 let now = ui.input(|i| i.time);
18 if let Some((t, files)) = ui.data(|d| d.get_temp::<(f64, Vec<PathBuf>)>(id))
19 && now - t < 2.0
20 {
21 return files;
22 }
23 let mut files: Vec<PathBuf> = std::fs::read_dir(dir)
24 .into_iter()
25 .flatten()
26 .flatten()
27 .map(|e| e.path())
28 .filter(|p| {
29 p.extension()
30 .and_then(|e| e.to_str())
31 .is_some_and(|e| EXTENSIONS.contains(&e.to_ascii_lowercase().as_str()))
32 })
33 .collect();
34 files.sort();
35 ui.data_mut(|d| d.insert_temp(id, (now, files.clone())));
36 files
37}
38
39fn row(ui: &mut Ui, text: &str, color: egui::Color32, indent: f32) -> egui::Response {
40 ui.horizontal(|ui| {
41 ui.add_space(indent);
42 ui.add(
43 egui::Label::new(
44 RichText::new(text)
45 .font(FontId::monospace(BODY - 1.0))
46 .color(color),
47 )
48 .sense(Sense::click())
49 .truncate(),
50 )
51 .on_hover_cursor(CursorIcon::PointingHand)
52 })
53 .inner
54}
55
56pub fn show(ui: &mut Ui, st: &mut State) {
57 let p = palette(ui.ctx());
58 egui::ScrollArea::vertical()
59 .auto_shrink([false; 2])
60 .show(ui, |ui| {
61 ui.label(p.label("Target"));
62 ui.add_space(4.0);
63 match st.doc() {
64 Some(doc) => {
65 ui.label(
66 RichText::new(doc.target())
67 .font(FontId::new(18.0, crate::theme::grotesk_medium()))
68 .color(p.text_display),
69 );
70 let built = if doc.session.build.is_none() {
71 "not built"
72 } else if doc.stale() {
73 "source changed"
74 } else {
75 "built"
76 };
77 ui.label(p.label(built).color(p.text_disabled));
78 }
79 None => {
80 ui.label(p.label("none").color(p.text_disabled));
81 }
82 }
83
84 widgets::section(ui, "Open files");
85 let mut act = None;
86 for (i, doc) in st.docs.iter().enumerate() {
87 let color = if i == st.active {
88 p.text_display
89 } else {
90 p.text_secondary
91 };
92 let name = if doc.dirty() {
93 format!("{} •", doc.name())
94 } else {
95 doc.name()
96 };
97 if row(ui, &name, color, 12.0).clicked() {
98 act = Some(Action::Activate(i));
99 }
100 }
101
102 let dir = st
103 .doc()
104 .and_then(|d| d.path.as_ref())
105 .and_then(|p| p.parent().map(Path::to_path_buf))
106 .or_else(|| std::env::current_dir().ok());
107 if let Some(dir) = dir {
108 let shown = dir
109 .file_name()
110 .map_or_else(|| dir.display().to_string(), |n| n.to_string_lossy().into());
111 widgets::section(ui, &format!("Folder · {shown}"));
112 let files = folder_files(ui, &dir);
113 if files.is_empty() {
114 ui.label(p.label("no sources").color(p.text_disabled));
115 }
116 for f in files {
117 let open = st.docs.iter().any(|d| d.path.as_deref() == Some(&f));
118 let name = f
119 .file_name()
120 .unwrap_or_default()
121 .to_string_lossy()
122 .into_owned();
123 let color = if open {
124 p.text_primary
125 } else {
126 p.text_secondary
127 };
128 if row(ui, &name, color, 12.0).double_clicked() {
129 act = Some(Action::OpenPath(f.clone()));
130 }
131 }
132 }
133 if let Some(a) = act {
134 st.actions.push(a);
135 }
136 });
137}