From 23d81bf84700f6ce60afa622400aeff2393dd3d1 Mon Sep 17 00:00:00 2001 From: WanderingPenwing Date: Wed, 7 Aug 2024 11:40:44 +0200 Subject: [PATCH] fmt --- build/build.rs | 4 +- calcifer.project | 2 +- shell.nix | 6 +- src/core/app.rs | 416 +++++------ src/core/state.rs | 74 +- src/core/ui.rs | 1146 ++++++++++++++++--------------- src/editor/syntax/javascript.rs | 103 ++- src/editor/syntax/mod.rs | 312 ++++----- src/main.rs | 519 +++++++------- src/panels/file_tree.rs | 289 ++++---- src/panels/project_mode.rs | 402 +++++------ src/panels/tabs.rs | 116 ++-- src/sub_windows/project_item.rs | 74 +- src/sub_windows/shortcuts.rs | 70 +- 14 files changed, 1845 insertions(+), 1688 deletions(-) diff --git a/build/build.rs b/build/build.rs index 7d92f7d..90f0304 100644 --- a/build/build.rs +++ b/build/build.rs @@ -1,4 +1,4 @@ fn main() { - // custom parameter to fix clipboard issue between calcifer and external - println!("cargo:rustc-cfg=web_sys_unstable_apis"); + // custom parameter to fix clipboard issue between calcifer and external + println!("cargo:rustc-cfg=web_sys_unstable_apis"); } diff --git a/calcifer.project b/calcifer.project index 96d7541..fefdf17 100644 --- a/calcifer.project +++ b/calcifer.project @@ -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":"+","content":[]}]} \ No newline at end of file +{"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":[]}]} \ No newline at end of file diff --git a/shell.nix b/shell.nix index d718dee..3515307 100644 --- a/shell.nix +++ b/shell.nix @@ -9,7 +9,7 @@ mkShell { libXi pkg-config ] ++ [ - cargo + #cargo rustc atk gdk-pixbuf @@ -19,12 +19,12 @@ mkShell { libGLU libxkbcommon gtk3-x11 - gnome.zenity + #gnome.zenity ]; buildInputs = [ latest.rustChannels.stable.rust xorg.libX11 - wayland + # wayland libxkbcommon ]; diff --git a/src/core/app.rs b/src/core/app.rs index 2f029f0..0ab0f90 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -1,6 +1,6 @@ use eframe::egui; use egui::Color32; -use std::{cmp::min, cmp::max, fs, path::Path, path::PathBuf}; +use std::{cmp::max, cmp::min, fs, path::Path, path::PathBuf}; use crate::core; use crate::editor::themes::DEFAULT_THEMES; @@ -11,240 +11,242 @@ use crate::Calcifer; use crate::TIME_LABELS; impl Calcifer { - pub fn handle_confirm(&mut self) { - if self.close_tab_confirm.proceed { - self.close_tab_confirm.close(); - self.delete_tab(self.tab_to_close); - } + pub fn handle_confirm(&mut self) { + if self.close_tab_confirm.proceed { + self.close_tab_confirm.close(); + self.delete_tab(self.tab_to_close); + } - if self.refresh_confirm.proceed { - self.refresh_confirm.close(); - self.tabs[self.selected_tab].refresh(); - } - } + if self.refresh_confirm.proceed { + self.refresh_confirm.close(); + self.tabs[self.selected_tab].refresh(); + } + } - pub fn save_tab(&self) -> Option { - if self.tabs[self.selected_tab] - .path - .file_name() - .map_or(true, |name| name.to_string_lossy() == "untitled") - { - self.save_tab_as() - } else { - if let Err(err) = fs::write( - &self.tabs[self.selected_tab].path, - &self.tabs[self.selected_tab].code, - ) { - eprintln!("Error writing file: {}", err); - return self.save_tab_as(); - } - Some(self.tabs[self.selected_tab].path.clone()) - } - } + pub fn save_tab(&self) -> Option { + if self.tabs[self.selected_tab] + .path + .file_name() + .map_or(true, |name| name.to_string_lossy() == "untitled") + { + self.save_tab_as() + } else { + if let Err(err) = fs::write( + &self.tabs[self.selected_tab].path, + &self.tabs[self.selected_tab].code, + ) { + eprintln!("Error writing file: {}", err); + return self.save_tab_as(); + } + Some(self.tabs[self.selected_tab].path.clone()) + } + } - pub fn save_tab_as(&self) -> Option { - let default_path = self.home.join("untitled"); - - let save_path = if self.tabs[self.selected_tab].path.file_name().map_or(true, |name| name.to_string_lossy() == "untitled") - { - default_path.to_string_lossy() - } else { - self.tabs[self.selected_tab].path.to_string_lossy() - }; - if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) - { - let path = PathBuf::from(path_string); - if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) { - eprintln!("Error writing file: {}", err); - return None; - } - return Some(path); - } - None - } + pub fn save_tab_as(&self) -> Option { + let default_path = self.home.join("untitled"); - pub fn handle_save_file(&mut self, path_option: Option) { - if let Some(path) = path_option { - println!("File saved successfully at: {:?}", path); - self.tabs[self.selected_tab].path = path; - self.tabs[self.selected_tab].saved = true; - } else { - println!("File save failed."); - } - } + let save_path = if self.tabs[self.selected_tab] + .path + .file_name() + .map_or(true, |name| name.to_string_lossy() == "untitled") + { + default_path.to_string_lossy() + } else { + self.tabs[self.selected_tab].path.to_string_lossy() + }; + if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) { + let path = PathBuf::from(path_string); + if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) { + eprintln!("Error writing file: {}", err); + return None; + } + return Some(path); + } + None + } - pub fn from_app_state(app_state: core::AppState, file_to_open: Option) -> Self { - let mut new = Self { - theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], - tabs: Vec::new(), - settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]), - ..Default::default() - }; + pub fn handle_save_file(&mut self, path_option: Option) { + if let Some(path) = path_option { + println!("File saved successfully at: {:?}", path); + self.tabs[self.selected_tab].path = path; + self.tabs[self.selected_tab].saved = true; + } else { + println!("File save failed."); + } + } - if app_state.zoom != 0.0 { - new.zoom = app_state.zoom; - } + pub fn from_app_state(app_state: core::AppState, file_to_open: Option) -> Self { + let mut new = Self { + theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], + tabs: Vec::new(), + settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]), + ..Default::default() + }; - for path in app_state.tabs { - if !path - .file_name() - .map_or(true, |name| name.to_string_lossy() == "untitled") - { - new.open_file(Some(&path)); - } - } - - if let Some(path) = file_to_open { - new.open_file(Some(&path)); - } + if app_state.zoom != 0.0 { + new.zoom = app_state.zoom; + } - if new.tabs == vec![] { - new.open_file(None); - } + for path in app_state.tabs { + if !path + .file_name() + .map_or(true, |name| name.to_string_lossy() == "untitled") + { + new.open_file(Some(&path)); + } + } - new - } + if let Some(path) = file_to_open { + new.open_file(Some(&path)); + } - pub fn save_state(&self) { - let mut state_theme: usize = 0; - if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { - state_theme = theme; - } + if new.tabs == vec![] { + new.open_file(None); + } - let mut state_tabs = vec![]; + new + } - for tab in &self.tabs { - state_tabs.push(tab.path.clone()); - } - let app_state = core::AppState { - tabs: state_tabs, - theme: state_theme, - zoom: self.zoom, - }; + pub fn save_state(&self) { + let mut state_theme: usize = 0; + if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { + state_theme = theme; + } - let _ = core::save_state(&app_state, save_path().as_path()); - } + let mut state_tabs = vec![]; - pub fn move_through_tabs(&mut self, forward: bool) { - let new_index = if forward { - (self.selected_tab + 1) % self.tabs.len() - } else { - self.selected_tab - .checked_sub(1) - .unwrap_or(self.tabs.len() - 1) - }; - self.selected_tab = new_index; - } + for tab in &self.tabs { + state_tabs.push(tab.path.clone()); + } + let app_state = core::AppState { + tabs: state_tabs, + theme: state_theme, + zoom: self.zoom, + }; - pub fn open_file(&mut self, path_option: Option<&Path>) { - if let Some(path) = path_option { - for (index, tab) in self.tabs.clone().iter().enumerate() { - if tab.path == path { - self.selected_tab = index; - return; - } - } - } - if let Some(path) = path_option { - self.tabs.push(panels::Tab::new(path.to_path_buf())); - } else { - self.tabs.push(panels::Tab::default()); - } - self.selected_tab = self.tabs.len() - 1; - } + let _ = core::save_state(&app_state, save_path().as_path()); + } - pub fn delete_tab(&mut self, index: usize) { - self.tabs.remove(index); - if self.tabs.len() != 0 { - self.selected_tab = min(index, self.tabs.len() - 1); - } - } + pub fn move_through_tabs(&mut self, forward: bool) { + let new_index = if forward { + (self.selected_tab + 1) % self.tabs.len() + } else { + self.selected_tab + .checked_sub(1) + .unwrap_or(self.tabs.len() - 1) + }; + self.selected_tab = new_index; + } - pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { - let bg_color: Color32; - let text_color: Color32; + pub fn open_file(&mut self, path_option: Option<&Path>) { + if let Some(path) = path_option { + for (index, tab) in self.tabs.clone().iter().enumerate() { + if tab.path == path { + self.selected_tab = index; + return; + } + } + } + if let Some(path) = path_option { + self.tabs.push(panels::Tab::new(path.to_path_buf())); + } else { + self.tabs.push(panels::Tab::default()); + } + self.selected_tab = self.tabs.len() - 1; + } - if display { - bg_color = hex_str_to_color(self.theme.functions); - text_color = hex_str_to_color(self.theme.bg); - } else { - bg_color = hex_str_to_color(self.theme.bg); - text_color = hex_str_to_color(self.theme.literals); - }; + pub fn delete_tab(&mut self, index: usize) { + self.tabs.remove(index); + if self.tabs.len() != 0 { + self.selected_tab = min(index, self.tabs.len() - 1); + } + } - ui.style_mut().visuals.override_text_color = Some(text_color); + pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { + let bg_color: Color32; + let text_color: Color32; - if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { - return !display; - } - ui.style_mut().visuals.override_text_color = None; + if display { + bg_color = hex_str_to_color(self.theme.functions); + text_color = hex_str_to_color(self.theme.bg); + } else { + bg_color = hex_str_to_color(self.theme.bg); + text_color = hex_str_to_color(self.theme.literals); + }; - display - } + ui.style_mut().visuals.override_text_color = Some(text_color); - pub fn profiler(&self) -> String { - if !self.profiler_visible { - return "".to_string(); - } - let combined_string: Vec = TIME_LABELS - .into_iter() - .zip(self.time_watch.clone()) - .map(|(s, v)| format!("{} : {:.1} ms", s, v)) - .collect(); + if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { + return !display; + } + ui.style_mut().visuals.override_text_color = None; - let mut result = combined_string.join(" ; "); - result.push_str(&format!( - " total : {:.1} ms", - self.time_watch.clone().iter().sum::() - )); - result - } + display + } - pub fn list_files( - &mut self, - ui: &mut egui::Ui, - file: &panels::FileEntry, - n_files: &mut usize, - ) -> bool { - *n_files += 1; + pub fn profiler(&self) -> String { + if !self.profiler_visible { + return "".to_string(); + } + let combined_string: Vec = TIME_LABELS + .into_iter() + .zip(self.time_watch.clone()) + .map(|(s, v)| format!("{} : {:.1} ms", s, v)) + .collect(); - if let Some(folder_content) = &file.folder_content { - let mut check_for_update: bool = false; - let file_path_id = panels::get_file_path_id(&file.path); - - let collapsing_response = egui::CollapsingHeader::new(file.name.clone()) - .id_source(&file.id) - .default_open(self.tree_dir_opened.contains(&file_path_id)) - .show(ui, |ui| { - if !self.tree_dir_opened.contains(&file_path_id) { - return; - } - for deeper_file in folder_content { - if self.list_files(ui, deeper_file, n_files) { - check_for_update = true; - } - } - }); - if collapsing_response.fully_closed() { - self.tree_dir_opened.retain(|s| s != &file_path_id); - } else if !self.tree_dir_opened.contains(&file_path_id) { - self.tree_dir_opened.push(file_path_id); - return !file.content_checked; - } - return check_for_update; - } else if ui.button(&file.name).clicked() { - self.open_file(Some(&file.path)); - } + let mut result = combined_string.join(" ; "); + result.push_str(&format!( + " total : {:.1} ms", + self.time_watch.clone().iter().sum::() + )); + result + } - false - } - - pub fn tab_area_size(&self) -> usize { - max(6, self.tabs.len() + 1) - } + pub fn list_files( + &mut self, + ui: &mut egui::Ui, + file: &panels::FileEntry, + n_files: &mut usize, + ) -> bool { + *n_files += 1; + + if let Some(folder_content) = &file.folder_content { + let mut check_for_update: bool = false; + let file_path_id = panels::get_file_path_id(&file.path); + + let collapsing_response = egui::CollapsingHeader::new(file.name.clone()) + .id_source(&file.id) + .default_open(self.tree_dir_opened.contains(&file_path_id)) + .show(ui, |ui| { + if !self.tree_dir_opened.contains(&file_path_id) { + return; + } + for deeper_file in folder_content { + if self.list_files(ui, deeper_file, n_files) { + check_for_update = true; + } + } + }); + if collapsing_response.fully_closed() { + self.tree_dir_opened.retain(|s| s != &file_path_id); + } else if !self.tree_dir_opened.contains(&file_path_id) { + self.tree_dir_opened.push(file_path_id); + return !file.content_checked; + } + return check_for_update; + } else if ui.button(&file.name).clicked() { + self.open_file(Some(&file.path)); + } + + false + } + + pub fn tab_area_size(&self) -> usize { + max(6, self.tabs.len() + 1) + } } #[allow(clippy::unnecessary_lazy_evaluations)] 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) } diff --git a/src/core/state.rs b/src/core/state.rs index 2d8ef34..d71fb3f 100644 --- a/src/core/state.rs +++ b/src/core/state.rs @@ -1,60 +1,60 @@ use eframe::egui; use image::GenericImageView; -use std::{ - error::Error, - fs, - fs::{read_to_string, OpenOptions}, - io::Write, - path::{Path, PathBuf}, -}; -use serde::Serialize; use serde::Deserialize; +use serde::Serialize; +use std::{ + error::Error, + fs, + fs::{read_to_string, OpenOptions}, + io::Write, + path::{Path, PathBuf}, +}; #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] pub struct AppState { - pub tabs: Vec, - pub theme: usize, - pub zoom: f32, + pub tabs: Vec, + pub theme: usize, + pub zoom: f32, } 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() { - fs::create_dir_all(parent_dir)?; - } + if let Some(parent_dir) = file_path.parent() { + fs::create_dir_all(parent_dir)?; + } - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(file_path)?; + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .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 { - 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> { - let (icon_rgba, icon_width, icon_height) = { - let icon = include_bytes!("../../assets/icon.png"); - let image = image::load_from_memory(icon)?; - let rgba = image.clone().into_rgba8().to_vec(); - let (width, height) = image.dimensions(); - (rgba, width, height) - }; + let (icon_rgba, icon_width, icon_height) = { + let icon = include_bytes!("../../assets/icon.png"); + let image = image::load_from_memory(icon)?; + let rgba = image.clone().into_rgba8().to_vec(); + let (width, height) = image.dimensions(); + (rgba, width, height) + }; - Ok(egui::IconData { - rgba: icon_rgba, - width: icon_width, - height: icon_height, - }) + Ok(egui::IconData { + rgba: icon_rgba, + width: icon_width, + height: icon_height, + }) } diff --git a/src/core/ui.rs b/src/core/ui.rs index 77fd314..4054adc 100644 --- a/src/core/ui.rs +++ b/src/core/ui.rs @@ -15,576 +15,648 @@ use crate::TERMINAL_RANGE; use editor::{CodeEditor, Syntax}; impl Calcifer { - pub fn draw_settings(&mut self, ctx: &egui::Context) { - egui::SidePanel::left("settings") - .resizable(false) - .exact_width(self.font_size * 1.8) - .show(ctx, |ui| { - ui.vertical(|ui| { - if ui.add(egui::Button::new("📁")).clicked() { - if let Some(path_string) = tinyfiledialogs::open_file_dialog("Open File", &self.home.to_string_lossy(), None) - { - self.open_file(Some(&Path::new(&path_string))); - } - } - ui.separator(); - - self.tree_visible = self.toggle(ui, self.tree_visible, "📦"); - ui.separator(); - - let toggle_terminal = self.toggle(ui, self.terminal_visible, "🖵"); - if toggle_terminal && !self.terminal_visible { - let mut path = self.tabs[self.selected_tab].path.clone(); - path.pop(); - panels::send_command(format!("cd {}", path.display())); - } - self.terminal_visible = toggle_terminal; - ui.separator(); - - self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍"); - ui.separator(); - - self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, "⚙"); - ui.separator(); - - self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, "⌨"); - ui.separator(); - - self.profiler_visible = self.toggle(ui, self.profiler_visible, "⚡"); + pub fn draw_settings(&mut self, ctx: &egui::Context) { + egui::SidePanel::left("settings") + .resizable(false) + .exact_width(self.font_size * 1.8) + .show(ctx, |ui| { + ui.vertical(|ui| { + if ui.add(egui::Button::new("📁")).clicked() { + if let Some(path_string) = tinyfiledialogs::open_file_dialog( + "Open File", + &self.home.to_string_lossy(), + None, + ) { + self.open_file(Some(&Path::new(&path_string))); + } + } + ui.separator(); - if self.tabs[self.selected_tab].language == PROJECT_EXTENSION { - ui.separator(); - self.project_content.item_window.visible = - self.toggle(ui, self.project_content.item_window.visible, "🖊"); - } - }); - }); - } + self.tree_visible = self.toggle(ui, self.tree_visible, "📦"); + ui.separator(); - pub fn draw_tree_panel(&mut self, ctx: &egui::Context) { - if !self.tree_visible { - return; - } - egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { - ui.horizontal(|ui| { - ui.label(format!("Bookshelf ({} files) ", self.n_file_displayed)); - if ui.button("↺").clicked() { - self.file_tree = None; - } - if ui.button("🗙").clicked() { - self.file_tree = None; - self.tree_dir_opened = vec![]; - } - }); - ui.separator(); - - let mut init_update : bool = false; - if self.file_tree.is_none() { - self.file_tree = Some(panels::generate_folder_entry(self.home.as_path())); - init_update = true - } - let mut n_files: usize = 0; - - egui::ScrollArea::vertical().show(ui, |ui| { - if let Some(file_tree) = self.file_tree.clone() { - let update_requested = self.list_files(ui, &file_tree, &mut n_files); - if update_requested || init_update { - self.file_tree = Some(panels::update_file_tree( - file_tree, - self.tree_dir_opened.clone(), - )); - } - } else { - ui.label("No book on the Bookshelf"); - } - ui.separator(); - }); - - self.n_file_displayed = n_files; - }); - } + let toggle_terminal = self.toggle(ui, self.terminal_visible, "🖵"); + if toggle_terminal && !self.terminal_visible { + let mut path = self.tabs[self.selected_tab].path.clone(); + path.pop(); + panels::send_command(format!("cd {}", path.display())); + } + self.terminal_visible = toggle_terminal; + ui.separator(); - pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::bottom("tray") - .default_height(self.font_size * 1.2) - .resizable(false) - .show(ctx, |ui| { - ui.label(self.profiler()); - }); - } + self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍"); + ui.separator(); - pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { - if !self.terminal_visible { - return; - } - egui::TopBottomPanel::bottom("terminal") - .default_height(TERMINAL_HEIGHT) - .height_range(Rangef::new(TERMINAL_RANGE.start, TERMINAL_RANGE.end)) - .resizable(true) - .show(ctx, |ui| { - ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { - let command_color = core::hex_str_to_color(self.theme.functions); - let entry_color = core::hex_str_to_color(self.theme.literals); - let bg_color = core::hex_str_to_color(self.theme.bg); + self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, "⚙"); + ui.separator(); - ui.label(""); + self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, "⌨"); + ui.separator(); - ui.horizontal(|ui| { - if ui.add(egui::Button::new("⟳")).clicked() { - self.command_history.retain(|e| !e.finished); - } - ui.style_mut().visuals.extreme_bg_color = bg_color; - let Self { command, .. } = self; - ui.colored_label( - command_color, - format_path(&env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))), - ); - let response = ui.add( - egui::TextEdit::singleline(command) - .desired_width(f32::INFINITY) - .lock_focus(true), - ); + self.profiler_visible = self.toggle(ui, self.profiler_visible, "⚡"); - if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) { - self.command_history - .push(panels::send_command(self.command.clone())); - self.command = "".into(); - response.request_focus(); - } - }); - ui.separator(); - egui::ScrollArea::vertical() - .stick_to_bottom(true) - .show(ui, |ui| { - ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { - ui.separator(); - ui.spacing_mut().item_spacing.y = 0.0; - ui.style_mut().visuals.hyperlink_color = - core::hex_str_to_color(self.theme.keywords); + if self.tabs[self.selected_tab].language == PROJECT_EXTENSION { + ui.separator(); + self.project_content.item_window.visible = + self.toggle(ui, self.project_content.item_window.visible, "🖊"); + } + }); + }); + } - let mut running_command = false; + pub fn draw_tree_panel(&mut self, ctx: &egui::Context) { + if !self.tree_visible { + return; + } + egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label(format!("Bookshelf ({} files) ", self.n_file_displayed)); + if ui.button("↺").clicked() { + self.file_tree = None; + } + if ui.button("🗙").clicked() { + self.file_tree = None; + self.tree_dir_opened = vec![]; + } + }); + ui.separator(); - for entry in &mut self.command_history { - ui.label(""); - ui.horizontal(|ui| { - if !entry.finished { - running_command = true; - entry.update(); - let _ = ui.link("(⌛)"); - } else if ui.link("(🗐)").clicked() { - entry.copy_error_code(); - } - ui.colored_label( - command_color, - format!("{} {}", entry.env, entry.command), - ); - }); + let mut init_update: bool = false; + if self.file_tree.is_none() { + self.file_tree = Some(panels::generate_folder_entry(self.home.as_path())); + init_update = true + } + let mut n_files: usize = 0; - for line in &entry.result { - let color = if line.error { RED } else { entry_color }; - ui.add( - egui::Label::new( - egui::RichText::new(&line.text) - .monospace() - .color(color), - ), //.selectable(true) - ); - } - } + egui::ScrollArea::vertical().show(ui, |ui| { + if let Some(file_tree) = self.file_tree.clone() { + let update_requested = self.list_files(ui, &file_tree, &mut n_files); + if update_requested || init_update { + self.file_tree = Some(panels::update_file_tree( + file_tree, + self.tree_dir_opened.clone(), + )); + } + } else { + ui.label("No book on the Bookshelf"); + } + ui.separator(); + }); - self.running_command = running_command; - }); - }); - }); - }); - } + self.n_file_displayed = n_files; + }); + } - pub fn draw_tab_panel(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::top("tabs") - .resizable(false) - .show(ctx, |ui| { - ui.painter().rect_filled( - ui.available_rect_before_wrap(), - 0.0, - core::hex_str_to_color(self.theme.bg), - ); - let response = StripBuilder::new(ui) - .sizes(Size::remainder(), self.tab_area_size()) - .sense(egui::Sense::click()) - .horizontal(|mut strip| { - for (index, tab) in self.tabs.clone().iter().enumerate() { - strip.cell(|ui| { - let mut color = core::hex_str_to_color(self.theme.comments); - if self.selected_tab == index { - ui.painter().rect_filled( - ui.available_rect_before_wrap(), - 0.0, - core::hex_str_to_color(self.theme.functions), - ); - color = core::hex_str_to_color(self.theme.bg) - } - let unsaved_indicator = if tab.saved { "" } else { "~ " }; - ui.with_layout( - egui::Layout::right_to_left(egui::Align::TOP), - |ui| { - ui.label(""); - if ui - .add( - egui::Label::new( - egui::RichText::new(" ❌ ").color(color), - ) - .sense(egui::Sense::click()), - ) - .clicked() - && !self.close_tab_confirm.visible - { - if tab.saved { - self.delete_tab(index); - } else { - self.close_tab_confirm.ask(); - self.tab_to_close = index; - } - } - ui.with_layout( - egui::Layout::left_to_right(egui::Align::TOP), - |ui| { - if ui.add( - egui::Label::new( - egui::RichText::new(format!( - " {}{}", - unsaved_indicator, - tab.get_name() - )) - .color(color), - ) - .truncate(true) - .sense(egui::Sense::click()), - ) - .clicked() || - ui.add_sized(ui.available_size(), egui::Label::new("").sense(egui::Sense::click())).clicked() - { - self.selected_tab = index; - } - }, - ); - }, - ); - }); - } - strip.cell(|ui| { - if ui - .add(egui::Label::new(" ➕").sense(egui::Sense::click())) - .clicked() - { - self.open_file(None); - } - }); - }); - self.tab_rect = response.rect; - }); - } + pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::bottom("tray") + .default_height(self.font_size * 1.2) + .resizable(false) + .show(ctx, |ui| { + ui.label(self.profiler()); + }); + } - pub fn draw_content_panel(&mut self, ctx: &egui::Context) { - egui::CentralPanel::default().show(ctx, |ui| { - if self.selected_tab >= self.tabs.len() { - return - } - ui.horizontal(|ui| { - ui.style_mut().visuals.hyperlink_color = core::hex_str_to_color(self.theme.comments); - if ui.link(self.tabs[self.selected_tab].path.to_string_lossy().to_string()).clicked() { - let mut current_path = self.tabs[self.selected_tab].path.clone(); - current_path.pop(); - - while let Some(parent) = current_path.parent() { - let dir_id = panels::get_file_path_id(¤t_path); - if !self.tree_dir_opened.contains(&dir_id) { - self.tree_dir_opened.push(dir_id); - } - current_path = parent.to_path_buf(); - } - - self.tree_visible = true; - self.file_tree = None; - } - }); + pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { + if !self.terminal_visible { + return; + } + egui::TopBottomPanel::bottom("terminal") + .default_height(TERMINAL_HEIGHT) + .height_range(Rangef::new(TERMINAL_RANGE.start, TERMINAL_RANGE.end)) + .resizable(true) + .show(ctx, |ui| { + ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { + let command_color = core::hex_str_to_color(self.theme.functions); + let entry_color = core::hex_str_to_color(self.theme.literals); + let bg_color = core::hex_str_to_color(self.theme.bg); - ui.separator(); - if self.tabs[self.selected_tab].language == PROJECT_EXTENSION { - self.draw_project_file(ctx, ui); - } else { - self.draw_code_file(ui); - } - }); - } + ui.label(""); - fn draw_code_file(&mut self, ui: &mut egui::Ui) { - let current_tab = &mut self.tabs[self.selected_tab]; - let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1; - let mut override_cursor: Option = None; + ui.horizontal(|ui| { + if ui.add(egui::Button::new("⟳")).clicked() { + self.command_history.retain(|e| !e.finished); + } + ui.style_mut().visuals.extreme_bg_color = bg_color; + let Self { command, .. } = self; + ui.colored_label( + command_color, + format_path(&env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))), + ); + let response = ui.add( + egui::TextEdit::singleline(command) + .desired_width(f32::INFINITY) + .lock_focus(true), + ); - if !self.search_menu.result_selected { - override_cursor = Some(CCursorRange::two( - CCursor::new(self.search_menu.get_cursor_start()), - CCursor::new(self.search_menu.get_cursor_end()), - )); - self.search_menu.result_selected = true; - } - - let tab_id = current_tab.path.clone().to_string_lossy().to_string(); - - if self.got_focus { - CodeEditor::default() - .id_source(&tab_id) - .with_rows(max(45, lines)) - .with_fontsize(self.font_size) - .with_theme(self.theme) - .with_syntax(to_syntax(¤t_tab.language)) - .with_numlines(true) - .show( - ui, - &mut current_tab.code.clone(), - &mut current_tab.saved.clone(), - &mut current_tab.last_cursor.clone(), - &mut current_tab.scroll_offset.clone(), - override_cursor.clone(), - ); - return - } + if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) { + self.command_history + .push(panels::send_command(self.command.clone())); + self.command = "".into(); + response.request_focus(); + } + }); + ui.separator(); + egui::ScrollArea::vertical() + .stick_to_bottom(true) + .show(ui, |ui| { + ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { + ui.separator(); + ui.spacing_mut().item_spacing.y = 0.0; + ui.style_mut().visuals.hyperlink_color = + core::hex_str_to_color(self.theme.keywords); - CodeEditor::default() - .id_source(&tab_id) - .with_rows(max(45, lines)) - .with_fontsize(self.font_size) - .with_theme(self.theme) - .with_syntax(to_syntax(¤t_tab.language)) - .with_numlines(true) - .show( - ui, - &mut current_tab.code, - &mut current_tab.saved, - &mut current_tab.last_cursor, - &mut current_tab.scroll_offset, - override_cursor, - ); - } + let mut running_command = false; - fn draw_project_file(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { - let current_tab = &mut self.tabs[self.selected_tab]; + for entry in &mut self.command_history { + ui.label(""); + ui.horizontal(|ui| { + if !entry.finished { + running_command = true; + entry.update(); + let _ = ui.link("(⌛)"); + } else if ui.link("(🗐)").clicked() { + entry.copy_error_code(); + } + ui.colored_label( + command_color, + format!("{} {}", entry.env, entry.command), + ); + }); - self.project_content - .update_from_code(current_tab.code.clone()); - panels::draw_project(ui, self.theme, &mut self.project_content); - - self.project_content.selected_item.category = min(self.project_content.categories.len() - 2, self.project_content.selected_item.category); - while self.project_content.categories[self.project_content.selected_item.category].content.is_empty() && self.project_content.selected_item.category > 0 { - self.project_content.selected_item.category -= 1; - } - if !self.project_content.categories[self.project_content.selected_item.category].content.is_empty() { - self.project_content.selected_item.row = min(self.project_content.categories[self.project_content.selected_item.category].content.len() - 1, self.project_content.selected_item.row); - } else { - self.project_content.selected_item.row = 0; - } - - if self.project_content.item_window.visible { - if self.project_content.categories.len() > 1 - && !self.project_content.categories[self.project_content.selected_item.category] - .content - .is_empty() - { - let delete_item = self.project_content.item_window.show( - ctx, - &mut self.project_content.categories - [self.project_content.selected_item.category] - .content[self.project_content.selected_item.row], - ); - - if delete_item { - self.project_content.item_window.visible = false; - self.project_content.categories - [self.project_content.selected_item.category] - .content.remove(self.project_content.selected_item.row); - if self.project_content.selected_item.row >= self.project_content.categories[self.project_content.selected_item.category].content.len() && self.project_content.selected_item.row > 0 { - self.project_content.selected_item.row -= 1; - } - } - } else { - self.project_content.item_window.visible = false; - } - } + for line in &entry.result { + let color = if line.error { RED } else { entry_color }; + ui.add( + egui::Label::new( + egui::RichText::new(&line.text) + .monospace() + .color(color), + ), //.selectable(true) + ); + } + } - match self.project_content.save_to_code() { - Ok(code) => { - if current_tab.code != code { - current_tab.code = code; - current_tab.saved = false; - } - } - Err(_err) => (), - } - } + self.running_command = running_command; + }); + }); + }); + }); + } - pub fn draw_windows(&mut self, ctx: &egui::Context) { - if self.search_menu.visible { - self.search_menu - .show(ctx, &mut self.tabs, &mut self.selected_tab); - } - if self.close_tab_confirm.visible { - self.close_tab_confirm.show(ctx); - } - if self.refresh_confirm.visible { - self.refresh_confirm.show(ctx); - } - if self.exit_confirm.visible { - self.exit_confirm.show(ctx); - } - if self.exit_confirm.proceed { - for tab in self.tabs.iter_mut() { - tab.saved = true; - } - egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); - } - if self.shortcuts_menu.visible { - self.shortcuts_menu.show(ctx); - } - if self.settings_menu.visible { - self.settings_menu.show(ctx); - } - if self.settings_menu.updated { - self.theme = self.settings_menu.theme; - } + pub fn draw_tab_panel(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::top("tabs") + .resizable(false) + .show(ctx, |ui| { + ui.painter().rect_filled( + ui.available_rect_before_wrap(), + 0.0, + core::hex_str_to_color(self.theme.bg), + ); + let response = StripBuilder::new(ui) + .sizes(Size::remainder(), self.tab_area_size()) + .sense(egui::Sense::click()) + .horizontal(|mut strip| { + for (index, tab) in self.tabs.clone().iter().enumerate() { + strip.cell(|ui| { + let mut color = core::hex_str_to_color(self.theme.comments); + if self.selected_tab == index { + ui.painter().rect_filled( + ui.available_rect_before_wrap(), + 0.0, + core::hex_str_to_color(self.theme.functions), + ); + color = core::hex_str_to_color(self.theme.bg) + } + let unsaved_indicator = if tab.saved { "" } else { "~ " }; + ui.with_layout( + egui::Layout::right_to_left(egui::Align::TOP), + |ui| { + ui.label(""); + if ui + .add( + egui::Label::new( + egui::RichText::new(" ❌ ").color(color), + ) + .sense(egui::Sense::click()), + ) + .clicked() + && !self.close_tab_confirm.visible + { + if tab.saved { + self.delete_tab(index); + } else { + self.close_tab_confirm.ask(); + self.tab_to_close = index; + } + } + ui.with_layout( + egui::Layout::left_to_right(egui::Align::TOP), + |ui| { + if ui + .add( + egui::Label::new( + egui::RichText::new(format!( + " {}{}", + unsaved_indicator, + tab.get_name() + )) + .color(color), + ) + .truncate(true) + .sense(egui::Sense::click()), + ) + .clicked() + || ui + .add_sized( + ui.available_size(), + egui::Label::new("") + .sense(egui::Sense::click()), + ) + .clicked() + { + self.selected_tab = index; + } + }, + ); + }, + ); + }); + } + strip.cell(|ui| { + if ui + .add(egui::Label::new(" ➕").sense(egui::Sense::click())) + .clicked() + { + self.open_file(None); + } + }); + }); + self.tab_rect = response.rect; + }); + } - self.handle_confirm(); - } - - pub fn draw_mouse_drag(&mut self, ctx: &egui::Context) { - if ctx.input(|i| i.pointer.is_decidedly_dragging()) { - if let Some(pos) = ctx.input(|i| i.pointer.interact_pos()) { - match self.mouse_holder { - panels::MouseHolder::TabHolder(index) => { - let snapped_pos = egui::Pos2::new(pos.x, (self.tab_rect.max.y + self.tab_rect.min.y) / 2.0); - - egui::Area::new(egui::Id::new("mouse_holder")).fixed_pos(snapped_pos).show(ctx, |ui| { - let (bg_color, text_color) = if self.selected_tab == index { - (core::hex_str_to_color(self.theme.functions), core::hex_str_to_color(self.theme.bg)) - } else { - (core::hex_str_to_color(self.theme.bg), core::hex_str_to_color(self.theme.comments)) - }; - - - let rect = egui::Rect::from_center_size( - snapped_pos, - egui::Vec2::new((self.tab_rect.max.x - self.tab_rect.min.x) / usize_to_f32(self.tab_area_size()), self.tab_rect.max.y - self.tab_rect.min.y) - ); - - ui.painter().rect_filled( - rect, - 0.0, - bg_color, - ); - let unsaved_indicator = if self.tabs[index].saved { "" } else { "~ " }; - - let _ = ui.put(rect, egui::Label::new(egui::RichText::new(format!(" {}{}", unsaved_indicator, self.tabs[index].get_name())).color(text_color))); - }); - } - panels::MouseHolder::None => { - if self.tab_rect.distance_to_pos(pos) == 0.0 { - let hover_pos : f32 = (pos.x - self.tab_rect.min.x) / ((self.tab_rect.max.x - self.tab_rect.min.x) / usize_to_f32(self.tab_area_size())); - - if let Some(index) = floor_f32(hover_pos) { - if index < self.tabs.len() { - self.mouse_holder = panels::MouseHolder::TabHolder(index); - } - } - } - } - } - } - return - } - - match self.mouse_holder { - panels::MouseHolder::TabHolder(initial_index) => { - if let Some(pos) = ctx.input(|i| i.pointer.interact_pos()) { - let snapped_pos = egui::Pos2::new(pos.x, (self.tab_rect.max.y + self.tab_rect.min.y) / 2.0); - if self.tab_rect.distance_to_pos(snapped_pos) == 0.0 { - let hover_pos : f32 = (pos.x - self.tab_rect.min.x) / ((self.tab_rect.max.x - self.tab_rect.min.x) / usize_to_f32(self.tab_area_size())); - - if let Some(final_index) = floor_f32(hover_pos) { - if final_index == initial_index { - return - } else if final_index < initial_index { - self.tabs.insert(final_index, self.tabs[initial_index].clone()); - self.tabs.remove(initial_index + 1); - } else { - self.tabs.insert(final_index + 1, self.tabs[initial_index].clone()); - self.tabs.remove(initial_index); - } - - if self.selected_tab == initial_index { - self.selected_tab = final_index; - } else if self.selected_tab < initial_index && self.selected_tab >= final_index { - self.selected_tab += 1; - } else if self.selected_tab > initial_index && self.selected_tab <= final_index { - self.selected_tab -= 1; - } - } - } - } - } - - panels::MouseHolder::None => {} - } - - self.mouse_holder = panels::MouseHolder::None; - } + pub fn draw_content_panel(&mut self, ctx: &egui::Context) { + egui::CentralPanel::default().show(ctx, |ui| { + if self.selected_tab >= self.tabs.len() { + return; + } + ui.horizontal(|ui| { + ui.style_mut().visuals.hyperlink_color = + core::hex_str_to_color(self.theme.comments); + if ui + .link( + self.tabs[self.selected_tab] + .path + .to_string_lossy() + .to_string(), + ) + .clicked() + { + let mut current_path = self.tabs[self.selected_tab].path.clone(); + current_path.pop(); + + while let Some(parent) = current_path.parent() { + let dir_id = panels::get_file_path_id(¤t_path); + if !self.tree_dir_opened.contains(&dir_id) { + self.tree_dir_opened.push(dir_id); + } + current_path = parent.to_path_buf(); + } + + self.tree_visible = true; + self.file_tree = None; + } + }); + + ui.separator(); + if self.tabs[self.selected_tab].language == PROJECT_EXTENSION { + self.draw_project_file(ctx, ui); + } else { + self.draw_code_file(ui); + } + }); + } + + fn draw_code_file(&mut self, ui: &mut egui::Ui) { + let current_tab = &mut self.tabs[self.selected_tab]; + let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1; + let mut override_cursor: Option = None; + + if !self.search_menu.result_selected { + override_cursor = Some(CCursorRange::two( + CCursor::new(self.search_menu.get_cursor_start()), + CCursor::new(self.search_menu.get_cursor_end()), + )); + self.search_menu.result_selected = true; + } + + let tab_id = current_tab.path.clone().to_string_lossy().to_string(); + + if self.got_focus { + CodeEditor::default() + .id_source(&tab_id) + .with_rows(max(45, lines)) + .with_fontsize(self.font_size) + .with_theme(self.theme) + .with_syntax(to_syntax(¤t_tab.language)) + .with_numlines(true) + .show( + ui, + &mut current_tab.code.clone(), + &mut current_tab.saved.clone(), + &mut current_tab.last_cursor.clone(), + &mut current_tab.scroll_offset.clone(), + override_cursor.clone(), + ); + return; + } + + CodeEditor::default() + .id_source(&tab_id) + .with_rows(max(45, lines)) + .with_fontsize(self.font_size) + .with_theme(self.theme) + .with_syntax(to_syntax(¤t_tab.language)) + .with_numlines(true) + .show( + ui, + &mut current_tab.code, + &mut current_tab.saved, + &mut current_tab.last_cursor, + &mut current_tab.scroll_offset, + override_cursor, + ); + } + + fn draw_project_file(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { + let current_tab = &mut self.tabs[self.selected_tab]; + + self.project_content + .update_from_code(current_tab.code.clone()); + panels::draw_project(ui, self.theme, &mut self.project_content); + + self.project_content.selected_item.category = min( + self.project_content.categories.len() - 2, + self.project_content.selected_item.category, + ); + while self.project_content.categories[self.project_content.selected_item.category] + .content + .is_empty() + && self.project_content.selected_item.category > 0 + { + self.project_content.selected_item.category -= 1; + } + if !self.project_content.categories[self.project_content.selected_item.category] + .content + .is_empty() + { + self.project_content.selected_item.row = min( + self.project_content.categories[self.project_content.selected_item.category] + .content + .len() + - 1, + self.project_content.selected_item.row, + ); + } else { + self.project_content.selected_item.row = 0; + } + + if self.project_content.item_window.visible { + if self.project_content.categories.len() > 1 + && !self.project_content.categories[self.project_content.selected_item.category] + .content + .is_empty() + { + let delete_item = self.project_content.item_window.show( + ctx, + &mut self.project_content.categories + [self.project_content.selected_item.category] + .content[self.project_content.selected_item.row], + ); + + if delete_item { + self.project_content.item_window.visible = false; + self.project_content.categories[self.project_content.selected_item.category] + .content + .remove(self.project_content.selected_item.row); + if self.project_content.selected_item.row + >= self.project_content.categories + [self.project_content.selected_item.category] + .content + .len() + && self.project_content.selected_item.row > 0 + { + self.project_content.selected_item.row -= 1; + } + } + } else { + self.project_content.item_window.visible = false; + } + } + + match self.project_content.save_to_code() { + Ok(code) => { + if current_tab.code != code { + current_tab.code = code; + current_tab.saved = false; + } + } + Err(_err) => (), + } + } + + pub fn draw_windows(&mut self, ctx: &egui::Context) { + if self.search_menu.visible { + self.search_menu + .show(ctx, &mut self.tabs, &mut self.selected_tab); + } + if self.close_tab_confirm.visible { + self.close_tab_confirm.show(ctx); + } + if self.refresh_confirm.visible { + self.refresh_confirm.show(ctx); + } + if self.exit_confirm.visible { + self.exit_confirm.show(ctx); + } + if self.exit_confirm.proceed { + for tab in self.tabs.iter_mut() { + tab.saved = true; + } + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); + } + if self.shortcuts_menu.visible { + self.shortcuts_menu.show(ctx); + } + if self.settings_menu.visible { + self.settings_menu.show(ctx); + } + if self.settings_menu.updated { + self.theme = self.settings_menu.theme; + } + + self.handle_confirm(); + } + + pub fn draw_mouse_drag(&mut self, ctx: &egui::Context) { + if ctx.input(|i| i.pointer.is_decidedly_dragging()) { + if let Some(pos) = ctx.input(|i| i.pointer.interact_pos()) { + match self.mouse_holder { + panels::MouseHolder::TabHolder(index) => { + let snapped_pos = egui::Pos2::new( + pos.x, + (self.tab_rect.max.y + self.tab_rect.min.y) / 2.0, + ); + + egui::Area::new(egui::Id::new("mouse_holder")) + .fixed_pos(snapped_pos) + .show(ctx, |ui| { + let (bg_color, text_color) = if self.selected_tab == index { + ( + core::hex_str_to_color(self.theme.functions), + core::hex_str_to_color(self.theme.bg), + ) + } else { + ( + core::hex_str_to_color(self.theme.bg), + core::hex_str_to_color(self.theme.comments), + ) + }; + + let rect = egui::Rect::from_center_size( + snapped_pos, + egui::Vec2::new( + (self.tab_rect.max.x - self.tab_rect.min.x) + / usize_to_f32(self.tab_area_size()), + self.tab_rect.max.y - self.tab_rect.min.y, + ), + ); + + ui.painter().rect_filled(rect, 0.0, bg_color); + let unsaved_indicator = + if self.tabs[index].saved { "" } else { "~ " }; + + let _ = ui.put( + rect, + egui::Label::new( + egui::RichText::new(format!( + " {}{}", + unsaved_indicator, + self.tabs[index].get_name() + )) + .color(text_color), + ), + ); + }); + } + panels::MouseHolder::None => { + if self.tab_rect.distance_to_pos(pos) == 0.0 { + let hover_pos: f32 = (pos.x - self.tab_rect.min.x) + / ((self.tab_rect.max.x - self.tab_rect.min.x) + / usize_to_f32(self.tab_area_size())); + + if let Some(index) = floor_f32(hover_pos) { + if index < self.tabs.len() { + self.mouse_holder = panels::MouseHolder::TabHolder(index); + } + } + } + } + } + } + return; + } + + match self.mouse_holder { + panels::MouseHolder::TabHolder(initial_index) => { + if let Some(pos) = ctx.input(|i| i.pointer.interact_pos()) { + let snapped_pos = + egui::Pos2::new(pos.x, (self.tab_rect.max.y + self.tab_rect.min.y) / 2.0); + if self.tab_rect.distance_to_pos(snapped_pos) == 0.0 { + let hover_pos: f32 = (pos.x - self.tab_rect.min.x) + / ((self.tab_rect.max.x - self.tab_rect.min.x) + / usize_to_f32(self.tab_area_size())); + + if let Some(final_index) = floor_f32(hover_pos) { + if final_index == initial_index { + return; + } else if final_index < initial_index { + self.tabs + .insert(final_index, self.tabs[initial_index].clone()); + self.tabs.remove(initial_index + 1); + } else { + self.tabs + .insert(final_index + 1, self.tabs[initial_index].clone()); + self.tabs.remove(initial_index); + } + + if self.selected_tab == initial_index { + self.selected_tab = final_index; + } else if self.selected_tab < initial_index + && self.selected_tab >= final_index + { + self.selected_tab += 1; + } else if self.selected_tab > initial_index + && self.selected_tab <= final_index + { + self.selected_tab -= 1; + } + } + } + } + } + + panels::MouseHolder::None => {} + } + + self.mouse_holder = panels::MouseHolder::None; + } } fn to_syntax(language: &str) -> Syntax { - match language { - "py" => Syntax::python(), - "rs" => Syntax::rust(), - "js" => Syntax::javascript(), - _ => Syntax::shell(), - } + match language { + "py" => Syntax::python(), + "rs" => Syntax::rust(), + "js" => Syntax::javascript(), + _ => Syntax::shell(), + } } pub fn format_path(path: &Path) -> String { - let components: Vec<&OsStr> = path - .components() - .rev() - .take(DISPLAY_PATH_DEPTH) - .filter_map(|component| match component { - Component::RootDir | Component::CurDir => None, - _ => Some(component.as_os_str()), - }) - .collect(); + let components: Vec<&OsStr> = path + .components() + .rev() + .take(DISPLAY_PATH_DEPTH) + .filter_map(|component| match component { + Component::RootDir | Component::CurDir => None, + _ => Some(component.as_os_str()), + }) + .collect(); - format!( - "{}>", - components - .iter() - .rev() - .map(|&c| c.to_string_lossy()) - .collect::>() - .join("/") - ) + format!( + "{}>", + components + .iter() + .rev() + .map(|&c| c.to_string_lossy()) + .collect::>() + .join("/") + ) } - fn usize_to_f32(value: usize) -> f32 { - const MAX_F32: f32 = f32::MAX; + const MAX_F32: f32 = f32::MAX; - if value as f64 > MAX_F32 as f64 { - MAX_F32 - } else { - value as f32 - } + if value as f64 > MAX_F32 as f64 { + MAX_F32 + } else { + value as f32 + } } fn floor_f32(value: f32) -> Option { - if value.is_nan() || value < 0.0 || value > usize::MAX as f32 { - None - } else { - Some(value.floor() as usize) - } -} \ No newline at end of file + if value.is_nan() || value < 0.0 || value > usize::MAX as f32 { + None + } else { + Some(value.floor() as usize) + } +} diff --git a/src/editor/syntax/javascript.rs b/src/editor/syntax/javascript.rs index 7f47934..0c1502b 100644 --- a/src/editor/syntax/javascript.rs +++ b/src/editor/syntax/javascript.rs @@ -2,23 +2,88 @@ use super::Syntax; use std::collections::BTreeSet; impl Syntax { - pub fn javascript() -> Syntax { - Syntax { - language: "Javascript", - case_sensitive: true, - comment: "//", - comment_multiline: ["/*", "*/"], - 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", - "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"]), - } - } + pub fn javascript() -> Syntax { + Syntax { + language: "Javascript", + case_sensitive: true, + comment: "//", + comment_multiline: ["/*", "*/"], + 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", + "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"]), + } + } } diff --git a/src/editor/syntax/mod.rs b/src/editor/syntax/mod.rs index c100d2e..f4d9c1e 100644 --- a/src/editor/syntax/mod.rs +++ b/src/editor/syntax/mod.rs @@ -19,179 +19,179 @@ type Float = bool; #[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum TokenType { - Comment(MultiLine), - Function, - Keyword, - Literal, - Numeric(Float), - Punctuation(char), - Special, - Str(char), - Type, - Whitespace(char), - #[default] - Unknown, + Comment(MultiLine), + Function, + Keyword, + Literal, + Numeric(Float), + Punctuation(char), + Special, + Str(char), + Type, + Whitespace(char), + #[default] + Unknown, } impl std::fmt::Debug for TokenType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut name = String::new(); - match &self { - TokenType::Comment(multiline) => { - name.push_str("Comment"); - { - if *multiline { - name.push_str(" MultiLine"); - } else { - name.push_str(" SingleLine"); - } - } - } - TokenType::Function => name.push_str("Function"), - TokenType::Keyword => name.push_str("Keyword"), - TokenType::Literal => name.push_str("Literal"), - TokenType::Numeric(float) => { - name.push_str("Numeric"); - if *float { - name.push_str(" Float"); - } else { - name.push_str(" Integer"); - } - } - TokenType::Punctuation(_) => name.push_str("Punctuation"), - TokenType::Special => name.push_str("Special"), - TokenType::Str(quote) => { - name.push_str("Str "); - name.push(*quote); - } - TokenType::Type => name.push_str("Type"), - TokenType::Whitespace(c) => { - name.push_str("Whitespace"); - match c { - ' ' => name.push_str(" Space"), - '\t' => name.push_str(" Tab"), - '\n' => name.push_str(" New Line"), - _ => (), - }; - } - TokenType::Unknown => name.push_str("Unknown"), - }; - write!(f, "{name}") - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut name = String::new(); + match &self { + TokenType::Comment(multiline) => { + name.push_str("Comment"); + { + if *multiline { + name.push_str(" MultiLine"); + } else { + name.push_str(" SingleLine"); + } + } + } + TokenType::Function => name.push_str("Function"), + TokenType::Keyword => name.push_str("Keyword"), + TokenType::Literal => name.push_str("Literal"), + TokenType::Numeric(float) => { + name.push_str("Numeric"); + if *float { + name.push_str(" Float"); + } else { + name.push_str(" Integer"); + } + } + TokenType::Punctuation(_) => name.push_str("Punctuation"), + TokenType::Special => name.push_str("Special"), + TokenType::Str(quote) => { + name.push_str("Str "); + name.push(*quote); + } + TokenType::Type => name.push_str("Type"), + TokenType::Whitespace(c) => { + name.push_str("Whitespace"); + match c { + ' ' => name.push_str(" Space"), + '\t' => name.push_str(" Tab"), + '\n' => name.push_str(" New Line"), + _ => (), + }; + } + TokenType::Unknown => name.push_str("Unknown"), + }; + write!(f, "{name}") + } } impl From for TokenType { - fn from(c: char) -> Self { - match c { - c if c.is_whitespace() => TokenType::Whitespace(c), - c if QUOTES.contains(&c) => TokenType::Str(c), - c if c.is_numeric() => TokenType::Numeric(false), - c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal, - c if c.is_ascii_punctuation() => TokenType::Punctuation(c), - _ => TokenType::Unknown, - } - } + fn from(c: char) -> Self { + match c { + c if c.is_whitespace() => TokenType::Whitespace(c), + c if QUOTES.contains(&c) => TokenType::Str(c), + c if c.is_numeric() => TokenType::Numeric(false), + c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal, + c if c.is_ascii_punctuation() => TokenType::Punctuation(c), + _ => TokenType::Unknown, + } + } } #[derive(Clone, Debug, PartialEq)] /// Rules for highlighting. pub struct Syntax { - pub language: &'static str, - pub case_sensitive: bool, - pub comment: &'static str, - pub comment_multiline: [&'static str; 2], - pub keywords: BTreeSet<&'static str>, - pub types: BTreeSet<&'static str>, - pub special: BTreeSet<&'static str>, + pub language: &'static str, + pub case_sensitive: bool, + pub comment: &'static str, + pub comment_multiline: [&'static str; 2], + pub keywords: BTreeSet<&'static str>, + pub types: BTreeSet<&'static str>, + pub special: BTreeSet<&'static str>, } impl Default for Syntax { - fn default() -> Self { - Syntax::rust() - } + fn default() -> Self { + Syntax::rust() + } } impl Hash for Syntax { - fn hash(&self, state: &mut H) { - self.language.hash(state); - } + fn hash(&self, state: &mut H) { + self.language.hash(state); + } } impl Syntax { - pub fn new(language: &'static str) -> Self { - Syntax { - language, - ..Default::default() - } - } - pub fn with_case_sensitive(self, case_sensitive: bool) -> Self { - Syntax { - case_sensitive, - ..self - } - } - pub fn with_comment(self, comment: &'static str) -> Self { - Syntax { comment, ..self } - } - pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self { - Syntax { - comment_multiline, - ..self - } - } - pub fn with_keywords>>(self, keywords: T) -> Self { - Syntax { - keywords: keywords.into(), - ..self - } - } - pub fn with_types>>(self, types: T) -> Self { - Syntax { - types: types.into(), - ..self - } - } - pub fn with_special>>(self, special: T) -> Self { - Syntax { - special: special.into(), - ..self - } - } + pub fn new(language: &'static str) -> Self { + Syntax { + language, + ..Default::default() + } + } + pub fn with_case_sensitive(self, case_sensitive: bool) -> Self { + Syntax { + case_sensitive, + ..self + } + } + pub fn with_comment(self, comment: &'static str) -> Self { + Syntax { comment, ..self } + } + pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self { + Syntax { + comment_multiline, + ..self + } + } + pub fn with_keywords>>(self, keywords: T) -> Self { + Syntax { + keywords: keywords.into(), + ..self + } + } + pub fn with_types>>(self, types: T) -> Self { + Syntax { + types: types.into(), + ..self + } + } + pub fn with_special>>(self, special: T) -> Self { + Syntax { + special: special.into(), + ..self + } + } - pub fn language(&self) -> &str { - self.language - } - pub fn comment(&self) -> &str { - self.comment - } - pub fn is_keyword(&self, word: &str) -> bool { - if self.case_sensitive { - self.keywords.contains(&word) - } else { - self.keywords.contains(word.to_ascii_uppercase().as_str()) - } - } - pub fn is_type(&self, word: &str) -> bool { - if self.case_sensitive { - self.types.contains(&word) - } else { - self.types.contains(word.to_ascii_uppercase().as_str()) - } - } - pub fn is_special(&self, word: &str) -> bool { - if self.case_sensitive { - self.special.contains(&word) - } else { - self.special.contains(word.to_ascii_uppercase().as_str()) - } - } + pub fn language(&self) -> &str { + self.language + } + pub fn comment(&self) -> &str { + self.comment + } + pub fn is_keyword(&self, word: &str) -> bool { + if self.case_sensitive { + self.keywords.contains(&word) + } else { + self.keywords.contains(word.to_ascii_uppercase().as_str()) + } + } + pub fn is_type(&self, word: &str) -> bool { + if self.case_sensitive { + self.types.contains(&word) + } else { + self.types.contains(word.to_ascii_uppercase().as_str()) + } + } + pub fn is_special(&self, word: &str) -> bool { + if self.case_sensitive { + self.special.contains(&word) + } else { + self.special.contains(word.to_ascii_uppercase().as_str()) + } + } } impl Syntax { - pub fn simple(comment: &'static str) -> Self { - Syntax { - language: "", - case_sensitive: false, - comment, - comment_multiline: [comment; 2], - keywords: BTreeSet::new(), - types: BTreeSet::new(), - special: BTreeSet::new(), - } - } + pub fn simple(comment: &'static str) -> Self { + Syntax { + language: "", + case_sensitive: false, + comment, + comment_multiline: [comment; 2], + keywords: BTreeSet::new(), + types: BTreeSet::new(), + special: BTreeSet::new(), + } + } } diff --git a/src/main.rs b/src/main.rs index 48cd9b4..8f834f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use eframe::egui; use egui::{ - FontFamily, FontId, - TextStyle::{Body, Button, Heading, Monospace, Small}, + FontFamily, FontId, + TextStyle::{Body, Button, Heading, Monospace, Small}, }; use homedir::get_my_home; -use std::{ops::Range, path::PathBuf, sync::Arc, thread, time}; use std::env; use std::time::Duration; +use std::{ops::Range, path::PathBuf, sync::Arc, thread, time}; mod core; mod editor; @@ -25,7 +25,13 @@ const TERMINAL_HEIGHT: f32 = 200.0; const TERMINAL_RANGE: Range = 100.0..600.0; const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99); const TIME_LABELS: [&str; 7] = [ - "input", "settings", "tree", "terminal+tray", "tabs", "content", "windows", + "input", + "settings", + "tree", + "terminal+tray", + "tabs", + "content", + "windows", ]; const ZOOM_FACTOR: f32 = 1.1; const MAX_FPS: f32 = 30.0; @@ -34,314 +40,317 @@ const MAX_PROJECT_COLUMNS: usize = 8; const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2; 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 { - viewport: egui::ViewportBuilder::default() - .with_inner_size([1200.0, 800.0]) - .with_icon(Arc::new(icon_data)), - ..Default::default() - }; + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([1200.0, 800.0]) + .with_icon(Arc::new(icon_data)), + ..Default::default() + }; - // Attempt to load previous state - let app_state: core::AppState = if save_path().exists() { - match core::load_state(save_path().as_path()) { - Ok(app_state) => app_state, - Err(_) => core::AppState::default(), - } - } else { - core::AppState::default() - }; - - let args: Vec = env::args().collect(); - let file_to_open = if args.len() > 1 { - println!("Opening file: {}", args[1].clone()); - let mut path = env::current_dir().unwrap_or_default(); - path.push(args[1].clone()); - Some(path) - } else { - None - }; + // Attempt to load previous state + let app_state: core::AppState = if save_path().exists() { + match core::load_state(save_path().as_path()) { + Ok(app_state) => app_state, + Err(_) => core::AppState::default(), + } + } else { + core::AppState::default() + }; - eframe::run_native( - &format!("Calcifer{}", TITLE), - options, - Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))), - ) + let args: Vec = env::args().collect(); + let file_to_open = if args.len() > 1 { + println!("Opening file: {}", args[1].clone()); + let mut path = env::current_dir().unwrap_or_default(); + path.push(args[1].clone()); + Some(path) + } else { + None + }; + + //panels::send_command("export RUSTFLAGS=--cfg=web_sys_unstable_apis".to_string()); + + eframe::run_native( + &format!("Calcifer{}", TITLE), + options, + Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))), + ) } struct Calcifer { - focused: bool, - got_focus: bool, - - selected_tab: usize, - tabs: Vec, - tab_rect: egui::Rect, - mouse_holder: panels::MouseHolder, + focused: bool, + got_focus: bool, - command: String, - command_history: Vec, - running_command: bool, + selected_tab: usize, + tabs: Vec, + tab_rect: egui::Rect, + mouse_holder: panels::MouseHolder, - theme: editor::ColorTheme, - font_size: f32, - zoom: f32, + command: String, + command_history: Vec, + running_command: bool, - project_content: panels::Project, + theme: editor::ColorTheme, + font_size: f32, + zoom: f32, - home: PathBuf, - tree_dir_opened: Vec, - file_tree: Option, - n_file_displayed: usize, + project_content: panels::Project, - tree_visible: bool, - profiler_visible: bool, - terminal_visible: bool, + home: PathBuf, + tree_dir_opened: Vec, + file_tree: Option, + n_file_displayed: usize, - close_tab_confirm: sub_windows::ConfirmWindow, - tab_to_close: usize, - refresh_confirm: sub_windows::ConfirmWindow, - exit_confirm: sub_windows::ConfirmWindow, + tree_visible: bool, + profiler_visible: bool, + terminal_visible: bool, - search_menu: sub_windows::SearchWindow, - settings_menu: sub_windows::SettingsWindow, - shortcuts_menu: sub_windows::ShortcutsWindow, + close_tab_confirm: sub_windows::ConfirmWindow, + tab_to_close: usize, + refresh_confirm: sub_windows::ConfirmWindow, + exit_confirm: sub_windows::ConfirmWindow, - time_watch: Vec, - next_frame: time::Instant, + search_menu: sub_windows::SearchWindow, + settings_menu: sub_windows::SettingsWindow, + shortcuts_menu: sub_windows::ShortcutsWindow, + + time_watch: Vec, + next_frame: time::Instant, } impl Default for Calcifer { - fn default() -> Self { - Self { - focused: true, - got_focus: false, - - selected_tab: 0, - tabs: vec![panels::Tab::default()], - tab_rect: egui::Rect::EVERYTHING, - mouse_holder: panels::MouseHolder::None, + fn default() -> Self { + Self { + focused: true, + got_focus: false, - command: String::new(), - command_history: Vec::new(), - running_command: false, + selected_tab: 0, + tabs: vec![panels::Tab::default()], + tab_rect: egui::Rect::EVERYTHING, + mouse_holder: panels::MouseHolder::None, - theme: editor::themes::DEFAULT_THEMES[0], - font_size: 14.0, - zoom: 1.0, + command: String::new(), + command_history: Vec::new(), + running_command: false, - project_content: panels::Project::new(), + theme: editor::themes::DEFAULT_THEMES[0], + font_size: 14.0, + zoom: 1.0, - home: get_my_home().unwrap().unwrap(), - tree_dir_opened: vec![], - file_tree: None, - n_file_displayed: 0, + project_content: panels::Project::new(), - tree_visible: false, - profiler_visible: false, - terminal_visible: false, + home: get_my_home().unwrap().unwrap(), + tree_dir_opened: vec![], + file_tree: None, + n_file_displayed: 0, - close_tab_confirm: sub_windows::ConfirmWindow::new( - "You have some unsaved changes, Do you still want to close this document ?", - "Confirm Close", - ), - tab_to_close: 0, - refresh_confirm: sub_windows::ConfirmWindow::new( - "You have some unsaved changes, Do you still want to refresh this document ?", - "Confirm Refresh", - ), - exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"), + tree_visible: false, + profiler_visible: false, + terminal_visible: false, - search_menu: sub_windows::SearchWindow::default(), - settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]), - shortcuts_menu: sub_windows::ShortcutsWindow::new(), + close_tab_confirm: sub_windows::ConfirmWindow::new( + "You have some unsaved changes, Do you still want to close this document ?", + "Confirm Close", + ), + tab_to_close: 0, + refresh_confirm: sub_windows::ConfirmWindow::new( + "You have some unsaved changes, Do you still want to refresh this document ?", + "Confirm Refresh", + ), + exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"), - time_watch: vec![0.0; TIME_LABELS.len()], - next_frame: time::Instant::now(), - } - } + search_menu: sub_windows::SearchWindow::default(), + settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]), + shortcuts_menu: sub_windows::ShortcutsWindow::new(), + + time_watch: vec![0.0; TIME_LABELS.len()], + next_frame: time::Instant::now(), + } + } } impl eframe::App for Calcifer { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - thread::sleep(time::Duration::from_secs_f32( - ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), - )); - self.next_frame = time::Instant::now(); + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + thread::sleep(time::Duration::from_secs_f32( + ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), + )); + self.next_frame = time::Instant::now(); - let mut watch = time::Instant::now(); + let mut watch = time::Instant::now(); - let mut style = (*ctx.style()).clone(); - style.text_styles = [ - ( - Heading, - FontId::new(self.font_size * 1.6, FontFamily::Proportional), - ), - (Body, FontId::new(self.font_size, FontFamily::Proportional)), - ( - Monospace, - FontId::new(self.font_size, FontFamily::Monospace), - ), - ( - Button, - FontId::new(self.font_size, FontFamily::Proportional), - ), - (Small, FontId::new(self.font_size, FontFamily::Proportional)), - ] - .into(); - ctx.set_style(style); + let mut style = (*ctx.style()).clone(); + style.text_styles = [ + ( + Heading, + FontId::new(self.font_size * 1.6, FontFamily::Proportional), + ), + (Body, FontId::new(self.font_size, FontFamily::Proportional)), + ( + Monospace, + FontId::new(self.font_size, FontFamily::Monospace), + ), + ( + Button, + FontId::new(self.font_size, FontFamily::Proportional), + ), + (Small, FontId::new(self.font_size, FontFamily::Proportional)), + ] + .into(); + ctx.set_style(style); - if ctx.zoom_factor() != self.zoom { - ctx.set_zoom_factor(self.zoom); - } + if ctx.zoom_factor() != self.zoom { + ctx.set_zoom_factor(self.zoom); + } - if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) - && !self.refresh_confirm.visible - { - if self.tabs[self.selected_tab].saved { - self.tabs[self.selected_tab].refresh(); - } else { - self.refresh_confirm.ask(); - } - } + if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) + && !self.refresh_confirm.visible + { + if self.tabs[self.selected_tab].saved { + self.tabs[self.selected_tab].refresh(); + } else { + self.refresh_confirm.ask(); + } + } - if ctx.input(|i| i.key_pressed(egui::Key::Enter)) && ctx.memory(|m| m.focus() == None) - && self.tabs[self.selected_tab].language == PROJECT_EXTENSION - { - self.project_content.item_window.visible = true; - } - - if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { - self.handle_save_file(self.save_tab()); - } + if ctx.input(|i| i.key_pressed(egui::Key::Enter)) + && ctx.memory(|m| m.focus() == None) + && self.tabs[self.selected_tab].language == PROJECT_EXTENSION + { + self.project_content.item_window.visible = true; + } - if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { - self.handle_save_file(self.save_tab_as()); - } + if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { + self.handle_save_file(self.save_tab()); + } - if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { - self.move_through_tabs(false); - } + if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { + self.handle_save_file(self.save_tab_as()); + } - if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { - self.move_through_tabs(true); - } + if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { + self.move_through_tabs(false); + } - if ctx.input(|i| i.zoom_delta() > 1.0) { - self.zoom = (self.zoom*ZOOM_FACTOR).min(10.0); - } + if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { + self.move_through_tabs(true); + } - if ctx.input(|i| i.zoom_delta() < 1.0) { - self.zoom = (self.zoom/ZOOM_FACTOR).max(0.1); - } + if ctx.input(|i| i.zoom_delta() > 1.0) { + self.zoom = (self.zoom * ZOOM_FACTOR).min(10.0); + } - if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { - self.search_menu.visible = !self.search_menu.visible; - self.search_menu.initialized = !self.search_menu.visible; - } - - self.got_focus = false; - if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) { - self.focused = false; - } else { - if !self.focused { - self.got_focus = true; - } - self.focused = true; - } + if ctx.input(|i| i.zoom_delta() < 1.0) { + self.zoom = (self.zoom / ZOOM_FACTOR).max(0.1); + } - if ctx.input(|i| i.viewport().close_requested()) { - let mut unsaved_tabs: Vec = vec![]; - for (index, tab) in self.tabs.iter().enumerate() { - if !tab.saved { - unsaved_tabs.push(index); - } - } - if !unsaved_tabs.is_empty() { - let mut unsaved_tabs_names: String = "".to_string(); - for index in unsaved_tabs.iter() { - unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); - } - egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); - self.exit_confirm.prompt = format!( - "You have some unsaved changes :\n{}\nDo you still want to exit ?", - unsaved_tabs_names - ); - self.exit_confirm.ask(); - } - } - - if self.tabs.len() == 0 { - egui::Context::send_viewport_cmd( - ctx, - egui::ViewportCommand::Close, - ); - return - } + if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { + self.search_menu.visible = !self.search_menu.visible; + self.search_menu.initialized = !self.search_menu.visible; + } - self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.got_focus = false; + if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) { + self.focused = false; + } else { + if !self.focused { + self.got_focus = true; + } + self.focused = true; + } - self.draw_settings(ctx); + if ctx.input(|i| i.viewport().close_requested()) { + let mut unsaved_tabs: Vec = vec![]; + for (index, tab) in self.tabs.iter().enumerate() { + if !tab.saved { + unsaved_tabs.push(index); + } + } + if !unsaved_tabs.is_empty() { + let mut unsaved_tabs_names: String = "".to_string(); + for index in unsaved_tabs.iter() { + unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); + } + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); + self.exit_confirm.prompt = format!( + "You have some unsaved changes :\n{}\nDo you still want to exit ?", + unsaved_tabs_names + ); + self.exit_confirm.ask(); + } + } - self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + if self.tabs.len() == 0 { + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); + return; + } - self.draw_tree_panel(ctx); + self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.draw_settings(ctx); - self.draw_bottom_tray(ctx); - self.draw_terminal_panel(ctx); + self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.draw_tree_panel(ctx); - self.draw_tab_panel(ctx); + self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.draw_bottom_tray(ctx); + self.draw_terminal_panel(ctx); - self.draw_content_panel(ctx); + self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.draw_tab_panel(ctx); - self.draw_windows(ctx); - self.draw_mouse_drag(ctx); + self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0; + self.draw_content_panel(ctx); - if self.running_command { - egui::Context::request_repaint_after(ctx, Duration::from_secs_f32(RUNNING_COMMAND_REFRESH_DELAY)); - } - } + self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { - self.save_state(); - } + self.draw_windows(ctx); + 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), + ); + } + } + + fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { + self.save_state(); + } } //save path fn save_path() -> PathBuf { - if TITLE.is_empty() { - get_my_home() - .unwrap() - .unwrap() - .as_path() - .join(".config") - .join("calcifer") - .join("save.json") - .to_path_buf() - } else { - get_my_home() - .unwrap() - .unwrap() - .as_path() - .join(".config") - .join("calcifer") - .join("debug_save.json") - .to_path_buf() - } + if TITLE.is_empty() { + get_my_home() + .unwrap() + .unwrap() + .as_path() + .join(".config") + .join("calcifer") + .join("save.json") + .to_path_buf() + } else { + get_my_home() + .unwrap() + .unwrap() + .as_path() + .join(".config") + .join("calcifer") + .join("debug_save.json") + .to_path_buf() + } } diff --git a/src/panels/file_tree.rs b/src/panels/file_tree.rs index 4a9a7c1..523e9a9 100644 --- a/src/panels/file_tree.rs +++ b/src/panels/file_tree.rs @@ -1,183 +1,186 @@ -use std::{ - cmp::Ordering, - ffi::OsStr, - fs, io, - 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 std::hash::Hasher; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; +use std::{ + cmp::Ordering, + ffi::OsStr, + fs, io, + path::{Path, PathBuf}, +}; //use crate::ALLOWED_FILE_EXTENSIONS; #[derive(Clone)] pub struct FileEntry { - pub name: String, - pub path: PathBuf, - pub folder_content: Option>, - pub content_checked: bool, - pub id: String, + pub name: String, + pub path: PathBuf, + pub folder_content: Option>, + pub content_checked: bool, + pub id: String, } impl FileEntry { - pub fn new_entry(name: String, path: PathBuf) -> Self { - Self { - name, - path: path.clone(), - folder_content: None, - content_checked: true, - id: Self::generate_unique_id(path), - } - } - pub fn end_of_branch(name: String, path: PathBuf) -> Self { - Self { - name, - path: path.clone(), - folder_content: Some(vec![]), - content_checked: false, - id: Self::generate_unique_id(path), - } - } - - fn generate_unique_id(path: PathBuf) -> String { - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + pub fn new_entry(name: String, path: PathBuf) -> Self { + Self { + name, + path: path.clone(), + folder_content: None, + content_checked: true, + id: Self::generate_unique_id(path), + } + } + pub fn end_of_branch(name: String, path: PathBuf) -> Self { + Self { + name, + path: path.clone(), + folder_content: Some(vec![]), + content_checked: false, + id: Self::generate_unique_id(path), + } + } + + fn generate_unique_id(path: PathBuf) -> String { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); let mut hasher = DefaultHasher::new(); now.hash(&mut hasher); let hash = hasher.finish(); - - format!("{}#{}", path.display(), hash) + + format!("{}#{}", path.display(), hash) } } pub fn update_file_tree(file: FileEntry, opened_dirs: Vec) -> FileEntry { - if !opened_dirs.contains(&get_file_path_id(&file.path)) { - return file; - } - - if !file.content_checked { - let folder = generate_folder_entry(&file.path); - return update_file_tree(folder, opened_dirs); - } - - if file.folder_content.is_none() { - return file; - } - - let folder_content = file.folder_content.expect("should have checked if none"); - - let updated_content: Vec = folder_content - .iter() - .map(|entry| update_file_tree(entry.clone(), opened_dirs.clone())) - .collect(); - FileEntry { - name: file.name, - path: file.path.clone(), - folder_content: Some(updated_content), - content_checked: true, - id: FileEntry::generate_unique_id(file.path), - } + if !opened_dirs.contains(&get_file_path_id(&file.path)) { + return file; + } + + if !file.content_checked { + let folder = generate_folder_entry(&file.path); + return update_file_tree(folder, opened_dirs); + } + + if file.folder_content.is_none() { + return file; + } + + let folder_content = file.folder_content.expect("should have checked if none"); + + let updated_content: Vec = folder_content + .iter() + .map(|entry| update_file_tree(entry.clone(), opened_dirs.clone())) + .collect(); + FileEntry { + name: file.name, + path: file.path.clone(), + folder_content: Some(updated_content), + content_checked: true, + id: FileEntry::generate_unique_id(file.path), + } } 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 { - if let Some(file_name) = path.file_name() { - let name = file_name.to_string_lossy().into_owned(); + if let Some(file_name) = path.file_name() { + let name = file_name.to_string_lossy().into_owned(); - match fs::read_dir(path) { - Err(err) => FileEntry::new_entry( - format!("Error reading directory: {}", err), - path.to_path_buf(), - ), - Ok(entries) => { - let mut paths: Vec> = entries - .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) - .collect(); + match fs::read_dir(path) { + Err(err) => FileEntry::new_entry( + format!("Error reading directory: {}", err), + path.to_path_buf(), + ), + Ok(entries) => { + let mut paths: Vec> = entries + .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) + .collect(); - paths.sort_by(|a, b| match (a, b) { - (Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b), - (Err(_), Ok(_)) => std::cmp::Ordering::Greater, - (Ok(_), Err(_)) => std::cmp::Ordering::Less, - (Err(_), Err(_)) => std::cmp::Ordering::Equal, - }); + paths.sort_by(|a, b| match (a, b) { + (Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b), + (Err(_), Ok(_)) => std::cmp::Ordering::Greater, + (Ok(_), Err(_)) => std::cmp::Ordering::Less, + (Err(_), Err(_)) => std::cmp::Ordering::Equal, + }); - let mut folder_content = Vec::new(); + let mut folder_content = Vec::new(); - for result in paths { - match result { - Ok(entry) => { - if let Some(file) = generate_entry(&entry.path()) { - folder_content.push(file); - } - } - Err(err) => { - folder_content.push(FileEntry::new_entry( - format!("Error reading entry: {}", err), - path.to_path_buf(), - )); - } - } - } + for result in paths { + match result { + Ok(entry) => { + if let Some(file) = generate_entry(&entry.path()) { + folder_content.push(file); + } + } + Err(err) => { + folder_content.push(FileEntry::new_entry( + format!("Error reading entry: {}", err), + path.to_path_buf(), + )); + } + } + } - FileEntry { - name, - path: path.to_path_buf(), - folder_content: Some(folder_content), - content_checked: true, - id: FileEntry::generate_unique_id(path.to_path_buf()), - } - } - } - } else { - FileEntry::new_entry( - "Error reading directory name".to_string(), - path.to_path_buf(), - ) - } + FileEntry { + name, + path: path.to_path_buf(), + folder_content: Some(folder_content), + content_checked: true, + id: FileEntry::generate_unique_id(path.to_path_buf()), + } + } + } + } else { + FileEntry::new_entry( + "Error reading directory name".to_string(), + path.to_path_buf(), + ) + } } fn generate_entry(path: &Path) -> Option { - if let Some(file_name) = path.file_name() { - if file_name.to_string_lossy().starts_with('.') { - return None; - } -// let extension = path.extension().and_then(|ext| ext.to_str()); -// if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) { -// return None; -// } - } else { - return None; - } + if let Some(file_name) = path.file_name() { + if file_name.to_string_lossy().starts_with('.') { + return None; + } + // let extension = path.extension().and_then(|ext| ext.to_str()); + // if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) { + // return None; + // } + } else { + return None; + } - let name = path - .file_name() - .unwrap_or_else(|| OsStr::new("")) - .to_string_lossy() - .into_owned(); + let name = path + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_string_lossy() + .into_owned(); - if !path.is_dir() { - return Some(FileEntry::new_entry(name, path.to_path_buf())); - } - Some(FileEntry::end_of_branch(name, path.to_path_buf())) + if !path.is_dir() { + return Some(FileEntry::new_entry(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 { - let a_is_dir = a.path().is_dir(); - let b_is_dir = b.path().is_dir(); + let a_is_dir = a.path().is_dir(); + let b_is_dir = b.path().is_dir(); - // Directories come first, then files - if a_is_dir && !b_is_dir { - Ordering::Less - } else if !a_is_dir && b_is_dir { - Ordering::Greater - } else { - // Both are either directories or files, sort alphabetically - a.path().cmp(&b.path()) - } + // Directories come first, then files + if a_is_dir && !b_is_dir { + Ordering::Less + } else if !a_is_dir && b_is_dir { + Ordering::Greater + } else { + // Both are either directories or files, sort alphabetically + a.path().cmp(&b.path()) + } } diff --git a/src/panels/project_mode.rs b/src/panels/project_mode.rs index 0c1cfeb..d3d3a1e 100644 --- a/src/panels/project_mode.rs +++ b/src/panels/project_mode.rs @@ -1,8 +1,8 @@ use eframe::egui; use serde::{Deserialize, Serialize}; use std::{ - cmp::max, - sync::atomic::{AtomicUsize, Ordering}, + cmp::max, + sync::atomic::{AtomicUsize, Ordering}, }; use crate::core::hex_str_to_color; @@ -12,247 +12,253 @@ use crate::MAX_PROJECT_COLUMNS; #[derive(Serialize, Deserialize)] pub struct ProjectSave { - pub categories: Vec, + pub categories: Vec, } impl ProjectSave { - pub fn from_project(project: &Project) -> Self { - Self { - categories: project.categories.clone(), - } - } + pub fn from_project(project: &Project) -> Self { + Self { + categories: project.categories.clone(), + } + } } pub struct Project { - pub categories: Vec, - pub selected_item: Location, - pub item_window: sub_windows::ProjectItemWindow, - was_moving: bool, + pub categories: Vec, + pub selected_item: Location, + pub item_window: sub_windows::ProjectItemWindow, + was_moving: bool, } impl Project { - pub fn new() -> Self { - Self { - categories: vec![Category::create()], - selected_item: Location::zero(), - was_moving: false, - item_window: sub_windows::ProjectItemWindow::new(), - } - } + pub fn new() -> Self { + Self { + categories: vec![Category::create()], + selected_item: Location::zero(), + was_moving: false, + item_window: sub_windows::ProjectItemWindow::new(), + } + } - pub fn update_from_code(&mut self, json: String) { - match serde_json::from_str::(&json) { - Ok(project_save) => self.categories = project_save.categories, - Err(_err) => self.categories = vec![Category::create()], - } - } + pub fn update_from_code(&mut self, json: String) { + match serde_json::from_str::(&json) { + Ok(project_save) => self.categories = project_save.categories, + Err(_err) => self.categories = vec![Category::create()], + } + } - pub fn save_to_code(&self) -> Result { - Ok(serde_json::to_string(&ProjectSave::from_project(self))?) - } + pub fn save_to_code(&self) -> Result { + Ok(serde_json::to_string(&ProjectSave::from_project(self))?) + } - fn add_category(&mut self) { - let last = self.categories.len() - 1; - self.categories[last].initialize(); - self.categories.push(Category::create()); - } + fn add_category(&mut self) { + let last = self.categories.len() - 1; + self.categories[last].initialize(); + self.categories.push(Category::create()); + } - fn delete_category(&mut self, index: usize) { - self.categories.remove(index); - let last = self.categories.len() - 1; - if self.categories[last].name != "+" { - self.categories.push(Category::create()); - } - } + fn delete_category(&mut self, index: usize) { + self.categories.remove(index); + let last = self.categories.len() - 1; + if self.categories[last].name != "+" { + self.categories.push(Category::create()); + } + } } #[derive(Clone, Serialize, Deserialize)] pub struct Category { - name: String, - pub content: Vec, + name: String, + pub content: Vec, } impl Category { - fn create() -> Self { - Self { - name: "+".into(), - content: vec![], - } - } + fn create() -> Self { + Self { + name: "+".into(), + content: vec![], + } + } - fn initialize(&mut self) { - self.name = "untitled".into(); - } + fn initialize(&mut self) { + self.name = "untitled".into(); + } } #[derive(Clone, Hash, Serialize, Deserialize)] pub struct Item { - pub name: String, - pub description: String, - id: usize, + pub name: String, + pub description: String, + id: usize, } impl Item { - fn new(name: &str) -> Self { - Self { - name: name.to_string(), - description: "// Hello there".to_string(), - id: get_id(), - } - } + fn new(name: &str) -> Self { + Self { + name: name.to_string(), + description: "// Hello there".to_string(), + id: get_id(), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Location { - pub category: usize, - pub row: usize, + pub category: usize, + pub row: usize, } impl Location { - fn zero() -> Self { - Self { - category: 0, - row: 0, - } - } + fn zero() -> Self { + Self { + category: 0, + row: 0, + } + } } fn get_id() -> usize { - static COUNTER: AtomicUsize = AtomicUsize::new(1); - COUNTER.fetch_add(1, Ordering::Relaxed) + static COUNTER: AtomicUsize = AtomicUsize::new(1); + COUNTER.fetch_add(1, Ordering::Relaxed) } pub fn draw_project(ui: &mut egui::Ui, theme: ColorTheme, project: &mut Project) { - ui.columns(max(MAX_PROJECT_COLUMNS, project.categories.len() + 1) , |uis| { - for (category_index, category) in project.categories.clone().into_iter().enumerate() { - let ui = &mut uis[category_index]; + ui.columns( + max(MAX_PROJECT_COLUMNS, project.categories.len() + 1), + |uis| { + for (category_index, category) in project.categories.clone().into_iter().enumerate() { + let ui = &mut uis[category_index]; - if category.name == "+" { - if ui.add(egui::Button::new("+")).clicked() { - project.add_category(); - } - continue; - } else { - let response = ui.add( - egui::TextEdit::singleline(&mut project.categories[category_index].name) - .desired_width(f32::INFINITY), - ); - if response.lost_focus() && project.categories[category_index].name.is_empty() { - project.delete_category(category_index); - } - } + if category.name == "+" { + if ui.add(egui::Button::new("+")).clicked() { + project.add_category(); + } + continue; + } else { + let response = ui.add( + egui::TextEdit::singleline(&mut project.categories[category_index].name) + .desired_width(f32::INFINITY), + ); + if response.lost_focus() && project.categories[category_index].name.is_empty() { + project.delete_category(category_index); + } + } - ui.separator(); + ui.separator(); - for (item_index, item) in category.content.iter().enumerate() { - if project.selected_item - == (Location { - category: category_index, - row: item_index, - }) - { - ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.bg)); - ui.add( - egui::Button::new(item.name.clone()) - .fill(hex_str_to_color(theme.functions)), - ); - } else { - ui.style_mut().visuals.override_text_color = - Some(hex_str_to_color(theme.literals)); - if ui - .add(egui::Button::new(item.name.clone()).fill(hex_str_to_color(theme.bg))) - .clicked() - { - 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] - .content - .push(Item::new("item")); - } - } - }); + for (item_index, item) in category.content.iter().enumerate() { + if project.selected_item + == (Location { + category: category_index, + row: item_index, + }) + { + ui.style_mut().visuals.override_text_color = + Some(hex_str_to_color(theme.bg)); + ui.add( + egui::Button::new(item.name.clone()) + .fill(hex_str_to_color(theme.functions)), + ); + } else { + ui.style_mut().visuals.override_text_color = + Some(hex_str_to_color(theme.literals)); + if ui + .add( + egui::Button::new(item.name.clone()) + .fill(hex_str_to_color(theme.bg)), + ) + .clicked() + { + 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] + .content + .push(Item::new("item")); + } + } + }, + ); - let mut moved = false; - let category = project.selected_item.category; - let row = project.selected_item.row; + let mut moved = false; + let category = project.selected_item.category; + let row = project.selected_item.row; - if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift) - && project.selected_item.category > 0 - { - moved = true; - if !project.was_moving { - let temp = project.categories[category].content[row].clone(); - project.categories[category - 1].content.push(temp); - project.categories[category].content.remove(row); - project.selected_item.category -= 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) - && project.selected_item.category < project.categories.len() - 2 - { - moved = true; - if !project.was_moving { - let temp = project.categories[category].content[row].clone(); - project.categories[category + 1].content.push(temp); - project.categories[category].content.remove(row); - project.selected_item.category += 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) - && project.selected_item.row > 0 - { - moved = true; - if !project.was_moving { - let temp = project.categories[category].content[row].clone(); - project.categories[category].content[row] = - project.categories[category].content[row - 1].clone(); - project.categories[category].content[row - 1] = temp.clone(); - project.selected_item.row -= 1; - } - } else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) { - moved = true; - if !project.was_moving { - let temp = project.categories[category].content[row].clone(); - project.categories[category].content[row] = - project.categories[category].content[row + 1].clone(); - project.categories[category].content[row + 1] = temp.clone(); - project.selected_item.row += 1; - } - } else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft)) - && project.selected_item.category > 0 - { - moved = true; - if !project.was_moving && project.categories[category - 1].content.len() > 0 { - project.selected_item.category -= 1; - } - } else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight)) - && project.selected_item.category < project.categories.len() - 2 - { - moved = true; - if !project.was_moving && project.categories[category + 1].content.len() > 0 { - project.selected_item.category += 1; - } - } else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 { - moved = true; - 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; - } - } + if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift) + && project.selected_item.category > 0 + { + moved = true; + if !project.was_moving { + let temp = project.categories[category].content[row].clone(); + project.categories[category - 1].content.push(temp); + project.categories[category].content.remove(row); + project.selected_item.category -= 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) + && project.selected_item.category < project.categories.len() - 2 + { + moved = true; + if !project.was_moving { + let temp = project.categories[category].content[row].clone(); + project.categories[category + 1].content.push(temp); + project.categories[category].content.remove(row); + project.selected_item.category += 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) + && project.selected_item.row > 0 + { + moved = true; + if !project.was_moving { + let temp = project.categories[category].content[row].clone(); + project.categories[category].content[row] = + project.categories[category].content[row - 1].clone(); + project.categories[category].content[row - 1] = temp.clone(); + project.selected_item.row -= 1; + } + } else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) { + moved = true; + if !project.was_moving { + let temp = project.categories[category].content[row].clone(); + project.categories[category].content[row] = + project.categories[category].content[row + 1].clone(); + project.categories[category].content[row + 1] = temp.clone(); + project.selected_item.row += 1; + } + } else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft)) + && project.selected_item.category > 0 + { + moved = true; + if !project.was_moving && project.categories[category - 1].content.len() > 0 { + project.selected_item.category -= 1; + } + } else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight)) + && project.selected_item.category < project.categories.len() - 2 + { + moved = true; + if !project.was_moving && project.categories[category + 1].content.len() > 0 { + project.selected_item.category += 1; + } + } else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 { + moved = true; + 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; } diff --git a/src/panels/tabs.rs b/src/panels/tabs.rs index f5aa347..dcac27b 100644 --- a/src/panels/tabs.rs +++ b/src/panels/tabs.rs @@ -3,81 +3,81 @@ use std::{fs::read_to_string, path::Path, path::PathBuf}; #[derive(Clone, PartialEq)] pub struct Tab { - pub path: PathBuf, - pub code: String, - pub language: String, - pub saved: bool, - pub scroll_offset: f32, - pub last_cursor: Option, + pub path: PathBuf, + pub code: String, + pub language: String, + pub saved: bool, + pub scroll_offset: f32, + pub last_cursor: Option, } impl Default for Tab { - fn default() -> Self { - Self { - path: "untitled".into(), - code: "// Hello there, Master".into(), - language: "rs".into(), - saved: false, - scroll_offset: 0.0, - last_cursor: None, - } - } + fn default() -> Self { + Self { + path: "untitled".into(), + code: "// Hello there, Master".into(), + language: "rs".into(), + saved: false, + scroll_offset: 0.0, + last_cursor: None, + } + } } impl Tab { - pub fn new(path: PathBuf) -> Self { - let text = read_file_contents(&path).replace(&" ".repeat(4), "\t"); - let file_path = format_file_path(&path, &text); - let extension = file_path - .extension() - .and_then(|ext| ext.to_str()) - .unwrap_or_default(); + pub fn new(path: PathBuf) -> Self { + let text = read_file_contents(&path).replace(&" ".repeat(4), "\t"); + let file_path = format_file_path(&path, &text); + let extension = file_path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or_default(); - Self { - path: file_path.clone(), - code: text, - language: extension.into(), - saved: true, - scroll_offset: 0.0, - last_cursor: None, - } - } + Self { + path: file_path.clone(), + code: text, + language: extension.into(), + saved: true, + scroll_offset: 0.0, + last_cursor: None, + } + } - pub fn get_name(&self) -> String { - self.path - .file_name() - .map_or("untitled".to_string(), |name| { - name.to_string_lossy().to_string() - }) - } + pub fn get_name(&self) -> String { + self.path + .file_name() + .map_or("untitled".to_string(), |name| { + name.to_string_lossy().to_string() + }) + } - pub fn refresh(&mut self) { - let text = read_file_contents(&self.path).replace(&" ".repeat(4), "\t"); - let file_path = format_file_path(&self.path, &text); + pub fn refresh(&mut self) { + let text = read_file_contents(&self.path).replace(&" ".repeat(4), "\t"); + let file_path = format_file_path(&self.path, &text); - self.code = text; - self.path = file_path; - self.saved = true; - } + self.code = text; + self.path = file_path; + self.saved = true; + } } pub enum MouseHolder { - TabHolder(usize), - None, + TabHolder(usize), + None, } fn read_file_contents(path: &Path) -> String { - let error_type = "reading file"; - read_to_string(path) - .map_err(|err| format!("// Error {}: {}", error_type, err)) - .unwrap_or_else(|err_msg| err_msg) + let error_type = "reading file"; + read_to_string(path) + .map_err(|err| format!("// Error {}: {}", error_type, err)) + .unwrap_or_else(|err_msg| err_msg) } fn format_file_path(path: &Path, contents: &str) -> PathBuf { - let error_type = "reading file"; - if contents.contains(&format!("Error {}", error_type)) { - "untitled".into() - } else { - path.to_path_buf() - } + let error_type = "reading file"; + if contents.contains(&format!("Error {}", error_type)) { + "untitled".into() + } else { + path.to_path_buf() + } } diff --git a/src/sub_windows/project_item.rs b/src/sub_windows/project_item.rs index 89160ab..74a5af8 100644 --- a/src/sub_windows/project_item.rs +++ b/src/sub_windows/project_item.rs @@ -3,46 +3,46 @@ use eframe::egui; use crate::panels; pub struct ProjectItemWindow { - pub visible: bool, + pub visible: bool, } impl ProjectItemWindow { - pub fn new() -> Self { - Self { visible: false } - } + pub fn new() -> Self { + Self { visible: false } + } - pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool { - let mut visible = self.visible; - let maybe_response = egui::Window::new("Item") - .open(&mut visible) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui, item)); - self.visible = self.visible && visible; - - if let Some(response) = maybe_response { - if let Some(delete_option) = response.inner { - return delete_option - } - } - return false - } + pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool { + let mut visible = self.visible; + let maybe_response = egui::Window::new("Item") + .open(&mut visible) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui, item)); + self.visible = self.visible && visible; - fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool { - let mut delete_item = false; - ui.set_min_width(250.0); - ui.set_min_height(250.0); - ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { - if ui.add(egui::Button::new("delete")).clicked() { - delete_item = true; - } - ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY)); - }); - ui.separator(); - ui.add_sized( - ui.available_size(), - egui::TextEdit::multiline(&mut item.description), - ); - return delete_item.clone() - } + if let Some(response) = maybe_response { + if let Some(delete_option) = response.inner { + return delete_option; + } + } + return false; + } + + fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool { + let mut delete_item = false; + ui.set_min_width(250.0); + ui.set_min_height(250.0); + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + if ui.add(egui::Button::new("delete")).clicked() { + delete_item = true; + } + ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY)); + }); + ui.separator(); + ui.add_sized( + ui.available_size(), + egui::TextEdit::multiline(&mut item.description), + ); + return delete_item.clone(); + } } diff --git a/src/sub_windows/shortcuts.rs b/src/sub_windows/shortcuts.rs index 1151924..3d624b4 100644 --- a/src/sub_windows/shortcuts.rs +++ b/src/sub_windows/shortcuts.rs @@ -1,44 +1,44 @@ use eframe::egui; pub struct ShortcutsWindow { - pub visible: bool, + pub visible: bool, } impl ShortcutsWindow { - pub fn new() -> Self { - Self { visible: false } - } + pub fn new() -> Self { + Self { visible: false } + } - pub fn show(&mut self, ctx: &egui::Context) { - let mut visible = self.visible; - egui::Window::new("Shortcuts") - .open(&mut visible) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui)); - self.visible = self.visible && visible; - } + pub fn show(&mut self, ctx: &egui::Context) { + let mut visible = self.visible; + egui::Window::new("Shortcuts") + .open(&mut visible) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui)); + self.visible = self.visible && visible; + } - fn ui(&mut self, ui: &mut egui::Ui) { - ui.set_min_width(250.0); - ui.label("Ctrl+S : save file"); - ui.label("Ctrl+Shift+S : save file as"); - ui.label("Ctrl+R : reload file"); - ui.separator(); - ui.label("Ctrl+F : open search window"); - ui.separator(); - ui.label("Ctrl+T : reload tree"); - ui.separator(); - ui.label("Ctrl+Z : undo"); - ui.label("Ctrl+Y : redo"); - ui.label("Tab on selection : add indent of selection"); - ui.label("Shift+Tab on selection : remove indent of selection"); - ui.label("Ctrl+E : comment selection"); - ui.separator(); - ui.label("Alt+Arrows : move between tabs"); - ui.separator(); - ui.label("Enter (project_mode) : edit item"); - ui.label("Arrows (project_mode) : change selected item"); - ui.label("Shift+Arrows (project_mode) : move selected item"); - } + fn ui(&mut self, ui: &mut egui::Ui) { + ui.set_min_width(250.0); + ui.label("Ctrl+S : save file"); + ui.label("Ctrl+Shift+S : save file as"); + ui.label("Ctrl+R : reload file"); + ui.separator(); + ui.label("Ctrl+F : open search window"); + ui.separator(); + ui.label("Ctrl+T : reload tree"); + ui.separator(); + ui.label("Ctrl+Z : undo"); + ui.label("Ctrl+Y : redo"); + ui.label("Tab on selection : add indent of selection"); + ui.label("Shift+Tab on selection : remove indent of selection"); + ui.label("Ctrl+E : comment selection"); + ui.separator(); + ui.label("Alt+Arrows : move between tabs"); + ui.separator(); + ui.label("Enter (project_mode) : edit item"); + ui.label("Arrows (project_mode) : change selected item"); + ui.label("Shift+Arrows (project_mode) : move selected item"); + } }