From 036712ec6e0bbd63ef36bf2c1c4eacfc2589047a Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 25 Oct 2023 07:35:38 -0600 Subject: [PATCH 01/14] Add menu --- Cargo.lock | 17 --------- Cargo.toml | 3 +- src/main.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 95 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0db41b..7064ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,7 +839,6 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -853,7 +852,6 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "quote", "syn 1.0.109", @@ -916,7 +914,6 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "almost", "cosmic-config", @@ -2047,7 +2044,6 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iced" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_accessibility", "iced_core", @@ -2063,7 +2059,6 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "accesskit", "accesskit_unix", @@ -2073,7 +2068,6 @@ dependencies = [ [[package]] name = "iced_core" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2088,7 +2082,6 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.7.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "futures", "iced_core", @@ -2100,7 +2093,6 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2118,7 +2110,6 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2131,7 +2122,6 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.1" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_accessibility", "iced_core", @@ -2143,7 +2133,6 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "enum-repr", "float-cmp", @@ -2167,7 +2156,6 @@ dependencies = [ [[package]] name = "iced_style" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_core", "once_cell", @@ -2177,7 +2165,6 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "bytemuck", "cosmic-text 0.9.0", @@ -2195,7 +2182,6 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.11.1" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2217,7 +2203,6 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.3" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_renderer", "iced_runtime", @@ -2232,7 +2217,6 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "iced_graphics", "iced_runtime", @@ -2479,7 +2463,6 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9" dependencies = [ "apply", "ashpd", diff --git a/Cargo.toml b/Cargo.toml index ba14a2b..de4c39d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,9 @@ features = ["syntect", "vi"] #path = "../cosmic-text" [dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" +#git = "https://github.com/pop-os/libcosmic" default-features = false +path = "../libcosmic" [features] default = ["wayland"] diff --git a/src/main.rs b/src/main.rs index dc1c54a..c6488b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,22 @@ use cosmic::{ widget::{column, row, text}, Alignment, Length, Limits, }, - widget::{self, icon, segmented_button, view_switcher}, + theme, + widget::{ + self, button, icon, + menu::{MenuBar, MenuTree}, + segmented_button, view_switcher, + }, ApplicationExt, Element, }; use cosmic_text::{ Attrs, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, ViEditor, ViMode, }; -use std::{env, fs, path::PathBuf, sync::Mutex}; +use std::{ + env, fs, io, + path::{Path, PathBuf}, + sync::Mutex, +}; use self::menu_list::MenuList; mod menu_list; @@ -45,6 +54,30 @@ fn main() -> Result<(), Box> { Ok(()) } +pub struct Project { + path: PathBuf, + name: String, +} + +impl Project { + pub fn new>(path: P) -> io::Result { + let path = fs::canonicalize(path)?; + let name = path + .file_name() + .ok_or(io::Error::new( + io::ErrorKind::Other, + format!("Path {:?} has no file name", path), + ))? + .to_str() + .ok_or(io::Error::new( + io::ErrorKind::Other, + format!("Path {:?} is not valid UTF-8", path), + ))? + .to_string(); + Ok(Self { path, name }) + } +} + pub struct Tab { path_opt: Option, attrs: Attrs<'static>, @@ -127,6 +160,7 @@ impl Tab { pub struct App { core: Core, + projects: Vec, tab_model: segmented_button::SingleSelectModel, } @@ -149,6 +183,15 @@ impl App { self.tab_model.active_data_mut() } + pub fn open_project>(&mut self, path: P) { + match Project::new(&path) { + Ok(project) => self.projects.push(project), + Err(err) => { + log::error!("failed to open '{}': {}", path.as_ref().display(), err); + } + } + } + pub fn open_tab(&mut self, path_opt: Option) { let mut tab = Tab::new(); if let Some(path) = path_opt { @@ -200,11 +243,17 @@ impl cosmic::Application for App { fn init(core: Core, _flags: Self::Flags) -> (Self, Command) { let mut app = App { core, + projects: Vec::new(), tab_model: segmented_button::Model::builder().build(), }; - for path in env::args().skip(1) { - app.open_tab(Some(PathBuf::from(path))); + for arg in env::args().skip(1) { + let path = PathBuf::from(arg); + if path.is_dir() { + app.open_project(path); + } else { + app.open_tab(Some(path)); + } } // Open an empty file if no arguments provided @@ -277,14 +326,25 @@ impl cosmic::Application for App { } fn view(&self) -> Element { + /* let menu_bar = row![ - MenuList::new(vec!["Open", "Save"], None, |item| { - match item { - "Open" => Message::Open, - "Save" => Message::Save, - _ => Message::Todo, + MenuList::new( + vec![ + "New file", + "New window", + "Open file...", + "Save", + "Save as..." + ], + None, + |item| { + match item { + "Open" => Message::Open, + "Save" => Message::Save, + _ => Message::Todo, + } } - }) + ) .padding(8) .placeholder("File"), MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("Edit"), @@ -294,6 +354,14 @@ impl cosmic::Application for App { .align_items(Alignment::Start) .padding(4) .spacing(16); + */ + + //TODO: port macros menu_bar! and menu_tree! + let menu_bar: Element<_> = MenuBar::new(vec![MenuTree::with_children( + button("File"), + vec![MenuTree::new(button("New file"))], + )]) + .into(); let mut tab_column = widget::column::with_capacity(3).padding([0, 16]); @@ -337,10 +405,22 @@ impl cosmic::Application for App { } }; - let content: Element<_> = column![menu_bar, tab_column].into(); + let mut project_row = widget::row::with_capacity(2); + if !self.projects.is_empty() { + /*TODO: project tree view + let mut project_list = widget::column::with_capacity(self.projects.len()); + for project in self.projects.iter() { + project_list = project_list.push(widget::text(&project.name)); + } + project_row = project_row.push(project_list); + */ + } + project_row = project_row.push(tab_column); + + let content: Element<_> = column![menu_bar, project_row].into(); // Uncomment to debug layout: - //content.explain(Color::WHITE) - content + content.explain(cosmic::iced::Color::WHITE) + //content } } From 8bd1a742e69e8303bc82f1be86a0637dadaa09fa Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 25 Oct 2023 21:11:08 -0600 Subject: [PATCH 02/14] Build out menu items --- src/main.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index c6488b3..16321d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,13 @@ use cosmic::{ app::{Command, Core, Settings}, executor, iced::{ - widget::{column, row, text}, + widget::{column, horizontal_rule, row, text}, Alignment, Length, Limits, }, theme, widget::{ self, button, icon, - menu::{MenuBar, MenuTree}, + menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}, segmented_button, view_switcher, }, ApplicationExt, Element, @@ -167,6 +167,7 @@ pub struct App { #[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum Message { + New, Open, Save, TabActivate(segmented_button::Entity), @@ -267,6 +268,10 @@ impl cosmic::Application for App { fn update(&mut self, message: Message) -> Command { match message { + Message::New => { + self.open_tab(None); + return self.update_title(); + } Message::Open => { if let Some(path) = rfd::FileDialog::new().pick_file() { self.open_tab(Some(path)); @@ -357,10 +362,77 @@ impl cosmic::Application for App { */ //TODO: port macros menu_bar! and menu_tree! - let menu_bar: Element<_> = MenuBar::new(vec![MenuTree::with_children( - button("File"), - vec![MenuTree::new(button("New file"))], - )]) + let menu_button = |label| { + button(label) + .padding([4, 12]) + .style(theme::Button::AppletMenu) + }; + let menu_item = |label, message| { + MenuTree::new(menu_button(label).on_press(message).width(Length::Fill)) + }; + let menu_bar: Element<_> = MenuBar::new(vec![ + MenuTree::with_children( + menu_button("File"), + vec![ + menu_item("New file", Message::New), + menu_item("New window", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Open file...", Message::Open), + menu_item("Open recent >", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Save", Message::Save), + menu_item("Save as...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Revert all changes", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Document statistics...", Message::Todo), + menu_item("Document type...", Message::Todo), + menu_item("Encoding...", Message::Todo), + menu_item("Print", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Quit", Message::Todo), + ], + ), + MenuTree::with_children( + menu_button("Edit"), + vec![ + menu_item("Undo", Message::Todo), + menu_item("Redo", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Cut", Message::Todo), + menu_item("Copy", Message::Todo), + menu_item("Paste", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Find", Message::Todo), + menu_item("Replace", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Spell check...", Message::Todo), + ], + ), + MenuTree::with_children( + menu_button("View"), + vec![ + menu_item("Indentation >", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Word wrap", Message::Todo), + menu_item("Show line numbers", Message::Todo), + menu_item("Highlight current line", Message::Todo), + menu_item("Syntax highlighting...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Settings...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Keyboard shortcuts...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("About COSMIC Text Editor", Message::Todo), + ], + ), + ]) + .cross_offset(12) + .item_height(ItemHeight::Dynamic(32)) + .item_width(ItemWidth::Uniform(360)) + .main_offset(12) + .padding(8) + .spacing(4.0) .into(); let mut tab_column = widget::column::with_capacity(3).padding([0, 16]); @@ -420,7 +492,7 @@ impl cosmic::Application for App { let content: Element<_> = column![menu_bar, project_row].into(); // Uncomment to debug layout: - content.explain(cosmic::iced::Color::WHITE) - //content + //content.explain(cosmic::iced::Color::WHITE) + content } } From ad15077f6fd69f450be7380fd43c50a93ad7bd29 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 25 Oct 2023 21:16:48 -0600 Subject: [PATCH 03/14] Add indentation menu --- src/main.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 16321d6..b96bf02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -412,7 +412,20 @@ impl cosmic::Application for App { MenuTree::with_children( menu_button("View"), vec![ - menu_item("Indentation >", Message::Todo), + MenuTree::with_children( + menu_button("Indentation").width(Length::Fill), + vec![ + menu_item("Automatic indentation", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Tab width: 1", Message::Todo), + menu_item("Tab width: 2", Message::Todo), + menu_item("Tab width: 4", Message::Todo), + menu_item("Tab width: 8", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Convert indentation to spaces", Message::Todo), + menu_item("Convert indentation to tabs", Message::Todo), + ], + ), MenuTree::new(horizontal_rule(1)), menu_item("Word wrap", Message::Todo), menu_item("Show line numbers", Message::Todo), From aae3bde54c959fe47f696e82b4163682915436a4 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 26 Oct 2023 09:19:33 -0600 Subject: [PATCH 04/14] Update dependencies --- Cargo.lock | 78 +++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7064ae9..03b2044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72832d73be48bac96a5d7944568f305d829ed55b0ce3b483647089dfaf6cf704" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "once_cell", @@ -1635,9 +1635,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1650,9 +1650,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -1660,15 +1660,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -1678,9 +1678,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -1699,9 +1699,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -1710,21 +1710,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1983,7 +1983,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] @@ -1992,7 +1992,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.4", + "ahash 0.8.6", "allocator-api2", ] @@ -3974,18 +3974,18 @@ checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -4531,21 +4531,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b150d2f463da7b52f12110d3995dc86598bf90d535e929e5f5af15ab89155011" +checksum = "2ef75d881185fd2df4a040793927c153d863651108a93c7e17a9e591baa95cc6" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.3", + "toml_edit 0.20.4", ] [[package]] name = "toml_datetime" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51cc078118ed25af325985ff674c00c8416b0f962be67da4946854ebfc99f334" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -4563,9 +4563,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2534c1aa199edef7108fb7d970facaa17f8f8cc5ce6bde75372cfa1051ed91" +checksum = "380f9e8120405471f7c9ad1860a713ef5ece6a670c7eae39225e477340f32fc4" dependencies = [ "indexmap 2.0.2", "serde", @@ -5837,18 +5837,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.11" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c19fae0c8a9efc6a8281f2e623db8af1db9e57852e04cde3e754dd2dc29340f" +checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.11" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc56589e9ddd1f1c28d4b4b5c773ce232910a6bb67a70133d61c9e347585efe9" +checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d" dependencies = [ "proc-macro2", "quote", From fc165694ebbeb21872ad33d52bfbac36c9dfa55e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 26 Oct 2023 09:19:54 -0600 Subject: [PATCH 05/14] Update menu design, use async file dialog for open --- src/main.rs | 119 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index b96bf02..a6fe00a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only use cosmic::{ - app::{Command, Core, Settings}, + app::{message, Command, Core, Settings}, executor, iced::{ - widget::{column, horizontal_rule, row, text}, + widget::{column, horizontal_rule, horizontal_space, row, text}, Alignment, Length, Limits, }, theme, @@ -165,10 +165,11 @@ pub struct App { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Message { New, - Open, + OpenDialog, + Open(PathBuf), Save, TabActivate(segmented_button::Entity), TabClose(segmented_button::Entity), @@ -272,11 +273,22 @@ impl cosmic::Application for App { self.open_tab(None); return self.update_title(); } - Message::Open => { - if let Some(path) = rfd::FileDialog::new().pick_file() { - self.open_tab(Some(path)); - return self.update_title(); - } + Message::OpenDialog => { + return Command::perform( + async { + if let Some(handle) = rfd::AsyncFileDialog::new().pick_file().await { + println!("{}", handle.path().display()); + message::app(Message::Open(handle.path().to_owned())) + } else { + message::none() + } + }, + |x| x, + ); + } + Message::Open(path) => { + self.open_tab(Some(path)); + return self.update_title(); } Message::Save => { let mut title_opt = None; @@ -284,6 +296,7 @@ impl cosmic::Application for App { match self.active_tab_mut() { Some(tab) => { if tab.path_opt.is_none() { + //TODO: use async file dialog tab.path_opt = rfd::FileDialog::new().save_file(); title_opt = Some(tab.title()); } @@ -344,7 +357,7 @@ impl cosmic::Application for App { None, |item| { match item { - "Open" => Message::Open, + "Open" => Message::OpenDialog, "Save" => Message::Save, _ => Message::Todo, } @@ -361,27 +374,59 @@ impl cosmic::Application for App { .spacing(16); */ - //TODO: port macros menu_bar! and menu_tree! - let menu_button = |label| { + //TODO: port to libcosmic + let menu_root = |label| { button(label) .padding([4, 12]) - .style(theme::Button::AppletMenu) + .style(theme::Button::MenuRoot) + }; + let menu_folder = |label| { + button( + row![text(label), horizontal_space(Length::Fill), text(">")] + .align_items(Alignment::Center), + ) + .height(Length::Fixed(32.0)) + .padding([4, 12]) + .width(Length::Fill) + .style(theme::Button::MenuItem) }; let menu_item = |label, message| { - MenuTree::new(menu_button(label).on_press(message).width(Length::Fill)) + MenuTree::new( + button(row![label].align_items(Alignment::Center)) + .height(Length::Fixed(32.0)) + .on_press(message) + .padding([4, 12]) + .width(Length::Fill) + .style(theme::Button::MenuItem), + ) + }; + let menu_key = |label, key, message| { + MenuTree::new( + button( + row![text(label), horizontal_space(Length::Fill), text(key)] + .align_items(Alignment::Center), + ) + .height(Length::Fixed(32.0)) + .on_press(message) + .padding([4, 12]) + .style(theme::Button::MenuItem), + ) }; let menu_bar: Element<_> = MenuBar::new(vec![ MenuTree::with_children( - menu_button("File"), + menu_root("File"), vec![ - menu_item("New file", Message::New), - menu_item("New window", Message::Todo), + menu_key("New file", "Ctrl + N", Message::New), + menu_key("New window", "Ctrl + Shift + N", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_item("Open file...", Message::Open), - menu_item("Open recent >", Message::Todo), + menu_key("Open file...", "Ctrl + O", Message::OpenDialog), + MenuTree::with_children( + menu_folder("Open recent"), + vec![menu_item("TODO", Message::Todo)], + ), MenuTree::new(horizontal_rule(1)), - menu_item("Save", Message::Save), - menu_item("Save as...", Message::Todo), + menu_key("Save", "Ctrl + S", Message::Save), + menu_key("Save as...", "Ctrl + Shift + S", Message::Todo), MenuTree::new(horizontal_rule(1)), menu_item("Revert all changes", Message::Todo), MenuTree::new(horizontal_rule(1)), @@ -390,30 +435,30 @@ impl cosmic::Application for App { menu_item("Encoding...", Message::Todo), menu_item("Print", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_item("Quit", Message::Todo), + menu_key("Quit", "Ctrl + Q", Message::Todo), ], ), MenuTree::with_children( - menu_button("Edit"), + menu_root("Edit"), vec![ - menu_item("Undo", Message::Todo), - menu_item("Redo", Message::Todo), + menu_key("Undo", "Ctrl + Z", Message::Todo), + menu_key("Redo", "Ctrl + Shift + Z", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_item("Cut", Message::Todo), - menu_item("Copy", Message::Todo), - menu_item("Paste", Message::Todo), + menu_key("Cut", "Ctrl + X", Message::Todo), + menu_key("Copy", "Ctrl + C", Message::Todo), + menu_key("Paste", "Ctrl + V", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_item("Find", Message::Todo), - menu_item("Replace", Message::Todo), + menu_key("Find", "Ctrl + F", Message::Todo), + menu_key("Replace", "Ctrl + H", Message::Todo), MenuTree::new(horizontal_rule(1)), menu_item("Spell check...", Message::Todo), ], ), MenuTree::with_children( - menu_button("View"), + menu_root("View"), vec![ MenuTree::with_children( - menu_button("Indentation").width(Length::Fill), + menu_folder("Indentation"), vec![ menu_item("Automatic indentation", Message::Todo), MenuTree::new(horizontal_rule(1)), @@ -432,7 +477,7 @@ impl cosmic::Application for App { menu_item("Highlight current line", Message::Todo), menu_item("Syntax highlighting...", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_item("Settings...", Message::Todo), + menu_key("Settings...", "Ctrl + ,", Message::Todo), MenuTree::new(horizontal_rule(1)), menu_item("Keyboard shortcuts...", Message::Todo), MenuTree::new(horizontal_rule(1)), @@ -440,10 +485,10 @@ impl cosmic::Application for App { ], ), ]) - .cross_offset(12) - .item_height(ItemHeight::Dynamic(32)) - .item_width(ItemWidth::Uniform(360)) - .main_offset(12) + .cross_offset(0) + .item_height(ItemHeight::Dynamic(40)) + .item_width(ItemWidth::Uniform(240)) + .main_offset(0) .padding(8) .spacing(4.0) .into(); From 49dcb4a84e62099fe26bcfa65c4b1a5f64c9f250 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 26 Oct 2023 10:15:09 -0600 Subject: [PATCH 06/14] Refactor --- src/main.rs | 290 ++-------------------- src/menu.rs | 130 ++++++++++ src/menu_list.rs | 622 ----------------------------------------------- src/project.rs | 28 +++ src/tab.rs | 95 ++++++++ 5 files changed, 268 insertions(+), 897 deletions(-) create mode 100644 src/menu.rs delete mode 100644 src/menu_list.rs create mode 100644 src/project.rs create mode 100644 src/tab.rs diff --git a/src/main.rs b/src/main.rs index a6fe00a..7db8fb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,46 +4,37 @@ use cosmic::{ app::{message, Command, Core, Settings}, executor, iced::{ - widget::{column, horizontal_rule, horizontal_space, row, text}, - Alignment, Length, Limits, - }, - theme, - widget::{ - self, button, icon, - menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}, - segmented_button, view_switcher, + widget::{column, text}, + Length, Limits, }, + widget::{self, icon, segmented_button, view_switcher}, ApplicationExt, Element, }; -use cosmic_text::{ - Attrs, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, ViEditor, ViMode, -}; +use cosmic_text::{FontSystem, SyntaxSystem, ViMode}; use std::{ - env, fs, io, + env, path::{Path, PathBuf}, sync::Mutex, }; -use self::menu_list::MenuList; -mod menu_list; +use self::menu::menu_bar; +mod menu; + +use self::project::Project; +mod project; + +use self::tab::Tab; +mod tab; use self::text_box::text_box; mod text_box; +//TODO: re-use iced FONT_SYSTEM lazy_static::lazy_static! { static ref FONT_SYSTEM: Mutex = Mutex::new(FontSystem::new()); static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::new(); } -static FONT_SIZES: &'static [Metrics] = &[ - Metrics::new(10.0, 14.0), // Caption - Metrics::new(14.0, 20.0), // Body - Metrics::new(20.0, 28.0), // Title 4 - Metrics::new(24.0, 32.0), // Title 3 - Metrics::new(28.0, 36.0), // Title 2 - Metrics::new(32.0, 44.0), // Title 1 -]; - fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -54,110 +45,6 @@ fn main() -> Result<(), Box> { Ok(()) } -pub struct Project { - path: PathBuf, - name: String, -} - -impl Project { - pub fn new>(path: P) -> io::Result { - let path = fs::canonicalize(path)?; - let name = path - .file_name() - .ok_or(io::Error::new( - io::ErrorKind::Other, - format!("Path {:?} has no file name", path), - ))? - .to_str() - .ok_or(io::Error::new( - io::ErrorKind::Other, - format!("Path {:?} is not valid UTF-8", path), - ))? - .to_string(); - Ok(Self { path, name }) - } -} - -pub struct Tab { - path_opt: Option, - attrs: Attrs<'static>, - editor: Mutex>, -} - -impl Tab { - pub fn new() -> Self { - let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace); - - let editor = SyntaxEditor::new( - Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), FONT_SIZES[1 /* Body */]), - &SYNTAX_SYSTEM, - "base16-eighties.dark", - ) - .unwrap(); - - let mut editor = ViEditor::new(editor); - editor.set_passthrough(false); - - Self { - path_opt: None, - attrs, - editor: Mutex::new(editor), - } - } - - pub fn open(&mut self, path: PathBuf) { - let mut editor = self.editor.lock().unwrap(); - let mut font_system = FONT_SYSTEM.lock().unwrap(); - let mut editor = editor.borrow_with(&mut font_system); - match editor.load_text(&path, self.attrs) { - Ok(()) => { - log::info!("opened '{}'", path.display()); - self.path_opt = Some(path); - } - Err(err) => { - log::error!("failed to open '{}': {}", path.display(), err); - self.path_opt = None; - } - } - } - - pub fn save(&mut self) { - if let Some(path) = &self.path_opt { - let editor = self.editor.lock().unwrap(); - let mut text = String::new(); - for line in editor.buffer().lines.iter() { - text.push_str(line.text()); - text.push('\n'); - } - match fs::write(path, text) { - Ok(()) => { - log::info!("saved '{}'", path.display()); - } - Err(err) => { - log::error!("failed to save '{}': {}", path.display(), err); - } - } - } else { - log::warn!("tab has no path yet"); - } - } - - pub fn title(&self) -> String { - //TODO: show full title when there is a conflict - if let Some(path) = &self.path_opt { - match path.file_name() { - Some(file_name_os) => match file_name_os.to_str() { - Some(file_name) => file_name.to_string(), - None => format!("{}", path.display()), - }, - None => format!("{}", path.display()), - } - } else { - "New document".to_string() - } - } -} - pub struct App { core: Core, projects: Vec, @@ -344,154 +231,7 @@ impl cosmic::Application for App { } fn view(&self) -> Element { - /* - let menu_bar = row![ - MenuList::new( - vec![ - "New file", - "New window", - "Open file...", - "Save", - "Save as..." - ], - None, - |item| { - match item { - "Open" => Message::OpenDialog, - "Save" => Message::Save, - _ => Message::Todo, - } - } - ) - .padding(8) - .placeholder("File"), - MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("Edit"), - MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("View"), - MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("Help"), - ] - .align_items(Alignment::Start) - .padding(4) - .spacing(16); - */ - - //TODO: port to libcosmic - let menu_root = |label| { - button(label) - .padding([4, 12]) - .style(theme::Button::MenuRoot) - }; - let menu_folder = |label| { - button( - row![text(label), horizontal_space(Length::Fill), text(">")] - .align_items(Alignment::Center), - ) - .height(Length::Fixed(32.0)) - .padding([4, 12]) - .width(Length::Fill) - .style(theme::Button::MenuItem) - }; - let menu_item = |label, message| { - MenuTree::new( - button(row![label].align_items(Alignment::Center)) - .height(Length::Fixed(32.0)) - .on_press(message) - .padding([4, 12]) - .width(Length::Fill) - .style(theme::Button::MenuItem), - ) - }; - let menu_key = |label, key, message| { - MenuTree::new( - button( - row![text(label), horizontal_space(Length::Fill), text(key)] - .align_items(Alignment::Center), - ) - .height(Length::Fixed(32.0)) - .on_press(message) - .padding([4, 12]) - .style(theme::Button::MenuItem), - ) - }; - let menu_bar: Element<_> = MenuBar::new(vec![ - MenuTree::with_children( - menu_root("File"), - vec![ - menu_key("New file", "Ctrl + N", Message::New), - menu_key("New window", "Ctrl + Shift + N", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_key("Open file...", "Ctrl + O", Message::OpenDialog), - MenuTree::with_children( - menu_folder("Open recent"), - vec![menu_item("TODO", Message::Todo)], - ), - MenuTree::new(horizontal_rule(1)), - menu_key("Save", "Ctrl + S", Message::Save), - menu_key("Save as...", "Ctrl + Shift + S", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Revert all changes", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Document statistics...", Message::Todo), - menu_item("Document type...", Message::Todo), - menu_item("Encoding...", Message::Todo), - menu_item("Print", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_key("Quit", "Ctrl + Q", Message::Todo), - ], - ), - MenuTree::with_children( - menu_root("Edit"), - vec![ - menu_key("Undo", "Ctrl + Z", Message::Todo), - menu_key("Redo", "Ctrl + Shift + Z", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_key("Cut", "Ctrl + X", Message::Todo), - menu_key("Copy", "Ctrl + C", Message::Todo), - menu_key("Paste", "Ctrl + V", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_key("Find", "Ctrl + F", Message::Todo), - menu_key("Replace", "Ctrl + H", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Spell check...", Message::Todo), - ], - ), - MenuTree::with_children( - menu_root("View"), - vec![ - MenuTree::with_children( - menu_folder("Indentation"), - vec![ - menu_item("Automatic indentation", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Tab width: 1", Message::Todo), - menu_item("Tab width: 2", Message::Todo), - menu_item("Tab width: 4", Message::Todo), - menu_item("Tab width: 8", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Convert indentation to spaces", Message::Todo), - menu_item("Convert indentation to tabs", Message::Todo), - ], - ), - MenuTree::new(horizontal_rule(1)), - menu_item("Word wrap", Message::Todo), - menu_item("Show line numbers", Message::Todo), - menu_item("Highlight current line", Message::Todo), - menu_item("Syntax highlighting...", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_key("Settings...", "Ctrl + ,", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("Keyboard shortcuts...", Message::Todo), - MenuTree::new(horizontal_rule(1)), - menu_item("About COSMIC Text Editor", Message::Todo), - ], - ), - ]) - .cross_offset(0) - .item_height(ItemHeight::Dynamic(40)) - .item_width(ItemWidth::Uniform(240)) - .main_offset(0) - .padding(8) - .spacing(4.0) - .into(); + let menu_bar = menu_bar(); let mut tab_column = widget::column::with_capacity(3).padding([0, 16]); diff --git a/src/menu.rs b/src/menu.rs new file mode 100644 index 0000000..17090fb --- /dev/null +++ b/src/menu.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::{ + //TODO: export in cosmic::widget + iced::{widget::horizontal_rule, Alignment, Length}, + theme, + widget::{ + self, button, horizontal_space, + menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}, + text, + }, + Element, +}; + +use crate::Message; + +pub fn menu_bar<'a>() -> Element<'a, Message> { + //TODO: port to libcosmic + let menu_root = |label| { + button(label) + .padding([4, 12]) + .style(theme::Button::MenuRoot) + }; + + macro_rules! menu_button { + ($($x:expr),+ $(,)?) => ( + button( + widget::Row::with_children( + vec![$(Element::from($x)),+] + ) + .align_items(Alignment::Center) + ) + .height(Length::Fixed(32.0)) + .padding([4, 16]) + .width(Length::Fill) + .style(theme::Button::MenuItem) + ); + } + + let menu_folder = |label| menu_button!(text(label), horizontal_space(Length::Fill), text(">")); + + let menu_item = |label, message| MenuTree::new(menu_button!(text(label)).on_press(message)); + + let menu_key = |label, key, message| { + MenuTree::new( + menu_button!(text(label), horizontal_space(Length::Fill), text(key)).on_press(message), + ) + }; + + MenuBar::new(vec![ + MenuTree::with_children( + menu_root("File"), + vec![ + menu_key("New file", "Ctrl + N", Message::New), + menu_key("New window", "Ctrl + Shift + N", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_key("Open file...", "Ctrl + O", Message::OpenDialog), + MenuTree::with_children( + menu_folder("Open recent"), + vec![menu_item("TODO", Message::Todo)], + ), + MenuTree::new(horizontal_rule(1)), + menu_key("Save", "Ctrl + S", Message::Save), + menu_key("Save as...", "Ctrl + Shift + S", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Revert all changes", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Document statistics...", Message::Todo), + menu_item("Document type...", Message::Todo), + menu_item("Encoding...", Message::Todo), + menu_item("Print", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_key("Quit", "Ctrl + Q", Message::Todo), + ], + ), + MenuTree::with_children( + menu_root("Edit"), + vec![ + menu_key("Undo", "Ctrl + Z", Message::Todo), + menu_key("Redo", "Ctrl + Shift + Z", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_key("Cut", "Ctrl + X", Message::Todo), + menu_key("Copy", "Ctrl + C", Message::Todo), + menu_key("Paste", "Ctrl + V", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_key("Find", "Ctrl + F", Message::Todo), + menu_key("Replace", "Ctrl + H", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Spell check...", Message::Todo), + ], + ), + MenuTree::with_children( + menu_root("View"), + vec![ + MenuTree::with_children( + menu_folder("Indentation"), + vec![ + menu_item("Automatic indentation", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Tab width: 1", Message::Todo), + menu_item("Tab width: 2", Message::Todo), + menu_item("Tab width: 4", Message::Todo), + menu_item("Tab width: 8", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Convert indentation to spaces", Message::Todo), + menu_item("Convert indentation to tabs", Message::Todo), + ], + ), + MenuTree::new(horizontal_rule(1)), + menu_item("Word wrap", Message::Todo), + menu_item("Show line numbers", Message::Todo), + menu_item("Highlight current line", Message::Todo), + menu_item("Syntax highlighting...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_key("Settings...", "Ctrl + ,", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("Keyboard shortcuts...", Message::Todo), + MenuTree::new(horizontal_rule(1)), + menu_item("About COSMIC Text Editor", Message::Todo), + ], + ), + ]) + .cross_offset(0) + .item_height(ItemHeight::Dynamic(40)) + .item_width(ItemWidth::Uniform(240)) + .main_offset(0) + .padding(8) + .spacing(4.0) + .into() +} diff --git a/src/menu_list.rs b/src/menu_list.rs deleted file mode 100644 index 5bcea71..0000000 --- a/src/menu_list.rs +++ /dev/null @@ -1,622 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic::{ - iced::{ - alignment, - event::{self, Event}, - keyboard, mouse, overlay, - overlay::menu::{self, Menu}, - touch, - widget::container, - widget::scrollable, - Background, Color, Element, Length, Padding, Rectangle, Size, - }, - iced_core::{ - clipboard::Clipboard, - layout::{self, Layout}, - renderer, - text::{self, LineHeight, Shaping, Text}, - widget::tree::{self, Tree}, - Shell, Widget, - }, -}; -use std::borrow::Cow; - -/// The appearance of a pick list. -#[derive(Debug, Clone, Copy)] -pub struct Appearance { - /// The text [`Color`] of the pick list. - pub text_color: Color, - /// The placeholder [`Color`] of the pick list. - pub placeholder_color: Color, - /// The [`Background`] of the pick list. - pub background: Background, - /// The border radius of the pick list. - pub border_radius: f32, - /// The border width of the pick list. - pub border_width: f32, - /// The border color of the pick list. - pub border_color: Color, -} - -/// A set of rules that dictate the style of a container. -pub trait StyleSheet { - /// The supported style of the [`StyleSheet`]. - type Style: Default + Clone; - - /// Produces the active [`Appearance`] of a pick list. - fn active(&self, style: &::Style) -> Appearance; - - /// Produces the hovered [`Appearance`] of a pick list. - fn hovered(&self, style: &::Style) -> Appearance; -} - -impl StyleSheet for cosmic::theme::Theme { - type Style = (); - - fn active(&self, _style: &()) -> Appearance { - let cosmic = &self.cosmic().primary.component; - - Appearance { - text_color: cosmic.on.into(), - background: Color::TRANSPARENT.into(), - placeholder_color: cosmic.on.into(), - border_radius: 8.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - } - } - - fn hovered(&self, style: &()) -> Appearance { - let cosmic = &self.cosmic().primary.component; - - Appearance { - background: Background::Color(cosmic.hover.into()), - ..self.active(style) - } - } -} - -/// A widget for selecting a single value from a list of options. -#[allow(missing_debug_implementations)] -pub struct MenuList<'a, T, Message, Renderer> -where - [T]: ToOwned>, - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, -{ - on_selected: Box Message + 'a>, - options: Cow<'a, [T]>, - placeholder: Option, - selected: Option, - width: Length, - padding: Padding, - text_size: Option, - font: Renderer::Font, - style: ::Style, -} - -impl<'a, T: 'a, Message, Renderer> MenuList<'a, T, Message, Renderer> -where - T: ToString + Eq, - [T]: ToOwned>, - Renderer: text::Renderer, - ::Font: std::default::Default, - Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet, - ::Style: From<::Style>, -{ - /// The default padding of a [`MenuList`]. - pub const DEFAULT_PADDING: Padding = Padding::new(8.0); - - /// Creates a new [`MenuList`] with the given list of options, the current - /// selected value, and the message to produce when an option is selected. - pub fn new( - options: impl Into>, - selected: Option, - on_selected: impl Fn(T) -> Message + 'a, - ) -> Self { - Self { - on_selected: Box::new(on_selected), - options: options.into(), - placeholder: None, - selected, - width: Length::Shrink, - text_size: None, - padding: Self::DEFAULT_PADDING, - font: Default::default(), - style: Default::default(), - } - } - - /// Sets the placeholder of the [`MenuList`]. - pub fn placeholder(mut self, placeholder: impl Into) -> Self { - self.placeholder = Some(placeholder.into()); - self - } - - /// Sets the width of the [`MenuList`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the [`Padding`] of the [`MenuList`]. - pub fn padding>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`MenuList`]. - pub fn text_size(mut self, size: u16) -> Self { - self.text_size = Some(size); - self - } - - /// Sets the font of the [`MenuList`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; - self - } - - /// Sets the style of the [`MenuList`]. - pub fn style(mut self, style: impl Into<::Style>) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, T: 'a, Message, Renderer> Widget for MenuList<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet, - ::Style: From<::Style>, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - tree::State::new(State::::new()) - } - - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - layout( - renderer, - limits, - self.width, - self.padding, - self.text_size, - &self.font, - self.placeholder.as_deref(), - &self.options, - ) - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: Event, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - _viewport: &Rectangle, - ) -> event::Status { - update( - event, - layout, - cursor_position, - shell, - self.on_selected.as_ref(), - self.selected.as_ref(), - &self.options, - || tree.state.downcast_mut::>(), - ) - } - - fn mouse_interaction( - &self, - _tree: &Tree, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse_interaction(layout, cursor_position) - } - - fn draw( - &self, - _tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - _viewport: &Rectangle, - ) { - draw( - renderer, - theme, - layout, - cursor_position, - self.padding, - self.text_size, - &self.font, - self.placeholder.as_deref(), - self.selected.as_ref(), - &self.style, - ) - } - - fn overlay<'b>( - &'b mut self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - let state = tree.state.downcast_mut::>(); - - overlay( - renderer, - layout, - state, - self.padding, - self.text_size, - self.font.clone(), - &self.options, - &self.on_selected, - self.style.clone(), - ) - } -} - -impl<'a, T: 'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - T: Clone + ToString + Eq + 'static, - [T]: ToOwned>, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet, - ::Style: From<::Style>, -{ - fn from(menu_list: MenuList<'a, T, Message, Renderer>) -> Self { - Self::new(menu_list) - } -} - -/// The local state of a [`MenuList`]. -#[derive(Debug)] -pub struct State { - menu: menu::State, - keyboard_modifiers: keyboard::Modifiers, - is_open: bool, - hovered_option: Option, - last_selection: Option, -} - -impl State { - /// Creates a new [`State`] for a [`MenuList`]. - pub fn new() -> Self { - Self { - menu: menu::State::default(), - keyboard_modifiers: keyboard::Modifiers::default(), - is_open: bool::default(), - hovered_option: Option::default(), - last_selection: Option::default(), - } - } -} - -impl Default for State { - fn default() -> Self { - Self::new() - } -} - -/// Computes the layout of a [`MenuList`]. -pub fn layout( - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - text_size: Option, - font: &Renderer::Font, - placeholder: Option<&str>, - options: &[T], -) -> layout::Node -where - Renderer: text::Renderer, - T: ToString, -{ - use std::f32; - - let limits = limits.width(width).height(Length::Shrink).pad(padding); - - let text_size = text_size.unwrap_or_else(|| renderer.default_size() as u16); - - let max_width = match width { - Length::Shrink => { - let measure = |label: &str| -> u32 { - let size = renderer.measure( - label, - text_size as f32, - LineHeight::default(), - font.clone(), - Size::new(f32::INFINITY, f32::INFINITY), - Shaping::Advanced, - ); - - size.width.round() as u32 - }; - - placeholder.map(measure).unwrap_or(100) - } - _ => 0, - }; - - let size = { - let intrinsic = Size::new(max_width as f32, f32::from(text_size)); - - limits.resolve(intrinsic).pad(padding) - }; - - layout::Node::new(size) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`MenuList`] -/// accordingly. -pub fn update<'a, T, Message>( - event: Event, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - shell: &mut Shell<'_, Message>, - on_selected: &dyn Fn(T) -> Message, - selected: Option<&T>, - options: &[T], - state: impl FnOnce() -> &'a mut State, -) -> event::Status -where - T: PartialEq + Clone + 'a, -{ - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - - let event_status = if state.is_open { - // Event wasn't processed by overlay, so cursor was clicked either outside it's - // bounds or on the drop-down, either way we close the overlay. - state.is_open = false; - - event::Status::Captured - } else if cursor_position.is_over(layout.bounds()) { - state.is_open = true; - state.hovered_option = options.iter().position(|option| Some(option) == selected); - - event::Status::Captured - } else { - event::Status::Ignored - }; - - if let Some(last_selection) = state.last_selection.take() { - shell.publish((on_selected)(last_selection)); - - state.is_open = false; - - event::Status::Captured - } else { - event_status - } - } - Event::Mouse(mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Lines { y, .. }, - }) => { - let state = state(); - - if state.keyboard_modifiers.command() - && cursor_position.is_over(layout.bounds()) - && !state.is_open - { - fn find_next<'a, T: PartialEq>( - selected: &'a T, - mut options: impl Iterator, - ) -> Option<&'a T> { - let _ = options.find(|&option| option == selected); - - options.next() - } - - let next_option = if y < 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter()) - } else { - options.first() - } - } else if y > 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter().rev()) - } else { - options.last() - } - } else { - None - }; - - if let Some(next_option) = next_option { - shell.publish((on_selected)(next_option.clone())); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - - event::Status::Ignored - } - _ => event::Status::Ignored, - } -} - -/// Returns the current [`mouse::Interaction`] of a [`MenuList`]. -pub fn mouse_interaction(layout: Layout<'_>, cursor_position: mouse::Cursor) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor_position.is_over(bounds); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } -} - -/// Returns the current overlay of a [`MenuList`]. -pub fn overlay<'a, T, Message, Renderer>( - renderer: &Renderer, - layout: Layout<'_>, - state: &'a mut State, - padding: Padding, - text_size: Option, - font: Renderer::Font, - options: &'a [T], - on_selected: &'a dyn Fn(T) -> Message, - style: ::Style, -) -> Option> -where - T: Clone + ToString, - Message: 'a, - Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet, - ::Style: From<::Style>, -{ - if state.is_open { - let bounds = layout.bounds(); - - let text_size = text_size.unwrap_or_else(|| renderer.default_size() as u16); - - let width = { - let measure = |label: &str| -> u32 { - let size = renderer.measure( - label, - text_size as f32, - LineHeight::default(), - font.clone(), - Size::new(f32::INFINITY, f32::INFINITY), - Shaping::Advanced, - ); - - size.width.round() as u32 - }; - - let labels = options.iter().map(ToString::to_string); - - let labels_width = labels.map(|label| measure(&label)).max().unwrap_or(100); - - labels_width as f32 + padding.left + padding.right - }; - - let menu = Menu::new( - &mut state.menu, - options, - &mut state.hovered_option, - |option| { - state.is_open = false; - - (on_selected)(option) - }, - None, - ) - .width(width) - .padding(padding) - .font(font) - .style(style) - .text_size(text_size); - - Some(menu.overlay(layout.position(), bounds.height)) - } else { - None - } -} - -/// Draws a [`MenuList`]. -pub fn draw( - renderer: &mut Renderer, - theme: &Renderer::Theme, - layout: Layout<'_>, - cursor_position: mouse::Cursor, - padding: Padding, - text_size: Option, - font: &Renderer::Font, - placeholder: Option<&str>, - selected: Option<&T>, - style: &::Style, -) where - Renderer: text::Renderer, - Renderer::Theme: StyleSheet, - T: ToString, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor_position.is_over(bounds); - let is_selected = selected.is_some(); - - let style = if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border_color: style.border_color, - border_width: style.border_width, - border_radius: style.border_radius.into(), - }, - style.background, - ); - - let label = selected.map(ToString::to_string); - - if let Some(label) = label.as_deref().or(placeholder) { - let text_size = text_size.map_or_else(|| renderer.default_size(), f32::from); - - renderer.fill_text(Text { - content: label, - bounds: Rectangle { - x: bounds.x + f32::from(padding.left), - y: bounds.center_y() - text_size / 2.0, - width: bounds.width - f32::from(padding.horizontal()), - height: text_size, - }, - size: text_size, - line_height: LineHeight::default(), - color: if is_selected { - style.text_color - } else { - style.placeholder_color - }, - font: font.clone(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: Shaping::Advanced, - }); - } -} diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..07b13e0 --- /dev/null +++ b/src/project.rs @@ -0,0 +1,28 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +pub struct Project { + path: PathBuf, + name: String, +} + +impl Project { + pub fn new>(path: P) -> io::Result { + let path = fs::canonicalize(path)?; + let name = path + .file_name() + .ok_or(io::Error::new( + io::ErrorKind::Other, + format!("Path {:?} has no file name", path), + ))? + .to_str() + .ok_or(io::Error::new( + io::ErrorKind::Other, + format!("Path {:?} is not valid UTF-8", path), + ))? + .to_string(); + Ok(Self { path, name }) + } +} diff --git a/src/tab.rs b/src/tab.rs new file mode 100644 index 0000000..30af24e --- /dev/null +++ b/src/tab.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_text::{Attrs, Buffer, Edit, Metrics, SyntaxEditor, ViEditor}; +use std::{fs, path::PathBuf, sync::Mutex}; + +use crate::{FONT_SYSTEM, SYNTAX_SYSTEM}; + +static FONT_SIZES: &'static [Metrics] = &[ + Metrics::new(10.0, 14.0), // Caption + Metrics::new(14.0, 20.0), // Body + Metrics::new(20.0, 28.0), // Title 4 + Metrics::new(24.0, 32.0), // Title 3 + Metrics::new(28.0, 36.0), // Title 2 + Metrics::new(32.0, 44.0), // Title 1 +]; + +pub struct Tab { + pub path_opt: Option, + attrs: Attrs<'static>, + pub editor: Mutex>, +} + +impl Tab { + pub fn new() -> Self { + let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace); + + let editor = SyntaxEditor::new( + Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), FONT_SIZES[1 /* Body */]), + &SYNTAX_SYSTEM, + "base16-eighties.dark", + ) + .unwrap(); + + let mut editor = ViEditor::new(editor); + editor.set_passthrough(false); + + Self { + path_opt: None, + attrs, + editor: Mutex::new(editor), + } + } + + pub fn open(&mut self, path: PathBuf) { + let mut editor = self.editor.lock().unwrap(); + let mut font_system = FONT_SYSTEM.lock().unwrap(); + let mut editor = editor.borrow_with(&mut font_system); + match editor.load_text(&path, self.attrs) { + Ok(()) => { + log::info!("opened '{}'", path.display()); + self.path_opt = Some(path); + } + Err(err) => { + log::error!("failed to open '{}': {}", path.display(), err); + self.path_opt = None; + } + } + } + + pub fn save(&mut self) { + if let Some(path) = &self.path_opt { + let editor = self.editor.lock().unwrap(); + let mut text = String::new(); + for line in editor.buffer().lines.iter() { + text.push_str(line.text()); + text.push('\n'); + } + match fs::write(path, text) { + Ok(()) => { + log::info!("saved '{}'", path.display()); + } + Err(err) => { + log::error!("failed to save '{}': {}", path.display(), err); + } + } + } else { + log::warn!("tab has no path yet"); + } + } + + pub fn title(&self) -> String { + //TODO: show full title when there is a conflict + if let Some(path) = &self.path_opt { + match path.file_name() { + Some(file_name_os) => match file_name_os.to_str() { + Some(file_name) => file_name.to_string(), + None => format!("{}", path.display()), + }, + None => format!("{}", path.display()), + } + } else { + "New document".to_string() + } + } +} From a472b47ee373248fe5f8646f7e0966f0e6e123fd Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 26 Oct 2023 11:25:14 -0600 Subject: [PATCH 07/14] Use more menu theming from libcosmic --- Cargo.lock | 27 ++++++++++++++++++++++----- Cargo.toml | 5 +++-- src/main.rs | 13 ++++++------- src/menu.rs | 18 ++++++------------ 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03b2044..ec26fd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,6 +839,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -852,6 +853,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "quote", "syn 1.0.109", @@ -914,6 +916,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "almost", "cosmic-config", @@ -2044,6 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iced" version = "0.10.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_accessibility", "iced_core", @@ -2059,6 +2063,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "accesskit", "accesskit_unix", @@ -2068,6 +2073,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.10.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2082,6 +2088,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.7.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "futures", "iced_core", @@ -2093,6 +2100,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.9.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2110,6 +2118,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2122,6 +2131,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.1" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_accessibility", "iced_core", @@ -2133,6 +2143,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "enum-repr", "float-cmp", @@ -2156,6 +2167,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.9.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_core", "once_cell", @@ -2165,6 +2177,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "bytemuck", "cosmic-text 0.9.0", @@ -2182,6 +2195,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.11.1" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2203,6 +2217,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.3" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_renderer", "iced_runtime", @@ -2217,6 +2232,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.10.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "iced_graphics", "iced_runtime", @@ -2463,6 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libcosmic" version = "0.1.0" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" dependencies = [ "apply", "ashpd", @@ -4531,14 +4548,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef75d881185fd2df4a040793927c153d863651108a93c7e17a9e591baa95cc6" +checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.4", + "toml_edit 0.20.5", ] [[package]] @@ -4563,9 +4580,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380f9e8120405471f7c9ad1860a713ef5ece6a670c7eae39225e477340f32fc4" +checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" dependencies = [ "indexmap 2.0.2", "serde", diff --git a/Cargo.toml b/Cargo.toml index de4c39d..630be1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,10 @@ features = ["syntect", "vi"] #path = "../cosmic-text" [dependencies.libcosmic] -#git = "https://github.com/pop-os/libcosmic" +git = "https://github.com/pop-os/libcosmic" +branch = "menu" default-features = false -path = "../libcosmic" +#path = "../libcosmic" [features] default = ["wayland"] diff --git a/src/main.rs b/src/main.rs index 7db8fb3..9d12ee2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,7 @@ use cosmic::{ app::{message, Command, Core, Settings}, executor, - iced::{ - widget::{column, text}, - Length, Limits, - }, + iced::{widget::text, Length, Limits}, widget::{self, icon, segmented_button, view_switcher}, ApplicationExt, Element, }; @@ -230,9 +227,11 @@ impl cosmic::Application for App { Command::none() } - fn view(&self) -> Element { - let menu_bar = menu_bar(); + fn header_start(&self) -> Vec> { + vec![menu_bar()] + } + fn view(&self) -> Element { let mut tab_column = widget::column::with_capacity(3).padding([0, 16]); tab_column = tab_column.push( @@ -287,7 +286,7 @@ impl cosmic::Application for App { } project_row = project_row.push(tab_column); - let content: Element<_> = column![menu_bar, project_row].into(); + let content: Element<_> = project_row.into(); // Uncomment to debug layout: //content.explain(cosmic::iced::Color::WHITE) diff --git a/src/menu.rs b/src/menu.rs index 17090fb..79c2154 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -5,9 +5,8 @@ use cosmic::{ iced::{widget::horizontal_rule, Alignment, Length}, theme, widget::{ - self, button, horizontal_space, + self, horizontal_space, menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}, - text, }, Element, }; @@ -17,14 +16,14 @@ use crate::Message; pub fn menu_bar<'a>() -> Element<'a, Message> { //TODO: port to libcosmic let menu_root = |label| { - button(label) + widget::button(label) .padding([4, 12]) .style(theme::Button::MenuRoot) }; macro_rules! menu_button { ($($x:expr),+ $(,)?) => ( - button( + widget::button( widget::Row::with_children( vec![$(Element::from($x)),+] ) @@ -37,14 +36,12 @@ pub fn menu_bar<'a>() -> Element<'a, Message> { ); } - let menu_folder = |label| menu_button!(text(label), horizontal_space(Length::Fill), text(">")); + let menu_folder = |label| menu_button!(label, horizontal_space(Length::Fill), ">"); - let menu_item = |label, message| MenuTree::new(menu_button!(text(label)).on_press(message)); + let menu_item = |label, message| MenuTree::new(menu_button!(label).on_press(message)); let menu_key = |label, key, message| { - MenuTree::new( - menu_button!(text(label), horizontal_space(Length::Fill), text(key)).on_press(message), - ) + MenuTree::new(menu_button!(label, horizontal_space(Length::Fill), key).on_press(message)) }; MenuBar::new(vec![ @@ -120,11 +117,8 @@ pub fn menu_bar<'a>() -> Element<'a, Message> { ], ), ]) - .cross_offset(0) .item_height(ItemHeight::Dynamic(40)) .item_width(ItemWidth::Uniform(240)) - .main_offset(0) - .padding(8) .spacing(4.0) .into() } From 4b25e866b543de22f7cf8e9ea1b87b435732388e Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 26 Oct 2023 15:16:10 -0600 Subject: [PATCH 08/14] Update libcosmic --- Cargo.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec26fd2..6bab1c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,7 +839,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -853,7 +853,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "quote", "syn 1.0.109", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "almost", "cosmic-config", @@ -929,9 +929,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -2047,7 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iced" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_accessibility", "iced_core", @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "accesskit", "accesskit_unix", @@ -2073,7 +2073,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2088,7 +2088,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.7.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "futures", "iced_core", @@ -2100,7 +2100,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2118,7 +2118,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2131,7 +2131,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.1" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_accessibility", "iced_core", @@ -2143,7 +2143,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "enum-repr", "float-cmp", @@ -2167,7 +2167,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_core", "once_cell", @@ -2177,7 +2177,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "bytemuck", "cosmic-text 0.9.0", @@ -2195,7 +2195,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.11.1" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2217,7 +2217,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.3" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_renderer", "iced_runtime", @@ -2232,7 +2232,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "iced_graphics", "iced_runtime", @@ -2479,7 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#496bebbf985f81b0fa25fbf6d24cbb1cfbb9437f" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" dependencies = [ "apply", "ashpd", From 8566c6f31f570552004ba07be2f0090268e0cf8b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 09:30:52 -0600 Subject: [PATCH 09/14] Add configuration and new tab button --- src/main.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++------- src/menu.rs | 4 ++-- src/tab.rs | 13 +++++++++++-- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9d12ee2..b675ed0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,12 @@ use cosmic::{ app::{message, Command, Core, Settings}, executor, - iced::{widget::text, Length, Limits}, - widget::{self, icon, segmented_button, view_switcher}, + iced::{ + widget::{row, text}, + Alignment, Length, Limits, + }, + style, + widget::{self, button, icon, segmented_button, view_switcher}, ApplicationExt, Element, }; use cosmic_text::{FontSystem, SyntaxSystem, ViMode}; @@ -42,10 +46,23 @@ fn main() -> Result<(), Box> { Ok(()) } +#[derive(Clone, Debug)] +pub struct Config { + wrap: bool, +} + +impl Config { + //TODO: load from cosmic-config + pub fn new() -> Self { + Self { wrap: false } + } +} + pub struct App { core: Core, projects: Vec, tab_model: segmented_button::SingleSelectModel, + config: Config, } #[allow(dead_code)] @@ -58,6 +75,7 @@ pub enum Message { TabActivate(segmented_button::Entity), TabClose(segmented_button::Entity), Todo, + Wrap(bool), } impl App { @@ -80,6 +98,7 @@ impl App { pub fn open_tab(&mut self, path_opt: Option) { let mut tab = Tab::new(); + tab.set_config(&self.config); if let Some(path) = path_opt { tab.open(path); } @@ -131,6 +150,7 @@ impl cosmic::Application for App { core, projects: Vec::new(), tab_model: segmented_button::Model::builder().build(), + config: Config::new(), }; for arg in env::args().skip(1) { @@ -222,23 +242,40 @@ impl cosmic::Application for App { Message::Todo => { log::warn!("TODO"); } + Message::Wrap(wrap) => { + self.config.wrap = wrap; + //TODO: provide iterator over data + let entities: Vec<_> = self.tab_model.iter().collect(); + for entity in entities { + if let Some(tab) = self.tab_model.data_mut::(entity) { + tab.set_config(&self.config); + } + } + } } Command::none() } fn header_start(&self) -> Vec> { - vec![menu_bar()] + vec![menu_bar(&self.config)] } fn view(&self) -> Element { let mut tab_column = widget::column::with_capacity(3).padding([0, 16]); tab_column = tab_column.push( - view_switcher::horizontal(&self.tab_model) - .on_activate(Message::TabActivate) - .on_close(Message::TabClose) - .width(Length::Shrink), + row![ + view_switcher::horizontal(&self.tab_model) + .on_activate(Message::TabActivate) + .on_close(Message::TabClose) + .width(Length::Shrink), + button(icon::from_name("list-add-symbolic").size(16).icon()) + .on_press(Message::New) + .padding(8) + .style(style::Button::Icon) + ] + .align_items(Alignment::Center), ); match self.active_tab() { diff --git a/src/menu.rs b/src/menu.rs index 79c2154..bd9af8c 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -11,9 +11,9 @@ use cosmic::{ Element, }; -use crate::Message; +use crate::{Config, Message}; -pub fn menu_bar<'a>() -> Element<'a, Message> { +pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> { //TODO: port to libcosmic let menu_root = |label| { widget::button(label) diff --git a/src/tab.rs b/src/tab.rs index 30af24e..42d7fb6 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use cosmic_text::{Attrs, Buffer, Edit, Metrics, SyntaxEditor, ViEditor}; +use cosmic_text::{Attrs, Buffer, Edit, Metrics, SyntaxEditor, ViEditor, Wrap}; use std::{fs, path::PathBuf, sync::Mutex}; -use crate::{FONT_SYSTEM, SYNTAX_SYSTEM}; +use crate::{Config, FONT_SYSTEM, SYNTAX_SYSTEM}; static FONT_SIZES: &'static [Metrics] = &[ Metrics::new(10.0, 14.0), // Caption @@ -41,6 +41,15 @@ impl Tab { } } + pub fn set_config(&mut self, config: &Config) { + let mut editor = self.editor.lock().unwrap(); + let mut font_system = FONT_SYSTEM.lock().unwrap(); + let mut editor = editor.borrow_with(&mut font_system); + editor + .buffer_mut() + .set_wrap(if config.wrap { Wrap::Word } else { Wrap::None }); + } + pub fn open(&mut self, path: PathBuf) { let mut editor = self.editor.lock().unwrap(); let mut font_system = FONT_SYSTEM.lock().unwrap(); From 05368dea2c996dd880c69edda940ac6ca226a5b1 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 11:43:30 -0600 Subject: [PATCH 10/14] Implement project tree --- Cargo.lock | 84 +++++++++++----------- src/main.rs | 184 ++++++++++++++++++++++++++++++++++++++++++------- src/project.rs | 66 ++++++++++++++++-- 3 files changed, 259 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bab1c6..716a443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ dependencies = [ "log", "parking", "polling 2.8.0", - "rustix 0.37.26", + "rustix 0.37.27", "slab", "socket2", "waker-fn", @@ -331,7 +331,7 @@ dependencies = [ "cfg-if", "event-listener 3.0.0", "futures-lite", - "rustix 0.38.20", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -358,7 +358,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.20", + "rustix 0.38.21", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -405,7 +405,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4d45f362125ed144544e57b0ec6de8fd6a296d41a6252fc4a20c0cf12e9ed3a" dependencies = [ - "rustix 0.38.20", + "rustix 0.38.21", "tempfile", "windows-sys 0.48.0", ] @@ -624,7 +624,7 @@ dependencies = [ "bitflags 2.4.1", "log", "polling 3.2.0", - "rustix 0.38.20", + "rustix 0.38.21", "slab", "thiserror", ] @@ -636,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop 0.12.3", - "rustix 0.38.20", + "rustix 0.38.21", "wayland-backend 0.3.2", "wayland-client 0.31.1", ] @@ -839,7 +839,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -853,7 +853,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "quote", "syn 1.0.109", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "almost", "cosmic-config", @@ -2047,7 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iced" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_accessibility", "iced_core", @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "accesskit", "accesskit_unix", @@ -2073,7 +2073,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2088,7 +2088,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.7.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "futures", "iced_core", @@ -2100,7 +2100,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2118,7 +2118,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2131,7 +2131,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.1" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_accessibility", "iced_core", @@ -2143,7 +2143,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "enum-repr", "float-cmp", @@ -2167,7 +2167,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_core", "once_cell", @@ -2177,7 +2177,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "bytemuck", "cosmic-text 0.9.0", @@ -2195,7 +2195,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.11.1" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2217,7 +2217,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.3" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_renderer", "iced_runtime", @@ -2232,7 +2232,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "iced_graphics", "iced_runtime", @@ -2357,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.20", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -2479,7 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e" +source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c" dependencies = [ "apply", "ashpd", @@ -3483,7 +3483,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.20", + "rustix 0.38.21", "tracing", "windows-sys 0.48.0", ] @@ -3841,9 +3841,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.26" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -3855,9 +3855,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.4.1", "errno", @@ -4375,14 +4375,14 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.3.5", - "rustix 0.38.20", + "redox_syscall 0.4.1", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -4548,14 +4548,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.5", + "toml_edit 0.20.7", ] [[package]] @@ -4580,9 +4580,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.0.2", "serde", @@ -5854,18 +5854,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f" +checksum = "c552e97c5a9b90bc8ddc545b5106e798807376356688ebaa3aee36f44f8c4b9e" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d" +checksum = "964bc0588d7ac1c0243d0427ef08482618313702bbb014806cb7ab3da34d3d99" dependencies = [ "proc-macro2", "quote", diff --git a/src/main.rs b/src/main.rs index b675ed0..ef219b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,12 @@ use cosmic::{ Alignment, Length, Limits, }, style, - widget::{self, button, icon, segmented_button, view_switcher}, + widget::{self, button, icon, nav_bar, segmented_button, view_switcher}, ApplicationExt, Element, }; use cosmic_text::{FontSystem, SyntaxSystem, ViMode}; use std::{ - env, + env, fs, path::{Path, PathBuf}, sync::Mutex, }; @@ -21,7 +21,7 @@ use std::{ use self::menu::menu_bar; mod menu; -use self::project::Project; +use self::project::ProjectNode; mod project; use self::tab::Tab; @@ -60,7 +60,7 @@ impl Config { pub struct App { core: Core, - projects: Vec, + nav_model: segmented_button::SingleSelectModel, tab_model: segmented_button::SingleSelectModel, config: Config, } @@ -87,13 +87,95 @@ impl App { self.tab_model.active_data_mut() } - pub fn open_project>(&mut self, path: P) { - match Project::new(&path) { - Ok(project) => self.projects.push(project), + fn open_folder>(&mut self, path: P, mut position: u16, indent: u16) { + let read_dir = match fs::read_dir(&path) { + Ok(ok) => ok, Err(err) => { - log::error!("failed to open '{}': {}", path.as_ref().display(), err); + log::error!("failed to read directory {:?}: {}", path.as_ref(), err); + return; } + }; + + let mut nodes = Vec::new(); + for entry_res in read_dir { + let entry = match entry_res { + Ok(ok) => ok, + Err(err) => { + log::error!( + "failed to read entry in directory {:?}: {}", + path.as_ref(), + err + ); + continue; + } + }; + + let entry_path = entry.path(); + let node = match ProjectNode::new(&entry_path) { + Ok(ok) => ok, + Err(err) => { + log::error!( + "failed to open directory {:?} entry {:?}: {}", + path.as_ref(), + entry_path, + err + ); + continue; + } + }; + nodes.push(node); } + + nodes.sort_by(|a, b| a.name().cmp(b.name())); + + for node in nodes { + self.nav_model + .insert() + .position(position) + .indent(indent) + .icon(icon::from_name(node.icon_name()).size(16).icon()) + .text(node.name().to_string()) + .data(node); + + position += 1; + } + } + + pub fn open_project>(&mut self, path: P) { + let node = match ProjectNode::new(&path) { + Ok(ok) => ok, + Err(err) => { + log::error!("failed to open project {:?}: {}", path.as_ref(), err); + return; + } + }; + + let project = match node { + ProjectNode::Folder { name, path, .. } => ProjectNode::Root { + name, + path, + open: true, + }, + _ => { + log::error!( + "failed to open project {:?}: not a directory", + path.as_ref() + ); + return; + } + }; + + let id = self + .nav_model + .insert() + .icon(icon::from_name(project.icon_name()).size(16).icon()) + .text(project.name().to_string()) + .data(project) + .id(); + + let position = self.nav_model.position(id).unwrap_or(0); + + self.open_folder(&path, position + 1, 1); } pub fn open_tab(&mut self, path_opt: Option) { @@ -105,8 +187,8 @@ impl App { self.tab_model .insert() .text(tab.title()) - .icon(icon::from_name("text-x-generic").icon()) - .data(tab) + .icon(icon::from_name("text-x-generic").size(16).icon()) + .data::(tab) .closable() .activate(); } @@ -148,7 +230,7 @@ impl cosmic::Application for App { fn init(core: Core, _flags: Self::Flags) -> (Self, Command) { let mut app = App { core, - projects: Vec::new(), + nav_model: nav_bar::Model::builder().build(), tab_model: segmented_button::Model::builder().build(), config: Config::new(), }; @@ -162,6 +244,11 @@ impl cosmic::Application for App { } } + // Show nav bar only if project is provided + if app.core.nav_bar_active() != app.nav_model.iter().next().is_some() { + app.core.nav_bar_toggle(); + } + // Open an empty file if no arguments provided if app.tab_model.iter().next().is_none() { app.open_tab(None); @@ -171,7 +258,65 @@ impl cosmic::Application for App { (app, command) } - fn update(&mut self, message: Message) -> Command { + fn nav_model(&self) -> Option<&nav_bar::Model> { + Some(&self.nav_model) + } + + fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { + let node = match self.nav_model.data_mut::(id) { + Some(node) => { + match node { + ProjectNode::Folder { open, .. } => { + *open = !*open; + } + _ => {} + } + node.clone() + } + None => { + log::warn!("no path found for id {:?}", id); + return Command::none(); + } + }; + + self.nav_model + .icon_set(id, icon::from_name(node.icon_name()).size(16).icon()); + + match node { + ProjectNode::Root { .. } => { + log::warn!("TODO: root node click"); + Command::none() + } + ProjectNode::Folder { path, open, .. } => { + let position = self.nav_model.position(id).unwrap_or(0); + let indent = self.nav_model.indent(id).unwrap_or(0); + if open { + self.open_folder(path, position + 1, indent + 1); + } else { + loop { + let child_id = match self.nav_model.entity_at(position + 1) { + Some(some) => some, + None => break, + }; + + if self.nav_model.indent(child_id).unwrap_or(0) > indent { + self.nav_model.remove(child_id); + } else { + break; + } + } + } + Command::none() + } + ProjectNode::File { path, .. } => { + //TODO: go to already open file if possible + self.open_tab(Some(path.clone())); + self.update_title() + } + } + } + + fn update(&mut self, message: Message) -> Command { match message { Message::New => { self.open_tab(None); @@ -181,7 +326,6 @@ impl cosmic::Application for App { return Command::perform( async { if let Some(handle) = rfd::AsyncFileDialog::new().pick_file().await { - println!("{}", handle.path().display()); message::app(Message::Open(handle.path().to_owned())) } else { message::none() @@ -311,19 +455,7 @@ impl cosmic::Application for App { } }; - let mut project_row = widget::row::with_capacity(2); - if !self.projects.is_empty() { - /*TODO: project tree view - let mut project_list = widget::column::with_capacity(self.projects.len()); - for project in self.projects.iter() { - project_list = project_list.push(widget::text(&project.name)); - } - project_row = project_row.push(project_list); - */ - } - project_row = project_row.push(tab_column); - - let content: Element<_> = project_row.into(); + let content: Element<_> = tab_column.into(); // Uncomment to debug layout: //content.explain(cosmic::iced::Color::WHITE) diff --git a/src/project.rs b/src/project.rs index 07b13e0..b95900e 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,28 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only + use std::{ fs, io, path::{Path, PathBuf}, }; -pub struct Project { - path: PathBuf, - name: String, +#[derive(Clone, Debug)] +pub enum ProjectNode { + Root { + name: String, + path: PathBuf, + open: bool, + }, + Folder { + name: String, + path: PathBuf, + open: bool, + }, + File { + name: String, + path: PathBuf, + }, } -impl Project { +impl ProjectNode { pub fn new>(path: P) -> io::Result { let path = fs::canonicalize(path)?; let name = path .file_name() .ok_or(io::Error::new( io::ErrorKind::Other, - format!("Path {:?} has no file name", path), + format!("path {:?} has no file name", path), ))? .to_str() .ok_or(io::Error::new( io::ErrorKind::Other, - format!("Path {:?} is not valid UTF-8", path), + format!("path {:?} is not valid UTF-8", path), ))? .to_string(); - Ok(Self { path, name }) + Ok(if path.is_dir() { + Self::Folder { + path, + name, + open: false, + } + } else { + Self::File { path, name } + }) + } + + pub fn icon_name(&self) -> &str { + match self { + //TODO: different icon for project? + ProjectNode::Root { open, .. } => { + if *open { + "go-down-symbolic" + } else { + "go-next-symbolic" + } + } + ProjectNode::Folder { open, .. } => { + if *open { + "go-down-symbolic" + } else { + "go-next-symbolic" + } + } + ProjectNode::File { .. } => "text-x-generic", + } + } + + pub fn name(&self) -> &str { + match self { + ProjectNode::Root { name, .. } => name, + ProjectNode::Folder { name, .. } => name, + ProjectNode::File { name, .. } => name, + } } } From 0117a7bc0251986920a049871d1b502951c65218 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 12:28:25 -0600 Subject: [PATCH 11/14] Sort folders first --- src/main.rs | 44 ++++++++++++++++++++------------------------ src/project.rs | 48 +++++++++++++++++++++++++++++------------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index ef219b0..850268f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,7 +126,7 @@ impl App { nodes.push(node); } - nodes.sort_by(|a, b| a.name().cmp(b.name())); + nodes.sort(); for node in nodes { self.nav_model @@ -143,34 +143,34 @@ impl App { pub fn open_project>(&mut self, path: P) { let node = match ProjectNode::new(&path) { - Ok(ok) => ok, + Ok(mut node) => { + match &mut node { + ProjectNode::Folder { open, root, .. } => { + *open = true; + *root = true; + } + _ => { + log::error!( + "failed to open project {:?}: not a directory", + path.as_ref() + ); + return; + } + } + node + } Err(err) => { log::error!("failed to open project {:?}: {}", path.as_ref(), err); return; } }; - let project = match node { - ProjectNode::Folder { name, path, .. } => ProjectNode::Root { - name, - path, - open: true, - }, - _ => { - log::error!( - "failed to open project {:?}: not a directory", - path.as_ref() - ); - return; - } - }; - let id = self .nav_model .insert() - .icon(icon::from_name(project.icon_name()).size(16).icon()) - .text(project.name().to_string()) - .data(project) + .icon(icon::from_name(node.icon_name()).size(16).icon()) + .text(node.name().to_string()) + .data(node) .id(); let position = self.nav_model.position(id).unwrap_or(0); @@ -283,10 +283,6 @@ impl cosmic::Application for App { .icon_set(id, icon::from_name(node.icon_name()).size(16).icon()); match node { - ProjectNode::Root { .. } => { - log::warn!("TODO: root node click"); - Command::none() - } ProjectNode::Folder { path, open, .. } => { let position = self.nav_model.position(id).unwrap_or(0); let indent = self.nav_model.indent(id).unwrap_or(0); diff --git a/src/project.rs b/src/project.rs index b95900e..e44de90 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,21 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-only use std::{ + cmp::Ordering, fs, io, path::{Path, PathBuf}, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ProjectNode { - Root { - name: String, - path: PathBuf, - open: bool, - }, Folder { name: String, path: PathBuf, open: bool, + root: bool, }, File { name: String, @@ -43,6 +40,7 @@ impl ProjectNode { path, name, open: false, + root: false, } } else { Self::File { path, name } @@ -51,30 +49,42 @@ impl ProjectNode { pub fn icon_name(&self) -> &str { match self { - //TODO: different icon for project? - ProjectNode::Root { open, .. } => { + //TODO: different icon for project root? + Self::Folder { open, .. } => { if *open { "go-down-symbolic" } else { "go-next-symbolic" } } - ProjectNode::Folder { open, .. } => { - if *open { - "go-down-symbolic" - } else { - "go-next-symbolic" - } - } - ProjectNode::File { .. } => "text-x-generic", + Self::File { .. } => "text-x-generic", } } pub fn name(&self) -> &str { match self { - ProjectNode::Root { name, .. } => name, - ProjectNode::Folder { name, .. } => name, - ProjectNode::File { name, .. } => name, + Self::Folder { name, .. } => name, + Self::File { name, .. } => name, } } } + +impl Ord for ProjectNode { + fn cmp(&self, other: &Self) -> Ordering { + match self { + // Folders are always before files + Self::Folder { .. } => match other { + Self::File { .. } => return Ordering::Less, + _ => {} + }, + _ => {} + } + self.name().cmp(other.name()) + } +} + +impl PartialOrd for ProjectNode { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} From 8dd38517abb1ddf695cce7dce45715c85b8d9bc7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 13:41:48 -0600 Subject: [PATCH 12/14] Add dialog for opening project, highlight active file in project menu --- Cargo.lock | 10 ++--- src/main.rs | 127 +++++++++++++++++++++++++++++++++++++--------------- src/menu.rs | 2 +- src/tab.rs | 16 ++++--- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 716a443..e65c524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -895,7 +895,7 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.10.0" -source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#c1e40363ab576c90edb7b78f1f257b3845558b1a" +source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#423fc2243930645036ff053e384a3ce64e70255e" dependencies = [ "fontdb 0.15.0", "libm", @@ -5854,18 +5854,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c552e97c5a9b90bc8ddc545b5106e798807376356688ebaa3aee36f44f8c4b9e" +checksum = "a1808c6e5aa42cb2b67578276190258f08679c275d92a19735aad5d359e1b154" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964bc0588d7ac1c0243d0427ef08482618313702bbb014806cb7ab3da34d3d99" +checksum = "ea597733532c780ce3e110401fc3b359b984cc78e17ac0917d3f371ffd1c1618" dependencies = [ "proc-macro2", "quote", diff --git a/src/main.rs b/src/main.rs index 850268f..e479a41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,8 +69,10 @@ pub struct App { #[derive(Clone, Debug)] pub enum Message { New, - OpenDialog, - Open(PathBuf), + OpenFileDialog, + OpenFile(PathBuf), + OpenProjectDialog, + OpenProject(PathBuf), Save, TabActivate(segmented_button::Entity), TabClose(segmented_button::Entity), @@ -194,10 +196,34 @@ impl App { } pub fn update_title(&mut self) -> Command { - let title = match self.active_tab() { - Some(tab) => tab.title(), - None => format!("No Open File"), + let (title, tab_path_opt) = match self.active_tab() { + Some(tab) => (tab.title(), tab.path_opt.clone()), + None => (format!("No Open File"), None), }; + + //TODO: is this the best place for this? + let mut active_id = segmented_button::Entity::default(); + match tab_path_opt { + Some(tab_path) => { + for id in self.nav_model.iter() { + match self.nav_model.data(id) { + Some(node) => match node { + ProjectNode::File { path, .. } => { + if path == &tab_path { + active_id = id; + break; + } + } + _ => {} + }, + None => {} + } + } + } + None => {} + } + self.nav_model.activate(active_id); + let window_title = format!("{title} - COSMIC Text Editor"); self.set_header_title(title.clone()); self.set_window_title(window_title) @@ -247,6 +273,10 @@ impl cosmic::Application for App { // Show nav bar only if project is provided if app.core.nav_bar_active() != app.nav_model.iter().next().is_some() { app.core.nav_bar_toggle(); + app.nav_model + .insert() + .icon(icon::from_name("folder-open-symbolic").size(16).icon()) + .text("Open project"); } // Open an empty file if no arguments provided @@ -263,7 +293,8 @@ impl cosmic::Application for App { } fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { - let node = match self.nav_model.data_mut::(id) { + // Toggle open state and get clone of node data + let node_opt = match self.nav_model.data_mut::(id) { Some(node) => { match node { ProjectNode::Folder { open, .. } => { @@ -271,43 +302,50 @@ impl cosmic::Application for App { } _ => {} } - node.clone() - } - None => { - log::warn!("no path found for id {:?}", id); - return Command::none(); + Some(node.clone()) } + None => None, }; - self.nav_model - .icon_set(id, icon::from_name(node.icon_name()).size(16).icon()); + match node_opt { + Some(node) => { + // Update icon + self.nav_model + .icon_set(id, icon::from_name(node.icon_name()).size(16).icon()); - match node { - ProjectNode::Folder { path, open, .. } => { - let position = self.nav_model.position(id).unwrap_or(0); - let indent = self.nav_model.indent(id).unwrap_or(0); - if open { - self.open_folder(path, position + 1, indent + 1); - } else { - loop { - let child_id = match self.nav_model.entity_at(position + 1) { - Some(some) => some, - None => break, - }; - - if self.nav_model.indent(child_id).unwrap_or(0) > indent { - self.nav_model.remove(child_id); + match node { + ProjectNode::Folder { path, open, .. } => { + let position = self.nav_model.position(id).unwrap_or(0); + let indent = self.nav_model.indent(id).unwrap_or(0); + if open { + // Open folder + self.open_folder(path, position + 1, indent + 1); } else { - break; + // Close folder + loop { + let child_id = match self.nav_model.entity_at(position + 1) { + Some(some) => some, + None => break, + }; + + if self.nav_model.indent(child_id).unwrap_or(0) > indent { + self.nav_model.remove(child_id); + } else { + break; + } + } } + Command::none() + } + ProjectNode::File { path, .. } => { + //TODO: go to already open file if possible + self.update(Message::OpenFile(path)) } } - Command::none() } - ProjectNode::File { path, .. } => { - //TODO: go to already open file if possible - self.open_tab(Some(path.clone())); - self.update_title() + None => { + // Open project + self.update(Message::OpenProjectDialog) } } } @@ -318,11 +356,11 @@ impl cosmic::Application for App { self.open_tab(None); return self.update_title(); } - Message::OpenDialog => { + Message::OpenFileDialog => { return Command::perform( async { if let Some(handle) = rfd::AsyncFileDialog::new().pick_file().await { - message::app(Message::Open(handle.path().to_owned())) + message::app(Message::OpenFile(handle.path().to_owned())) } else { message::none() } @@ -330,10 +368,25 @@ impl cosmic::Application for App { |x| x, ); } - Message::Open(path) => { + Message::OpenFile(path) => { self.open_tab(Some(path)); return self.update_title(); } + Message::OpenProjectDialog => { + return Command::perform( + async { + if let Some(handle) = rfd::AsyncFileDialog::new().pick_folder().await { + message::app(Message::OpenProject(handle.path().to_owned())) + } else { + message::none() + } + }, + |x| x, + ); + } + Message::OpenProject(path) => { + self.open_project(path); + } Message::Save => { let mut title_opt = None; diff --git a/src/menu.rs b/src/menu.rs index bd9af8c..3860723 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -51,7 +51,7 @@ pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> { menu_key("New file", "Ctrl + N", Message::New), menu_key("New window", "Ctrl + Shift + N", Message::Todo), MenuTree::new(horizontal_rule(1)), - menu_key("Open file...", "Ctrl + O", Message::OpenDialog), + menu_key("Open file...", "Ctrl + O", Message::OpenFileDialog), MenuTree::with_children( menu_folder("Open recent"), vec![menu_item("TODO", Message::Todo)], diff --git a/src/tab.rs b/src/tab.rs index 42d7fb6..2cd86aa 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -56,11 +56,17 @@ impl Tab { let mut editor = editor.borrow_with(&mut font_system); match editor.load_text(&path, self.attrs) { Ok(()) => { - log::info!("opened '{}'", path.display()); - self.path_opt = Some(path); + log::info!("opened {:?}", path); + self.path_opt = match fs::canonicalize(&path) { + Ok(ok) => Some(ok), + Err(err) => { + log::error!("failed to canonicalize {:?}: {}", path, err); + Some(path) + } + }; } Err(err) => { - log::error!("failed to open '{}': {}", path.display(), err); + log::error!("failed to open {:?}: {}", path, err); self.path_opt = None; } } @@ -76,10 +82,10 @@ impl Tab { } match fs::write(path, text) { Ok(()) => { - log::info!("saved '{}'", path.display()); + log::info!("saved {:?}", path); } Err(err) => { - log::error!("failed to save '{}': {}", path.display(), err); + log::error!("failed to save {:?}: {}", path, err); } } } else { From 7d4d25ef8508b6301079bdfebb0946116561dc1c Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 13:54:29 -0600 Subject: [PATCH 13/14] Expand project view to encompase open file --- src/main.rs | 59 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index e479a41..42d7300 100644 --- a/src/main.rs +++ b/src/main.rs @@ -195,34 +195,61 @@ impl App { .activate(); } - pub fn update_title(&mut self) -> Command { - let (title, tab_path_opt) = match self.active_tab() { - Some(tab) => (tab.title(), tab.path_opt.clone()), - None => (format!("No Open File"), None), + fn update_nav_bar_active(&mut self) { + let tab_path_opt = match self.active_tab() { + Some(tab) => tab.path_opt.clone(), + None => None, }; - //TODO: is this the best place for this? + // Locate tree node to activate let mut active_id = segmented_button::Entity::default(); match tab_path_opt { Some(tab_path) => { - for id in self.nav_model.iter() { - match self.nav_model.data(id) { - Some(node) => match node { - ProjectNode::File { path, .. } => { - if path == &tab_path { - active_id = id; - break; + // Automatically expand tree to find and select active file + loop { + let mut expand_opt = None; + for id in self.nav_model.iter() { + match self.nav_model.data(id) { + Some(node) => match node { + ProjectNode::Folder { path, open, .. } => { + if tab_path.starts_with(path) && !*open { + expand_opt = Some(id); + break; + } } - } - _ => {} - }, - None => {} + ProjectNode::File { path, .. } => { + if path == &tab_path { + active_id = id; + break; + } + } + }, + None => {} + } + } + match expand_opt { + Some(id) => { + //TODO: can this be optimized? + cosmic::Application::on_nav_select(self, id); + } + None => { + break; + } } } } None => {} } self.nav_model.activate(active_id); + } + + pub fn update_title(&mut self) -> Command { + self.update_nav_bar_active(); + + let title = match self.active_tab() { + Some(tab) => tab.title(), + None => format!("No Open File"), + }; let window_title = format!("{title} - COSMIC Text Editor"); self.set_header_title(title.clone()); From c0d2f235e441b6c8db9b3cef24f34bcc3cf32f37 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 27 Oct 2023 14:59:41 -0600 Subject: [PATCH 14/14] Add debian packaging --- .gitignore | 9 +++++- debian/changelog | 5 ++++ debian/control | 19 +++++++++++++ debian/copyright | 7 +++++ debian/rules | 22 +++++++++++++++ debian/source/format | 1 + debian/source/options | 4 +++ justfile | 66 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- 9 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/source/options create mode 100644 justfile diff --git a/.gitignore b/.gitignore index ea8c4bf..699219b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ -/target +/.cargo/ +/debian/*debhelper* +/debian/cosmic-greeter.substvars +/debian/cosmic-greeter/ +/debian/files +/target/ +/vendor.tar +/vendor/ diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..7c23fd0 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +cosmic-edit (0.1.0) jammy; urgency=medium + + * Initial release. + + -- Jeremy Soller Tue, 03 Oct 2023 07:37:28 -0600 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..548f8a9 --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: cosmic-edit +Section: admin +Priority: optional +Maintainer: Jeremy Soller +Build-Depends: + debhelper-compat (=13), + just (>= 1.13.0), + libclang-dev, + libwayland-dev, + libxkbcommon-dev, + pkg-config, + rust-all, +Standards-Version: 4.6.2 +Homepage: https://github.com/pop-os/cosmic-edit + +Package: cosmic-edit +Architecture: amd64 arm64 +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: Cosmic Text Editor diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ef7e2a5 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: cosmic-edit +Upstream-Contact: Jeremy Soller +Source: https://github.com/pop-os/cosmic-edit +Files: * +Copyright: System76 +License: GPL-3.0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..eb62ec5 --- /dev/null +++ b/debian/rules @@ -0,0 +1,22 @@ +#!/usr/bin/make -f + +export DESTDIR = debian/cosmic-edit +export VENDOR ?= 1 + +%: + dh $@ + +override_dh_auto_clean: + if ! ischroot && test "${VENDOR}" = "1"; then \ + mkdir -p .cargo; \ + cargo vendor | head -n -1 > .cargo/config; \ + echo 'directory = "vendor"' >> .cargo/config; \ + tar pcf vendor.tar vendor; \ + rm -rf vendor; \ + fi + +override_dh_auto_build: + just build-vendored + +override_dh_auto_install: + just rootdir=$(DESTDIR) install diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 0000000..5d4ff73 --- /dev/null +++ b/debian/source/options @@ -0,0 +1,4 @@ +tar-ignore=.github +tar-ignore=.vscode +tar-ignore=vendor +tar-ignore=target \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..b8092ff --- /dev/null +++ b/justfile @@ -0,0 +1,66 @@ +name := 'cosmic-edit' +export APPID := 'com.system76.CosmicEdit' + +rootdir := '' +prefix := '/usr' + +base-dir := absolute_path(clean(rootdir / prefix)) + +export INSTALL_DIR := base-dir / 'share' + +bin-src := 'target' / 'release' / name +bin-dst := base-dir / 'bin' / name + +# Default recipe which runs `just build-release` +default: build-release + +# Runs `cargo clean` +clean: + cargo clean + +# `cargo clean` and removes vendored dependencies +clean-dist: clean + rm -rf .cargo vendor vendor.tar + +# Compiles with debug profile +build-debug *args: + cargo build {{args}} + +# Compiles with release profile +build-release *args: (build-debug '--release' args) + +# Compiles release profile with vendored dependencies +build-vendored *args: vendor-extract (build-release '--frozen --offline' args) + +# Runs a clippy check +check *args: + cargo clippy --all-features {{args}} -- -W clippy::pedantic + +# Runs a clippy check with JSON message format +check-json: (check '--message-format=json') + +# Run with debug logs +run *args: + env RUST_LOG=debug RUST_BACKTRACE=full cargo run --release {{args}} + +# Installs files +install: + install -Dm0755 {{bin-src}} {{bin-dst}} + +# Uninstalls installed files +uninstall: + rm {{bin-dst}} + +# Vendor dependencies locally +vendor: + mkdir -p .cargo + cargo vendor --sync Cargo.toml \ + | head -n -1 > .cargo/config + echo 'directory = "vendor"' >> .cargo/config + tar pcf vendor.tar vendor + rm -rf vendor + +# Extracts vendored dependencies +vendor-extract: + rm -rf vendor + tar pxf vendor.tar diff --git a/src/main.rs b/src/main.rs index 42d7300..a68a908 100644 --- a/src/main.rs +++ b/src/main.rs @@ -269,7 +269,7 @@ impl cosmic::Application for App { type Message = Message; /// The unique application ID to supply to the window manager. - const APP_ID: &'static str = "com.system76.CosmicTextEditor"; + const APP_ID: &'static str = "com.system76.CosmicEdit"; fn core(&self) -> &Core { &self.core