Compare commits

..

10 commits

Author SHA1 Message Date
WanderingPenwing 23d81bf847 fmt 2024-08-07 11:40:44 +02:00
WanderingPenwing 1febc424a2 fixed project mode crash 2024-07-27 23:17:42 +02:00
WanderingPenwing acc2c27770 fixed dragging tab disappear if mouse too far 2024-07-25 10:43:51 +02:00
WanderingPenwing c508a18941 draggable tabs 2024-07-24 11:26:37 +02:00
WanderingPenwing f9a712fea4 better last tab closing behaviour 2024-07-23 21:18:36 +02:00
WanderingPenwing f86a8ab094 modified workflow compiled tar gz name 2024-07-22 22:51:03 +02:00
WanderingPenwing 85b2b587c1 back to working 1.4 2024-07-22 22:49:05 +02:00
WanderingPenwing 1d13e429ff removed custom build.rs 2024-07-22 20:36:00 +02:00
WanderingPenwing d3ed74d3b2 dependency check in workflow 2024-07-22 20:28:06 +02:00
WanderingPenwing d0f8c4d7fd removed tinyfiledialog from cargo toml 2024-07-22 20:17:35 +02:00
14 changed files with 1796 additions and 1591 deletions

View file

@ -1,59 +0,0 @@
permissions:
contents: write
actions: read
checks: write
deployments: write
issues: write
packages: write
pull-requests: write
statuses: write
on:
release:
types: [created]
jobs:
release:
name: release ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
# Temporarily disabling Windows compilation
# - x86_64-pc-windows-gnu
- x86_64-unknown-linux-musl
steps:
- uses: actions/checkout@v2
- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install target
run: rustup target add ${{ matrix.target }}
- name: Check Rust target installation
run: rustup target list --installed
- name: Compile the app
run: |
echo "Compiling for target: ${{ matrix.target }}"
cargo build --release --target ${{ matrix.target }}
- name: Create tarball
run: |
release_tag=${{ github.event.release.tag_name }}
tar -czvf calcifer_v${release_tag}.tar.gz -C target/${{ matrix.target }}/release calcifer
- name: Upload release assets
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: calcifer_v${{ github.event.release.tag_name }}.tar.gz
asset_name: calcifer_v${{ github.event.release.tag_name }}.tar.gz
asset_content_type: application/gzip

View file

@ -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":"in progress","content":[{"name":"export copy paste fix","description":"// Hello there","id":1}]},{"name":"done","content":[{"name":"move .project file","description":"// Hello there","id":4},{"name":"move config","description":"config from .calcifer/save.json\nto .config/calcifer/state.json","id":1},{"name":"add id to textarea per tab","description":"to improve undo, make each code area of each tab have a unique id (no more undo into another tab)","id":1},{"name":"file tree id ?","description":"// Hello there","id":1},{"name":"open dir in tree ?","description":"// Hello there","id":2},{"name":"fix tab title","description":"// Hello there","id":2}]},{"name":"+","content":[]}]} {"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":[]}]}

View file

@ -9,7 +9,7 @@ mkShell {
libXi libXi
pkg-config pkg-config
] ++ [ ] ++ [
cargo #cargo
rustc rustc
atk atk
gdk-pixbuf gdk-pixbuf
@ -19,12 +19,12 @@ mkShell {
libGLU libGLU
libxkbcommon libxkbcommon
gtk3-x11 gtk3-x11
gnome.zenity #gnome.zenity
]; ];
buildInputs = [ buildInputs = [
latest.rustChannels.stable.rust latest.rustChannels.stable.rust
xorg.libX11 xorg.libX11
wayland # wayland
libxkbcommon libxkbcommon
]; ];

View file

@ -1,6 +1,6 @@
use eframe::egui; use eframe::egui;
use egui::Color32; use egui::Color32;
use std::{cmp::min, fs, path::Path, path::PathBuf}; use std::{cmp::max, cmp::min, fs, path::Path, path::PathBuf};
use crate::core; use crate::core;
use crate::editor::themes::DEFAULT_THEMES; use crate::editor::themes::DEFAULT_THEMES;
@ -11,235 +11,242 @@ use crate::Calcifer;
use crate::TIME_LABELS; use crate::TIME_LABELS;
impl Calcifer { impl Calcifer {
pub fn handle_confirm(&mut self) { pub fn handle_confirm(&mut self) {
if self.close_tab_confirm.proceed { if self.close_tab_confirm.proceed {
self.close_tab_confirm.close(); self.close_tab_confirm.close();
self.delete_tab(self.tab_to_close); self.delete_tab(self.tab_to_close);
} }
if self.refresh_confirm.proceed { if self.refresh_confirm.proceed {
self.refresh_confirm.close(); self.refresh_confirm.close();
self.tabs[self.selected_tab].refresh(); self.tabs[self.selected_tab].refresh();
} }
} }
pub fn save_tab(&self) -> Option<PathBuf> { pub fn save_tab(&self) -> Option<PathBuf> {
if self.tabs[self.selected_tab] if self.tabs[self.selected_tab]
.path .path
.file_name() .file_name()
.map_or(true, |name| name.to_string_lossy() == "untitled") .map_or(true, |name| name.to_string_lossy() == "untitled")
{ {
self.save_tab_as() self.save_tab_as()
} else { } else {
if let Err(err) = fs::write( if let Err(err) = fs::write(
&self.tabs[self.selected_tab].path, &self.tabs[self.selected_tab].path,
&self.tabs[self.selected_tab].code, &self.tabs[self.selected_tab].code,
) { ) {
eprintln!("Error writing file: {}", err); eprintln!("Error writing file: {}", err);
return self.save_tab_as(); return self.save_tab_as();
} }
Some(self.tabs[self.selected_tab].path.clone()) Some(self.tabs[self.selected_tab].path.clone())
} }
} }
pub fn save_tab_as(&self) -> Option<PathBuf> { pub fn save_tab_as(&self) -> Option<PathBuf> {
let default_path = self.home.join("untitled"); let default_path = self.home.join("untitled");
let save_path = if self.tabs[self.selected_tab].path.file_name().map_or(true, |name| name.to_string_lossy() == "untitled") let save_path = if self.tabs[self.selected_tab]
{ .path
default_path.to_string_lossy() .file_name()
} else { .map_or(true, |name| name.to_string_lossy() == "untitled")
self.tabs[self.selected_tab].path.to_string_lossy() {
}; default_path.to_string_lossy()
println!("app : tried to open dialog at {}", save_path); } else {
// if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) self.tabs[self.selected_tab].path.to_string_lossy()
// { };
// let path = PathBuf::from(path_string); if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) {
// if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) { let path = PathBuf::from(path_string);
// eprintln!("Error writing file: {}", err); if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) {
// return None; eprintln!("Error writing file: {}", err);
// } return None;
// return Some(path); }
// } return Some(path);
None }
} None
}
pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) { pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) {
if let Some(path) = path_option { if let Some(path) = path_option {
println!("File saved successfully at: {:?}", path); println!("File saved successfully at: {:?}", path);
self.tabs[self.selected_tab].path = path; self.tabs[self.selected_tab].path = path;
self.tabs[self.selected_tab].saved = true; self.tabs[self.selected_tab].saved = true;
} else { } else {
println!("File save failed."); println!("File save failed.");
} }
} }
pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self { pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self {
let mut new = Self { let mut new = Self {
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
tabs: Vec::new(), tabs: Vec::new(),
settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]), settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]),
..Default::default() ..Default::default()
}; };
if app_state.zoom != 0.0 { if app_state.zoom != 0.0 {
new.zoom = app_state.zoom; new.zoom = app_state.zoom;
} }
for path in app_state.tabs { for path in app_state.tabs {
if !path if !path
.file_name() .file_name()
.map_or(true, |name| name.to_string_lossy() == "untitled") .map_or(true, |name| name.to_string_lossy() == "untitled")
{ {
new.open_file(Some(&path)); new.open_file(Some(&path));
} }
} }
if let Some(path) = file_to_open { if let Some(path) = file_to_open {
new.open_file(Some(&path)); new.open_file(Some(&path));
} }
if new.tabs == vec![] { if new.tabs == vec![] {
new.open_file(None); new.open_file(None);
} }
new new
} }
pub fn save_state(&self) { pub fn save_state(&self) {
let mut state_theme: usize = 0; let mut state_theme: usize = 0;
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
state_theme = theme; state_theme = theme;
} }
let mut state_tabs = vec![]; let mut state_tabs = vec![];
for tab in &self.tabs { for tab in &self.tabs {
state_tabs.push(tab.path.clone()); state_tabs.push(tab.path.clone());
} }
let app_state = core::AppState { let app_state = core::AppState {
tabs: state_tabs, tabs: state_tabs,
theme: state_theme, theme: state_theme,
zoom: self.zoom, zoom: self.zoom,
}; };
let _ = core::save_state(&app_state, save_path().as_path()); let _ = core::save_state(&app_state, save_path().as_path());
} }
pub fn move_through_tabs(&mut self, forward: bool) { pub fn move_through_tabs(&mut self, forward: bool) {
let new_index = if forward { let new_index = if forward {
(self.selected_tab + 1) % self.tabs.len() (self.selected_tab + 1) % self.tabs.len()
} else { } else {
self.selected_tab self.selected_tab
.checked_sub(1) .checked_sub(1)
.unwrap_or(self.tabs.len() - 1) .unwrap_or(self.tabs.len() - 1)
}; };
self.selected_tab = new_index; self.selected_tab = new_index;
} }
pub fn open_file(&mut self, path_option: Option<&Path>) { pub fn open_file(&mut self, path_option: Option<&Path>) {
if let Some(path) = path_option { if let Some(path) = path_option {
for (index, tab) in self.tabs.clone().iter().enumerate() { for (index, tab) in self.tabs.clone().iter().enumerate() {
if tab.path == path { if tab.path == path {
self.selected_tab = index; self.selected_tab = index;
return; return;
} }
} }
} }
if let Some(path) = path_option { if let Some(path) = path_option {
self.tabs.push(panels::Tab::new(path.to_path_buf())); self.tabs.push(panels::Tab::new(path.to_path_buf()));
} else { } else {
self.tabs.push(panels::Tab::default()); self.tabs.push(panels::Tab::default());
} }
self.selected_tab = self.tabs.len() - 1; self.selected_tab = self.tabs.len() - 1;
} }
pub fn delete_tab(&mut self, index: usize) { pub fn delete_tab(&mut self, index: usize) {
self.tabs.remove(index); self.tabs.remove(index);
self.selected_tab = min(index, self.tabs.len() - 1); if self.tabs.len() != 0 {
} self.selected_tab = min(index, self.tabs.len() - 1);
}
}
pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool {
let bg_color: Color32; let bg_color: Color32;
let text_color: Color32; let text_color: Color32;
if display { if display {
bg_color = hex_str_to_color(self.theme.functions); bg_color = hex_str_to_color(self.theme.functions);
text_color = hex_str_to_color(self.theme.bg); text_color = hex_str_to_color(self.theme.bg);
} else { } else {
bg_color = hex_str_to_color(self.theme.bg); bg_color = hex_str_to_color(self.theme.bg);
text_color = hex_str_to_color(self.theme.literals); text_color = hex_str_to_color(self.theme.literals);
}; };
ui.style_mut().visuals.override_text_color = Some(text_color); ui.style_mut().visuals.override_text_color = Some(text_color);
if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { if ui.add(egui::Button::new(title).fill(bg_color)).clicked() {
return !display; return !display;
} }
ui.style_mut().visuals.override_text_color = None; ui.style_mut().visuals.override_text_color = None;
display display
} }
pub fn profiler(&self) -> String { pub fn profiler(&self) -> String {
if !self.profiler_visible { if !self.profiler_visible {
return "".to_string(); return "".to_string();
} }
let combined_string: Vec<String> = TIME_LABELS let combined_string: Vec<String> = TIME_LABELS
.into_iter() .into_iter()
.zip(self.time_watch.clone()) .zip(self.time_watch.clone())
.map(|(s, v)| format!("{} : {:.1} ms", s, v)) .map(|(s, v)| format!("{} : {:.1} ms", s, v))
.collect(); .collect();
let mut result = combined_string.join(" ; "); let mut result = combined_string.join(" ; ");
result.push_str(&format!( result.push_str(&format!(
" total : {:.1} ms", " total : {:.1} ms",
self.time_watch.clone().iter().sum::<f32>() self.time_watch.clone().iter().sum::<f32>()
)); ));
result result
} }
pub fn list_files( pub fn list_files(
&mut self, &mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
file: &panels::FileEntry, file: &panels::FileEntry,
n_files: &mut usize, n_files: &mut usize,
) -> bool { ) -> bool {
*n_files += 1; *n_files += 1;
if let Some(folder_content) = &file.folder_content { if let Some(folder_content) = &file.folder_content {
let mut check_for_update: bool = false; let mut check_for_update: bool = false;
let file_path_id = panels::get_file_path_id(&file.path); let file_path_id = panels::get_file_path_id(&file.path);
let collapsing_response = egui::CollapsingHeader::new(file.name.clone()) let collapsing_response = egui::CollapsingHeader::new(file.name.clone())
.id_source(&file.id) .id_source(&file.id)
.default_open(self.tree_dir_opened.contains(&file_path_id)) .default_open(self.tree_dir_opened.contains(&file_path_id))
.show(ui, |ui| { .show(ui, |ui| {
if !self.tree_dir_opened.contains(&file_path_id) { if !self.tree_dir_opened.contains(&file_path_id) {
return; return;
} }
for deeper_file in folder_content { for deeper_file in folder_content {
if self.list_files(ui, deeper_file, n_files) { if self.list_files(ui, deeper_file, n_files) {
check_for_update = true; check_for_update = true;
} }
} }
}); });
if collapsing_response.fully_closed() { if collapsing_response.fully_closed() {
self.tree_dir_opened.retain(|s| s != &file_path_id); self.tree_dir_opened.retain(|s| s != &file_path_id);
} else if !self.tree_dir_opened.contains(&file_path_id) { } else if !self.tree_dir_opened.contains(&file_path_id) {
self.tree_dir_opened.push(file_path_id); self.tree_dir_opened.push(file_path_id);
return !file.content_checked; return !file.content_checked;
} }
return check_for_update; return check_for_update;
} else if ui.button(&file.name).clicked() { } else if ui.button(&file.name).clicked() {
self.open_file(Some(&file.path)); self.open_file(Some(&file.path));
} }
false false
} }
pub fn tab_area_size(&self) -> usize {
max(6, self.tabs.len() + 1)
}
} }
#[allow(clippy::unnecessary_lazy_evaluations)] #[allow(clippy::unnecessary_lazy_evaluations)]
pub fn hex_str_to_color(hex_str: &str) -> Color32 { pub fn hex_str_to_color(hex_str: &str) -> Color32 {
Color32::from_hex(hex_str).unwrap_or_else(|_| Color32::BLACK) Color32::from_hex(hex_str).unwrap_or_else(|_| Color32::BLACK)
} }

View file

@ -1,60 +1,60 @@
use eframe::egui; use eframe::egui;
use image::GenericImageView; 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::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)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
pub struct AppState { pub struct AppState {
pub tabs: Vec<PathBuf>, pub tabs: Vec<PathBuf>,
pub theme: usize, pub theme: usize,
pub zoom: f32, pub zoom: f32,
} }
pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> { pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> {
let serialized_state = serde_json::to_string(state)?; let serialized_state = serde_json::to_string(state)?;
if let Some(parent_dir) = file_path.parent() { if let Some(parent_dir) = file_path.parent() {
fs::create_dir_all(parent_dir)?; fs::create_dir_all(parent_dir)?;
} }
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(file_path)?; .open(file_path)?;
file.write_all(serialized_state.as_bytes())?; file.write_all(serialized_state.as_bytes())?;
println!("Saved state at {}", file_path.display()); println!("Saved state at {}", file_path.display());
Ok(()) Ok(())
} }
pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> { pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> {
let serialized_state = read_to_string(file_path)?; let serialized_state = read_to_string(file_path)?;
Ok(serde_json::from_str(&serialized_state)?) Ok(serde_json::from_str(&serialized_state)?)
} }
pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> { pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> {
let (icon_rgba, icon_width, icon_height) = { let (icon_rgba, icon_width, icon_height) = {
let icon = include_bytes!("../../assets/icon.png"); let icon = include_bytes!("../../assets/icon.png");
let image = image::load_from_memory(icon)?; let image = image::load_from_memory(icon)?;
let rgba = image.clone().into_rgba8().to_vec(); let rgba = image.clone().into_rgba8().to_vec();
let (width, height) = image.dimensions(); let (width, height) = image.dimensions();
(rgba, width, height) (rgba, width, height)
}; };
Ok(egui::IconData { Ok(egui::IconData {
rgba: icon_rgba, rgba: icon_rgba,
width: icon_width, width: icon_width,
height: icon_height, height: icon_height,
}) })
} }

File diff suppressed because it is too large Load diff

View file

@ -2,23 +2,88 @@ use super::Syntax;
use std::collections::BTreeSet; use std::collections::BTreeSet;
impl Syntax { impl Syntax {
pub fn javascript() -> Syntax { pub fn javascript() -> Syntax {
Syntax { Syntax {
language: "Javascript", language: "Javascript",
case_sensitive: true, case_sensitive: true,
comment: "//", comment: "//",
comment_multiline: ["/*", "*/"], comment_multiline: ["/*", "*/"],
keywords: BTreeSet::from([ keywords: BTreeSet::from([
"&&", "||", "!", "let", "var", "abstract", "arguments", "await", "break", "case", "catch", "class", "const", "continue", "&&",
"debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "final", "finally", "for", "function", "||",
"goto", "if", "implements", "import", "in", "instanceof", "interface", "let", "native", "new", "package", "private", "protected", "!",
"public", "return", "static", "super", "switch", "synchronized", "this","throw", "throws", "transient", "try", "typeof", "let",
"var", "volatile", "while", "with", "yield", "var",
]), "abstract",
types: BTreeSet::from([ "arguments",
"Boolean", "Number", "BigInt", "Undefined", "Null", "String", "Symbol", "byte", "char", "float", "int", "long", "short", "void", "await",
]), "break",
special: BTreeSet::from(["false", "null", "true"]), "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"]),
}
}
} }

View file

@ -19,179 +19,179 @@ type Float = bool;
#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TokenType { pub enum TokenType {
Comment(MultiLine), Comment(MultiLine),
Function, Function,
Keyword, Keyword,
Literal, Literal,
Numeric(Float), Numeric(Float),
Punctuation(char), Punctuation(char),
Special, Special,
Str(char), Str(char),
Type, Type,
Whitespace(char), Whitespace(char),
#[default] #[default]
Unknown, Unknown,
} }
impl std::fmt::Debug for TokenType { impl std::fmt::Debug for TokenType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut name = String::new(); let mut name = String::new();
match &self { match &self {
TokenType::Comment(multiline) => { TokenType::Comment(multiline) => {
name.push_str("Comment"); name.push_str("Comment");
{ {
if *multiline { if *multiline {
name.push_str(" MultiLine"); name.push_str(" MultiLine");
} else { } else {
name.push_str(" SingleLine"); name.push_str(" SingleLine");
} }
} }
} }
TokenType::Function => name.push_str("Function"), TokenType::Function => name.push_str("Function"),
TokenType::Keyword => name.push_str("Keyword"), TokenType::Keyword => name.push_str("Keyword"),
TokenType::Literal => name.push_str("Literal"), TokenType::Literal => name.push_str("Literal"),
TokenType::Numeric(float) => { TokenType::Numeric(float) => {
name.push_str("Numeric"); name.push_str("Numeric");
if *float { if *float {
name.push_str(" Float"); name.push_str(" Float");
} else { } else {
name.push_str(" Integer"); name.push_str(" Integer");
} }
} }
TokenType::Punctuation(_) => name.push_str("Punctuation"), TokenType::Punctuation(_) => name.push_str("Punctuation"),
TokenType::Special => name.push_str("Special"), TokenType::Special => name.push_str("Special"),
TokenType::Str(quote) => { TokenType::Str(quote) => {
name.push_str("Str "); name.push_str("Str ");
name.push(*quote); name.push(*quote);
} }
TokenType::Type => name.push_str("Type"), TokenType::Type => name.push_str("Type"),
TokenType::Whitespace(c) => { TokenType::Whitespace(c) => {
name.push_str("Whitespace"); name.push_str("Whitespace");
match c { match c {
' ' => name.push_str(" Space"), ' ' => name.push_str(" Space"),
'\t' => name.push_str(" Tab"), '\t' => name.push_str(" Tab"),
'\n' => name.push_str(" New Line"), '\n' => name.push_str(" New Line"),
_ => (), _ => (),
}; };
} }
TokenType::Unknown => name.push_str("Unknown"), TokenType::Unknown => name.push_str("Unknown"),
}; };
write!(f, "{name}") write!(f, "{name}")
} }
} }
impl From<char> for TokenType { impl From<char> for TokenType {
fn from(c: char) -> Self { fn from(c: char) -> Self {
match c { match c {
c if c.is_whitespace() => TokenType::Whitespace(c), c if c.is_whitespace() => TokenType::Whitespace(c),
c if QUOTES.contains(&c) => TokenType::Str(c), c if QUOTES.contains(&c) => TokenType::Str(c),
c if c.is_numeric() => TokenType::Numeric(false), c if c.is_numeric() => TokenType::Numeric(false),
c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal, c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal,
c if c.is_ascii_punctuation() => TokenType::Punctuation(c), c if c.is_ascii_punctuation() => TokenType::Punctuation(c),
_ => TokenType::Unknown, _ => TokenType::Unknown,
} }
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
/// Rules for highlighting. /// Rules for highlighting.
pub struct Syntax { pub struct Syntax {
pub language: &'static str, pub language: &'static str,
pub case_sensitive: bool, pub case_sensitive: bool,
pub comment: &'static str, pub comment: &'static str,
pub comment_multiline: [&'static str; 2], pub comment_multiline: [&'static str; 2],
pub keywords: BTreeSet<&'static str>, pub keywords: BTreeSet<&'static str>,
pub types: BTreeSet<&'static str>, pub types: BTreeSet<&'static str>,
pub special: BTreeSet<&'static str>, pub special: BTreeSet<&'static str>,
} }
impl Default for Syntax { impl Default for Syntax {
fn default() -> Self { fn default() -> Self {
Syntax::rust() Syntax::rust()
} }
} }
impl Hash for Syntax { impl Hash for Syntax {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.language.hash(state); self.language.hash(state);
} }
} }
impl Syntax { impl Syntax {
pub fn new(language: &'static str) -> Self { pub fn new(language: &'static str) -> Self {
Syntax { Syntax {
language, language,
..Default::default() ..Default::default()
} }
} }
pub fn with_case_sensitive(self, case_sensitive: bool) -> Self { pub fn with_case_sensitive(self, case_sensitive: bool) -> Self {
Syntax { Syntax {
case_sensitive, case_sensitive,
..self ..self
} }
} }
pub fn with_comment(self, comment: &'static str) -> Self { pub fn with_comment(self, comment: &'static str) -> Self {
Syntax { comment, ..self } Syntax { comment, ..self }
} }
pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self { pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self {
Syntax { Syntax {
comment_multiline, comment_multiline,
..self ..self
} }
} }
pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self { pub fn with_keywords<T: Into<BTreeSet<&'static str>>>(self, keywords: T) -> Self {
Syntax { Syntax {
keywords: keywords.into(), keywords: keywords.into(),
..self ..self
} }
} }
pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self { pub fn with_types<T: Into<BTreeSet<&'static str>>>(self, types: T) -> Self {
Syntax { Syntax {
types: types.into(), types: types.into(),
..self ..self
} }
} }
pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self { pub fn with_special<T: Into<BTreeSet<&'static str>>>(self, special: T) -> Self {
Syntax { Syntax {
special: special.into(), special: special.into(),
..self ..self
} }
} }
pub fn language(&self) -> &str { pub fn language(&self) -> &str {
self.language self.language
} }
pub fn comment(&self) -> &str { pub fn comment(&self) -> &str {
self.comment self.comment
} }
pub fn is_keyword(&self, word: &str) -> bool { pub fn is_keyword(&self, word: &str) -> bool {
if self.case_sensitive { if self.case_sensitive {
self.keywords.contains(&word) self.keywords.contains(&word)
} else { } else {
self.keywords.contains(word.to_ascii_uppercase().as_str()) self.keywords.contains(word.to_ascii_uppercase().as_str())
} }
} }
pub fn is_type(&self, word: &str) -> bool { pub fn is_type(&self, word: &str) -> bool {
if self.case_sensitive { if self.case_sensitive {
self.types.contains(&word) self.types.contains(&word)
} else { } else {
self.types.contains(word.to_ascii_uppercase().as_str()) self.types.contains(word.to_ascii_uppercase().as_str())
} }
} }
pub fn is_special(&self, word: &str) -> bool { pub fn is_special(&self, word: &str) -> bool {
if self.case_sensitive { if self.case_sensitive {
self.special.contains(&word) self.special.contains(&word)
} else { } else {
self.special.contains(word.to_ascii_uppercase().as_str()) self.special.contains(word.to_ascii_uppercase().as_str())
} }
} }
} }
impl Syntax { impl Syntax {
pub fn simple(comment: &'static str) -> Self { pub fn simple(comment: &'static str) -> Self {
Syntax { Syntax {
language: "", language: "",
case_sensitive: false, case_sensitive: false,
comment, comment,
comment_multiline: [comment; 2], comment_multiline: [comment; 2],
keywords: BTreeSet::new(), keywords: BTreeSet::new(),
types: BTreeSet::new(), types: BTreeSet::new(),
special: BTreeSet::new(), special: BTreeSet::new(),
} }
} }
} }

View file

@ -1,12 +1,12 @@
use eframe::egui; use eframe::egui;
use egui::{ use egui::{
FontFamily, FontId, FontFamily, FontId,
TextStyle::{Body, Button, Heading, Monospace, Small}, TextStyle::{Body, Button, Heading, Monospace, Small},
}; };
use homedir::get_my_home; use homedir::get_my_home;
use std::{ops::Range, path::PathBuf, sync::Arc, thread, time};
use std::env; use std::env;
use std::time::Duration; use std::time::Duration;
use std::{ops::Range, path::PathBuf, sync::Arc, thread, time};
mod core; mod core;
mod editor; mod editor;
@ -25,7 +25,13 @@ 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+tray",
"tabs",
"content",
"windows",
]; ];
const ZOOM_FACTOR: f32 = 1.1; const ZOOM_FACTOR: f32 = 1.1;
const MAX_FPS: f32 = 30.0; const MAX_FPS: f32 = 30.0;
@ -34,301 +40,317 @@ const MAX_PROJECT_COLUMNS: usize = 8;
const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2; const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
let icon_data = core::load_icon().unwrap_or_default(); let icon_data = core::load_icon().unwrap_or_default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_inner_size([1200.0, 800.0]) .with_inner_size([1200.0, 800.0])
.with_icon(Arc::new(icon_data)), .with_icon(Arc::new(icon_data)),
..Default::default() ..Default::default()
}; };
// Attempt to load previous state // Attempt to load previous state
let app_state: core::AppState = if save_path().exists() { let app_state: core::AppState = if save_path().exists() {
match core::load_state(save_path().as_path()) { match core::load_state(save_path().as_path()) {
Ok(app_state) => app_state, Ok(app_state) => app_state,
Err(_) => core::AppState::default(), Err(_) => core::AppState::default(),
} }
} else { } else {
core::AppState::default() core::AppState::default()
}; };
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let file_to_open = if args.len() > 1 { let file_to_open = if args.len() > 1 {
println!("Opening file: {}", args[1].clone()); println!("Opening file: {}", args[1].clone());
let mut path = env::current_dir().unwrap_or_default(); let mut path = env::current_dir().unwrap_or_default();
path.push(args[1].clone()); path.push(args[1].clone());
Some(path) Some(path)
} else { } else {
None None
}; };
eframe::run_native( //panels::send_command("export RUSTFLAGS=--cfg=web_sys_unstable_apis".to_string());
&format!("Calcifer{}", TITLE),
options, eframe::run_native(
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))), &format!("Calcifer{}", TITLE),
) options,
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))),
)
} }
struct Calcifer { struct Calcifer {
focused: bool, focused: bool,
got_focus: bool, got_focus: bool,
selected_tab: usize, selected_tab: usize,
tabs: Vec<panels::Tab>, tabs: Vec<panels::Tab>,
tab_rect: egui::Rect,
mouse_holder: panels::MouseHolder,
command: String, command: String,
command_history: Vec<panels::CommandEntry>, command_history: Vec<panels::CommandEntry>,
running_command: bool, running_command: bool,
theme: editor::ColorTheme, theme: editor::ColorTheme,
font_size: f32, font_size: f32,
zoom: f32, zoom: f32,
project_content: panels::Project, project_content: panels::Project,
home: PathBuf, home: PathBuf,
tree_dir_opened: Vec<String>, tree_dir_opened: Vec<String>,
file_tree: Option<panels::FileEntry>, file_tree: Option<panels::FileEntry>,
n_file_displayed: usize, n_file_displayed: usize,
tree_visible: bool, tree_visible: bool,
profiler_visible: bool, profiler_visible: bool,
terminal_visible: bool, terminal_visible: bool,
close_tab_confirm: sub_windows::ConfirmWindow, close_tab_confirm: sub_windows::ConfirmWindow,
tab_to_close: usize, tab_to_close: usize,
refresh_confirm: sub_windows::ConfirmWindow, refresh_confirm: sub_windows::ConfirmWindow,
exit_confirm: sub_windows::ConfirmWindow, exit_confirm: sub_windows::ConfirmWindow,
search_menu: sub_windows::SearchWindow, search_menu: sub_windows::SearchWindow,
settings_menu: sub_windows::SettingsWindow, settings_menu: sub_windows::SettingsWindow,
shortcuts_menu: sub_windows::ShortcutsWindow, shortcuts_menu: sub_windows::ShortcutsWindow,
time_watch: Vec<f32>, time_watch: Vec<f32>,
next_frame: time::Instant, next_frame: time::Instant,
} }
impl Default for Calcifer { impl Default for Calcifer {
fn default() -> Self { fn default() -> Self {
Self { Self {
focused: true, focused: true,
got_focus: false, got_focus: false,
selected_tab: 0, selected_tab: 0,
tabs: vec![panels::Tab::default()], tabs: vec![panels::Tab::default()],
tab_rect: egui::Rect::EVERYTHING,
mouse_holder: panels::MouseHolder::None,
command: String::new(), command: String::new(),
command_history: Vec::new(), command_history: Vec::new(),
running_command: false, running_command: false,
theme: editor::themes::DEFAULT_THEMES[0], theme: editor::themes::DEFAULT_THEMES[0],
font_size: 14.0, font_size: 14.0,
zoom: 1.0, zoom: 1.0,
project_content: panels::Project::new(), project_content: panels::Project::new(),
home: get_my_home().unwrap().unwrap(), home: get_my_home().unwrap().unwrap(),
tree_dir_opened: vec![], tree_dir_opened: vec![],
file_tree: None, file_tree: None,
n_file_displayed: 0, n_file_displayed: 0,
tree_visible: false, tree_visible: false,
profiler_visible: false, profiler_visible: false,
terminal_visible: false, terminal_visible: false,
close_tab_confirm: sub_windows::ConfirmWindow::new( close_tab_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to close this document ?", "You have some unsaved changes, Do you still want to close this document ?",
"Confirm Close", "Confirm Close",
), ),
tab_to_close: 0, tab_to_close: 0,
refresh_confirm: sub_windows::ConfirmWindow::new( refresh_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to refresh this document ?", "You have some unsaved changes, Do you still want to refresh this document ?",
"Confirm Refresh", "Confirm Refresh",
), ),
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"), exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
search_menu: sub_windows::SearchWindow::default(), search_menu: sub_windows::SearchWindow::default(),
settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]), settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]),
shortcuts_menu: sub_windows::ShortcutsWindow::new(), shortcuts_menu: sub_windows::ShortcutsWindow::new(),
time_watch: vec![0.0; TIME_LABELS.len()], time_watch: vec![0.0; TIME_LABELS.len()],
next_frame: time::Instant::now(), next_frame: time::Instant::now(),
} }
} }
} }
impl eframe::App for Calcifer { impl eframe::App for Calcifer {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
thread::sleep(time::Duration::from_secs_f32( thread::sleep(time::Duration::from_secs_f32(
((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0),
)); ));
self.next_frame = time::Instant::now(); self.next_frame = time::Instant::now();
let mut watch = time::Instant::now(); let mut watch = time::Instant::now();
let mut style = (*ctx.style()).clone(); let mut style = (*ctx.style()).clone();
style.text_styles = [ style.text_styles = [
( (
Heading, Heading,
FontId::new(self.font_size * 1.6, FontFamily::Proportional), FontId::new(self.font_size * 1.6, FontFamily::Proportional),
), ),
(Body, FontId::new(self.font_size, FontFamily::Proportional)), (Body, FontId::new(self.font_size, FontFamily::Proportional)),
( (
Monospace, Monospace,
FontId::new(self.font_size, FontFamily::Monospace), FontId::new(self.font_size, FontFamily::Monospace),
), ),
( (
Button, Button,
FontId::new(self.font_size, FontFamily::Proportional), FontId::new(self.font_size, FontFamily::Proportional),
), ),
(Small, FontId::new(self.font_size, FontFamily::Proportional)), (Small, FontId::new(self.font_size, FontFamily::Proportional)),
] ]
.into(); .into();
ctx.set_style(style); ctx.set_style(style);
if ctx.zoom_factor() != self.zoom { if ctx.zoom_factor() != self.zoom {
ctx.set_zoom_factor(self.zoom); ctx.set_zoom_factor(self.zoom);
} }
if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl)
&& !self.refresh_confirm.visible && !self.refresh_confirm.visible
{ {
if self.tabs[self.selected_tab].saved { if self.tabs[self.selected_tab].saved {
self.tabs[self.selected_tab].refresh(); self.tabs[self.selected_tab].refresh();
} else { } else {
self.refresh_confirm.ask(); self.refresh_confirm.ask();
} }
} }
if ctx.input(|i| i.key_pressed(egui::Key::Enter)) && ctx.memory(|m| m.focus() == None) if ctx.input(|i| i.key_pressed(egui::Key::Enter))
&& self.tabs[self.selected_tab].language == PROJECT_EXTENSION && ctx.memory(|m| m.focus() == None)
{ && self.tabs[self.selected_tab].language == PROJECT_EXTENSION
self.project_content.item_window.visible = true; {
} self.project_content.item_window.visible = true;
}
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
self.handle_save_file(self.save_tab()); self.handle_save_file(self.save_tab());
} }
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) {
self.handle_save_file(self.save_tab_as()); self.handle_save_file(self.save_tab_as());
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) {
self.move_through_tabs(false); self.move_through_tabs(false);
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) {
self.move_through_tabs(true); self.move_through_tabs(true);
} }
if ctx.input(|i| i.zoom_delta() > 1.0) { if ctx.input(|i| i.zoom_delta() > 1.0) {
self.zoom = (self.zoom*ZOOM_FACTOR).min(10.0); self.zoom = (self.zoom * ZOOM_FACTOR).min(10.0);
} }
if ctx.input(|i| i.zoom_delta() < 1.0) { if ctx.input(|i| i.zoom_delta() < 1.0) {
self.zoom = (self.zoom/ZOOM_FACTOR).max(0.1); self.zoom = (self.zoom / ZOOM_FACTOR).max(0.1);
} }
if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) {
self.search_menu.visible = !self.search_menu.visible; self.search_menu.visible = !self.search_menu.visible;
self.search_menu.initialized = !self.search_menu.visible; self.search_menu.initialized = !self.search_menu.visible;
} }
self.got_focus = false; self.got_focus = false;
if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) { if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) {
self.focused = false; self.focused = false;
} else { } else {
if !self.focused { if !self.focused {
self.got_focus = true; self.got_focus = true;
} }
self.focused = true; self.focused = true;
} }
if ctx.input(|i| i.viewport().close_requested()) { if ctx.input(|i| i.viewport().close_requested()) {
let mut unsaved_tabs: Vec<usize> = vec![]; let mut unsaved_tabs: Vec<usize> = vec![];
for (index, tab) in self.tabs.iter().enumerate() { for (index, tab) in self.tabs.iter().enumerate() {
if !tab.saved { if !tab.saved {
unsaved_tabs.push(index); unsaved_tabs.push(index);
} }
} }
if !unsaved_tabs.is_empty() { if !unsaved_tabs.is_empty() {
let mut unsaved_tabs_names: String = "".to_string(); let mut unsaved_tabs_names: String = "".to_string();
for index in unsaved_tabs.iter() { for index in unsaved_tabs.iter() {
unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
} }
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
self.exit_confirm.prompt = format!( self.exit_confirm.prompt = format!(
"You have some unsaved changes :\n{}\nDo you still want to exit ?", "You have some unsaved changes :\n{}\nDo you still want to exit ?",
unsaved_tabs_names unsaved_tabs_names
); );
self.exit_confirm.ask(); self.exit_confirm.ask();
} }
} }
self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; if self.tabs.len() == 0 {
watch = time::Instant::now(); egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close);
return;
}
self.draw_settings(ctx); self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now();
self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_settings(ctx);
watch = time::Instant::now();
self.draw_tree_panel(ctx); self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now();
self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_tree_panel(ctx);
watch = time::Instant::now();
self.draw_bottom_tray(ctx); self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0;
self.draw_terminal_panel(ctx); watch = time::Instant::now();
self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_bottom_tray(ctx);
watch = time::Instant::now(); self.draw_terminal_panel(ctx);
self.draw_tab_panel(ctx); self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now();
self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_tab_panel(ctx);
watch = time::Instant::now();
self.draw_content_panel(ctx); self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0;
watch = time::Instant::now();
self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_content_panel(ctx);
watch = time::Instant::now();
self.draw_windows(ctx); self.time_watch[5] = 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_windows(ctx);
self.draw_mouse_drag(ctx);
if self.running_command { self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
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>) { if self.running_command {
self.save_state(); 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 //save path
fn save_path() -> PathBuf { fn save_path() -> PathBuf {
if TITLE.is_empty() { if TITLE.is_empty() {
get_my_home() get_my_home()
.unwrap() .unwrap()
.unwrap() .unwrap()
.as_path() .as_path()
.join(".config") .join(".config")
.join("calcifer") .join("calcifer")
.join("save.json") .join("save.json")
.to_path_buf() .to_path_buf()
} else { } else {
get_my_home() get_my_home()
.unwrap() .unwrap()
.unwrap() .unwrap()
.as_path() .as_path()
.join(".config") .join(".config")
.join("calcifer") .join("calcifer")
.join("debug_save.json") .join("debug_save.json")
.to_path_buf() .to_path_buf()
} }
} }

View file

@ -1,183 +1,186 @@
use std::{
cmp::Ordering,
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
};
use std::hash::DefaultHasher; use std::hash::DefaultHasher;
use std::time::UNIX_EPOCH;
use std::time::SystemTime;
use std::hash::Hasher;
use std::hash::Hash; 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; //use crate::ALLOWED_FILE_EXTENSIONS;
#[derive(Clone)] #[derive(Clone)]
pub struct FileEntry { pub struct FileEntry {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,
pub folder_content: Option<Vec<FileEntry>>, pub folder_content: Option<Vec<FileEntry>>,
pub content_checked: bool, pub content_checked: bool,
pub id: String, pub id: String,
} }
impl FileEntry { impl FileEntry {
pub fn new_entry(name: String, path: PathBuf) -> Self { pub fn new_entry(name: String, path: PathBuf) -> Self {
Self { Self {
name, name,
path: path.clone(), path: path.clone(),
folder_content: None, folder_content: None,
content_checked: true, content_checked: true,
id: Self::generate_unique_id(path), id: Self::generate_unique_id(path),
} }
} }
pub fn end_of_branch(name: String, path: PathBuf) -> Self { pub fn end_of_branch(name: String, path: PathBuf) -> Self {
Self { Self {
name, name,
path: path.clone(), path: path.clone(),
folder_content: Some(vec![]), folder_content: Some(vec![]),
content_checked: false, content_checked: false,
id: Self::generate_unique_id(path), id: Self::generate_unique_id(path),
} }
} }
fn generate_unique_id(path: PathBuf) -> String { fn generate_unique_id(path: PathBuf) -> String {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
now.hash(&mut hasher); now.hash(&mut hasher);
let hash = hasher.finish(); let hash = hasher.finish();
format!("{}#{}", path.display(), hash) format!("{}#{}", path.display(), hash)
} }
} }
pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry { pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry {
if !opened_dirs.contains(&get_file_path_id(&file.path)) { if !opened_dirs.contains(&get_file_path_id(&file.path)) {
return file; return file;
} }
if !file.content_checked { if !file.content_checked {
let folder = generate_folder_entry(&file.path); let folder = generate_folder_entry(&file.path);
return update_file_tree(folder, opened_dirs); return update_file_tree(folder, opened_dirs);
} }
if file.folder_content.is_none() { if file.folder_content.is_none() {
return file; return file;
} }
let folder_content = file.folder_content.expect("should have checked if none"); let folder_content = file.folder_content.expect("should have checked if none");
let updated_content: Vec<FileEntry> = folder_content let updated_content: Vec<FileEntry> = folder_content
.iter() .iter()
.map(|entry| update_file_tree(entry.clone(), opened_dirs.clone())) .map(|entry| update_file_tree(entry.clone(), opened_dirs.clone()))
.collect(); .collect();
FileEntry { FileEntry {
name: file.name, name: file.name,
path: file.path.clone(), path: file.path.clone(),
folder_content: Some(updated_content), folder_content: Some(updated_content),
content_checked: true, content_checked: true,
id: FileEntry::generate_unique_id(file.path), id: FileEntry::generate_unique_id(file.path),
} }
} }
pub fn get_file_path_id(path: &PathBuf) -> String { pub fn get_file_path_id(path: &PathBuf) -> String {
format!("#{}", path.clone().display()) format!("#{}", path.clone().display())
} }
pub fn generate_folder_entry(path: &Path) -> FileEntry { pub fn generate_folder_entry(path: &Path) -> FileEntry {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
let name = file_name.to_string_lossy().into_owned(); let name = file_name.to_string_lossy().into_owned();
match fs::read_dir(path) { match fs::read_dir(path) {
Err(err) => FileEntry::new_entry( Err(err) => FileEntry::new_entry(
format!("Error reading directory: {}", err), format!("Error reading directory: {}", err),
path.to_path_buf(), path.to_path_buf(),
), ),
Ok(entries) => { Ok(entries) => {
let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries
.map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
.collect(); .collect();
paths.sort_by(|a, b| match (a, b) { paths.sort_by(|a, b| match (a, b) {
(Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b), (Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b),
(Err(_), Ok(_)) => std::cmp::Ordering::Greater, (Err(_), Ok(_)) => std::cmp::Ordering::Greater,
(Ok(_), Err(_)) => std::cmp::Ordering::Less, (Ok(_), Err(_)) => std::cmp::Ordering::Less,
(Err(_), Err(_)) => std::cmp::Ordering::Equal, (Err(_), Err(_)) => std::cmp::Ordering::Equal,
}); });
let mut folder_content = Vec::new(); let mut folder_content = Vec::new();
for result in paths { for result in paths {
match result { match result {
Ok(entry) => { Ok(entry) => {
if let Some(file) = generate_entry(&entry.path()) { if let Some(file) = generate_entry(&entry.path()) {
folder_content.push(file); folder_content.push(file);
} }
} }
Err(err) => { Err(err) => {
folder_content.push(FileEntry::new_entry( folder_content.push(FileEntry::new_entry(
format!("Error reading entry: {}", err), format!("Error reading entry: {}", err),
path.to_path_buf(), path.to_path_buf(),
)); ));
} }
} }
} }
FileEntry { FileEntry {
name, name,
path: path.to_path_buf(), path: path.to_path_buf(),
folder_content: Some(folder_content), folder_content: Some(folder_content),
content_checked: true, content_checked: true,
id: FileEntry::generate_unique_id(path.to_path_buf()), id: FileEntry::generate_unique_id(path.to_path_buf()),
} }
} }
} }
} else { } else {
FileEntry::new_entry( FileEntry::new_entry(
"Error reading directory name".to_string(), "Error reading directory name".to_string(),
path.to_path_buf(), path.to_path_buf(),
) )
} }
} }
fn generate_entry(path: &Path) -> Option<FileEntry> { fn generate_entry(path: &Path) -> Option<FileEntry> {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
if file_name.to_string_lossy().starts_with('.') { if file_name.to_string_lossy().starts_with('.') {
return None; return None;
} }
// let extension = path.extension().and_then(|ext| ext.to_str()); // let extension = path.extension().and_then(|ext| ext.to_str());
// if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) { // if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) {
// return None; // return None;
// } // }
} else { } else {
return None; return None;
} }
let name = path let name = path
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("")) .unwrap_or_else(|| OsStr::new(""))
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned();
if !path.is_dir() { if !path.is_dir() {
return Some(FileEntry::new_entry(name, path.to_path_buf())); return Some(FileEntry::new_entry(name, path.to_path_buf()));
} }
Some(FileEntry::end_of_branch(name, path.to_path_buf())) Some(FileEntry::end_of_branch(name, path.to_path_buf()))
} }
fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering { fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering {
let a_is_dir = a.path().is_dir(); let a_is_dir = a.path().is_dir();
let b_is_dir = b.path().is_dir(); let b_is_dir = b.path().is_dir();
// Directories come first, then files // Directories come first, then files
if a_is_dir && !b_is_dir { if a_is_dir && !b_is_dir {
Ordering::Less Ordering::Less
} else if !a_is_dir && b_is_dir { } else if !a_is_dir && b_is_dir {
Ordering::Greater Ordering::Greater
} else { } else {
// Both are either directories or files, sort alphabetically // Both are either directories or files, sort alphabetically
a.path().cmp(&b.path()) a.path().cmp(&b.path())
} }
} }

View file

@ -1,9 +1,8 @@
use eframe::egui; use eframe::egui;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
cmp::min, cmp::max,
cmp::max, sync::atomic::{AtomicUsize, Ordering},
sync::atomic::{AtomicUsize, Ordering},
}; };
use crate::core::hex_str_to_color; use crate::core::hex_str_to_color;
@ -13,262 +12,253 @@ use crate::MAX_PROJECT_COLUMNS;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ProjectSave { pub struct ProjectSave {
pub categories: Vec<Category>, pub categories: Vec<Category>,
} }
impl ProjectSave { impl ProjectSave {
pub fn from_project(project: &Project) -> Self { pub fn from_project(project: &Project) -> Self {
Self { Self {
categories: project.categories.clone(), categories: project.categories.clone(),
} }
} }
} }
pub struct Project { pub struct Project {
pub categories: Vec<Category>, pub categories: Vec<Category>,
pub selected_item: Location, pub selected_item: Location,
pub item_window: sub_windows::ProjectItemWindow, pub item_window: sub_windows::ProjectItemWindow,
was_moving: bool, was_moving: bool,
} }
impl Project { impl Project {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
categories: vec![Category::create()], categories: vec![Category::create()],
selected_item: Location::zero(), selected_item: Location::zero(),
was_moving: false, was_moving: false,
item_window: sub_windows::ProjectItemWindow::new(), item_window: sub_windows::ProjectItemWindow::new(),
} }
} }
pub fn update_from_code(&mut self, json: String) { pub fn update_from_code(&mut self, json: String) {
match serde_json::from_str::<ProjectSave>(&json) { match serde_json::from_str::<ProjectSave>(&json) {
Ok(project_save) => self.categories = project_save.categories, Ok(project_save) => self.categories = project_save.categories,
Err(_err) => self.categories = vec![Category::create()], Err(_err) => self.categories = vec![Category::create()],
} }
} }
pub fn save_to_code(&self) -> Result<String, std::io::Error> { pub fn save_to_code(&self) -> Result<String, std::io::Error> {
Ok(serde_json::to_string(&ProjectSave::from_project(self))?) Ok(serde_json::to_string(&ProjectSave::from_project(self))?)
} }
fn add_category(&mut self) { fn add_category(&mut self) {
let last = self.categories.len() - 1; let last = self.categories.len() - 1;
self.categories[last].initialize(); self.categories[last].initialize();
self.categories.push(Category::create()); self.categories.push(Category::create());
} }
fn delete_category(&mut self, index: usize) { fn delete_category(&mut self, index: usize) {
self.categories.remove(index); self.categories.remove(index);
let last = self.categories.len() - 1; let last = self.categories.len() - 1;
if self.categories[last].name != "+" { if self.categories[last].name != "+" {
self.categories.push(Category::create()); self.categories.push(Category::create());
} }
} }
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Category { pub struct Category {
name: String, name: String,
pub content: Vec<Item>, pub content: Vec<Item>,
} }
impl Category { impl Category {
fn create() -> Self { fn create() -> Self {
Self { Self {
name: "+".into(), name: "+".into(),
content: vec![], content: vec![],
} }
} }
fn initialize(&mut self) { fn initialize(&mut self) {
self.name = "untitled".into(); self.name = "untitled".into();
} }
} }
#[derive(Clone, Hash, Serialize, Deserialize)] #[derive(Clone, Hash, Serialize, Deserialize)]
pub struct Item { pub struct Item {
pub name: String, pub name: String,
pub description: String, pub description: String,
id: usize, id: usize,
} }
impl Item { impl Item {
fn new(name: &str) -> Self { fn new(name: &str) -> Self {
Self { Self {
name: name.to_string(), name: name.to_string(),
description: "// Hello there".to_string(), description: "// Hello there".to_string(),
id: get_id(), id: get_id(),
} }
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Location { pub struct Location {
pub category: usize, pub category: usize,
pub row: usize, pub row: usize,
} }
impl Location { impl Location {
fn zero() -> Self { fn zero() -> Self {
Self { Self {
category: 0, category: 0,
row: 0, row: 0,
} }
} }
} }
fn get_id() -> usize { fn get_id() -> usize {
static COUNTER: AtomicUsize = AtomicUsize::new(1); static COUNTER: AtomicUsize = AtomicUsize::new(1);
COUNTER.fetch_add(1, Ordering::Relaxed) COUNTER.fetch_add(1, Ordering::Relaxed)
} }
pub fn draw_project(ui: &mut egui::Ui, theme: ColorTheme, project: &mut Project) { pub fn draw_project(ui: &mut egui::Ui, theme: ColorTheme, project: &mut Project) {
ui.columns(max(MAX_PROJECT_COLUMNS, project.categories.len() + 1) , |uis| { ui.columns(
for (category_index, category) in project.categories.clone().into_iter().enumerate() { max(MAX_PROJECT_COLUMNS, project.categories.len() + 1),
let ui = &mut uis[category_index]; |uis| {
for (category_index, category) in project.categories.clone().into_iter().enumerate() {
let ui = &mut uis[category_index];
if category.name == "+" { if category.name == "+" {
if ui.add(egui::Button::new("+")).clicked() { if ui.add(egui::Button::new("+")).clicked() {
project.add_category(); project.add_category();
} }
continue; continue;
} else { } else {
let response = ui.add( let response = ui.add(
egui::TextEdit::singleline(&mut project.categories[category_index].name) egui::TextEdit::singleline(&mut project.categories[category_index].name)
.desired_width(f32::INFINITY), .desired_width(f32::INFINITY),
); );
if response.lost_focus() && project.categories[category_index].name.is_empty() { if response.lost_focus() && project.categories[category_index].name.is_empty() {
project.delete_category(category_index); project.delete_category(category_index);
} }
} }
ui.separator(); ui.separator();
for (item_index, item) in category.content.iter().enumerate() { for (item_index, item) in category.content.iter().enumerate() {
if project.selected_item if project.selected_item
== (Location { == (Location {
category: category_index, category: category_index,
row: item_index, row: item_index,
}) })
{ {
ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.bg)); ui.style_mut().visuals.override_text_color =
ui.add( Some(hex_str_to_color(theme.bg));
egui::Button::new(item.name.clone()) ui.add(
.fill(hex_str_to_color(theme.functions)), egui::Button::new(item.name.clone())
); .fill(hex_str_to_color(theme.functions)),
} else { );
ui.style_mut().visuals.override_text_color = } else {
Some(hex_str_to_color(theme.literals)); ui.style_mut().visuals.override_text_color =
if ui Some(hex_str_to_color(theme.literals));
.add(egui::Button::new(item.name.clone()).fill(hex_str_to_color(theme.bg))) if ui
.clicked() .add(
{ egui::Button::new(item.name.clone())
project.selected_item = Location { .fill(hex_str_to_color(theme.bg)),
category: category_index, )
row: item_index, .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 ui.style_mut().visuals.override_text_color = Some(hex_str_to_color(theme.literals));
.push(Item::new("item")); if category.name != "+" && ui.add(egui::Button::new("+")).clicked() {
} project.categories[category_index]
// if category.name != "+" { .content
// if ui.add(egui::Button::new("+")).clicked() { .push(Item::new("item"));
// project.categories[category_index] }
// .content }
// .push(Item::new("item")); },
// } );
// }
}
});
let mut moved = false; let mut moved = false;
let category = project.selected_item.category; let category = project.selected_item.category;
let row = project.selected_item.row; let row = project.selected_item.row;
if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift) if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.shift)
&& project.selected_item.category > 0 && project.selected_item.category > 0
{ {
moved = true; moved = true;
if !project.was_moving { if !project.was_moving {
let temp = project.categories[category].content[row].clone(); let temp = project.categories[category].content[row].clone();
project.categories[category - 1].content.push(temp); project.categories[category - 1].content.push(temp);
project.categories[category].content.remove(row); project.categories[category].content.remove(row);
project.selected_item.category -= 1; project.selected_item.category -= 1;
project.selected_item.row = project.categories[category - 1].content.len() - 1; project.selected_item.row = project.categories[category - 1].content.len() - 1;
} }
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.shift) } else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.shift)
&& project.selected_item.category < project.categories.len() - 2 && project.selected_item.category < project.categories.len() - 2
{ {
moved = true; moved = true;
if !project.was_moving { if !project.was_moving {
let temp = project.categories[category].content[row].clone(); let temp = project.categories[category].content[row].clone();
project.categories[category + 1].content.push(temp); project.categories[category + 1].content.push(temp);
project.categories[category].content.remove(row); project.categories[category].content.remove(row);
project.selected_item.category += 1; project.selected_item.category += 1;
project.selected_item.row = project.categories[category + 1].content.len() - 1; project.selected_item.row = project.categories[category + 1].content.len() - 1;
} }
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) && i.modifiers.shift) } else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) && i.modifiers.shift)
&& project.selected_item.row > 0 && project.selected_item.row > 0
{ {
moved = true; moved = true;
if !project.was_moving { if !project.was_moving {
let temp = project.categories[category].content[row].clone(); let temp = project.categories[category].content[row].clone();
project.categories[category].content[row] = project.categories[category].content[row] =
project.categories[category].content[row - 1].clone(); project.categories[category].content[row - 1].clone();
project.categories[category].content[row - 1] = temp.clone(); project.categories[category].content[row - 1] = temp.clone();
project.selected_item.row -= 1; project.selected_item.row -= 1;
} }
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) { } else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) && i.modifiers.shift) {
moved = true; moved = true;
if !project.was_moving { if !project.was_moving {
let temp = project.categories[category].content[row].clone(); let temp = project.categories[category].content[row].clone();
project.categories[category].content[row] = project.categories[category].content[row] =
project.categories[category].content[row + 1].clone(); project.categories[category].content[row + 1].clone();
project.categories[category].content[row + 1] = temp.clone(); project.categories[category].content[row + 1] = temp.clone();
project.selected_item.row += 1; project.selected_item.row += 1;
} }
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft)) } else if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft))
&& project.selected_item.category > 0 && project.selected_item.category > 0
{ {
moved = true; moved = true;
if !project.was_moving { if !project.was_moving && project.categories[category - 1].content.len() > 0 {
project.selected_item.category -= 1; project.selected_item.category -= 1;
project.selected_item.row = min( }
project.categories[category].content.len() - 1, } else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight))
project.selected_item.row, && project.selected_item.category < project.categories.len() - 2
); {
} moved = true;
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowRight)) if !project.was_moving && project.categories[category + 1].content.len() > 0 {
&& project.selected_item.category < project.categories.len() - 2 project.selected_item.category += 1;
{ }
moved = true; } else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 {
if !project.was_moving { moved = true;
project.selected_item.category += 1; if !project.was_moving {
project.selected_item.row = min( project.selected_item.row -= 1;
project.categories[category].content.len() - 1, }
project.selected_item.row, } else if ui.input(|i| i.key_pressed(egui::Key::ArrowDown))
); && project.selected_item.row < project.categories[category].content.len() - 1
} {
} else if ui.input(|i| i.key_pressed(egui::Key::ArrowUp)) && project.selected_item.row > 0 { moved = true;
moved = true; if !project.was_moving {
if !project.was_moving { project.selected_item.row += 1;
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;
} }

View file

@ -61,6 +61,11 @@ impl Tab {
} }
} }
pub enum MouseHolder {
TabHolder(usize),
None,
}
fn read_file_contents(path: &Path) -> String { fn read_file_contents(path: &Path) -> String {
let error_type = "reading file"; let error_type = "reading file";
read_to_string(path) read_to_string(path)

View file

@ -3,46 +3,46 @@ use eframe::egui;
use crate::panels; use crate::panels;
pub struct ProjectItemWindow { pub struct ProjectItemWindow {
pub visible: bool, pub visible: bool,
} }
impl ProjectItemWindow { impl ProjectItemWindow {
pub fn new() -> Self { pub fn new() -> Self {
Self { visible: false } Self { visible: false }
} }
pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool { pub fn show(&mut self, ctx: &egui::Context, item: &mut panels::Item) -> bool {
let mut visible = self.visible; let mut visible = self.visible;
let maybe_response = egui::Window::new("Item") let maybe_response = egui::Window::new("Item")
.open(&mut visible) .open(&mut visible)
.vscroll(true) .vscroll(true)
.hscroll(true) .hscroll(true)
.show(ctx, |ui| self.ui(ui, item)); .show(ctx, |ui| self.ui(ui, item));
self.visible = self.visible && visible; self.visible = self.visible && visible;
if let Some(response) = maybe_response { if let Some(response) = maybe_response {
if let Some(delete_option) = response.inner { if let Some(delete_option) = response.inner {
return delete_option return delete_option;
} }
} }
return false return false;
} }
fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool { fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool {
let mut delete_item = false; let mut delete_item = false;
ui.set_min_width(250.0); ui.set_min_width(250.0);
ui.set_min_height(250.0); ui.set_min_height(250.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
if ui.add(egui::Button::new("delete")).clicked() { if ui.add(egui::Button::new("delete")).clicked() {
delete_item = true; delete_item = true;
} }
ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY)); ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY));
}); });
ui.separator(); ui.separator();
ui.add_sized( ui.add_sized(
ui.available_size(), ui.available_size(),
egui::TextEdit::multiline(&mut item.description), egui::TextEdit::multiline(&mut item.description),
); );
return delete_item.clone() return delete_item.clone();
} }
} }

View file

@ -1,44 +1,44 @@
use eframe::egui; use eframe::egui;
pub struct ShortcutsWindow { pub struct ShortcutsWindow {
pub visible: bool, pub visible: bool,
} }
impl ShortcutsWindow { impl ShortcutsWindow {
pub fn new() -> Self { pub fn new() -> Self {
Self { visible: false } Self { visible: false }
} }
pub fn show(&mut self, ctx: &egui::Context) { pub fn show(&mut self, ctx: &egui::Context) {
let mut visible = self.visible; let mut visible = self.visible;
egui::Window::new("Shortcuts") egui::Window::new("Shortcuts")
.open(&mut visible) .open(&mut visible)
.vscroll(true) .vscroll(true)
.hscroll(true) .hscroll(true)
.show(ctx, |ui| self.ui(ui)); .show(ctx, |ui| self.ui(ui));
self.visible = self.visible && visible; self.visible = self.visible && visible;
} }
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui) {
ui.set_min_width(250.0); ui.set_min_width(250.0);
ui.label("Ctrl+S : save file"); ui.label("Ctrl+S : save file");
ui.label("Ctrl+Shift+S : save file as"); ui.label("Ctrl+Shift+S : save file as");
ui.label("Ctrl+R : reload file"); ui.label("Ctrl+R : reload file");
ui.separator(); ui.separator();
ui.label("Ctrl+F : open search window"); ui.label("Ctrl+F : open search window");
ui.separator(); ui.separator();
ui.label("Ctrl+T : reload tree"); ui.label("Ctrl+T : reload tree");
ui.separator(); ui.separator();
ui.label("Ctrl+Z : undo"); ui.label("Ctrl+Z : undo");
ui.label("Ctrl+Y : redo"); ui.label("Ctrl+Y : redo");
ui.label("Tab on selection : add indent of selection"); ui.label("Tab on selection : add indent of selection");
ui.label("Shift+Tab on selection : remove indent of selection"); ui.label("Shift+Tab on selection : remove indent of selection");
ui.label("Ctrl+E : comment selection"); ui.label("Ctrl+E : comment selection");
ui.separator(); ui.separator();
ui.label("Alt+Arrows : move between tabs"); ui.label("Alt+Arrows : move between tabs");
ui.separator(); ui.separator();
ui.label("Enter (project_mode) : edit item"); ui.label("Enter (project_mode) : edit item");
ui.label("Arrows (project_mode) : change selected item"); ui.label("Arrows (project_mode) : change selected item");
ui.label("Shift+Arrows (project_mode) : move selected item"); ui.label("Shift+Arrows (project_mode) : move selected item");
} }
} }