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