project mode structuure

This commit is contained in:
Penwing 2024-02-01 13:12:24 +01:00
parent 3b47ef0ca6
commit 9155e09b48
8 changed files with 699 additions and 496 deletions

64
Cargo.lock generated
View file

@ -591,6 +591,7 @@ version = "1.1.0"
dependencies = [ dependencies = [
"arboard", "arboard",
"eframe", "eframe",
"egui_dnd",
"egui_extras", "egui_extras",
"homedir", "homedir",
"image", "image",
@ -743,6 +744,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "concat-idents"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d"
dependencies = [
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.4.0" version = "2.4.0"
@ -978,11 +989,34 @@ dependencies = [
"log", "log",
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"smithay-clipboard", "smithay-clipboard",
"web-time", "web-time 0.2.4",
"webbrowser", "webbrowser",
"winit", "winit",
] ]
[[package]]
name = "egui_animation"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9ccebd83685921eceb3a89df08ce02bbee7c9f7b79472f1a45d12332188a174"
dependencies = [
"egui",
"hello_egui_utils",
"simple-easing",
]
[[package]]
name = "egui_dnd"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ff7872d7974d88b5aa5c55d76d4e43b1628005974cb45758402224526233b2"
dependencies = [
"egui",
"egui_animation",
"simple-easing",
"web-time 1.0.0",
]
[[package]] [[package]]
name = "egui_extras" name = "egui_extras"
version = "0.25.0" version = "0.25.0"
@ -1630,6 +1664,16 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hello_egui_utils"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7831675138f982346346b0473a4b4e4fe5794a847ef546b0d794a2e4d8c1a364"
dependencies = [
"concat-idents",
"egui",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.3" version = "0.3.3"
@ -2611,6 +2655,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simple-easing"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832ddd7df0d98d6fd93b973c330b7c8e0742d5cb8f1afc7dea89dba4d2531aa1"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -3217,6 +3267,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee269d72cc29bf77a2c4bc689cc750fb39f5cbd493d2205bbb3f5c7779cf7b0"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "webbrowser" name = "webbrowser"
version = "0.8.12" version = "0.8.12"
@ -3602,7 +3662,7 @@ dependencies = [
"wayland-protocols", "wayland-protocols",
"wayland-protocols-plasma", "wayland-protocols-plasma",
"web-sys", "web-sys",
"web-time", "web-time 0.2.4",
"windows-sys 0.48.0", "windows-sys 0.48.0",
"x11-dl", "x11-dl",
"x11rb 0.13.0", "x11rb 0.13.0",

View file

@ -15,3 +15,4 @@ serde_json = "1.0.111"
nix = { version = "0.27.1", features = ["fs"] } nix = { version = "0.27.1", features = ["fs"] }
homedir = "0.2.1" homedir = "0.2.1"
arboard = "3.3.0" arboard = "3.3.0"
egui_dnd = "0.6.0"

View file

@ -45,4 +45,8 @@ Async terminal !
Real Ui Real Ui
# 1.1.0 : # 1.1.0 :
Better error handling Better error handling
# releases :
latest : command can fetch multiple lines in the buffer in one frame

View file

@ -15,334 +15,334 @@ use crate::TERMINAL_RANGE;
use editor::{CodeEditor, Syntax}; use editor::{CodeEditor, Syntax};
impl Calcifer { impl Calcifer {
pub fn draw_settings(&mut self, ctx: &egui::Context) { pub fn draw_settings(&mut self, ctx: &egui::Context) {
egui::SidePanel::left("settings") egui::SidePanel::left("settings")
.resizable(false) .resizable(false)
.exact_width(self.font_size * 1.8) .exact_width(self.font_size * 1.8)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.vertical(|ui| { ui.vertical(|ui| {
if ui.add(egui::Button::new("📁")).clicked() { if ui.add(egui::Button::new("📁")).clicked() {
if let Some(path) = rfd::FileDialog::new() if let Some(path) = rfd::FileDialog::new()
.set_directory(self.home.as_path()) .set_directory(self.home.as_path())
.pick_file() .pick_file()
{ {
self.open_file(Some(&path)); self.open_file(Some(&path));
} }
} }
ui.separator(); ui.separator();
self.tree_visible = self.toggle(ui, self.tree_visible, "📦"); self.tree_visible = self.toggle(ui, self.tree_visible, "📦");
ui.separator(); ui.separator();
self.terminal_visible = self.toggle(ui, self.terminal_visible, "🖵"); self.terminal_visible = self.toggle(ui, self.terminal_visible, "🖵");
ui.separator(); ui.separator();
self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍"); self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍");
ui.separator(); ui.separator();
self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, ""); self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, "");
ui.separator(); ui.separator();
self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, ""); self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, "");
ui.separator(); ui.separator();
self.profiler_visible = self.toggle(ui, self.profiler_visible, ""); self.profiler_visible = self.toggle(ui, self.profiler_visible, "");
if self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION { if self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION {
ui.separator(); ui.separator();
self.project_mode = self.toggle(ui, self.project_mode, ""); self.project_mode = self.toggle(ui, self.project_mode, "c");
} }
}); });
}); });
} }
pub fn draw_tree_panel(&mut self, ctx: &egui::Context) { pub fn draw_tree_panel(&mut self, ctx: &egui::Context) {
if !self.tree_visible { if !self.tree_visible {
return; return;
} }
if self.file_tree.is_none() { if self.file_tree.is_none() {
self.file_tree = Some(panels::generate_folder_entry(self.home.as_path())); self.file_tree = Some(panels::generate_folder_entry(self.home.as_path()));
} }
let mut n_files: usize = 0; let mut n_files: usize = 0;
egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { egui::SidePanel::left("file_tree_panel").show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Bookshelf "); ui.label("Bookshelf ");
}); });
ui.separator(); ui.separator();
ui.label(format!("{} files displayed", self.n_file_displayed)); ui.label(format!("{} files displayed", self.n_file_displayed));
ui.separator(); ui.separator();
egui::ScrollArea::vertical().show(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| {
if let Some(file_tree) = self.file_tree.clone() { if let Some(file_tree) = self.file_tree.clone() {
let update_requested = self.list_files(ui, &file_tree, &mut n_files); let update_requested = self.list_files(ui, &file_tree, &mut n_files);
if update_requested { if update_requested {
self.file_tree = Some(panels::update_file_tree( self.file_tree = Some(panels::update_file_tree(
file_tree, file_tree,
self.tree_dir_opened.clone(), self.tree_dir_opened.clone(),
)); ));
} }
} else { } else {
ui.label("No book on the Bookshelf"); ui.label("No book on the Bookshelf");
} }
ui.separator(); ui.separator();
}); });
}); });
self.n_file_displayed = n_files.clone(); self.n_file_displayed = n_files.clone();
} }
pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) { pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) {
egui::TopBottomPanel::bottom("tray") egui::TopBottomPanel::bottom("tray")
.default_height(self.font_size * 1.2) .default_height(self.font_size * 1.2)
.resizable(false) .resizable(false)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.label(self.profiler()); ui.label(self.profiler());
}); });
} }
pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) {
if !self.terminal_visible { if !self.terminal_visible {
return; return;
} }
egui::TopBottomPanel::bottom("terminal") egui::TopBottomPanel::bottom("terminal")
.default_height(TERMINAL_HEIGHT) .default_height(TERMINAL_HEIGHT)
.height_range(Rangef::new(TERMINAL_RANGE.start, TERMINAL_RANGE.end)) .height_range(Rangef::new(TERMINAL_RANGE.start, TERMINAL_RANGE.end))
.resizable(true) .resizable(true)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
let command_color = core::hex_str_to_color(self.theme.functions); let command_color = core::hex_str_to_color(self.theme.functions);
let entry_color = core::hex_str_to_color(self.theme.literals); let entry_color = core::hex_str_to_color(self.theme.literals);
let bg_color = core::hex_str_to_color(self.theme.bg); let bg_color = core::hex_str_to_color(self.theme.bg);
ui.label(""); ui.label("");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.add(egui::Button::new("")).clicked() { if ui.add(egui::Button::new("")).clicked() {
self.command_history.retain(|e| !e.finished); self.command_history.retain(|e| !e.finished);
} }
ui.style_mut().visuals.extreme_bg_color = bg_color; ui.style_mut().visuals.extreme_bg_color = bg_color;
let Self { command, .. } = self; let Self { command, .. } = self;
ui.colored_label( ui.colored_label(
command_color, command_color,
format_path(&env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))), format_path(&env::current_dir().unwrap_or_else(|_| PathBuf::from("/"))),
); );
let response = ui.add( let response = ui.add(
egui::TextEdit::singleline(command) egui::TextEdit::singleline(command)
.desired_width(f32::INFINITY) .desired_width(f32::INFINITY)
.lock_focus(true), .lock_focus(true),
); );
if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) { if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) {
self.command_history self.command_history
.push(panels::send_command(self.command.clone())); .push(panels::send_command(self.command.clone()));
self.command = "".into(); self.command = "".into();
response.request_focus(); response.request_focus();
} }
}); });
ui.separator(); ui.separator();
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.stick_to_bottom(true) .stick_to_bottom(true)
.show(ui, |ui| { .show(ui, |ui| {
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
ui.separator(); ui.separator();
ui.spacing_mut().item_spacing.y = 0.0; ui.spacing_mut().item_spacing.y = 0.0;
ui.style_mut().visuals.hyperlink_color = ui.style_mut().visuals.hyperlink_color =
core::hex_str_to_color(self.theme.keywords); core::hex_str_to_color(self.theme.keywords);
for entry in &mut self.command_history { for entry in &mut self.command_history {
ui.label(""); ui.label("");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if !entry.finished { if !entry.finished {
entry.update(); entry.update();
let _ = ui.link("(⌛)"); let _ = ui.link("(⌛)");
} else if ui.link("(🗐)").clicked() { } else if ui.link("(🗐)").clicked() {
entry.copy_error_code(); entry.copy_error_code();
} }
ui.colored_label( ui.colored_label(
command_color, command_color,
format!("{} {}", entry.env, entry.command), format!("{} {}", entry.env, entry.command),
); );
}); });
for line in &entry.result { for line in &entry.result {
let color = if line.error { RED } else { entry_color }; let color = if line.error { RED } else { entry_color };
ui.colored_label(color, &line.text); ui.label(egui::RichText::new(&line.text).monospace().color(color));
} }
} }
}); });
}); });
}); });
}); });
} }
pub fn draw_tab_panel(&mut self, ctx: &egui::Context) { pub fn draw_tab_panel(&mut self, ctx: &egui::Context) {
egui::TopBottomPanel::top("tabs") egui::TopBottomPanel::top("tabs")
.resizable(false) .resizable(false)
.show(ctx, |ui| { .show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.style_mut().visuals.selection.bg_fill = ui.style_mut().visuals.selection.bg_fill =
core::hex_str_to_color(self.theme.functions); core::hex_str_to_color(self.theme.functions);
ui.style_mut().visuals.hyperlink_color = ui.style_mut().visuals.hyperlink_color =
core::hex_str_to_color(self.theme.functions); core::hex_str_to_color(self.theme.functions);
for (index, tab) in self.tabs.clone().iter().enumerate() { for (index, tab) in self.tabs.clone().iter().enumerate() {
let mut title = tab.get_name(); let mut title = tab.get_name();
if !tab.saved { if !tab.saved {
title += " ~"; title += " ~";
} }
if self.selected_tab == panels::TabNumber::from_index(index) { if self.selected_tab == panels::TabNumber::from_index(index) {
ui.style_mut().visuals.override_text_color = ui.style_mut().visuals.override_text_color =
Some(core::hex_str_to_color(self.theme.bg)); Some(core::hex_str_to_color(self.theme.bg));
} }
ui.selectable_value( ui.selectable_value(
&mut self.selected_tab, &mut self.selected_tab,
panels::TabNumber::from_index(index), panels::TabNumber::from_index(index),
title, title,
); );
ui.style_mut().visuals.override_text_color = None; ui.style_mut().visuals.override_text_color = None;
if ui.link("X").clicked() && !self.close_tab_confirm.visible { if ui.link("X").clicked() && !self.close_tab_confirm.visible {
if self.tabs.len() > 1 { if self.tabs.len() > 1 {
if tab.saved { if tab.saved {
self.delete_tab(index); self.delete_tab(index);
} else { } else {
self.close_tab_confirm.ask(); self.close_tab_confirm.ask();
self.tab_to_close = index; self.tab_to_close = index;
} }
} else { } else {
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close);
} }
} }
ui.separator(); ui.separator();
} }
if self.tabs.len() < MAX_TABS { if self.tabs.len() < MAX_TABS {
ui.selectable_value(&mut self.selected_tab, panels::TabNumber::Open, "+"); ui.selectable_value(&mut self.selected_tab, panels::TabNumber::Open, "+");
} }
if self.selected_tab == panels::TabNumber::Open { if self.selected_tab == panels::TabNumber::Open {
self.open_file(None); self.open_file(None);
} }
}); });
}); });
} }
pub fn draw_content_panel(&mut self, ctx: &egui::Context) { pub fn draw_content_panel(&mut self, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui if ui
.add(egui::Button::new("open directory in terminal")) .add(egui::Button::new("open directory in terminal"))
.clicked() .clicked()
{ {
let mut path = self.tabs[self.selected_tab.to_index()].path.clone(); let mut path = self.tabs[self.selected_tab.to_index()].path.clone();
path.pop(); path.pop();
panels::send_command(format!("cd {}", path.display())); panels::send_command(format!("cd {}", path.display()));
} }
ui.label("Picked file:"); ui.label("Picked file:");
ui.monospace( ui.monospace(
self.tabs[self.selected_tab.to_index()] self.tabs[self.selected_tab.to_index()]
.path .path
.to_string_lossy() .to_string_lossy()
.to_string(), .to_string(),
); );
}); });
ui.separator(); ui.separator();
if self.project_mode if self.project_mode
&& self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION && self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION
{ {
self.draw_project_file(ui); self.draw_project_file(ui);
} else { } else {
self.draw_code_file(ui); self.draw_code_file(ui);
} }
}); });
} }
fn draw_code_file(&mut self, ui: &mut egui::Ui) { fn draw_code_file(&mut self, ui: &mut egui::Ui) {
let current_tab = &mut self.tabs[self.selected_tab.to_index()]; let current_tab = &mut self.tabs[self.selected_tab.to_index()];
let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1; let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1;
let mut override_cursor: Option<CCursorRange> = None; let mut override_cursor: Option<CCursorRange> = None;
if !self.search_menu.result_selected { if !self.search_menu.result_selected {
override_cursor = Some(CCursorRange::two( override_cursor = Some(CCursorRange::two(
CCursor::new(self.search_menu.get_cursor_start()), CCursor::new(self.search_menu.get_cursor_start()),
CCursor::new(self.search_menu.get_cursor_end()), CCursor::new(self.search_menu.get_cursor_end()),
)); ));
self.search_menu.result_selected = true; self.search_menu.result_selected = true;
} }
CodeEditor::default() CodeEditor::default()
.id_source("code editor") .id_source("code editor")
.with_rows(max(45, lines)) .with_rows(max(45, lines))
.with_fontsize(self.font_size) .with_fontsize(self.font_size)
.with_theme(self.theme) .with_theme(self.theme)
.with_syntax(to_syntax(&current_tab.language)) .with_syntax(to_syntax(&current_tab.language))
.with_numlines(true) .with_numlines(true)
.show( .show(
ui, ui,
&mut current_tab.code, &mut current_tab.code,
&mut current_tab.saved, &mut current_tab.saved,
&mut current_tab.last_cursor, &mut current_tab.last_cursor,
&mut current_tab.scroll_offset, &mut current_tab.scroll_offset,
override_cursor, override_cursor,
); );
} }
fn draw_project_file(&mut self, ui: &mut egui::Ui) { fn draw_project_file(&mut self, ui: &mut egui::Ui) {
ui.label("project mode"); panels::draw_project(ui, self.theme.clone(), &mut self.project_content);
} }
pub fn draw_windows(&mut self, ctx: &egui::Context) { pub fn draw_windows(&mut self, ctx: &egui::Context) {
if self.search_menu.visible { if self.search_menu.visible {
self.search_menu self.search_menu
.show(ctx, &mut self.tabs, &mut self.selected_tab); .show(ctx, &mut self.tabs, &mut self.selected_tab);
} }
if self.close_tab_confirm.visible { if self.close_tab_confirm.visible {
self.close_tab_confirm.show(ctx); self.close_tab_confirm.show(ctx);
} }
if self.refresh_confirm.visible { if self.refresh_confirm.visible {
self.refresh_confirm.show(ctx); self.refresh_confirm.show(ctx);
} }
if self.exit_confirm.visible { if self.exit_confirm.visible {
self.exit_confirm.show(ctx); self.exit_confirm.show(ctx);
} }
if self.exit_confirm.proceed { if self.exit_confirm.proceed {
for tab in self.tabs.iter_mut() { for tab in self.tabs.iter_mut() {
tab.saved = true; tab.saved = true;
} }
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close);
} }
if self.shortcuts_menu.visible { if self.shortcuts_menu.visible {
self.shortcuts_menu.show(ctx); self.shortcuts_menu.show(ctx);
} }
if self.settings_menu.visible { if self.settings_menu.visible {
self.settings_menu.show(ctx); self.settings_menu.show(ctx);
} }
if self.settings_menu.updated { if self.settings_menu.updated {
self.theme = self.settings_menu.theme; self.theme = self.settings_menu.theme;
} }
self.handle_confirm(); self.handle_confirm();
} }
} }
fn to_syntax(language: &str) -> Syntax { fn to_syntax(language: &str) -> Syntax {
match language { match language {
"py" => Syntax::python(), "py" => Syntax::python(),
"rs" => Syntax::rust(), "rs" => Syntax::rust(),
_ => Syntax::shell(), _ => Syntax::shell(),
} }
} }
pub fn format_path(path: &Path) -> String { pub fn format_path(path: &Path) -> String {
let components: Vec<&OsStr> = path let components: Vec<&OsStr> = path
.components() .components()
.rev() .rev()
.take(DISPLAY_PATH_DEPTH) .take(DISPLAY_PATH_DEPTH)
.filter_map(|component| match component { .filter_map(|component| match component {
Component::RootDir | Component::CurDir => None, Component::RootDir | Component::CurDir => None,
_ => Some(component.as_os_str()), _ => Some(component.as_os_str()),
}) })
.collect(); .collect();
format!( format!(
"{}>", "{}>",
components components
.iter() .iter()
.rev() .rev()
.map(|&c| c.to_string_lossy()) .map(|&c| c.to_string_lossy())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("/") .join("/")
) )
} }

