Compare commits
No commits in common. "23d81bf84700f6ce60afa622400aeff2393dd3d1" and "2af316c867c8c622f1f1b0c88821900cc69b0bb4" have entirely different histories.
23d81bf847
...
2af316c867
59
.github/workflows/release.yml
vendored
Normal file
59
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: read
|
||||||
|
checks: write
|
||||||
|
deployments: write
|
||||||
|
issues: write
|
||||||
|
packages: write
|
||||||
|
pull-requests: write
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: release ${{ matrix.target }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
# Temporarily disabling Windows compilation
|
||||||
|
# - x86_64-pc-windows-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install target
|
||||||
|
run: rustup target add ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Check Rust target installation
|
||||||
|
run: rustup target list --installed
|
||||||
|
|
||||||
|
- name: Compile the app
|
||||||
|
run: |
|
||||||
|
echo "Compiling for target: ${{ matrix.target }}"
|
||||||
|
cargo build --release --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Create tarball
|
||||||
|
run: |
|
||||||
|
release_tag=${{ github.event.release.tag_name }}
|
||||||
|
tar -czvf calcifer_v${release_tag}.tar.gz -C target/${{ matrix.target }}/release calcifer
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ github.event.release.upload_url }}
|
||||||
|
asset_path: calcifer_v${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
asset_name: calcifer_v${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
asset_content_type: application/gzip
|
|
@ -1 +1 @@
|
||||||
{"categories":[{"name":"to do","content":[{"name":"update workflow .yml","description":"make a workflow compiling the calcifer and put the linux in calcifer-{version}\nand the windows in calcifer_windows_{version}\n\nupdate nix\nupdate jiji","id":5},{"name":"draggable item for project mode","description":"// Hello there","id":2}]},{"name":"in progress","content":[]},{"name":"done","content":[{"name":"move .project file","description":"// Hello there","id":4},{"name":"move config","description":"config from .calcifer/save.json\nto .config/calcifer/state.json","id":1},{"name":"add id to textarea per tab","description":"to improve undo, make each code area of each tab have a unique id (no more undo into another tab)","id":1},{"name":"file tree id ?","description":"// Hello there","id":1},{"name":"open dir in tree ?","description":"// Hello there","id":2},{"name":"fix tab title","description":"// Hello there","id":2},{"name":"when closing last tab","description":"close tab and THEN close calcifer (to save no tab in save.json)","id":1},{"name":"draggable tabs","description":"// Hello there","id":2},{"name":"repair build.rs","description":"// Hello there","id":1},{"name":"export copy paste fix","description":"// Hello there","id":1}]},{"name":"bug","content":[{"name":"ctrl f ","description":"I had crash when going up in the selection","id":1}]},{"name":"+","content":[]}]}
|
{"categories":[{"name":"to do","content":[{"name":"update workflow .yml","description":"make a workflow compiling the calcifer and put the linux in calcifer-{version}\nand the windows in calcifer_windows_{version}\n\nupdate nix\nupdate jiji","id":5}]},{"name":"in progress","content":[{"name":"export copy paste fix","description":"// Hello there","id":1}]},{"name":"done","content":[{"name":"move .project file","description":"// Hello there","id":4},{"name":"move config","description":"config from .calcifer/save.json\nto .config/calcifer/state.json","id":1},{"name":"add id to textarea per tab","description":"to improve undo, make each code area of each tab have a unique id (no more undo into another tab)","id":1},{"name":"file tree id ?","description":"// Hello there","id":1},{"name":"open dir in tree ?","description":"// Hello there","id":2},{"name":"fix tab title","description":"// Hello there","id":2}]},{"name":"+","content":[]}]}
|
|
@ -9,7 +9,7 @@ mkShell {
|
||||||
libXi
|
libXi
|
||||||
pkg-config
|
pkg-config
|
||||||
] ++ [
|
] ++ [
|
||||||
#cargo
|
cargo
|
||||||
rustc
|
rustc
|
||||||
atk
|
atk
|
||||||
gdk-pixbuf
|
gdk-pixbuf
|
||||||
|
@ -19,12 +19,12 @@ mkShell {
|
||||||
libGLU
|
libGLU
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
gtk3-x11
|
gtk3-x11
|
||||||
#gnome.zenity
|
gnome.zenity
|
||||||
];
|
];
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
latest.rustChannels.stable.rust
|
latest.rustChannels.stable.rust
|
||||||
xorg.libX11
|
xorg.libX11
|
||||||
# wayland
|
wayland
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
405
src/core/app.rs
405
src/core/app.rs
|
@ -1,6 +1,6 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::Color32;
|
use egui::Color32;
|
||||||
use std::{cmp::max, cmp::min, fs, path::Path, path::PathBuf};
|
use std::{cmp::min, fs, path::Path, path::PathBuf};
|
||||||
|
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::editor::themes::DEFAULT_THEMES;
|
use crate::editor::themes::DEFAULT_THEMES;
|
||||||
|
@ -11,242 +11,235 @@ use crate::Calcifer;
|
||||||
use crate::TIME_LABELS;
|
use crate::TIME_LABELS;
|
||||||
|
|
||||||
impl Calcifer {
|
impl Calcifer {
|
||||||
pub fn handle_confirm(&mut self) {
|
pub fn handle_confirm(&mut self) {
|
||||||
if self.close_tab_confirm.proceed {
|
if self.close_tab_confirm.proceed {
|
||||||
self.close_tab_confirm.close();
|
self.close_tab_confirm.close();
|
||||||
self.delete_tab(self.tab_to_close);
|
self.delete_tab(self.tab_to_close);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.refresh_confirm.proceed {
|
if self.refresh_confirm.proceed {
|
||||||
self.refresh_confirm.close();
|
self.refresh_confirm.close();
|
||||||
self.tabs[self.selected_tab].refresh();
|
self.tabs[self.selected_tab].refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_tab(&self) -> Option<PathBuf> {
|
pub fn save_tab(&self) -> Option<PathBuf> {
|
||||||
if self.tabs[self.selected_tab]
|
if self.tabs[self.selected_tab]
|
||||||
.path
|
.path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map_or(true, |name| name.to_string_lossy() == "untitled")
|
.map_or(true, |name| name.to_string_lossy() == "untitled")
|
||||||
{
|
{
|
||||||
self.save_tab_as()
|
self.save_tab_as()
|
||||||
} else {
|
} else {
|
||||||
if let Err(err) = fs::write(
|
if let Err(err) = fs::write(
|
||||||
&self.tabs[self.selected_tab].path,
|
&self.tabs[self.selected_tab].path,
|
||||||
&self.tabs[self.selected_tab].code,
|
&self.tabs[self.selected_tab].code,
|
||||||
) {
|
) {
|
||||||
eprintln!("Error writing file: {}", err);
|
eprintln!("Error writing file: {}", err);
|
||||||
return self.save_tab_as();
|
return self.save_tab_as();
|
||||||
}
|
}
|
||||||
Some(self.tabs[self.selected_tab].path.clone())
|
Some(self.tabs[self.selected_tab].path.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_tab_as(&self) -> Option<PathBuf> {
|
pub fn save_tab_as(&self) -> Option<PathBuf> {
|
||||||
let default_path = self.home.join("untitled");
|
let default_path = self.home.join("untitled");
|
||||||
|
|
||||||
let save_path = if self.tabs[self.selected_tab]
|
let save_path = if self.tabs[self.selected_tab].path.file_name().map_or(true, |name| name.to_string_lossy() == "untitled")
|
||||||
.path
|
{
|
||||||
.file_name()
|
default_path.to_string_lossy()
|
||||||
.map_or(true, |name| name.to_string_lossy() == "untitled")
|
} else {
|
||||||
{
|
self.tabs[self.selected_tab].path.to_string_lossy()
|
||||||
default_path.to_string_lossy()
|
};
|
||||||
} else {
|
println!("app : tried to open dialog at {}", save_path);
|
||||||
self.tabs[self.selected_tab].path.to_string_lossy()
|
// if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path)
|
||||||
};
|
// {
|
||||||
if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) {
|
// let path = PathBuf::from(path_string);
|
||||||
let path = PathBuf::from(path_string);
|
// if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) {
|
||||||
if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) {
|
// eprintln!("Error writing file: {}", err);
|
||||||
eprintln!("Error writing file: {}", err);
|
// return None;
|
||||||
return None;
|
// }
|
||||||
}
|
// return Some(path);
|
||||||
return Some(path);
|
// }
|
||||||
}
|
None
|
||||||
None
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) {
|
pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) {
|
||||||
if let Some(path) = path_option {
|
if let Some(path) = path_option {
|
||||||
println!("File saved successfully at: {:?}", path);
|
println!("File saved successfully at: {:?}", path);
|
||||||
self.tabs[self.selected_tab].path = path;
|
self.tabs[self.selected_tab].path = path;
|
||||||
self.tabs[self.selected_tab].saved = true;
|
self.tabs[self.selected_tab].saved = true;
|
||||||
} else {
|
} else {
|
||||||
println!("File save failed.");
|
println!("File save failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self {
|
pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self {
|
||||||
let mut new = Self {
|
let mut new = Self {
|
||||||
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
|
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
|
||||||
tabs: Vec::new(),
|
tabs: Vec::new(),
|
||||||
settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]),
|
settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if app_state.zoom != 0.0 {
|
if app_state.zoom != 0.0 {
|
||||||
new.zoom = app_state.zoom;
|
new.zoom = app_state.zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
for path in app_state.tabs {
|
for path in app_state.tabs {
|
||||||
if !path
|
if !path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map_or(true, |name| name.to_string_lossy() == "untitled")
|
.map_or(true, |name| name.to_string_lossy() == "untitled")
|
||||||
{
|
{
|
||||||
new.open_file(Some(&path));
|
new.open_file(Some(&path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) = file_to_open {
|
if let Some(path) = file_to_open {
|
||||||
new.open_file(Some(&path));
|
new.open_file(Some(&path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if new.tabs == vec![] {
|
if new.tabs == vec![] {
|
||||||
new.open_file(None);
|
new.open_file(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_state(&self) {
|
pub fn save_state(&self) {
|
||||||
let mut state_theme: usize = 0;
|
let mut state_theme: usize = 0;
|
||||||
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
|
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
|
||||||
state_theme = theme;
|
state_theme = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut state_tabs = vec![];
|
let mut state_tabs = vec![];
|
||||||
|
|
||||||
for tab in &self.tabs {
|
for tab in &self.tabs {
|
||||||
state_tabs.push(tab.path.clone());
|
state_tabs.push(tab.path.clone());
|
||||||
}
|
}
|
||||||
let app_state = core::AppState {
|
let app_state = core::AppState {
|
||||||
tabs: state_tabs,
|
tabs: state_tabs,
|
||||||
theme: state_theme,
|
theme: state_theme,
|
||||||
zoom: self.zoom,
|
zoom: self.zoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = core::save_state(&app_state, save_path().as_path());
|
let _ = core::save_state(&app_state, save_path().as_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_through_tabs(&mut self, forward: bool) {
|
pub fn move_through_tabs(&mut self, forward: bool) {
|
||||||
let new_index = if forward {
|
let new_index = if forward {
|
||||||
(self.selected_tab + 1) % self.tabs.len()
|
(self.selected_tab + 1) % self.tabs.len()
|
||||||
} else {
|
} else {
|
||||||
self.selected_tab
|
self.selected_tab
|
||||||
.checked_sub(1)
|
.checked_sub(1)
|
||||||
.unwrap_or(self.tabs.len() - 1)
|
.unwrap_or(self.tabs.len() - 1)
|
||||||
};
|
};
|
||||||
self.selected_tab = new_index;
|
self.selected_tab = new_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_file(&mut self, path_option: Option<&Path>) {
|
pub fn open_file(&mut self, path_option: Option<&Path>) {
|
||||||
if let Some(path) = path_option {
|
if let Some(path) = path_option {
|
||||||
for (index, tab) in self.tabs.clone().iter().enumerate() {
|
for (index, tab) in self.tabs.clone().iter().enumerate() {
|
||||||
if tab.path == path {
|
if tab.path == path {
|
||||||
self.selected_tab = index;
|
self.selected_tab = index;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(path) = path_option {
|
if let Some(path) = path_option {
|
||||||
self.tabs.push(panels::Tab::new(path.to_path_buf()));
|
self.tabs.push(panels::Tab::new(path.to_path_buf()));
|
||||||
} else {
|
} else {
|
||||||
self.tabs.push(panels::Tab::default());
|
self.tabs.push(panels::Tab::default());
|
||||||
}
|
}
|
||||||
self.selected_tab = self.tabs.len() - 1;
|
self.selected_tab = self.tabs.len() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_tab(&mut self, index: usize) {
|
pub fn delete_tab(&mut self, index: usize) {
|
||||||
self.tabs.remove(index);
|
self.tabs.remove(index);
|
||||||
if self.tabs.len() != 0 {
|
self.selected_tab = min(index, self.tabs.len() - 1);
|
||||||
self.selected_tab = min(index, self.tabs.len() - 1);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool {
|
pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool {
|
||||||
let bg_color: Color32;
|
let bg_color: Color32;
|
||||||
let text_color: Color32;
|
let text_color: Color32;
|
||||||
|
|
||||||
if display {
|
if display {
|
||||||
bg_color = hex_str_to_color(self.theme.functions);
|
bg_color = hex_str_to_color(self.theme.functions);
|
||||||
text_color = hex_str_to_color(self.theme.bg);
|
text_color = hex_str_to_color(self.theme.bg);
|
||||||
} else {
|
} else {
|
||||||
bg_color = hex_str_to_color(self.theme.bg);
|
bg_color = hex_str_to_color(self.theme.bg);
|
||||||
text_color = hex_str_to_color(self.theme.literals);
|
text_color = hex_str_to_color(self.theme.literals);
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.style_mut().visuals.override_text_color = Some(text_color);
|
ui.style_mut().visuals.override_text_color = Some(text_color);
|
||||||
|
|
||||||
if ui.add(egui::Button::new(title).fill(bg_color)).clicked() {
|
if ui.add(egui::Button::new(title).fill(bg_color)).clicked() {
|
||||||
return !display;
|
return !display;
|
||||||
}
|
}
|
||||||
ui.style_mut().visuals.override_text_color = None;
|
ui.style_mut().visuals.override_text_color = None;
|
||||||
|
|
||||||
display
|
display
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profiler(&self) -> String {
|
pub fn profiler(&self) -> String {
|
||||||
if !self.profiler_visible {
|
if !self.profiler_visible {
|
||||||
return "".to_string();
|
return "".to_string();
|
||||||
}
|
}
|
||||||
let combined_string: Vec<String> = TIME_LABELS
|
let combined_string: Vec<String> = TIME_LABELS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(self.time_watch.clone())
|
.zip(self.time_watch.clone())
|
||||||
.map(|(s, v)| format!("{} : {:.1} ms", s, v))
|
.map(|(s, v)| format!("{} : {:.1} ms", s, v))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut result = combined_string.join(" ; ");
|
let mut result = combined_string.join(" ; ");
|
||||||
result.push_str(&format!(
|
result.push_str(&format!(
|
||||||
" total : {:.1} ms",
|
" total : {:.1} ms",
|
||||||
self.time_watch.clone().iter().sum::<f32>()
|
self.time_watch.clone().iter().sum::<f32>()
|
||||||
));
|
));
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_files(
|
pub fn list_files(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
file: &panels::FileEntry,
|
file: &panels::FileEntry,
|
||||||
n_files: &mut usize,
|
n_files: &mut usize,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
*n_files += 1;
|
*n_files += 1;
|
||||||
|
|
||||||
if let Some(folder_content) = &file.folder_content {
|
if let Some(folder_content) = &file.folder_content {
|
||||||
let mut check_for_update: bool = false;
|
let mut check_for_update: bool = false;
|
||||||
let file_path_id = panels::get_file_path_id(&file.path);
|
let file_path_id = panels::get_file_path_id(&file.path);
|
||||||
|
|
||||||
let collapsing_response = egui::CollapsingHeader::new(file.name.clone())
|
let collapsing_response = egui::CollapsingHeader::new(file.name.clone())
|
||||||
.id_source(&file.id)
|
.id_source(&file.id)
|
||||||
.default_open(self.tree_dir_opened.contains(&file_path_id))
|
.default_open(self.tree_dir_opened.contains(&file_path_id))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
if !self.tree_dir_opened.contains(&file_path_id) {
|
if !self.tree_dir_opened.contains(&file_path_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for deeper_file in folder_content {
|
for deeper_file in folder_content {
|
||||||
if self.list_files(ui, deeper_file, n_files) {
|
if self.list_files(ui, deeper_file, n_files) {
|
||||||
check_for_update = true;
|
check_for_update = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if collapsing_response.fully_closed() {
|
if collapsing_response.fully_closed() {
|
||||||
self.tree_dir_opened.retain(|s| s != &file_path_id);
|
self.tree_dir_opened.retain(|s| s != &file_path_id);
|
||||||
} else if !self.tree_dir_opened.contains(&file_path_id) {
|
} else if !self.tree_dir_opened.contains(&file_path_id) {
|
||||||
self.tree_dir_opened.push(file_path_id);
|
self.tree_dir_opened.push(file_path_id);
|
||||||
return !file.content_checked;
|
return !file.content_checked;
|
||||||
}
|
}
|
||||||
return check_for_update;
|
return check_for_update;
|
||||||
} else if ui.button(&file.name).clicked() {
|
} else if ui.button(&file.name).clicked() {
|
||||||
self.open_file(Some(&file.path));
|
self.open_file(Some(&file.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_area_size(&self) -> usize {
|
|
||||||
max(6, self.tabs.len() + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unnecessary_lazy_evaluations)]
|
#[allow(clippy::unnecessary_lazy_evaluations)]
|
||||||
pub fn hex_str_to_color(hex_str: &str) -> Color32 {
|
pub fn hex_str_to_color(hex_str: &str) -> Color32 {
|
||||||
Color32::from_hex(hex_str).unwrap_or_else(|_| Color32::BLACK)
|
Color32::from_hex(hex_str).unwrap_or_else(|_| Color32::BLACK)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs,
|
fs,
|
||||||
fs::{read_to_string, OpenOptions},
|
fs::{read_to_string, OpenOptions},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub tabs: Vec<PathBuf>,
|
pub tabs: Vec<PathBuf>,
|
||||||
pub theme: usize,
|
pub theme: usize,
|
||||||
pub zoom: f32,
|
pub zoom: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> {
|
pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> {
|
||||||
let serialized_state = serde_json::to_string(state)?;
|
let serialized_state = serde_json::to_string(state)?;
|
||||||
|
|
||||||
if let Some(parent_dir) = file_path.parent() {
|
if let Some(parent_dir) = file_path.parent() {
|
||||||
fs::create_dir_all(parent_dir)?;
|
fs::create_dir_all(parent_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(file_path)?;
|
.open(file_path)?;
|
||||||
|
|
||||||
file.write_all(serialized_state.as_bytes())?;
|
file.write_all(serialized_state.as_bytes())?;
|
||||||
|
|
||||||
println!("Saved state at {}", file_path.display());
|
println!("Saved state at {}", file_path.display());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> {
|
pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> {
|
||||||
let serialized_state = read_to_string(file_path)?;
|
let serialized_state = read_to_string(file_path)?;
|
||||||
|
|
||||||
Ok(serde_json::from_str(&serialized_state)?)
|
Ok(serde_json::from_str(&serialized_state)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> {
|
pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> {
|
||||||
let (icon_rgba, icon_width, icon_height) = {
|
let (icon_rgba, icon_width, icon_height) = {
|
||||||
let icon = include_bytes!("../../assets/icon.png");
|
let icon = include_bytes!("../../assets/icon.png");
|
||||||
let image = image::load_from_memory(icon)?;
|
let image = image::load_from_memory(icon)?;
|
||||||
let rgba = image.clone().into_rgba8().to_vec();
|
let rgba = image.clone().into_rgba8().to_vec();
|
||||||
let (width, height) = image.dimensions();
|
let (width, height) = image.dimensions();
|
||||||
(rgba, width, height)
|
(rgba, width, height)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(egui::IconData {
|
Ok(egui::IconData {
|
||||||
rgba: icon_rgba,
|
rgba: icon_rgba,
|
||||||
width: icon_width,
|
width: icon_width,
|
||||||
height: icon_height,
|
height: icon_height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
1038
src/core/ui.rs
1038
src/core/ui.rs
File diff suppressed because it is too large
Load diff
|
@ -2,88 +2,23 @@ use super::Syntax;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
impl Syntax {
|
impl Syntax {
|
||||||
pub fn javascript() -> Syntax {
|
pub fn javascript() -> Syntax {
|
||||||
Syntax {
|
Syntax {
|
||||||
language: "Javascript",
|
language: "Javascript",
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
comment: "//",
|
comment: "//",
|
||||||
comment_multiline: ["/*", "*/"],
|
comment_multiline: ["/*", "*/"],
|
||||||
keywords: BTreeSet::from([
|
keywords: BTreeSet::from([
|
||||||
"&&",
|
"&&", "||", "!", "let", "var", "abstract", "arguments", "await", "break", "case", "catch", "class", "const", "continue",
|
||||||
"||",
|
"debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "final", "finally", "for", "function",
|
||||||
"!",
|
"goto", "if", "implements", "import", "in", "instanceof", "interface", "let", "native", "new", "package", "private", "protected",
|
||||||
"let",
|
"public", "return", "static", "super", "switch", "synchronized", "this","throw", "throws", "transient", "try", "typeof",
|
||||||
"var",
|
"var", "volatile", "while", "with", "yield",
|
||||||
"abstract",
|
]),
|
||||||
"arguments",
|
types: BTreeSet::from([
|
||||||
"await",
|
"Boolean", "Number", "BigInt", "Undefined", "Null", "String", "Symbol", "byte", "char", "float", "int", "long", "short", "void",
|
||||||
"break",
|
]),
|
||||||
"case",
|
special: BTreeSet::from(["false", "null", "true"]),
|
||||||
"catch",
|
}
|
||||||
"class",
|
}
|
||||||
"const",
|
|
||||||
"continue",
|
|
||||||
"debugger",
|
|
||||||
"default",
|
|
||||||
"delete",
|
|
||||||
"do",
|
|
||||||
"else",
|
|
||||||
"enum",
|
|
||||||
"eval",
|
|
||||||
"export",
|
|
||||||
"extends",
|
|
||||||
"final",
|
|
||||||
"finally",
|
|
||||||
"for",
|
|
||||||
"function",
|
|
||||||
"goto",
|
|
||||||
"if",
|
|
||||||
"implements",
|
|
||||||
"import",
|
|
||||||
"in",
|
|
||||||
"instanceof",
|
|
||||||
"interface",
|
|
||||||
"let",
|
|
||||||
"native",
|
|
||||||
"new",
|
|
||||||
"package",
|
|
||||||
"private",
|
|
||||||
"protected",
|
|
||||||
"public",
|
|
||||||
"return",
|
|
||||||
"static",
|
|
||||||
"super",
|
|
||||||
"switch",
|
|
||||||
"synchronized",
|
|
||||||
"this",
|
|
||||||
"throw",
|
|
||||||
"throws",
|
|
||||||
"transient",
|
|
||||||
"try",
|
|
||||||
"typeof",
|
|
||||||
"var",
|
|
||||||
"volatile",
|
|
||||||
"while",
|
|
||||||
"with",
|
|
||||||
"yield",
|
|
||||||
]),
|
|
||||||
types: BTreeSet::from([
|
|
||||||
"Boolean",
|
|
||||||
"Number",
|
|
||||||
"BigInt",
|
|
||||||
"Undefined",
|
|
||||||
"Null",
|
|
||||||
"String",
|
|
||||||
"Symbol",
|
|
||||||
"byte",
|
|
||||||
"char",
|
|
||||||
"float",
|
|
||||||
"int",
|
|
||||||
"long",
|
|
||||||
"short",
|
|
||||||
"void",
|
|
||||||
]),
|
|
||||||
special: BTreeSet::from(["false", "null", "true"]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,179 +19,179 @@ type Float = bool;
|
||||||
#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
Comment(MultiLine),
|
Comment(MultiLine),
|
||||||
Function,
|
Function,
|
||||||
Keyword,
|
Keyword,
|
||||||
Literal,
|
Literal,
|
||||||
Numeric(Float),
|
Numeric(Float),
|
||||||
Punctuation(char),
|
Punctuation(char),
|
||||||
Special,
|
Special,
|
||||||
Str(char),
|
Str(char),
|
||||||
Type,
|
Type,
|
||||||
Whitespace(char),
|
Whitespace(char),
|
||||||
#[default]
|
#[default]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
impl std::fmt::Debug for TokenType {
|
impl std::fmt::Debug for TokenType {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
match &self {
|
match &self {
|
||||||
TokenType::Comment(multiline) => {
|
TokenType::Comment(multiline) => {
|
||||||
name.push_str("Comment");
|
name.push_str("Comment");
|
||||||
{
|
{
|
||||||
if *multiline {
|
if *multiline {
|
||||||
name.push_str(" MultiLine");
|
name.push_str(" MultiLine");
|
||||||
} else {
|
} else {
|
||||||
name.push_str(" SingleLine");
|
name.push_str(" SingleLine");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenType::Function => name.push_str("Function"),
|
TokenType::Function => name.push_str("Function"),
|
||||||
TokenType::Keyword => name.push_str("Keyword"),
|
TokenType::Keyword => name.push_str("Keyword"),
|
||||||
TokenType::Literal => name.push_str("Literal"),
|
TokenType::Literal => name.push_str("Literal"),
|
||||||
TokenType::Numeric(float) => {
|
TokenType::Numeric(float) => {
|
||||||
name.push_str("Numeric");
|
name.push_str("Numeric");
|
||||||
if *float {
|
if *float {
|
||||||
name.push_str(" Float");
|
name.push_str(" Float");
|
||||||
} else {
|
} else {
|
||||||
name.push_str(" Integer");
|
name.push_str(" Integer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenType::Punctuation(_) => name.push_str("Punctuation"),
|
TokenType::Punctuation(_) => name.push_str("Punctuation"),
|
||||||
TokenType::Special => name.push_str("Special"),
|
TokenType::Special => name.push_str("Special"),
|
||||||
TokenType::Str(quote) => {
|
TokenType::Str(quote) => {
|
||||||
name.push_str("Str ");
|
name.push_str("Str ");
|
||||||
name.push(*quote);
|
name.push(*quote);
|
||||||
}
|
}
|
||||||
TokenType::Type => name.push_str("Type"),
|
TokenType::Type => name.push_str("Type"),
|
||||||
TokenType::Whitespace(c) => {
|
TokenType::Whitespace(c) => {
|
||||||
name.push_str("Whitespace");
|
name.push_str("Whitespace");
|
||||||
match c {
|
match c {
|
||||||
' ' => name.push_str(" Space"),
|
' ' => name.push_str(" Space"),
|
||||||
'\t' => name.push_str(" Tab"),
|
'\t' => name.push_str(" Tab"),
|
||||||
'\n' => name.push_str(" New Line"),
|
'\n' => name.push_str(" New Line"),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
TokenType::Unknown => name.push_str("Unknown"),
|
TokenType::Unknown => name.push_str("Unknown"),
|
||||||
};
|
};
|
||||||
write!(f, "{name}")
|
write!(f, "{name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<char> for TokenType {
|
impl From<char> for TokenType {
|
||||||
fn from(c: char) -> Self {
|
fn from(c: char) -> Self {
|
||||||
match c {
|
match c {
|
||||||
c if c.is_whitespace() => TokenType::Whitespace(c),
|
c if c.is_whitespace() => TokenType::Whitespace(c),
|
||||||
c if QUOTES.contains(&c) => TokenType::Str(c),
|
c if QUOTES.contains(&c) => TokenType::Str(c),
|
||||||
c if c.is_numeric() => TokenType::Numeric(false),
|
c if c.is_numeric() => TokenType::Numeric(false),
|
||||||
c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal,
|
c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal,
|
||||||
c if c.is_ascii_punctuation() => TokenType::Punctuation(c),
|
c if c.is_ascii_punctuation() => TokenType::Punctuation(c),
|
||||||
_ => TokenType::Unknown,
|
_ => TokenType::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// Rules for highlighting.
|
/// Rules for highlighting.
|
||||||
pub struct Syntax {
|
pub struct Syntax {
|
||||||
pub language: &'static str,
|
pub language: &'static str,
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
pub comment: &'static str,
|
pub comment: &'static str,
|
||||||
pub comment_multiline: [&'static str; 2],
|
pub comment_multiline: [&'static str; 2],
|
||||||
pub keywords: BTreeSet<&'static str>,
|
pub keywords: BTreeSet<&'static str>,
|
||||||
pub types: BTreeSet<&'static str>,
|
pub types: BTreeSet<&'static str>,
|
||||||
pub special: BTreeSet<&'static str>,
|
pub special: BTreeSet<&'static str>,
|
||||||
}
|
}
|
||||||
impl Default for Syntax {
|
impl Default for Syntax {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Syntax::rust()
|
Syntax::rust()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Hash for Syntax {
|
impl Hash for Syntax {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.language.hash(state);
|
self.language.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Syntax {
|
impl Syntax {
|
||||||
pub fn new(language: &'static str) -> Self {
|
pub fn new(language: &'static str) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
language,
|
language,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_case_sensitive(self, case_sensitive: bool) -> Self {
|
pub fn with_case_sensitive(self, case_sensitive: bool) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
case_sensitive,
|
case_sensitive,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_comment(self, comment: &'static str) -> Self {
|
pub fn with_comment(self, comment: &'static str) -> Self {
|
||||||
Syntax { comment, ..self }
|
Syntax { comment, ..self }
|
||||||
}
|
}
|
||||||
pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self {
|
pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
comment_multiline,
|
comment_multiline,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self {
|
pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
keywords: keywords.into(),
|
keywords: keywords.into(),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self {
|
pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
types: types.into(),
|
types: types.into(),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self {
|
pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
special: special.into(),
|
special: special.into(),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language(&self) -> &str {
|
pub fn language(&self) -> &str {
|
||||||
self.language
|
self.language
|
||||||
}
|
}
|
||||||
pub fn comment(&self) -> &str {
|
pub fn comment(&self) -> &str {
|
||||||
self.comment
|
self.comment
|
||||||
}
|
}
|
||||||
pub fn is_keyword(&self, word: &str) -> bool {
|
pub fn is_keyword(&self, word: &str) -> bool {
|
||||||
if self.case_sensitive {
|
if self.case_sensitive {
|
||||||
self.keywords.contains(&word)
|
self.keywords.contains(&word)
|
||||||
} else {
|
} else {
|
||||||
self.keywords.contains(word.to_ascii_uppercase().as_str())
|
self.keywords.contains(word.to_ascii_uppercase().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_type(&self, word: &str) -> bool {
|
pub fn is_type(&self, word: &str) -> bool {
|
||||||
if self.case_sensitive {
|
if self.case_sensitive {
|
||||||
self.types.contains(&word)
|
self.types.contains(&word)
|
||||||
} else {
|
} else {
|
||||||
self.types.contains(word.to_ascii_uppercase().as_str())
|
self.types.contains(word.to_ascii_uppercase().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_special(&self, word: &str) -> bool {
|
pub fn is_special(&self, word: &str) -> bool {
|
||||||
if self.case_sensitive {
|
if self.case_sensitive {
|
||||||
self.special.contains(&word)
|
self.special.contains(&word)
|
||||||
} else {
|
} else {
|
||||||
self.special.contains(word.to_ascii_uppercase().as_str())
|
self.special.contains(word.to_ascii_uppercase().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Syntax {
|
impl Syntax {
|
||||||
pub fn simple(comment: &'static str) -> Self {
|
pub fn simple(comment: &'static str) -> Self {
|
||||||
Syntax {
|
Syntax {
|
||||||
language: "",
|
language: "",
|
||||||
case_sensitive: false,
|
case_sensitive: false,
|
||||||
comment,
|
comment,
|
||||||
comment_multiline: [comment; 2],
|
comment_multiline: [comment; 2],
|
||||||
keywords: BTreeSet::new(),
|
keywords: BTreeSet::new(),
|
||||||
types: BTreeSet::new(),
|
types: BTreeSet::new(),
|
||||||
special: BTreeSet::new(),
|
special: BTreeSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
496
src/main.rs
496
src/main.rs
|
@ -1,12 +1,12 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{
|
use egui::{
|
||||||
FontFamily, FontId,
|
FontFamily, FontId,
|
||||||
TextStyle::{Body, Button, Heading, Monospace, Small},
|
TextStyle::{Body, Button, Heading, Monospace, Small},
|
||||||
};
|
};
|
||||||
use homedir::get_my_home;
|
use homedir::get_my_home;
|
||||||
|
use std::{ops::Range, path::PathBuf, sync::Arc, thread, time};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{ops::Range, path::PathBuf, sync::Arc, thread, time};
|
|
||||||
|
|
||||||
mod core;
|
mod core;
|
||||||
mod editor;
|
mod editor;
|
||||||
|
@ -25,13 +25,7 @@ const TERMINAL_HEIGHT: f32 = 200.0;
|
||||||
const TERMINAL_RANGE: Range<f32> = 100.0..600.0;
|
const TERMINAL_RANGE: Range<f32> = 100.0..600.0;
|
||||||
const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
|
const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
|
||||||
const TIME_LABELS: [&str; 7] = [
|
const TIME_LABELS: [&str; 7] = [
|
||||||
"input",
|
"input", "settings", "tree", "terminal", "tabs", "content", "windows",
|
||||||
"settings",
|
|
||||||
"tree",
|
|
||||||
"terminal+tray",
|
|
||||||
"tabs",
|
|
||||||
"content",
|
|
||||||
"windows",
|
|
||||||
];
|
];
|
||||||
const ZOOM_FACTOR: f32 = 1.1;
|
const ZOOM_FACTOR: f32 = 1.1;
|
||||||
const MAX_FPS: f32 = 30.0;
|
const MAX_FPS: f32 = 30.0;
|
||||||
|
@ -40,317 +34,301 @@ const MAX_PROJECT_COLUMNS: usize = 8;
|
||||||
const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2;
|
const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2;
|
||||||
|
|
||||||
fn main() -> Result<(), eframe::Error> {
|
fn main() -> Result<(), eframe::Error> {
|
||||||
let icon_data = core::load_icon().unwrap_or_default();
|
let icon_data = core::load_icon().unwrap_or_default();
|
||||||
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
viewport: egui::ViewportBuilder::default()
|
||||||
.with_inner_size([1200.0, 800.0])
|
.with_inner_size([1200.0, 800.0])
|
||||||
.with_icon(Arc::new(icon_data)),
|
.with_icon(Arc::new(icon_data)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attempt to load previous state
|
// Attempt to load previous state
|
||||||
let app_state: core::AppState = if save_path().exists() {
|
let app_state: core::AppState = if save_path().exists() {
|
||||||
match core::load_state(save_path().as_path()) {
|
match core::load_state(save_path().as_path()) {
|
||||||
Ok(app_state) => app_state,
|
Ok(app_state) => app_state,
|
||||||
Err(_) => core::AppState::default(),
|
Err(_) => core::AppState::default(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
core::AppState::default()
|
core::AppState::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let file_to_open = if args.len() > 1 {
|
let file_to_open = if args.len() > 1 {
|
||||||
println!("Opening file: {}", args[1].clone());
|
println!("Opening file: {}", args[1].clone());
|
||||||
let mut path = env::current_dir().unwrap_or_default();
|
let mut path = env::current_dir().unwrap_or_default();
|
||||||
path.push(args[1].clone());
|
path.push(args[1].clone());
|
||||||
Some(path)
|
Some(path)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
//panels::send_command("export RUSTFLAGS=--cfg=web_sys_unstable_apis".to_string());
|
eframe::run_native(
|
||||||
|
&format!("Calcifer{}", TITLE),
|
||||||
eframe::run_native(
|
options,
|
||||||
&format!("Calcifer{}", TITLE),
|
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))),
|
||||||
options,
|
)
|
||||||
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Calcifer {
|
struct Calcifer {
|
||||||
focused: bool,
|
focused: bool,
|
||||||
got_focus: bool,
|
got_focus: bool,
|
||||||
|
|
||||||
selected_tab: usize,
|
selected_tab: usize,
|
||||||
tabs: Vec<panels::Tab>,
|
tabs: Vec<panels::Tab>,
|
||||||
tab_rect: egui::Rect,
|
|
||||||
mouse_holder: panels::MouseHolder,
|
|
||||||
|
|
||||||
command: String,
|
command: String,
|
||||||
command_history: Vec<panels::CommandEntry>,
|
command_history: Vec<panels::CommandEntry>,
|
||||||
running_command: bool,
|
running_command: bool,
|
||||||
|
|
||||||
theme: editor::ColorTheme,
|
theme: editor::ColorTheme,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
zoom: f32,
|
zoom: f32,
|
||||||
|
|
||||||
project_content: panels::Project,
|
project_content: panels::Project,
|
||||||
|
|
||||||
home: PathBuf,
|
home: PathBuf,
|
||||||
tree_dir_opened: Vec<String>,
|
tree_dir_opened: Vec<String>,
|
||||||
file_tree: Option<panels::FileEntry>,
|
file_tree: Option<panels::FileEntry>,
|
||||||
n_file_displayed: usize,
|
n_file_displayed: usize,
|
||||||
|
|
||||||
tree_visible: bool,
|
tree_visible: bool,
|
||||||
profiler_visible: bool,
|
profiler_visible: bool,
|
||||||
terminal_visible: bool,
|
terminal_visible: bool,
|
||||||
|
|
||||||
close_tab_confirm: sub_windows::ConfirmWindow,
|
close_tab_confirm: sub_windows::ConfirmWindow,
|
||||||
tab_to_close: usize,
|
tab_to_close: usize,
|
||||||
refresh_confirm: sub_windows::ConfirmWindow,
|
refresh_confirm: sub_windows::ConfirmWindow,
|
||||||
exit_confirm: sub_windows::ConfirmWindow,
|
exit_confirm: sub_windows::ConfirmWindow,
|
||||||
|
|
||||||
search_menu: sub_windows::SearchWindow,
|
search_menu: sub_windows::SearchWindow,
|
||||||
settings_menu: sub_windows::SettingsWindow,
|
settings_menu: sub_windows::SettingsWindow,
|
||||||
shortcuts_menu: sub_windows::ShortcutsWindow,
|
shortcuts_menu: sub_windows::ShortcutsWindow,
|
||||||
|
|
||||||
time_watch: Vec<f32>,
|
time_watch: Vec<f32>,
|
||||||
next_frame: time::Instant,
|
next_frame: time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Calcifer {
|
impl Default for Calcifer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
focused: true,
|
focused: true,
|
||||||
got_focus: false,
|
got_focus: false,
|
||||||
|
|
||||||
selected_tab: 0,
|
selected_tab: 0,
|
||||||
tabs: vec![panels::Tab::default()],
|
tabs: vec![panels::Tab::default()],
|
||||||
tab_rect: egui::Rect::EVERYTHING,
|
|
||||||
mouse_holder: panels::MouseHolder::None,
|
|
||||||
|
|
||||||
command: String::new(),
|
command: String::new(),
|
||||||
command_history: Vec::new(),
|
command_history: Vec::new(),
|
||||||
running_command: false,
|
running_command: false,
|
||||||
|
|
||||||
theme: editor::themes::DEFAULT_THEMES[0],
|
theme: editor::themes::DEFAULT_THEMES[0],
|
||||||
font_size: 14.0,
|
font_size: 14.0,
|
||||||
zoom: 1.0,
|
zoom: 1.0,
|
||||||
|
|
||||||
project_content: panels::Project::new(),
|
project_content: panels::Project::new(),
|
||||||
|
|
||||||
home: get_my_home().unwrap().unwrap(),
|
home: get_my_home().unwrap().unwrap(),
|
||||||
tree_dir_opened: vec![],
|
tree_dir_opened: vec![],
|
||||||
file_tree: None,
|
file_tree: None,
|
||||||
n_file_displayed: 0,
|
n_file_displayed: 0,
|
||||||
|
|
||||||
tree_visible: false,
|
tree_visible: false,
|
||||||
profiler_visible: false,
|
profiler_visible: false,
|
||||||
terminal_visible: false,
|
terminal_visible: false,
|
||||||
|
|
||||||
close_tab_confirm: sub_windows::ConfirmWindow::new(
|
close_tab_confirm: sub_windows::ConfirmWindow::new(
|
||||||
"You have some unsaved changes, Do you still want to close this document ?",
|
"You have some unsaved changes, Do you still want to close this document ?",
|
||||||
"Confirm Close",
|
"Confirm Close",
|
||||||
),
|
),
|
||||||
tab_to_close: 0,
|
tab_to_close: 0,
|
||||||
refresh_confirm: sub_windows::ConfirmWindow::new(
|
refresh_confirm: sub_windows::ConfirmWindow::new(
|
||||||
"You have some unsaved changes, Do you still want to refresh this document ?",
|
"You have some unsaved changes, Do you still want to refresh this document ?",
|
||||||
"Confirm Refresh",
|
"Confirm Refresh",
|
||||||
),
|
),
|
||||||
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
|
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
|
||||||
|
|
||||||
search_menu: sub_windows::SearchWindow::default(),
|
search_menu: sub_windows::SearchWindow::default(),
|
||||||
settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]),
|
settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]),
|
||||||
shortcuts_menu: sub_windows::ShortcutsWindow::new(),
|
shortcuts_menu: sub_windows::ShortcutsWindow::new(),
|
||||||
|
|
||||||
time_watch: vec![0.0; TIME_LABELS.len()],
|
time_watch: vec![0.0; TIME_LABELS.len()],
|
||||||
next_frame: time::Instant::now(),
|
next_frame: time::Instant::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for Calcifer {
|
impl eframe::App for Calcifer {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
thread::sleep(time::Duration::from_secs_f32(
|
thread::sleep(time::Duration::from_secs_f32(
|
||||||
((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0),
|
((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0),
|
||||||
));
|
));
|
||||||
self.next_frame = time::Instant::now();
|
self.next_frame = time::Instant::now();
|
||||||
|
|
||||||
let mut watch = time::Instant::now();
|
let mut watch = time::Instant::now();
|
||||||
|
|
||||||
let mut style = (*ctx.style()).clone();
|
let mut style = (*ctx.style()).clone();
|
||||||
style.text_styles = [
|
style.text_styles = [
|
||||||
(
|
(
|
||||||
Heading,
|
Heading,
|
||||||
FontId::new(self.font_size * 1.6, FontFamily::Proportional),
|
FontId::new(self.font_size * 1.6, FontFamily::Proportional),
|
||||||
),
|
),
|
||||||
(Body, FontId::new(self.font_size, FontFamily::Proportional)),
|
(Body, FontId::new(self.font_size, FontFamily::Proportional)),
|
||||||
(
|
(
|
||||||
Monospace,
|
Monospace,
|
||||||
FontId::new(self.font_size, FontFamily::Monospace),
|
FontId::new(self.font_size, FontFamily::Monospace),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Button,
|
Button,
|
||||||
FontId::new(self.font_size, FontFamily::Proportional),
|
FontId::new(self.font_size, FontFamily::Proportional),
|
||||||
),
|
),
|
||||||
(Small, FontId::new(self.font_size, FontFamily::Proportional)),
|
(Small, FontId::new(self.font_size, FontFamily::Proportional)),
|
||||||
]
|
]
|
||||||
.into();
|
.into();
|
||||||
ctx.set_style(style);
|
ctx.set_style(style);
|
||||||
|
|
||||||
if ctx.zoom_factor() != self.zoom {
|
if ctx.zoom_factor() != self.zoom {
|
||||||
ctx.set_zoom_factor(self.zoom);
|
ctx.set_zoom_factor(self.zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl)
|
if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl)
|
||||||
&& !self.refresh_confirm.visible
|
&& !self.refresh_confirm.visible
|
||||||
{
|
{
|
||||||
if self.tabs[self.selected_tab].saved {
|
if self.tabs[self.selected_tab].saved {
|
||||||
self.tabs[self.selected_tab].refresh();
|
self.tabs[self.selected_tab].refresh();
|
||||||
} else {
|
} else {
|
||||||
self.refresh_confirm.ask();
|
self.refresh_confirm.ask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::Enter))
|
if ctx.input(|i| i.key_pressed(egui::Key::Enter)) && ctx.memory(|m| m.focus() == None)
|
||||||
&& ctx.memory(|m| m.focus() == None)
|
&& self.tabs[self.selected_tab].language == PROJECT_EXTENSION
|
||||||
&& self.tabs[self.selected_tab].language == PROJECT_EXTENSION
|
{
|
||||||
{
|
self.project_content.item_window.visible = true;
|
||||||
self.project_content.item_window.visible = true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
|
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
|
||||||
self.handle_save_file(self.save_tab());
|
self.handle_save_file(self.save_tab());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) {
|
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) {
|
||||||
self.handle_save_file(self.save_tab_as());
|
self.handle_save_file(self.save_tab_as());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) {
|
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) {
|
||||||
self.move_through_tabs(false);
|
self.move_through_tabs(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) {
|
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) {
|
||||||
self.move_through_tabs(true);
|
self.move_through_tabs(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.zoom_delta() > 1.0) {
|
if ctx.input(|i| i.zoom_delta() > 1.0) {
|
||||||
self.zoom = (self.zoom * ZOOM_FACTOR).min(10.0);
|
self.zoom = (self.zoom*ZOOM_FACTOR).min(10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.zoom_delta() < 1.0) {
|
if ctx.input(|i| i.zoom_delta() < 1.0) {
|
||||||
self.zoom = (self.zoom / ZOOM_FACTOR).max(0.1);
|
self.zoom = (self.zoom/ZOOM_FACTOR).max(0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) {
|
if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) {
|
||||||
self.search_menu.visible = !self.search_menu.visible;
|
self.search_menu.visible = !self.search_menu.visible;
|
||||||
self.search_menu.initialized = !self.search_menu.visible;
|
self.search_menu.initialized = !self.search_menu.visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.got_focus = false;
|
self.got_focus = false;
|
||||||
if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) {
|
if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) {
|
||||||
self.focused = false;
|
self.focused = false;
|
||||||
} else {
|
} else {
|
||||||
if !self.focused {
|
if !self.focused {
|
||||||
self.got_focus = true;
|
self.got_focus = true;
|
||||||
}
|
}
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
if ctx.input(|i| i.viewport().close_requested()) {
|
||||||
let mut unsaved_tabs: Vec<usize> = vec![];
|
let mut unsaved_tabs: Vec<usize> = vec![];
|
||||||
for (index, tab) in self.tabs.iter().enumerate() {
|
for (index, tab) in self.tabs.iter().enumerate() {
|
||||||
if !tab.saved {
|
if !tab.saved {
|
||||||
unsaved_tabs.push(index);
|
unsaved_tabs.push(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !unsaved_tabs.is_empty() {
|
if !unsaved_tabs.is_empty() {
|
||||||
let mut unsaved_tabs_names: String = "".to_string();
|
let mut unsaved_tabs_names: String = "".to_string();
|
||||||
for index in unsaved_tabs.iter() {
|
for index in unsaved_tabs.iter() {
|
||||||
unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
|
unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
|
||||||
}
|
}
|
||||||
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
|
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
|
||||||
self.exit_confirm.prompt = format!(
|
self.exit_confirm.prompt = format!(
|
||||||
"You have some unsaved changes :\n{}\nDo you still want to exit ?",
|
"You have some unsaved changes :\n{}\nDo you still want to exit ?",
|
||||||
unsaved_tabs_names
|
unsaved_tabs_names
|
||||||
);
|
);
|
||||||
self.exit_confirm.ask();
|
self.exit_confirm.ask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tabs.len() == 0 {
|
self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close);
|
watch = time::Instant::now();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_settings(ctx);
|
||||||
watch = time::Instant::now();
|
|
||||||
|
|
||||||
self.draw_settings(ctx);
|
self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
|
watch = time::Instant::now();
|
||||||
|
|
||||||
self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_tree_panel(ctx);
|
||||||
watch = time::Instant::now();
|
|
||||||
|
|
||||||
self.draw_tree_panel(ctx);
|
self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
|
watch = time::Instant::now();
|
||||||
|
|
||||||
self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_bottom_tray(ctx);
|
||||||
watch = time::Instant::now();
|
self.draw_terminal_panel(ctx);
|
||||||
|
|
||||||
self.draw_bottom_tray(ctx);
|
self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
self.draw_terminal_panel(ctx);
|
watch = time::Instant::now();
|
||||||
|
|
||||||
self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_tab_panel(ctx);
|
||||||
watch = time::Instant::now();
|
|
||||||
|
|
||||||
self.draw_tab_panel(ctx);
|
self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
|
watch = time::Instant::now();
|
||||||
|
|
||||||
self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_content_panel(ctx);
|
||||||
watch = time::Instant::now();
|
|
||||||
|
|
||||||
self.draw_content_panel(ctx);
|
self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
|
watch = time::Instant::now();
|
||||||
|
|
||||||
self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0;
|
self.draw_windows(ctx);
|
||||||
watch = time::Instant::now();
|
|
||||||
|
|
||||||
self.draw_windows(ctx);
|
self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
|
||||||
self.draw_mouse_drag(ctx);
|
|
||||||
|
|
||||||
self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
|
if self.running_command {
|
||||||
|
egui::Context::request_repaint_after(ctx, Duration::from_secs_f32(RUNNING_COMMAND_REFRESH_DELAY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.running_command {
|
fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) {
|
||||||
egui::Context::request_repaint_after(
|
self.save_state();
|
||||||
ctx,
|
}
|
||||||
Duration::from_secs_f32(RUNNING_COMMAND_REFRESH_DELAY),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) {
|
|
||||||
self.save_state();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//save path
|
//save path
|
||||||
fn save_path() -> PathBuf {
|
fn save_path() -> PathBuf {
|
||||||
if TITLE.is_empty() {
|
if TITLE.is_empty() {
|
||||||
get_my_home()
|
get_my_home()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_path()
|
.as_path()
|
||||||
.join(".config")
|
.join(".config")
|
||||||
.join("calcifer")
|
.join("calcifer")
|
||||||
.join("save.json")
|
.join("save.json")
|
||||||
.to_path_buf()
|
.to_path_buf()
|
||||||
} else {
|
} else {
|
||||||
get_my_home()
|
get_my_home()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_path()
|
.as_path()
|
||||||
.join(".config")
|
.join(".config")
|
||||||
.join("calcifer")
|
.join("calcifer")
|
||||||
.join("debug_save.json")
|
.join("debug_save.json")
|
||||||
.to_path_buf()
|
.to_path_buf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,186 +1,183 @@
|
||||||
use std::hash::DefaultHasher;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::hash::Hasher;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::time::UNIX_EPOCH;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fs, io,
|
fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
use std::hash::DefaultHasher;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
//use crate::ALLOWED_FILE_EXTENSIONS;
|
//use crate::ALLOWED_FILE_EXTENSIONS;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FileEntry {
|
pub struct FileEntry {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub folder_content: Option<Vec<FileEntry>>,
|
pub folder_content: Option<Vec<FileEntry>>,
|
||||||
pub content_checked: bool,
|
pub content_checked: bool,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileEntry {
|
impl FileEntry {
|
||||||
pub fn new_entry(name: String, path: PathBuf) -> Self {
|
pub fn new_entry(name: String, path: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
folder_content: None,
|
folder_content: None,
|
||||||
content_checked: true,
|
content_checked: true,
|
||||||
id: Self::generate_unique_id(path),
|
id: Self::generate_unique_id(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn end_of_branch(name: String, path: PathBuf) -> Self {
|
pub fn end_of_branch(name: String, path: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
folder_content: Some(vec![]),
|
folder_content: Some(vec![]),
|
||||||
content_checked: false,
|
content_checked: false,
|
||||||
id: Self::generate_unique_id(path),
|
id: Self::generate_unique_id(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_unique_id(path: PathBuf) -> String {
|
fn generate_unique_id(path: PathBuf) -> String {
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs();
|
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
now.hash(&mut hasher);
|
now.hash(&mut hasher);
|
||||||
|
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
format!("{}#{}", path.display(), hash)
|
format!("{}#{}", path.display(), hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry {
|
pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry {
|
||||||
if !opened_dirs.contains(&get_file_path_id(&file.path)) {
|
if !opened_dirs.contains(&get_file_path_id(&file.path)) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file.content_checked {
|
if !file.content_checked {
|
||||||
let folder = generate_folder_entry(&file.path);
|
let folder = generate_folder_entry(&file.path);
|
||||||
return update_file_tree(folder, opened_dirs);
|
return update_file_tree(folder, opened_dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.folder_content.is_none() {
|
if file.folder_content.is_none() {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
let folder_content = file.folder_content.expect("should have checked if none");
|
let folder_content = file.folder_content.expect("should have checked if none");
|
||||||
|
|
||||||
let updated_content: Vec<FileEntry> = folder_content
|
let updated_content: Vec<FileEntry> = folder_content
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| update_file_tree(entry.clone(), opened_dirs.clone()))
|
.map(|entry| update_file_tree(entry.clone(), opened_dirs.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
FileEntry {
|
FileEntry {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
path: file.path.clone(),
|
path: file.path.clone(),
|
||||||
folder_content: Some(updated_content),
|
folder_content: Some(updated_content),
|
||||||
content_checked: true,
|
content_checked: true,
|
||||||
id: FileEntry::generate_unique_id(file.path),
|
id: FileEntry::generate_unique_id(file.path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_file_path_id(path: &PathBuf) -> String {
|
pub fn get_file_path_id(path: &PathBuf) -> String {
|
||||||
format!("#{}", path.clone().display())
|
format!("#{}", path.clone().display())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_folder_entry(path: &Path) -> FileEntry {
|
pub fn generate_folder_entry(path: &Path) -> FileEntry {
|
||||||
if let Some(file_name) = path.file_name() {
|
if let Some(file_name) = path.file_name() {
|
||||||
let name = file_name.to_string_lossy().into_owned();
|
let name = file_name.to_string_lossy().into_owned();
|
||||||
|
|
||||||
match fs::read_dir(path) {
|
match fs::read_dir(path) {
|
||||||
Err(err) => FileEntry::new_entry(
|
Err(err) => FileEntry::new_entry(
|
||||||
format!("Error reading directory: {}", err),
|
format!("Error reading directory: {}", err),
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
),
|
),
|
||||||
Ok(entries) => {
|
Ok(entries) => {
|
||||||
let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries
|
let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries
|
||||||
.map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
|
.map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
paths.sort_by(|a, b| match (a, b) {
|
paths.sort_by(|a, b| match (a, b) {
|
||||||
(Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b),
|
(Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b),
|
||||||
(Err(_), Ok(_)) => std::cmp::Ordering::Greater,
|
(Err(_), Ok(_)) => std::cmp::Ordering::Greater,
|
||||||
(Ok(_), Err(_)) => std::cmp::Ordering::Less,
|
(Ok(_), Err(_)) => std::cmp::Ordering::Less,
|
||||||
(Err(_), Err(_)) => std::cmp::Ordering::Equal,
|
(Err(_), Err(_)) => std::cmp::Ordering::Equal,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut folder_content = Vec::new();
|
let mut folder_content = Vec::new();
|
||||||
|
|
||||||
for result in paths {
|
for result in paths {
|
||||||
match result {
|
match result {
|
||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
if let Some(file) = generate_entry(&entry.path()) {
|
if let Some(file) = generate_entry(&entry.path()) {
|
||||||
folder_content.push(file);
|
folder_content.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
folder_content.push(FileEntry::new_entry(
|
folder_content.push(FileEntry::new_entry(
|
||||||
format!("Error reading entry: {}", err),
|
format!("Error reading entry: {}", err),
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileEntry {
|
FileEntry {
|
||||||
name,
|
name,
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
folder_content: Some(folder_content),
|
folder_content: Some(folder_content),
|
||||||
content_checked: true,
|
content_checked: true,
|
||||||
id: FileEntry::generate_unique_id(path.to_path_buf()),
|
id: FileEntry::generate_unique_id(path.to_path_buf()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FileEntry::new_entry(
|
FileEntry::new_entry(
|
||||||
"Error reading directory name".to_string(),
|
"Error reading directory name".to_string(),
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_entry(path: &Path) -> Option<FileEntry> {
|
fn generate_entry(path: &Path) -> Option<FileEntry> {
|
||||||
if let Some(file_name) = path.file_name() {
|
if let Some(file_name) = path.file_name() {
|
||||||
if file_name.to_string_lossy().starts_with('.') {
|
if file_name.to_string_lossy().starts_with('.') {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// let extension = path.extension().and_then(|ext| ext.to_str());
|
// let extension = path.extension().and_then(|ext| ext.to_str());
|
||||||
// if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) {
|
// if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) {
|
||||||
// return None;
|
// return None;
|
||||||
// }
|
// }
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_else(|| OsStr::new(""))
|
.unwrap_or_else(|| OsStr::new(""))
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Some(FileEntry::new_entry(name, path.to_path_buf()));
|
return Some(FileEntry::new_entry(name, path.to_path_buf()));
|
||||||
}
|
}
|
||||||
Some(FileEntry::end_of_branch(name, path.to_path_buf()))
|
Some(FileEntry::end_of_branch(name, path.to_path_buf()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering {
|
fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering {
|
||||||
let a_is_dir = a.path().is_dir();
|
let a_is_dir = a.path().is_dir();
|
||||||
let b_is_dir = b.path().is_dir();
|
let b_is_dir = b.path().is_dir();
|
||||||
|
|
||||||
// Directories come first, then files
|
// Directories come first, then files
|
||||||
if a_is_dir && !b_is_dir {
|
if a_is_dir && !b_is_dir {
|
||||||
Ordering::Less
|
Ordering::Less
|
||||||
} else if !a_is_dir && b_is_dir {
|
} else if !a_is_dir && b_is_dir {
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
} else {
|
} else {
|
||||||
// Both are either directories or files, sort alphabetically
|
// Both are either directories or files, sort alphabetically
|
||||||
a.path().cmp(&b.path())
|
a.path().cmp(&b.path())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::max,
|
cmp::min,
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
cmp::max,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core::hex_str_to_color;
|
use crate::core::hex_str_to_color;
|
||||||
|
@ -12,253 +13,262 @@ use crate::MAX_PROJECT_COLUMNS;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct ProjectSave {
|
pub struct ProjectSave {
|
||||||
pub categories: Vec<Category>,
|
pub categories: Vec<Category>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectSave {
|
impl ProjectSave {
|
||||||
pub fn from_project(project: &Project) -> Self {
|
pub fn from_project(project: &Project) -> Self {
|
||||||
Self {
|
Self {
|
||||||
categories: project.categories.clone(),
|
categories: project.categories.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
pub categories: Vec<Category>,
|
pub categories: Vec<Category>,
|
||||||
pub selected_item: Location,
|
pub selected_item: Location,
|
||||||
pub item_window: sub_windows::ProjectItemWindow,
|
pub item_window: sub_windows::ProjectItemWindow,
|
||||||
was_moving: bool,
|
was_moving: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
categories: vec![Category::create()],
|
categories: vec![Category::create()],
|
||||||
selected_item: Location::zero(),
|
selected_item: Location::zero(),
|
||||||
was_moving: false,
|
was_moving: false,
|
||||||
item_window: sub_windows::ProjectItemWindow::new(),
|
item_window: sub_windows::ProjectItemWindow::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_from_code(&mut self, json: String) {
|
pub fn update_from_code(&mut self, json: String) {
|
||||||
match serde_json::from_str::<ProjectSave>(&json) {
|
match serde_json::from_str::<ProjectSave>(&json) {
|
||||||
Ok(project_save) => self.categories = project_save.categories,
|
Ok(project_save) => self.categories = project_save.categories,
|
||||||
Err(_err) => self.categories = vec![Category::create()],
|
Err(_err) => self.categories = vec![Category::create()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_to_code(&self) -> Result<String, std::io::Error> {
|
pub fn save_to_code(&self) -> Result<String, std::io::Error> {
|
||||||
Ok(serde_json::to_string(&ProjectSave::from_project(self))?)
|
Ok(serde_json::to_string(&ProjectSave::from_project(self))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_category(&mut self) {
|
fn add_category(&mut self) {
|
||||||
let last = self.categories.len() - 1;
|
let last = self.categories.len() - 1;
|
||||||
self.categories[last].initialize();
|
self.categories[last].initialize();
|
||||||
self.categories.push(Category::create());
|
self.categories.push(Category::create());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_category(&mut self, index: usize) {
|
fn delete_category(&mut self, index: usize) {
|
||||||
self.categories.remove(index);
|
self.categories.remove(index);
|
||||||
let last = self.categories.len() - 1;
|
let last = self.categories.len() - 1;
|
||||||
if self.categories[last].name != "+" {
|
if self.categories[last].name != "+" {
|
||||||
self.categories.push(Category::create());
|
self.categories.push(Category::create());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Category {
|
pub struct Category {
|
||||||
name: String,
|
name: String,
|
||||||
pub content: Vec<Item>,
|
pub content: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Category {
|
impl Category {
|
||||||
fn create() -> Self {
|
fn create() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "+".into(),
|
name: "+".into(),
|
||||||
content: vec![],
|
content: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self) {
|
fn initialize(&mut self) {
|
||||||
self.name = "untitled".into();
|
self.name = "untitled".into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Hash, Serialize, Deserialize)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
id: usize,
|
id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
fn new(name: &str) -> Self {
|
fn new(name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
description: "// Hello there".to_string(),
|
description: "// Hello there".to_string(),
|
||||||
id: get_id(),
|
id: get_id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Location {
|
pub struct Location {
|
||||||
pub category: usize,
|
pub category: usize,
|
||||||
pub row: usize,
|
pub row: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Location {
|
impl Location {
|
||||||
fn zero() -> Self {
|
fn zero() -> Self {
|
||||||
Self {
|
Self {
|
||||||
category: 0,
|
category: 0,
|
||||||
row: 0,
|
row: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_id() -> usize {
|
fn get_id() -> usize {
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_project(ui: &mut egui::Ui, theme: ColorTheme, project: &mut Project) {
|
pub fn draw_project(ui: &mut egui::Ui, theme: ColorTheme, project: &mut Project) {
|
||||||
ui.columns(
|
ui.columns(max(MAX_PROJECT_COLUMNS, project.categories.len() + 1) , |uis| {
|
||||||
max(MAX_PROJECT_COLUMNS, project.categories.len() + 1),
|
for (category_index, category) in project.categories.clone().into_iter().enumerate() {
|
||||||
|uis| {
|
let ui = &mut uis[category_index];
|
||||||
for (category_index, category) in project.categories.clone().into_iter().enumerate() {
|
|
||||||
let ui = &mut uis[category_index];
|
|
||||||
|
|
||||||
if category.name == "+" {
|
if category.name == "+" {
|
||||||
if ui.add(egui::Button::new("+")).clicked() {
|
if ui.add(egui::Button::new("+")).clicked() {
|
||||||
project.add_category();
|
project.add_category();
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
let response = ui.add(
|
let response = ui.add(
|
||||||
egui::TextEdit::singleline(&mut project.categories[category_index].name)
|
egui::TextEdit::singleline(&mut project.categories[category_index].name)
|
||||||
.desired_width(f32::INFINITY),
|
.desired_width(f32::INFINITY),
|
||||||
);
|
);
|
||||||
if response.lost_focus() && project.categories[category_index].name.is_empty() {
|
if response.lost_focus() && project.categories[category_index].name.is_empty() {
|
||||||
project.delete_category(category_index);
|
project.delete_category(category_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
for (item_index, item) in category.content.iter().enumerate() {
|
for (item_index, item) in category.content.iter().enumerate() {
|
||||||
if project.selected_item
|
if project.selected_item
|
||||||
== (Location {
|
== (Location {
|
||||||
category: category_index,
|
category: category_index,
|
||||||
row: item_index,
|
row: item_index,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
ui.style_mut().visuals.override_text_color =
|
ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.bg));
|
||||||
Some(hex_str_to_color(theme.bg));
|
ui.add(
|
||||||
ui.add(
|
egui::Button::new(item.name.clone())
|
||||||
egui::Button::new(item.name.clone())
|
.fill(hex_str_to_color(theme.functions)),
|
||||||
.fill(hex_str_to_color(theme.functions)),
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
ui.style_mut().visuals.override_text_color =
|
||||||
ui.style_mut().visuals.override_text_color =
|
Some(hex_str_to_color(theme.literals));
|
||||||
Some(hex_str_to_color(theme.literals));
|
if ui
|
||||||
if ui
|
.add(egui::Button::new(item.name.clone()).fill(hex_str_to_color(theme.bg)))
|
||||||
.add(
|
.clicked()
|
||||||
egui::Button::new(item.name.clone())
|
{
|
||||||
.fill(hex_str_to_color(theme.bg)),
|
project.selected_item = Location {
|
||||||
)
|
category: category_index,
|
||||||
.clicked()
|
row: item_index,
|
||||||
{
|
};
|
||||||
project.selected_item = Location {
|
}
|
||||||
category: category_index,
|
}
|
||||||
row: item_index,
|
}
|
||||||
};
|
ui.style_mut().visuals.override_text_color =
|
||||||
}
|
Some(hex_str_to_color(theme.literals));
|
||||||
}
|
if category.name != "+" && ui.add(egui::Button::new("+")).clicked() {
|
||||||
}
|
project.categories[category_index]
|
||||||
ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.literals));
|
.content
|
||||||
if category.name != "+" && ui.add(egui::Button::new("+")).clicked() {
|
.push(Item::new("item"));
|
||||||
project.categories[category_index]
|
}
|
||||||
.content
|
// if category.name != "+" {
|
||||||
.push(Item::new("item"));
|
// if ui.add(egui::Button::new("+")).clicked() {
|
||||||
}
|
// project.categories[category_index]
|
||||||
}
|
// .content
|
||||||
},
|
// .push(Item::new("item"));
|
||||||
);
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut moved = false;
|
let mut moved = false;
|
||||||
let category = project.selected_item.category;
|
let category = project.selected_item.category;
|
||||||
let row = project.selected_item.row;
|
let row = project.selected_item.row;
|
||||||
|
|
||||||
if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift)
|
if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift)
|
||||||
&& project.selected_item.category > 0
|
&& project.selected_item.category > 0
|
||||||
{
|
{
|
||||||
moved = true;
|
moved = true;
|
||||||
if !project.was_moving {
|
if !project.was_moving {
|
||||||
let temp = project.categories[category].content[row].clone();
|
let temp = project.categories[category].content[row].clone();
|
||||||
project.categories[category - 1].content.push(temp);
|
project.categories[category - 1].content.push(temp);
|
||||||
project.categories[category].content.remove(row);
|
project.categories[category].content.remove(row);
|
||||||
project.selected_item.category -= 1;
|
project.selected_item.category -= 1;
|
||||||
project.selected_item.row = project.categories[category - 1].content.len() - 1;
|
project.selected_item.row = project.categories[category - 1].content.len() - 1;
|
||||||
}
|
}
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.shift)
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.shift)
|
||||||
&& project.selected_item.category < project.categories.len() - 2
|
&& project.selected_item.category < project.categories.len() - 2
|
||||||
{
|
{
|
||||||
moved = true;
|
moved = true;
|
||||||
if !project.was_moving {
|
if !project.was_moving {
|
||||||
let temp = project.categories[category].content[row].clone();
|
let temp = project.categories[category].content[row].clone();
|
||||||
project.categories[category + 1].content.push(temp);
|
project.categories[category + 1].content.push(temp);
|
||||||
project.categories[category].content.remove(row);
|
project.categories[category].content.remove(row);
|
||||||
project.selected_item.category += 1;
|
project.selected_item.category += 1;
|
||||||
project.selected_item.row = project.categories[category + 1].content.len() - 1;
|
project.selected_item.row = project.categories[category + 1].content.len() - 1;
|
||||||
}
|
}
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) && i.modifiers.shift)
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) && i.modifiers.shift)
|
||||||
&& project.selected_item.row > 0
|
&& project.selected_item.row > 0
|
||||||
{
|
{
|
||||||
moved = true;
|
moved = true;
|
||||||
if !project.was_moving {
|
if !project.was_moving {
|
||||||
let temp = project.categories[category].content[row].clone();
|
let temp = project.categories[category].content[row].clone();
|
||||||
project.categories[category].content[row] =
|
project.categories[category].content[row] =
|
||||||
project.categories[category].content[row - 1].clone();
|
project.categories[category].content[row - 1].clone();
|
||||||
project.categories[category].content[row - 1] = temp.clone();
|
project.categories[category].content[row - 1] = temp.clone();
|
||||||
project.selected_item.row -= 1;
|
project.selected_item.row -= 1;
|
||||||
}
|
}
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) {
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) {
|
||||||
moved = true;
|
moved = true;
|
||||||
if !project.was_moving {
|
if !project.was_moving {
|
||||||
let temp = project.categories[category].content[row].clone();
|
let temp = project.categories[category].content[row].clone();
|
||||||
project.categories[category].content[row] =
|
project.categories[category].content[row] =
|
||||||
project.categories[category].content[row + 1].clone();
|
project.categories[category].content[row + 1].clone();
|
||||||
project.categories[category].content[row + 1] = temp.clone();
|
project.categories[category].content[row + 1] = temp.clone();
|
||||||
project.selected_item.row += 1;
|
project.selected_item.row += 1;
|
||||||
}
|
}
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft))
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft))
|
||||||
&& project.selected_item.category > 0
|
&& project.selected_item.category > 0
|
||||||
{
|
{
|
||||||
moved = true;
|
moved = true;
|
||||||
if !project.was_moving && project.categories[category - 1].content.len() > 0 {
|
if !project.was_moving {
|
||||||
project.selected_item.category -= 1;
|
project.selected_item.category -= 1;
|
||||||
}
|
project.selected_item.row = min(
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight))
|
project.categories[category].content.len() - 1,
|
||||||
&& project.selected_item.category < project.categories.len() - 2
|
project.selected_item.row,
|
||||||
{
|
);
|
||||||
moved = true;
|
}
|
||||||
if !project.was_moving && project.categories[category + 1].content.len() > 0 {
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight))
|
||||||
project.selected_item.category += 1;
|
&& project.selected_item.category < project.categories.len() - 2
|
||||||
}
|
{
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 {
|
moved = true;
|
||||||
moved = true;
|
if !project.was_moving {
|
||||||
if !project.was_moving {
|
project.selected_item.category += 1;
|
||||||
project.selected_item.row -= 1;
|
project.selected_item.row = min(
|
||||||
}
|
project.categories[category].content.len() - 1,
|
||||||
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown))
|
project.selected_item.row,
|
||||||
&& project.selected_item.row < project.categories[category].content.len() - 1
|
);
|
||||||
{
|
}
|
||||||
moved = true;
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 {
|
||||||
if !project.was_moving {
|
moved = true;
|
||||||
project.selected_item.row += 1;
|
if !project.was_moving {
|
||||||
}
|
project.selected_item.row -= 1;
|
||||||
}
|
}
|
||||||
|
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown))
|
||||||
|
&& project.selected_item.row < project.categories[category].content.len() - 1
|
||||||
|
{
|
||||||
|
moved = true;
|
||||||
|
if !project.was_moving {
|
||||||
|
project.selected_item.row += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project.was_moving = moved;
|
project.was_moving = moved;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,11 +61,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MouseHolder {
|
|
||||||
TabHolder(usize),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_file_contents(path: &Path) -> String {
|
fn read_file_contents(path: &Path) -> String {
|
||||||
let error_type = "reading file";
|
let error_type = "reading file";
|
||||||
read_to_string(path)
|
read_to_string(path)
|
||||||
|
|
|
@ -3,46 +3,46 @@ use eframe::egui;
|
||||||
use crate::panels;
|
use crate::panels;
|
||||||
|
|
||||||
pub struct ProjectItemWindow {
|
pub struct ProjectItemWindow {
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectItemWindow {
|
impl ProjectItemWindow {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { visible: false }
|
Self { visible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool {
|
pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool {
|
||||||
let mut visible = self.visible;
|
let mut visible = self.visible;
|
||||||
let maybe_response = egui::Window::new("Item")
|
let maybe_response = egui::Window::new("Item")
|
||||||
.open(&mut visible)
|
.open(&mut visible)
|
||||||
.vscroll(true)
|
.vscroll(true)
|
||||||
.hscroll(true)
|
.hscroll(true)
|
||||||
.show(ctx, |ui| self.ui(ui, item));
|
.show(ctx, |ui| self.ui(ui, item));
|
||||||
self.visible = self.visible && visible;
|
self.visible = self.visible && visible;
|
||||||
|
|
||||||
if let Some(response) = maybe_response {
|
if let Some(response) = maybe_response {
|
||||||
if let Some(delete_option) = response.inner {
|
if let Some(delete_option) = response.inner {
|
||||||
return delete_option;
|
return delete_option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool {
|
fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool {
|
||||||
let mut delete_item = false;
|
let mut delete_item = false;
|
||||||
ui.set_min_width(250.0);
|
ui.set_min_width(250.0);
|
||||||
ui.set_min_height(250.0);
|
ui.set_min_height(250.0);
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
if ui.add(egui::Button::new("delete")).clicked() {
|
if ui.add(egui::Button::new("delete")).clicked() {
|
||||||
delete_item = true;
|
delete_item = true;
|
||||||
}
|
}
|
||||||
ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY));
|
ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY));
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add_sized(
|
ui.add_sized(
|
||||||
ui.available_size(),
|
ui.available_size(),
|
||||||
egui::TextEdit::multiline(&mut item.description),
|
egui::TextEdit::multiline(&mut item.description),
|
||||||
);
|
);
|
||||||
return delete_item.clone();
|
return delete_item.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
|
||||||
pub struct ShortcutsWindow {
|
pub struct ShortcutsWindow {
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShortcutsWindow {
|
impl ShortcutsWindow {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { visible: false }
|
Self { visible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, ctx: &egui::Context) {
|
pub fn show(&mut self, ctx: &egui::Context) {
|
||||||
let mut visible = self.visible;
|
let mut visible = self.visible;
|
||||||
egui::Window::new("Shortcuts")
|
egui::Window::new("Shortcuts")
|
||||||
.open(&mut visible)
|
.open(&mut visible)
|
||||||
.vscroll(true)
|
.vscroll(true)
|
||||||
.hscroll(true)
|
.hscroll(true)
|
||||||
.show(ctx, |ui| self.ui(ui));
|
.show(ctx, |ui| self.ui(ui));
|
||||||
self.visible = self.visible && visible;
|
self.visible = self.visible && visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.set_min_width(250.0);
|
ui.set_min_width(250.0);
|
||||||
ui.label("Ctrl+S : save file");
|
ui.label("Ctrl+S : save file");
|
||||||
ui.label("Ctrl+Shift+S : save file as");
|
ui.label("Ctrl+Shift+S : save file as");
|
||||||
ui.label("Ctrl+R : reload file");
|
ui.label("Ctrl+R : reload file");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Ctrl+F : open search window");
|
ui.label("Ctrl+F : open search window");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Ctrl+T : reload tree");
|
ui.label("Ctrl+T : reload tree");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Ctrl+Z : undo");
|
ui.label("Ctrl+Z : undo");
|
||||||
ui.label("Ctrl+Y : redo");
|
ui.label("Ctrl+Y : redo");
|
||||||
ui.label("Tab on selection : add indent of selection");
|
ui.label("Tab on selection : add indent of selection");
|
||||||
ui.label("Shift+Tab on selection : remove indent of selection");
|
ui.label("Shift+Tab on selection : remove indent of selection");
|
||||||
ui.label("Ctrl+E : comment selection");
|
ui.label("Ctrl+E : comment selection");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Alt+Arrows : move between tabs");
|
ui.label("Alt+Arrows : move between tabs");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Enter (project_mode) : edit item");
|
ui.label("Enter (project_mode) : edit item");
|
||||||
ui.label("Arrows (project_mode) : change selected item");
|
ui.label("Arrows (project_mode) : change selected item");
|
||||||
ui.label("Shift+Arrows (project_mode) : move selected item");
|
ui.label("Shift+Arrows (project_mode) : move selected item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue