Compare commits

..

No commits in common. "23d81bf84700f6ce60afa622400aeff2393dd3d1" and "2af316c867c8c622f1f1b0c88821900cc69b0bb4" have entirely different histories.

14 changed files with 1590 additions and 1795 deletions

59
.github/workflows/release.yml vendored Normal file
View file

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

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":"draggable item for project mode","description":"// Hello there","id":2}]},{"name":"in progress","content":[]},{"name":"done","content":[{"name":"move .project file","description":"// Hello there","id":4},{"name":"move config","description":"config from .calcifer/save.json\nto .config/calcifer/state.json","id":1},{"name":"add id to textarea per tab","description":"to improve undo, make each code area of each tab have a unique id (no more undo into another tab)","id":1},{"name":"file tree id ?","description":"// Hello there","id":1},{"name":"open dir in tree ?","description":"// Hello there","id":2},{"name":"fix tab title","description":"// Hello there","id":2},{"name":"when closing last tab","description":"close tab and THEN close calcifer (to save no tab in save.json)","id":1},{"name":"draggable tabs","description":"// Hello there","id":2},{"name":"repair build.rs","description":"// Hello there","id":1},{"name":"export copy paste fix","description":"// Hello there","id":1}]},{"name":"bug","content":[{"name":"ctrl f ","description":"I had crash when going up in the selection","id":1}]},{"name":"+","content":[]}]} {"categories":[{"name":"to do","content":[{"name":"update workflow .yml","description":"make a workflow compiling the calcifer and put the linux in calcifer-{version}\nand the windows in calcifer_windows_{version}\n\nupdate nix\nupdate jiji","id":5}]},{"name":"in progress","content":[{"name":"export copy paste fix","description":"// Hello there","id":1}]},{"name":"done","content":[{"name":"move .project file","description":"// Hello there","id":4},{"name":"move config","description":"config from .calcifer/save.json\nto .config/calcifer/state.json","id":1},{"name":"add id to textarea per tab","description":"to improve undo, make each code area of each tab have a unique id (no more undo into another tab)","id":1},{"name":"file tree id ?","description":"// Hello there","id":1},{"name":"open dir in tree ?","description":"// Hello there","id":2},{"name":"fix tab title","description":"// Hello there","id":2}]},{"name":"+","content":[]}]}

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::max, cmp::min, fs, path::Path, path::PathBuf}; use std::{cmp::min, fs, path::Path, path::PathBuf};
use crate::core; use crate::core;
use crate::editor::themes::DEFAULT_THEMES; use crate::editor::themes::DEFAULT_THEMES;
@ -11,242 +11,235 @@ use crate::Calcifer;
use crate::TIME_LABELS; use crate::TIME_LABELS;
impl Calcifer { impl Calcifer {
pub fn handle_confirm(&mut self) { pub fn handle_confirm(&mut self) {
if self.close_tab_confirm.proceed { if self.close_tab_confirm.proceed {
self.close_tab_confirm.close(); self.close_tab_confirm.close();
self.delete_tab(self.tab_to_close); self.delete_tab(self.tab_to_close);
} }
if self.refresh_confirm.proceed { if self.refresh_confirm.proceed {
self.refresh_confirm.close(); self.refresh_confirm.close();
self.tabs[self.selected_tab].refresh(); self.tabs[self.selected_tab].refresh();
} }
} }
pub fn save_tab(&self) -> Option<PathBuf> { pub fn save_tab(&self) -> Option<PathBuf> {
if self.tabs[self.selected_tab] if self.tabs[self.selected_tab]
.path .path
.file_name() .file_name()
.map_or(true, |name| name.to_string_lossy() == "untitled") .map_or(true, |name| name.to_string_lossy() == "untitled")
{ {
self.save_tab_as() self.save_tab_as()
} else { } else {
if let Err(err) = fs::write( if let Err(err) = fs::write(
&self.tabs[self.selected_tab].path, &self.tabs[self.selected_tab].path,
&self.tabs[self.selected_tab].code, &self.tabs[self.selected_tab].code,
) { ) {
eprintln!("Error writing file: {}", err); eprintln!("Error writing file: {}", err);
return self.save_tab_as(); return self.save_tab_as();
} }
Some(self.tabs[self.selected_tab].path.clone()) Some(self.tabs[self.selected_tab].path.clone())
} }
} }
pub fn save_tab_as(&self) -> Option<PathBuf> { pub fn save_tab_as(&self) -> Option<PathBuf> {
let default_path = self.home.join("untitled"); let default_path = self.home.join("untitled");
let save_path = if self.tabs[self.selected_tab].path.file_name().map_or(true, |name| name.to_string_lossy() == "untitled")
{
default_path.to_string_lossy()
} else {
self.tabs[self.selected_tab].path.to_string_lossy()
};
println!("app : tried to open dialog at {}", save_path);
// if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path)
// {
// let path = PathBuf::from(path_string);
// if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) {
// eprintln!("Error writing file: {}", err);
// return None;
// }
// return Some(path);
// }
None
}
let save_path = if self.tabs[self.selected_tab] pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) {
.path if let Some(path) = path_option {
.file_name() println!("File saved successfully at: {:?}", path);
.map_or(true, |name| name.to_string_lossy() == "untitled") self.tabs[self.selected_tab].path = path;
{ self.tabs[self.selected_tab].saved = true;
default_path.to_string_lossy() } else {
} else { println!("File save failed.");
self.tabs[self.selected_tab].path.to_string_lossy() }
}; }
if let Some(path_string) = tinyfiledialogs::save_file_dialog("Save as", &save_path) {
let path = PathBuf::from(path_string);
if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab].code) {
eprintln!("Error writing file: {}", err);
return None;
}
return Some(path);
}
None
}
pub fn handle_save_file(&mut self, path_option: Option<PathBuf>) { pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self {
if let Some(path) = path_option { let mut new = Self {
println!("File saved successfully at: {:?}", path); theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
self.tabs[self.selected_tab].path = path; tabs: Vec::new(),
self.tabs[self.selected_tab].saved = true; settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]),
} else { ..Default::default()
println!("File save failed."); };
}
}
pub fn from_app_state(app_state: core::AppState, file_to_open: Option<PathBuf>) -> Self { if app_state.zoom != 0.0 {
let mut new = Self { new.zoom = app_state.zoom;
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], }
tabs: Vec::new(),
settings_menu: sub_windows::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]),
..Default::default()
};
if app_state.zoom != 0.0 { for path in app_state.tabs {
new.zoom = app_state.zoom; if !path
} .file_name()
.map_or(true, |name| name.to_string_lossy() == "untitled")
{
new.open_file(Some(&path));
}
}
if let Some(path) = file_to_open {
new.open_file(Some(&path));
}
for path in app_state.tabs { if new.tabs == vec![] {
if !path new.open_file(None);
.file_name() }
.map_or(true, |name| name.to_string_lossy() == "untitled")
{
new.open_file(Some(&path));
}
}
if let Some(path) = file_to_open { new
new.open_file(Some(&path)); }
}
if new.tabs == vec![] { pub fn save_state(&self) {
new.open_file(None); let mut state_theme: usize = 0;
} if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
state_theme = theme;
}
new let mut state_tabs = vec![];
}
pub fn save_state(&self) { for tab in &self.tabs {
let mut state_theme: usize = 0; state_tabs.push(tab.path.clone());
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { }
state_theme = theme; let app_state = core::AppState {
} tabs: state_tabs,
theme: state_theme,
zoom: self.zoom,
};
let mut state_tabs = vec![]; let _ = core::save_state(&app_state, save_path().as_path());
}
for tab in &self.tabs { pub fn move_through_tabs(&mut self, forward: bool) {
state_tabs.push(tab.path.clone()); let new_index = if forward {
} (self.selected_tab + 1) % self.tabs.len()
let app_state = core::AppState { } else {
tabs: state_tabs, self.selected_tab
theme: state_theme, .checked_sub(1)
zoom: self.zoom, .unwrap_or(self.tabs.len() - 1)
}; };
self.selected_tab = new_index;
}
let _ = core::save_state(&app_state, save_path().as_path()); pub fn open_file(&mut self, path_option: Option<&Path>) {
} if let Some(path) = path_option {
for (index, tab) in self.tabs.clone().iter().enumerate() {
if tab.path == path {
self.selected_tab = index;
return;
}
}
}
if let Some(path) = path_option {
self.tabs.push(panels::Tab::new(path.to_path_buf()));
} else {
self.tabs.push(panels::Tab::default());
}
self.selected_tab = self.tabs.len() - 1;
}
pub fn move_through_tabs(&mut self, forward: bool) { pub fn delete_tab(&mut self, index: usize) {
let new_index = if forward { self.tabs.remove(index);
(self.selected_tab + 1) % self.tabs.len() self.selected_tab = min(index, self.tabs.len() - 1);
} else { }
self.selected_tab
.checked_sub(1)
.unwrap_or(self.tabs.len() - 1)
};
self.selected_tab = new_index;
}
pub fn open_file(&mut self, path_option: Option<&Path>) { pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool {
if let Some(path) = path_option { let bg_color: Color32;
for (index, tab) in self.tabs.clone().iter().enumerate() { let text_color: Color32;
if tab.path == path {
self.selected_tab = index;
return;
}
}
}
if let Some(path) = path_option {
self.tabs.push(panels::Tab::new(path.to_path_buf()));
} else {
self.tabs.push(panels::Tab::default());
}
self.selected_tab = self.tabs.len() - 1;
}
pub fn delete_tab(&mut self, index: usize) { if display {
self.tabs.remove(index); bg_color = hex_str_to_color(self.theme.functions);
if self.tabs.len() != 0 { text_color = hex_str_to_color(self.theme.bg);
self.selected_tab = min(index, self.tabs.len() - 1); } else {
} bg_color = hex_str_to_color(self.theme.bg);
} text_color = hex_str_to_color(self.theme.literals);
};
pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { ui.style_mut().visuals.override_text_color = Some(text_color);
let bg_color: Color32;
let text_color: Color32;
if display { if ui.add(egui::Button::new(title).fill(bg_color)).clicked() {
bg_color = hex_str_to_color(self.theme.functions); return !display;
text_color = hex_str_to_color(self.theme.bg); }
} else { ui.style_mut().visuals.override_text_color = None;
bg_color = hex_str_to_color(self.theme.bg);
text_color = hex_str_to_color(self.theme.literals);
};
ui.style_mut().visuals.override_text_color = Some(text_color); display
}
if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { pub fn profiler(&self) -> String {
return !display; if !self.profiler_visible {
} return "".to_string();
ui.style_mut().visuals.override_text_color = None; }
let combined_string: Vec<String> = TIME_LABELS
.into_iter()
.zip(self.time_watch.clone())
.map(|(s, v)| format!("{} : {:.1} ms", s, v))
.collect();
display let mut result = combined_string.join(" ; ");
} result.push_str(&format!(
" total : {:.1} ms",
self.time_watch.clone().iter().sum::<f32>()
));
result
}
pub fn profiler(&self) -> String { pub fn list_files(
if !self.profiler_visible { &mut self,
return "".to_string(); ui: &mut egui::Ui,
} file: &panels::FileEntry,
let combined_string: Vec<String> = TIME_LABELS n_files: &mut usize,
.into_iter() ) -> bool {
.zip(self.time_watch.clone()) *n_files += 1;
.map(|(s, v)| format!("{} : {:.1} ms", s, v))
.collect();
let mut result = combined_string.join(" ; "); if let Some(folder_content) = &file.folder_content {
result.push_str(&format!( let mut check_for_update: bool = false;
" total : {:.1} ms", let file_path_id = panels::get_file_path_id(&file.path);
self.time_watch.clone().iter().sum::<f32>()
)); let collapsing_response = egui::CollapsingHeader::new(file.name.clone())
result .id_source(&file.id)
} .default_open(self.tree_dir_opened.contains(&file_path_id))
.show(ui, |ui| {
if !self.tree_dir_opened.contains(&file_path_id) {
return;
}
for deeper_file in folder_content {
if self.list_files(ui, deeper_file, n_files) {
check_for_update = true;
}
}
});
if collapsing_response.fully_closed() {
self.tree_dir_opened.retain(|s| s != &file_path_id);
} else if !self.tree_dir_opened.contains(&file_path_id) {
self.tree_dir_opened.push(file_path_id);
return !file.content_checked;
}
return check_for_update;
} else if ui.button(&file.name).clicked() {
self.open_file(Some(&file.path));
}
pub fn list_files( false
&mut self, }
ui: &mut egui::Ui,
file: &panels::FileEntry,
n_files: &mut usize,
) -> bool {
*n_files += 1;
if let Some(folder_content) = &file.folder_content {
let mut check_for_update: bool = false;
let file_path_id = panels::get_file_path_id(&file.path);
let collapsing_response = egui::CollapsingHeader::new(file.name.clone())
.id_source(&file.id)
.default_open(self.tree_dir_opened.contains(&file_path_id))
.show(ui, |ui| {
if !self.tree_dir_opened.contains(&file_path_id) {
return;
}
for deeper_file in folder_content {
if self.list_files(ui, deeper_file, n_files) {
check_for_update = true;
}
}
});
if collapsing_response.fully_closed() {
self.tree_dir_opened.retain(|s| s != &file_path_id);
} else if !self.tree_dir_opened.contains(&file_path_id) {
self.tree_dir_opened.push(file_path_id);
return !file.content_checked;
}
return check_for_update;
} else if ui.button(&file.name).clicked() {
self.open_file(Some(&file.path));
}
false
}
pub fn tab_area_size(&self) -> usize {
max(6, self.tabs.len() + 1)
}
} }
#[allow(clippy::unnecessary_lazy_evaluations)] #[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 serde::Deserialize;
use serde::Serialize;
use std::{ use std::{
error::Error, error::Error,
fs, fs,
fs::{read_to_string, OpenOptions}, fs::{read_to_string, OpenOptions},
io::Write, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use serde::Serialize;
use serde::Deserialize;
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
pub struct AppState { pub struct AppState {
pub tabs: Vec<PathBuf>, pub tabs: Vec<PathBuf>,
pub theme: usize, pub theme: usize,
pub zoom: f32, pub zoom: f32,
} }
pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> { pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> {
let serialized_state = serde_json::to_string(state)?; let serialized_state = serde_json::to_string(state)?;
if let Some(parent_dir) = file_path.parent() { if let Some(parent_dir) = file_path.parent() {
fs::create_dir_all(parent_dir)?; fs::create_dir_all(parent_dir)?;
} }
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(file_path)?; .open(file_path)?;
file.write_all(serialized_state.as_bytes())?; file.write_all(serialized_state.as_bytes())?;
println!("Saved state at {}", file_path.display()); println!("Saved state at {}", file_path.display());
Ok(()) Ok(())
} }
pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> { pub fn load_state(file_path: &Path) -> Result<AppState, std::io::Error> {
let serialized_state = read_to_string(file_path)?; let serialized_state = read_to_string(file_path)?;
Ok(serde_json::from_str(&serialized_state)?) Ok(serde_json::from_str(&serialized_state)?)
} }
pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> { pub fn load_icon() -> Result<egui::IconData, Box<dyn Error>> {
let (icon_rgba, icon_width, icon_height) = { let (icon_rgba, icon_width, icon_height) = {
let icon = include_bytes!("../../assets/icon.png"); let icon = include_bytes!("../../assets/icon.png");
let image = image::load_from_memory(icon)?; let image = image::load_from_memory(icon)?;
let rgba = image.clone().into_rgba8().to_vec(); let rgba = image.clone().into_rgba8().to_vec();
let (width, height) = image.dimensions(); let (width, height) = image.dimensions();
(rgba, width, height) (rgba, width, height)
}; };
Ok(egui::IconData { Ok(egui::IconData {
rgba: icon_rgba, rgba: icon_rgba,
width: icon_width, width: icon_width,
height: icon_height, height: icon_height,
}) })
} }

File diff suppressed because it is too large Load diff

View file

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

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,13 +25,7 @@ const TERMINAL_HEIGHT: f32 = 200.0;
const TERMINAL_RANGE: Range<f32> = 100.0..600.0; const TERMINAL_RANGE: Range<f32> = 100.0..600.0;
const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99); const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99);
const TIME_LABELS: [&str; 7] = [ const TIME_LABELS: [&str; 7] = [
"input", "input", "settings", "tree", "terminal", "tabs", "content", "windows",
"settings",
"tree",
"terminal+tray",
"tabs",
"content",
"windows",
]; ];
const ZOOM_FACTOR: f32 = 1.1; const ZOOM_FACTOR: f32 = 1.1;
const MAX_FPS: f32 = 30.0; const MAX_FPS: f32 = 30.0;
@ -40,317 +34,301 @@ const MAX_PROJECT_COLUMNS: usize = 8;
const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2; const RUNNING_COMMAND_REFRESH_DELAY: f32 = 0.2;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
let icon_data = core::load_icon().unwrap_or_default(); let icon_data = core::load_icon().unwrap_or_default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_inner_size([1200.0, 800.0]) .with_inner_size([1200.0, 800.0])
.with_icon(Arc::new(icon_data)), .with_icon(Arc::new(icon_data)),
..Default::default() ..Default::default()
}; };
// Attempt to load previous state // Attempt to load previous state
let app_state: core::AppState = if save_path().exists() { let app_state: core::AppState = if save_path().exists() {
match core::load_state(save_path().as_path()) { match core::load_state(save_path().as_path()) {
Ok(app_state) => app_state, Ok(app_state) => app_state,
Err(_) => core::AppState::default(), Err(_) => core::AppState::default(),
} }
} else { } else {
core::AppState::default() core::AppState::default()
}; };
let args: Vec<String> = env::args().collect();
let file_to_open = if args.len() > 1 {
println!("Opening file: {}", args[1].clone());
let mut path = env::current_dir().unwrap_or_default();
path.push(args[1].clone());
Some(path)
} else {
None
};
let args: Vec<String> = env::args().collect(); eframe::run_native(
let file_to_open = if args.len() > 1 { &format!("Calcifer{}", TITLE),
println!("Opening file: {}", args[1].clone()); options,
let mut path = env::current_dir().unwrap_or_default(); Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))),
path.push(args[1].clone()); )
Some(path)
} else {
None
};
//panels::send_command("export RUSTFLAGS=--cfg=web_sys_unstable_apis".to_string());
eframe::run_native(
&format!("Calcifer{}", TITLE),
options,
Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state, file_to_open))),
)
} }
struct Calcifer { struct Calcifer {
focused: bool, focused: bool,
got_focus: bool, got_focus: bool,
selected_tab: usize,
tabs: Vec<panels::Tab>,
selected_tab: usize, command: String,
tabs: Vec<panels::Tab>, command_history: Vec<panels::CommandEntry>,
tab_rect: egui::Rect, running_command: bool,
mouse_holder: panels::MouseHolder,
command: String, theme: editor::ColorTheme,
command_history: Vec<panels::CommandEntry>, font_size: f32,
running_command: bool, zoom: f32,
theme: editor::ColorTheme, project_content: panels::Project,
font_size: f32,
zoom: f32,
project_content: panels::Project, home: PathBuf,
tree_dir_opened: Vec<String>,
file_tree: Option<panels::FileEntry>,
n_file_displayed: usize,
home: PathBuf, tree_visible: bool,
tree_dir_opened: Vec<String>, profiler_visible: bool,
file_tree: Option<panels::FileEntry>, terminal_visible: bool,
n_file_displayed: usize,
tree_visible: bool, close_tab_confirm: sub_windows::ConfirmWindow,
profiler_visible: bool, tab_to_close: usize,
terminal_visible: bool, refresh_confirm: sub_windows::ConfirmWindow,
exit_confirm: sub_windows::ConfirmWindow,
close_tab_confirm: sub_windows::ConfirmWindow, search_menu: sub_windows::SearchWindow,
tab_to_close: usize, settings_menu: sub_windows::SettingsWindow,
refresh_confirm: sub_windows::ConfirmWindow, shortcuts_menu: sub_windows::ShortcutsWindow,
exit_confirm: sub_windows::ConfirmWindow,
search_menu: sub_windows::SearchWindow, time_watch: Vec<f32>,
settings_menu: sub_windows::SettingsWindow, next_frame: time::Instant,
shortcuts_menu: sub_windows::ShortcutsWindow,
time_watch: Vec<f32>,
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,
tabs: vec![panels::Tab::default()],
selected_tab: 0, command: String::new(),
tabs: vec![panels::Tab::default()], command_history: Vec::new(),
tab_rect: egui::Rect::EVERYTHING, running_command: false,
mouse_holder: panels::MouseHolder::None,
command: String::new(), theme: editor::themes::DEFAULT_THEMES[0],
command_history: Vec::new(), font_size: 14.0,
running_command: false, zoom: 1.0,
theme: editor::themes::DEFAULT_THEMES[0], project_content: panels::Project::new(),
font_size: 14.0,
zoom: 1.0,
project_content: panels::Project::new(), home: get_my_home().unwrap().unwrap(),
tree_dir_opened: vec![],
file_tree: None,
n_file_displayed: 0,
home: get_my_home().unwrap().unwrap(), tree_visible: false,
tree_dir_opened: vec![], profiler_visible: false,
file_tree: None, terminal_visible: false,
n_file_displayed: 0,
tree_visible: false, close_tab_confirm: sub_windows::ConfirmWindow::new(
profiler_visible: false, "You have some unsaved changes, Do you still want to close this document ?",
terminal_visible: false, "Confirm Close",
),
tab_to_close: 0,
refresh_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to refresh this document ?",
"Confirm Refresh",
),
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
close_tab_confirm: sub_windows::ConfirmWindow::new( search_menu: sub_windows::SearchWindow::default(),
"You have some unsaved changes, Do you still want to close this document ?", settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]),
"Confirm Close", shortcuts_menu: sub_windows::ShortcutsWindow::new(),
),
tab_to_close: 0,
refresh_confirm: sub_windows::ConfirmWindow::new(
"You have some unsaved changes, Do you still want to refresh this document ?",
"Confirm Refresh",
),
exit_confirm: sub_windows::ConfirmWindow::new("", "Confirm Exit"),
search_menu: sub_windows::SearchWindow::default(), time_watch: vec![0.0; TIME_LABELS.len()],
settings_menu: sub_windows::SettingsWindow::new(editor::themes::DEFAULT_THEMES[0]), next_frame: time::Instant::now(),
shortcuts_menu: sub_windows::ShortcutsWindow::new(), }
}
time_watch: vec![0.0; TIME_LABELS.len()],
next_frame: time::Instant::now(),
}
}
} }
impl eframe::App for Calcifer { impl eframe::App for Calcifer {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
thread::sleep(time::Duration::from_secs_f32( thread::sleep(time::Duration::from_secs_f32(
((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0),
)); ));
self.next_frame = time::Instant::now(); self.next_frame = time::Instant::now();
let mut watch = time::Instant::now(); let mut watch = time::Instant::now();
let mut style = (*ctx.style()).clone(); let mut style = (*ctx.style()).clone();
style.text_styles = [ style.text_styles = [
( (
Heading, Heading,
FontId::new(self.font_size * 1.6, FontFamily::Proportional), FontId::new(self.font_size * 1.6, FontFamily::Proportional),
), ),
(Body, FontId::new(self.font_size, FontFamily::Proportional)), (Body, FontId::new(self.font_size, FontFamily::Proportional)),
( (
Monospace, Monospace,
FontId::new(self.font_size, FontFamily::Monospace), FontId::new(self.font_size, FontFamily::Monospace),
), ),
( (
Button, Button,
FontId::new(self.font_size, FontFamily::Proportional), FontId::new(self.font_size, FontFamily::Proportional),
), ),
(Small, FontId::new(self.font_size, FontFamily::Proportional)), (Small, FontId::new(self.font_size, FontFamily::Proportional)),
] ]
.into(); .into();
ctx.set_style(style); ctx.set_style(style);
if ctx.zoom_factor() != self.zoom { if ctx.zoom_factor() != self.zoom {
ctx.set_zoom_factor(self.zoom); ctx.set_zoom_factor(self.zoom);
} }
if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl)
&& !self.refresh_confirm.visible && !self.refresh_confirm.visible
{ {
if self.tabs[self.selected_tab].saved { if self.tabs[self.selected_tab].saved {
self.tabs[self.selected_tab].refresh(); self.tabs[self.selected_tab].refresh();
} else { } else {
self.refresh_confirm.ask(); self.refresh_confirm.ask();
} }
} }
if ctx.input(|i| i.key_pressed(egui::Key::Enter)) if ctx.input(|i| i.key_pressed(egui::Key::Enter)) && ctx.memory(|m| m.focus() == None)
&& ctx.memory(|m| m.focus() == None) && self.tabs[self.selected_tab].language == PROJECT_EXTENSION
&& self.tabs[self.selected_tab].language == PROJECT_EXTENSION {
{ self.project_content.item_window.visible = true;
self.project_content.item_window.visible = true; }
}
if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
self.handle_save_file(self.save_tab());
}
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 && i.modifiers.shift) {
self.handle_save_file(self.save_tab()); self.handle_save_file(self.save_tab_as());
} }
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::ArrowLeft) && i.modifiers.alt) {
self.handle_save_file(self.save_tab_as()); self.move_through_tabs(false);
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) {
self.move_through_tabs(false); self.move_through_tabs(true);
} }
if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { if ctx.input(|i| i.zoom_delta() > 1.0) {
self.move_through_tabs(true); 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).min(10.0); self.zoom = (self.zoom/ZOOM_FACTOR).max(0.1);
} }
if ctx.input(|i| i.zoom_delta() < 1.0) { if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) {
self.zoom = (self.zoom / ZOOM_FACTOR).max(0.1); self.search_menu.visible = !self.search_menu.visible;
} self.search_menu.initialized = !self.search_menu.visible;
}
self.got_focus = false;
if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) {
self.focused = false;
} else {
if !self.focused {
self.got_focus = true;
}
self.focused = true;
}
if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { if ctx.input(|i| i.viewport().close_requested()) {
self.search_menu.visible = !self.search_menu.visible; let mut unsaved_tabs: Vec<usize> = vec![];
self.search_menu.initialized = !self.search_menu.visible; for (index, tab) in self.tabs.iter().enumerate() {
} if !tab.saved {
unsaved_tabs.push(index);
}
}
if !unsaved_tabs.is_empty() {
let mut unsaved_tabs_names: String = "".to_string();
for index in unsaved_tabs.iter() {
unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
}
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
self.exit_confirm.prompt = format!(
"You have some unsaved changes :\n{}\nDo you still want to exit ?",
unsaved_tabs_names
);
self.exit_confirm.ask();
}
}
self.got_focus = false; self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0;
if ctx.input(|i| !i.viewport().focused.unwrap_or_default()) { watch = time::Instant::now();
self.focused = false;
} else {
if !self.focused {
self.got_focus = true;
}
self.focused = true;
}
if ctx.input(|i| i.viewport().close_requested()) { self.draw_settings(ctx);
let mut unsaved_tabs: Vec<usize> = vec![];
for (index, tab) in self.tabs.iter().enumerate() {
if !tab.saved {
unsaved_tabs.push(index);
}
}
if !unsaved_tabs.is_empty() {
let mut unsaved_tabs_names: String = "".to_string();
for index in unsaved_tabs.iter() {
unsaved_tabs_names.push_str(&self.tabs[*index].get_name());
}
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose);
self.exit_confirm.prompt = format!(
"You have some unsaved changes :\n{}\nDo you still want to exit ?",
unsaved_tabs_names
);
self.exit_confirm.ask();
}
}
if self.tabs.len() == 0 { self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0;
egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); watch = time::Instant::now();
return;
}
self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; self.draw_tree_panel(ctx);
watch = time::Instant::now();
self.draw_settings(ctx); self.time_watch[2] = 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_bottom_tray(ctx);
watch = time::Instant::now(); self.draw_terminal_panel(ctx);
self.draw_tree_panel(ctx); self.time_watch[3] = 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_tab_panel(ctx);
watch = time::Instant::now();
self.draw_bottom_tray(ctx); self.time_watch[4] = 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_content_panel(ctx);
watch = time::Instant::now();
self.draw_tab_panel(ctx); self.time_watch[5] = 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_windows(ctx);
watch = time::Instant::now();
self.draw_content_panel(ctx); self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; if self.running_command {
watch = time::Instant::now(); egui::Context::request_repaint_after(ctx, Duration::from_secs_f32(RUNNING_COMMAND_REFRESH_DELAY));
}
}
self.draw_windows(ctx); fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) {
self.draw_mouse_drag(ctx); self.save_state();
}
self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0;
if self.running_command {
egui::Context::request_repaint_after(
ctx,
Duration::from_secs_f32(RUNNING_COMMAND_REFRESH_DELAY),
);
}
}
fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) {
self.save_state();
}
} }
//save path //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,186 +1,183 @@
use std::hash::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
ffi::OsStr, ffi::OsStr,
fs, io, fs, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use std::hash::DefaultHasher;
use std::time::UNIX_EPOCH;
use std::time::SystemTime;
use std::hash::Hasher;
use std::hash::Hash;
//use crate::ALLOWED_FILE_EXTENSIONS; //use crate::ALLOWED_FILE_EXTENSIONS;
#[derive(Clone)] #[derive(Clone)]
pub struct FileEntry { pub struct FileEntry {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,
pub folder_content: Option<Vec<FileEntry>>, pub folder_content: Option<Vec<FileEntry>>,
pub content_checked: bool, pub content_checked: bool,
pub id: String, pub id: String,
} }
impl FileEntry { impl FileEntry {
pub fn new_entry(name: String, path: PathBuf) -> Self { pub fn new_entry(name: String, path: PathBuf) -> Self {
Self { Self {
name, name,
path: path.clone(), path: path.clone(),
folder_content: None, folder_content: None,
content_checked: true, content_checked: true,
id: Self::generate_unique_id(path), id: Self::generate_unique_id(path),
} }
} }
pub fn end_of_branch(name: String, path: PathBuf) -> Self { pub fn end_of_branch(name: String, path: PathBuf) -> Self {
Self { Self {
name, name,
path: path.clone(), path: path.clone(),
folder_content: Some(vec![]), folder_content: Some(vec![]),
content_checked: false, content_checked: false,
id: Self::generate_unique_id(path), id: Self::generate_unique_id(path),
} }
} }
fn generate_unique_id(path: PathBuf) -> String { fn generate_unique_id(path: PathBuf) -> String {
let now = SystemTime::now() let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
now.hash(&mut hasher); now.hash(&mut hasher);
let hash = hasher.finish(); let hash = hasher.finish();
format!("{}#{}", path.display(), hash) format!("{}#{}", path.display(), hash)
} }
} }
pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry { pub fn update_file_tree(file: FileEntry, opened_dirs: Vec<String>) -> FileEntry {
if !opened_dirs.contains(&get_file_path_id(&file.path)) { if !opened_dirs.contains(&get_file_path_id(&file.path)) {
return file; return file;
} }
if !file.content_checked { if !file.content_checked {
let folder = generate_folder_entry(&file.path); let folder = generate_folder_entry(&file.path);
return update_file_tree(folder, opened_dirs); return update_file_tree(folder, opened_dirs);
} }
if file.folder_content.is_none() { if file.folder_content.is_none() {
return file; return file;
} }
let folder_content = file.folder_content.expect("should have checked if none"); let folder_content = file.folder_content.expect("should have checked if none");
let updated_content: Vec<FileEntry> = folder_content let updated_content: Vec<FileEntry> = folder_content
.iter() .iter()
.map(|entry| update_file_tree(entry.clone(), opened_dirs.clone())) .map(|entry| update_file_tree(entry.clone(), opened_dirs.clone()))
.collect(); .collect();
FileEntry { FileEntry {
name: file.name, name: file.name,
path: file.path.clone(), path: file.path.clone(),
folder_content: Some(updated_content), folder_content: Some(updated_content),
content_checked: true, content_checked: true,
id: FileEntry::generate_unique_id(file.path), id: FileEntry::generate_unique_id(file.path),
} }
} }
pub fn get_file_path_id(path: &PathBuf) -> String { pub fn get_file_path_id(path: &PathBuf) -> String {
format!("#{}", path.clone().display()) format!("#{}", path.clone().display())
} }
pub fn generate_folder_entry(path: &Path) -> FileEntry { pub fn generate_folder_entry(path: &Path) -> FileEntry {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
let name = file_name.to_string_lossy().into_owned(); let name = file_name.to_string_lossy().into_owned();
match fs::read_dir(path) { match fs::read_dir(path) {
Err(err) => FileEntry::new_entry( Err(err) => FileEntry::new_entry(
format!("Error reading directory: {}", err), format!("Error reading directory: {}", err),
path.to_path_buf(), path.to_path_buf(),
), ),
Ok(entries) => { Ok(entries) => {
let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries let mut paths: Vec<Result<fs::DirEntry, io::Error>> = entries
.map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
.collect(); .collect();
paths.sort_by(|a, b| match (a, b) { paths.sort_by(|a, b| match (a, b) {
(Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b), (Ok(entry_a), Ok(entry_b)) => sort_directories_first(entry_a, entry_b),
(Err(_), Ok(_)) => std::cmp::Ordering::Greater, (Err(_), Ok(_)) => std::cmp::Ordering::Greater,
(Ok(_), Err(_)) => std::cmp::Ordering::Less, (Ok(_), Err(_)) => std::cmp::Ordering::Less,
(Err(_), Err(_)) => std::cmp::Ordering::Equal, (Err(_), Err(_)) => std::cmp::Ordering::Equal,
}); });
let mut folder_content = Vec::new(); let mut folder_content = Vec::new();
for result in paths { for result in paths {
match result { match result {
Ok(entry) => { Ok(entry) => {
if let Some(file) = generate_entry(&entry.path()) { if let Some(file) = generate_entry(&entry.path()) {
folder_content.push(file); folder_content.push(file);
} }
} }
Err(err) => { Err(err) => {
folder_content.push(FileEntry::new_entry( folder_content.push(FileEntry::new_entry(
format!("Error reading entry: {}", err), format!("Error reading entry: {}", err),
path.to_path_buf(), path.to_path_buf(),
)); ));
} }
} }
} }
FileEntry { FileEntry {
name, name,
path: path.to_path_buf(), path: path.to_path_buf(),
folder_content: Some(folder_content), folder_content: Some(folder_content),
content_checked: true, content_checked: true,
id: FileEntry::generate_unique_id(path.to_path_buf()), id: FileEntry::generate_unique_id(path.to_path_buf()),
} }
} }
} }
} else { } else {
FileEntry::new_entry( FileEntry::new_entry(
"Error reading directory name".to_string(), "Error reading directory name".to_string(),
path.to_path_buf(), path.to_path_buf(),
) )
} }
} }
fn generate_entry(path: &Path) -> Option<FileEntry> { fn generate_entry(path: &Path) -> Option<FileEntry> {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
if file_name.to_string_lossy().starts_with('.') { if file_name.to_string_lossy().starts_with('.') {
return None; return None;
} }
// let extension = path.extension().and_then(|ext| ext.to_str()); // let extension = path.extension().and_then(|ext| ext.to_str());
// if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) { // if !ALLOWED_FILE_EXTENSIONS.contains(&extension.unwrap_or_default()) {
// return None; // return None;
// } // }
} else { } else {
return None; return None;
} }
let name = path let name = path
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("")) .unwrap_or_else(|| OsStr::new(""))
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned();
if !path.is_dir() { if !path.is_dir() {
return Some(FileEntry::new_entry(name, path.to_path_buf())); return Some(FileEntry::new_entry(name, path.to_path_buf()));
} }
Some(FileEntry::end_of_branch(name, path.to_path_buf())) Some(FileEntry::end_of_branch(name, path.to_path_buf()))
} }
fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering { fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering {
let a_is_dir = a.path().is_dir(); let a_is_dir = a.path().is_dir();
let b_is_dir = b.path().is_dir(); let b_is_dir = b.path().is_dir();
// Directories come first, then files // Directories come first, then files
if a_is_dir && !b_is_dir { if a_is_dir && !b_is_dir {
Ordering::Less Ordering::Less
} else if !a_is_dir && b_is_dir { } else if !a_is_dir && b_is_dir {
Ordering::Greater Ordering::Greater
} else { } else {
// Both are either directories or files, sort alphabetically // Both are either directories or files, sort alphabetically
a.path().cmp(&b.path()) a.path().cmp(&b.path())
} }
} }

View file

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

View file

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

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(delete_option) = response.inner {
return delete_option
}
}
return false
}
if let Some(response) = maybe_response { fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool {
if let Some(delete_option) = response.inner { let mut delete_item = false;
return delete_option; ui.set_min_width(250.0);
} ui.set_min_height(250.0);
} ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
return false; if ui.add(egui::Button::new("delete")).clicked() {
} delete_item = true;
}
fn ui(&mut self, ui: &mut egui::Ui, item: &mut panels::Item) -> bool { ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY));
let mut delete_item = false; });
ui.set_min_width(250.0); ui.separator();
ui.set_min_height(250.0); ui.add_sized(
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { ui.available_size(),
if ui.add(egui::Button::new("delete")).clicked() { egui::TextEdit::multiline(&mut item.description),
delete_item = true; );
} return delete_item.clone()
ui.add(egui::TextEdit::singleline(&mut item.name).desired_width(f32::INFINITY)); }
});
ui.separator();
ui.add_sized(
ui.available_size(),
egui::TextEdit::multiline(&mut item.description),
);
return delete_item.clone();
}
} }

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");
} }
} }