View file

@ -1,8 +1,8 @@
use eframe::egui; use eframe::egui;
use egui::{ use egui::{
FontFamily::Proportional, FontFamily,
FontId, FontId,
TextStyle::{Body, Button, Heading, Monospace, Small}, TextStyle::{Body, Button, Heading, Monospace, Small},
}; };
use homedir::get_my_home; use homedir::get_my_home;
use std::{ops::Range, path::PathBuf, sync::Arc, thread, time}; use std::{ops::Range, path::PathBuf, sync::Arc, thread, time};
@ -24,253 +24,256 @@ const TERMINAL_HEIGHT: f32 = 200.0;
const TERMINAL_RANGE: Range<f32> = 100.0..600.0; const TERMINAL_RANGE: Range<f32> = 100.0..600.0;
const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99); const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
const TIME_LABELS: [&str; 7] = [ const TIME_LABELS: [&str; 7] = [
"input", "settings", "tree", "terminal", "tabs", "content", "windows", "input", "settings", "tree", "terminal", "tabs", "content", "windows",
]; ];
const MAX_FPS: f32 = 30.0; const MAX_FPS: f32 = 30.0;
const DISPLAY_PATH_DEPTH: usize = 3; const DISPLAY_PATH_DEPTH: usize = 3;
const MAX_TABS: usize = 20; const MAX_TABS: usize = 20;
const MAX_PROJECT_COLUMNS: usize = 8;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
let icon_data = core::load_icon().unwrap_or_default(); let icon_data = core::load_icon().unwrap_or_default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_inner_size([1200.0, 800.0]) .with_inner_size([1200.0, 800.0])
.with_icon(Arc::new(icon_data)), .with_icon(Arc::new(icon_data)),
..Default::default() ..Default::default()
}; };
// Attempt to load previous state // Attempt to load previous state
let app_state: core::AppState = if save_path().exists() { let app_state: core::AppState = if save_path().exists() {
match core::load_state(save_path().as_path()) { match core::load_state(save_path().as_path()) {
Ok(app_state) => app_state, Ok(app_state) => app_state,
Err(_) => core::AppState::default(), Err(_) => core::AppState::default(),
} }
} else { } else {
core::AppState::default() core::AppState::default()
}; };
eframe::run_native( eframe::run_native(
&format!("Calcifer{}", TITLE), &format!("Calcifer{}", TITLE),
options, options,
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state))), Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state))),
) )
} }
struct Calcifer { struct Calcifer {
selected_tab: panels::TabNumber, selected_tab: panels::TabNumber,
tabs: Vec<panels::Tab>, tabs: Vec<panels::Tab>,
command: String, command: String,
command_history: Vec<panels::CommandEntry>, command_history: Vec<panels::CommandEntry>,
theme: editor::ColorTheme, theme: editor::ColorTheme,
font_size: f32, font_size: f32,
project_mode: bool, project_mode: bool,
project_content: panels::Project,
home: PathBuf, home: PathBuf,
tree_dir_opened: Vec<String>, tree_dir_opened: Vec<String>,
file_tree: Option<panels::FileEntry>, file_tree: Option<panels::FileEntry>,
n_file_displayed: usize, n_file_displayed: usize,
tree_visible: bool, tree_visible: bool,
profiler_visible: bool, profiler_visible: bool,
terminal_visible: bool, terminal_visible: bool,
close_tab_confirm: sub_windows::ConfirmWindow, close_tab_confirm: sub_windows::ConfirmWindow,
tab_to_close: usize, tab_to_close: usize,
refresh_confirm: sub_windows::ConfirmWindow, refresh_confirm: sub_windows::ConfirmWindow,
exit_confirm: sub_windows::ConfirmWindow, exit_confirm: sub_windows::ConfirmWindow,
search_menu: sub_windows::SearchWindow, search_menu: sub_windows::SearchWindow,
settings_menu: sub_windows::SettingsWindow, settings_menu: sub_windows::SettingsWindow,
shortcuts_menu: sub_windows::ShortcutsWindow, shortcuts_menu: sub_windows::ShortcutsWindow,
time_watch: Vec<f32>, time_watch: Vec<f32>,
next_frame: time::Instant, next_frame: time::Instant,
} }
impl Default for Calcifer { impl Default for Calcifer {
fn default() -> Self { fn default() -> Self {
Self { Self {
selected_tab: panels::TabNumber::from_index(0), selected_tab: panels::TabNumber::from_index(0),
tabs: vec![panels::Tab::default()], tabs: vec![panels::Tab::default()],
command: String::new(), command: String::new(),
command_history: Vec::new(), command_history: Vec::new(),
theme: editor::themes::DEFAULT_THEMES[0], theme: editor::themes::DEFAULT_THEMES[0],
font_size: 14.0, font_size: 14.0,
project_mode: true, project_mode: true,
project_content: panels::Project::new(),
home: get_my_home().unwrap().unwrap(), home: get_my_home().unwrap().unwrap(),
tree_dir_opened: vec![], tree_dir_opened: vec![],
file_tree: None, file_tree: None,
n_file_displayed: 0, n_file_displayed: 0,
tree_visible: false, tree_visible: false,
profiler_visible: false, profiler_visible: false,
terminal_visible: false, terminal_visible: false,
close_tab_confirm: sub_windows::ConfirmWindow::new( close_tab_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to close this document ?", "You have some unsaved changes, Do you still want to close this document ?",
"Confirm Close", "Confirm Close",
), ),
tab_to_close: 0, tab_to_close: 0,
refresh_confirm: sub_windows::ConfirmWindow::new( refresh_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to refresh this document ?", "You have some unsaved changes, Do you still want to refresh this document ?",
"Confirm Refresh", "Confirm Refresh",
), ),
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"), exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
search_menu: sub_windows::SearchWindow::default(), search_menu: sub_windows::SearchWindow::default(),
settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]), settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]),
shortcuts_menu: sub_windows::ShortcutsWindow::new(), shortcuts_menu: sub_windows::ShortcutsWindow::new(),
time_watch: vec![0.0; TIME_LABELS.len()], time_watch: vec![0.0; TIME_LABELS.len()],
next_frame: time::Instant::now(), next_frame: time::Instant::now(),
} }
} }
} }
impl eframe::App for Calcifer { impl eframe::App for Calcifer {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
thread::sleep(time::Duration::from_secs_f32( thread::sleep(time::Duration::from_secs_f32(
((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0),
)); ));
self.next_frame = time::Instant::now(); self.next_frame = time::Instant::now();
let mut watch = time::Instant::now(); let mut watch = time::Instant::now();
let mut style = (*ctx.style()).clone(); let mut style = (*ctx.style()).clone();
style.text_styles = [ style.text_styles = [
(Heading, FontId::new(self.font_size * 1.6, Proportional)), (Heading, FontId::new(self.font_size * 1.6, FontFamily::Proportional)),
(Body, FontId::new(self.font_size, Proportional)), (Body, FontId::new(self.font_size, FontFamily::Proportional)),
(Monospace, FontId::new(self.font_size, Proportional)), (Monospace, FontId::new(self.font_size * 0.8, FontFamily::Monospace)),
(Button, FontId::new(self.font_size, Proportional)), (Button, FontId::new(self.font_size, FontFamily::Proportional)),
(Small, FontId::new(self.font_size, Proportional)), (Small, FontId::new(self.font_size, FontFamily::Proportional)),
] ]
.into(); .into();
ctx.set_style(style); ctx.set_style(style);
if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl)
&& !self.refresh_confirm.visible && !self.refresh_confirm.visible
{ {
if self.tabs[self.selected_tab.to_index()].saved { if self.tabs[self.selected_tab.to_index()].saved {
self.tabs[self.selected_tab.to_index()].refresh(); self.tabs[self.selected_tab.to_index()].refresh();
} else { } else {
self.refresh_confirm.ask(); self.refresh_confirm.ask();
} }
} }
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
self.handle_save_file(self.save_tab()); self.handle_save_file(self.save_tab());
} }
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) {
self.handle_save_file(self.save_tab_as()); self.handle_save_file(self.save_tab_as());
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) {
self.move_through_tabs(false); self.move_through_tabs(false);
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) {
self.move_through_tabs(true); self.move_through_tabs(true);
} }
if ctx.input(|i| i.zoom_delta() > 1.0) { if ctx.input(|i| i.zoom_delta() > 1.0) {
self.font_size = (self.font_size * 1.1).min(30.0); self.font_size = (self.font_size * 1.1).min(30.0);
} }
if ctx.input(|i| i.zoom_delta() < 1.0) { if ctx.input(|i| i.zoom_delta() < 1.0) {
self.font_size = (self.font_size / 1.1).max(10.0); self.font_size = (self.font_size / 1.1).max(10.0);
} }
if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) {
self.search_menu.visible = !self.search_menu.visible; self.search_menu.visible = !self.search_menu.visible;
self.search_menu.initialized = !self.search_menu.visible; self.search_menu.initialized = !self.search_menu.visible;
} }
if ctx.input(|i| i.viewport().close_requested()) { if ctx.input(|i| i.viewport().close_requested()) {
let mut unsaved_tabs: Vec<usize> = vec![]; let mut unsaved_tabs: Vec<usize> = vec![];
for (index, tab) in self.tabs.iter().enumerate() { for (index, tab) in self.tabs.iter().enumerate() {
if !tab.saved { if !tab.saved {
unsaved_tabs.push(index); unsaved_tabs.push(index);
} }
} }
if !unsaved_tabs.is_empty() { if !unsaved_tabs.is_empty() {
let mut unsaved_tabs_names: String = "".to_string(); let mut unsaved_tabs_names: String = "".to_string();
for index in unsaved_tabs.iter() { for index in unsaved_tabs.iter() {
unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
} }
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
self.exit_confirm.prompt = format!( self.exit_confirm.prompt = format!(
"You have some unsaved changes :\n{}\nDo you still want to exit ?", "You have some unsaved changes :\n{}\nDo you still want to exit ?",
unsaved_tabs_names unsaved_tabs_names
); );
self.exit_confirm.ask(); self.exit_confirm.ask();
} }
} }
self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_settings(ctx); self.draw_settings(ctx);
self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_tree_panel(ctx); self.draw_tree_panel(ctx);
self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_bottom_tray(ctx); self.draw_bottom_tray(ctx);
self.draw_terminal_panel(ctx); self.draw_terminal_panel(ctx);
self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_tab_panel(ctx); self.draw_tab_panel(ctx);
self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_content_panel(ctx); self.draw_content_panel(ctx);
self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now(); watch = time::Instant::now();
self.draw_windows(ctx); self.draw_windows(ctx);
self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0; self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
} }
fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) {
self.save_state(); self.save_state();
} }
} }
fn save_path() -> PathBuf { fn save_path() -> PathBuf {
if TITLE.is_empty() { if TITLE.is_empty() {
get_my_home() get_my_home()
.unwrap() .unwrap()
.unwrap() .unwrap()
.as_path() .as_path()
.join(".calcifer") .join(".calcifer")
.join("save.json") .join("save.json")
.to_path_buf() .to_path_buf()
} else { } else {
get_my_home() get_my_home()
.unwrap() .unwrap()
.unwrap() .unwrap()
.as_path() .as_path()
.join(".calcifer") .join(".calcifer")
.join("debug") .join("debug")
.join("save.json") .join("save.json")
.to_path_buf() .to_path_buf()
} }
} }

View file

@ -7,3 +7,6 @@ pub use file_tree::*;
mod terminal; mod terminal;
pub use terminal::*; pub use terminal::*;
mod project_mode;
pub use project_mode::*;

131
src/panels/project_mode.rs Normal file
View file

@ -0,0 +1,131 @@
use eframe::egui;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::MAX_PROJECT_COLUMNS;
use crate::core::hex_str_to_color;
use crate::editor::ColorTheme;
#[derive(Clone)]
pub struct Project {
categories: Vec<Category>,
selected_item: Option<[usize; 2]>,
}
impl Project {
pub fn new() -> Self {
Self {
categories: vec![Category::create()],
selected_item: None,
}
}
fn add_category(&mut self) {
let last = self.categories.len() - 1;
self.categories[last].initialize();
if self.categories.len() < MAX_PROJECT_COLUMNS {
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)]
struct Category {
name: String,
content: Vec<Item>,
}
impl Category {
fn create() -> Self {
Self {
name: "+".into(),
content: vec![],
}
}
fn initialize(&mut self) {
self.name = "untitled".into();
}
}
#[derive(Clone, Hash)]
struct Item {
name: String,
description: String,
id: usize,
}
impl Item {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
description: "".to_string(),
id: get_id(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Location {
category: usize,
row: usize,
}
fn get_id() -> usize {
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_PROJECT_COLUMNS, |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();
}
} 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);
}
}
for (item_index, item) in category.content.iter().enumerate() {
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.literals));
if ui.add(egui::Button::new("")).clicked() {
println!("yes");
}
if project.selected_item == Some([category_index, 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 = Some([category_index, item_index]);
}
}
});
}
if category.name != "+" {
if ui.add(egui::Button::new("+")).clicked() {
project.categories[category_index].content.push(Item::new("item"));
}
}
}
});
}

View file

@ -92,7 +92,8 @@ fn read_file_contents(path: &Path) -> String {
} }
fn format_file_path(path: &Path, contents: &str) -> PathBuf { fn format_file_path(path: &Path, contents: &str) -> PathBuf {
if contents.contains("Error reading file") { let error_type = "reading file";
if contents.contains(&format!("Error {}", error_type)) {
"untitled".into() "untitled".into()
} else { } else {
path.to_path_buf() path.to_path_buf()