Make each monitor an independant tab

This commit is contained in:
Jeremy Soller 2024-10-04 11:53:27 -06:00
parent 01f0b0967a
commit cc2a62a14c
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
4 changed files with 112 additions and 58 deletions

View file

@ -44,9 +44,7 @@ use slotmap::Key as SlotMapKey;
use std::{ use std::{
any::TypeId, any::TypeId,
collections::{BTreeMap, HashMap, HashSet, VecDeque}, collections::{BTreeMap, HashMap, HashSet, VecDeque},
env, fmt, fs, env, fmt, fs, io,
future::pending,
io,
num::NonZeroU16, num::NonZeroU16,
path::PathBuf, path::PathBuf,
process, process,
@ -61,7 +59,7 @@ use wayland_client::{protocol::wl_output::WlOutput, Proxy};
use crate::{ use crate::{
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
config::{AppTheme, Config, Favorite, IconSizes, TabConfig}, config::{AppTheme, Config, Favorite, IconSizes, TabConfig},
fl, home_dir, desktop_dir, fl, home_dir,
key_bind::key_binds, key_bind::key_binds,
localize::LANGUAGE_SORTER, localize::LANGUAGE_SORTER,
menu, mime_app, mime_icon, menu, mime_app, mime_icon,
@ -176,7 +174,7 @@ impl Action {
Action::OpenTerminal => Message::OpenTerminal(entity_opt), Action::OpenTerminal => Message::OpenTerminal(entity_opt),
Action::OpenWith => Message::OpenWithDialog(entity_opt), Action::OpenWith => Message::OpenWithDialog(entity_opt),
Action::Paste => Message::Paste(entity_opt), Action::Paste => Message::Paste(entity_opt),
Action::Preview => Message::Preview, Action::Preview => Message::Preview(entity_opt),
Action::Rename => Message::Rename(entity_opt), Action::Rename => Message::Rename(entity_opt),
Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt), Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt),
Action::SearchActivate => Message::SearchActivate, Action::SearchActivate => Message::SearchActivate,
@ -298,7 +296,7 @@ pub enum Message {
PendingComplete(u64), PendingComplete(u64),
PendingError(u64, String), PendingError(u64, String),
PendingProgress(u64, f32), PendingProgress(u64, f32),
Preview, Preview(Option<Entity>),
RescanTrash, RescanTrash,
Rename(Option<Entity>), Rename(Option<Entity>),
ReplaceResult(ReplaceResult), ReplaceResult(ReplaceResult),
@ -447,7 +445,7 @@ pub struct MounterData(MounterKey, MounterItem);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum WindowKind { pub enum WindowKind {
Main, Desktop(Entity),
Preview(Option<Entity>, PreviewKind), Preview(Option<Entity>, PreviewKind),
} }
@ -516,12 +514,12 @@ pub struct App {
} }
impl App { impl App {
fn open_tab( fn open_tab_entity(
&mut self, &mut self,
location: Location, location: Location,
activate: bool, activate: bool,
selection_path: Option<PathBuf>, selection_path: Option<PathBuf>,
) -> Command<Message> { ) -> (Entity, Command<Message>) {
let mut tab = Tab::new(location.clone(), self.config.tab); let mut tab = Tab::new(location.clone(), self.config.tab);
tab.mode = match self.mode { tab.mode = match self.mode {
Mode::App => tab::Mode::App, Mode::App => tab::Mode::App,
@ -543,11 +541,23 @@ impl App {
entity.id() entity.id()
}; };
Command::batch([ (
self.update_title(), entity,
self.update_watcher(), Command::batch([
self.rescan_tab(entity, location, selection_path), self.update_title(),
]) self.update_watcher(),
self.rescan_tab(entity, location, selection_path),
]),
)
}
fn open_tab(
&mut self,
location: Location,
activate: bool,
selection_path: Option<PathBuf>,
) -> Command<Message> {
self.open_tab_entity(location, activate, selection_path).1
} }
fn operation(&mut self, operation: Operation) { fn operation(&mut self, operation: Operation) {
@ -556,12 +566,23 @@ impl App {
self.pending_operations.insert(id, (operation, 0.0)); self.pending_operations.insert(id, (operation, 0.0));
} }
fn remove_window(&mut self, id: &window::Id) {
match self.windows.remove(id) {
Some(WindowKind::Desktop(entity)) => {
// Remove the tab from the tab model
self.tab_model.remove(entity);
}
_ => {}
}
}
fn rescan_tab( fn rescan_tab(
&mut self, &mut self,
entity: Entity, entity: Entity,
location: Location, location: Location,
selection_path: Option<PathBuf>, selection_path: Option<PathBuf>,
) -> Command<Message> { ) -> Command<Message> {
log::info!("rescan_tab {entity:?} {location:?} {selection_path:?}");
let mounters = self.mounters.clone(); let mounters = self.mounters.clone();
let icon_sizes = self.config.tab.icon_sizes; let icon_sizes = self.config.tab.icon_sizes;
Command::perform( Command::perform(
@ -1286,7 +1307,7 @@ impl Application for App {
ContextPage::Preview(_, _) => { ContextPage::Preview(_, _) => {
// Persist state of preview page // Persist state of preview page
if self.core.window.show_context != self.config.show_details { if self.core.window.show_context != self.config.show_details {
return self.update(Message::Preview); return self.update(Message::Preview(None));
} }
} }
_ => {} _ => {}
@ -2056,7 +2077,7 @@ impl Application for App {
} }
return self.update_notification(); return self.update_notification();
} }
Message::Preview => { Message::Preview(entity_opt) => {
match self.mode { match self.mode {
Mode::App => { Mode::App => {
let show_details = !self.config.show_details; let show_details = !self.config.show_details;
@ -2067,7 +2088,7 @@ impl Application for App {
return self.update_config(); return self.update_config();
} }
Mode::Desktop => { Mode::Desktop => {
let selected_paths = self.selected_paths(None); let selected_paths = self.selected_paths(entity_opt);
let mut commands = Vec::with_capacity(selected_paths.len()); let mut commands = Vec::with_capacity(selected_paths.len());
for path in selected_paths { for path in selected_paths {
let mut settings = window::Settings::default(); let mut settings = window::Settings::default();
@ -2088,7 +2109,7 @@ impl Application for App {
self.windows.insert( self.windows.insert(
id, id,
WindowKind::Preview( WindowKind::Preview(
None, entity_opt,
PreviewKind::Location(Location::Path(path)), PreviewKind::Location(Location::Path(path)),
), ),
); );
@ -2552,7 +2573,7 @@ impl Application for App {
} }
} }
Message::WindowCloseRequested(id) => { Message::WindowCloseRequested(id) => {
self.windows.remove(&id); self.remove_window(&id);
} }
Message::WindowNew => match env::current_exe() { Message::WindowNew => match env::current_exe() {
Ok(exe) => match process::Command::new(&exe).spawn() { Ok(exe) => match process::Command::new(&exe).spawn() {
@ -2849,29 +2870,36 @@ impl Application for App {
} }
} }
return Command::batch([get_layer_surface(SctkLayerSurfaceSettings { let (entity, command) =
id: surface_id, self.open_tab_entity(Location::Path(desktop_dir()), false, None);
layer: Layer::Bottom, self.windows.insert(surface_id, WindowKind::Desktop(entity));
keyboard_interactivity: KeyboardInteractivity::OnDemand, return Command::batch([
pointer_interactivity: true, command,
anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT, get_layer_surface(SctkLayerSurfaceSettings {
output: IcedOutput::Output(output), id: surface_id,
namespace: "cosmic-files-applet".into(), layer: Layer::Bottom,
size: Some((None, None)), keyboard_interactivity: KeyboardInteractivity::OnDemand,
margin: IcedMargin { pointer_interactivity: true,
top: 0, anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
bottom: 0, output: IcedOutput::Output(output),
left: 0, namespace: "cosmic-files-applet".into(),
right: 0, size: Some((None, None)),
}, margin: IcedMargin {
exclusive_zone: -1, top: 0,
size_limits: Limits::NONE.min_width(1.0).min_height(1.0), bottom: 0,
})]); left: 0,
right: 0,
},
exclusive_zone: -1,
size_limits: Limits::NONE.min_width(1.0).min_height(1.0),
}),
]);
} }
OutputEvent::Removed => { OutputEvent::Removed => {
log::info!("output {}: removed", output.id()); log::info!("output {}: removed", output.id());
match self.surface_ids.remove(&output) { match self.surface_ids.remove(&output) {
Some(surface_id) => { Some(surface_id) => {
self.remove_window(&surface_id);
self.surface_names.remove(&surface_id); self.surface_names.remove(&surface_id);
return destroy_layer_surface(surface_id); return destroy_layer_surface(surface_id);
} }
@ -3528,7 +3556,31 @@ impl Application for App {
fn view_window(&self, id: WindowId) -> Element<Self::Message> { fn view_window(&self, id: WindowId) -> Element<Self::Message> {
let content = match self.windows.get(&id) { let content = match self.windows.get(&id) {
Some(WindowKind::Main) | None => { Some(WindowKind::Desktop(entity)) => {
let mut tab_column = widget::column::with_capacity(2);
match self.tab_model.data::<Tab>(*entity) {
Some(tab) => {
let tab_view = tab
.view(&self.key_binds)
.map(move |message| Message::TabMessage(Some(*entity), message));
tab_column = tab_column.push(tab_view);
}
None => {
//TODO
}
}
// The toaster is added on top of an empty element to ensure that it does not override context menus
tab_column = tab_column.push(widget::toaster(
&self.toasts,
widget::horizontal_space(Length::Fill),
));
return tab_column.into();
}
Some(WindowKind::Preview(entity_opt, kind)) => self.preview(entity_opt, kind, false),
None => {
//TODO: distinct views per monitor in desktop mode //TODO: distinct views per monitor in desktop mode
return self.view_main().map(|message| match message { return self.view_main().map(|message| match message {
app::Message::App(app) => app, app::Message::App(app) => app,
@ -3536,15 +3588,16 @@ impl Application for App {
app::Message::None => Message::None, app::Message::None => Message::None,
}); });
} }
Some(WindowKind::Preview(entity_opt, kind)) => self.preview(entity_opt, kind, false),
}; };
//TODO: these are hacks to have a sane scroll bar //TODO: these are hacks to have a sane scroll bar
let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing; let cosmic_theme::Spacing { space_l, .. } = theme::active().cosmic().spacing;
let scrollbar_width = 8; let scrollbar_width = 8;
let scrollbar_margin = 8;
widget::container( widget::container(
widget::scrollable(widget::row::with_children(vec![ widget::scrollable(widget::row::with_children(vec![
content, content,
widget::horizontal_space(Length::Fixed(scrollbar_width.into())).into(), widget::horizontal_space(Length::Fixed((scrollbar_width + scrollbar_margin).into())).into(),
])) ]))
.direction(scrollable::Direction::Vertical( .direction(scrollable::Direction::Vertical(
scrollable::Properties::new() scrollable::Properties::new()
@ -3554,7 +3607,7 @@ impl Application for App {
) )
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.padding([0, space_l - scrollbar_width, space_l, space_l]) .padding([0, space_l - (scrollbar_width + scrollbar_margin), space_l, space_l])
.style(theme::Container::WindowBackground) .style(theme::Container::WindowBackground)
.into() .into()
} }
@ -3807,7 +3860,7 @@ impl Application for App {
.await .await
.unwrap(); .unwrap();
pending().await std::future::pending().await
}, },
)); ));
} }

View file

@ -331,7 +331,7 @@ enum Message {
impl From<AppMessage> for Message { impl From<AppMessage> for Message {
fn from(app_message: AppMessage) -> Message { fn from(app_message: AppMessage) -> Message {
match app_message { match app_message {
AppMessage::Preview => Message::Preview, AppMessage::Preview(_entity_opt) => Message::Preview,
AppMessage::SearchActivate => Message::SearchActivate, AppMessage::SearchActivate => Message::SearchActivate,
AppMessage::TabMessage(_entity_opt, tab_message) => Message::TabMessage(tab_message), AppMessage::TabMessage(_entity_opt, tab_message) => Message::TabMessage(tab_message),
unsupported => { unsupported => {

View file

@ -27,12 +27,24 @@ pub(crate) fn err_str<T: ToString>(err: T) -> String {
err.to_string() err.to_string()
} }
pub fn desktop_dir() -> PathBuf {
match dirs::desktop_dir() {
Some(path) => path,
None => {
let path = home_dir().join("Desktop");
log::warn!("failed to locate desktop directory, falling back to {path:?}");
path
}
}
}
pub fn home_dir() -> PathBuf { pub fn home_dir() -> PathBuf {
match dirs::home_dir() { match dirs::home_dir() {
Some(home) => home, Some(home) => home,
None => { None => {
log::warn!("failed to locate home directory"); let path = PathBuf::from("/");
PathBuf::from("/") log::warn!("failed to locate home directory, falling back to {path:?}");
path
} }
} }
} }
@ -46,17 +58,6 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
let (config_handler, config) = Config::load(); let (config_handler, config) = Config::load();
let locations = vec![
match dirs::desktop_dir() {
Some(path) => Location::Path(path),
None => {
let path = home_dir().join("Desktop");
log::warn!("failed to find XDG_DESKTOP_DIR, falling back to {path:?}");
Location::Path(path)
}
}
];
let mut settings = Settings::default(); let mut settings = Settings::default();
settings = settings.theme(config.app_theme.theme()); settings = settings.theme(config.app_theme.theme());
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0)); settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
@ -71,7 +72,7 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
config_handler, config_handler,
config, config,
mode: app::Mode::Desktop, mode: app::Mode::Desktop,
locations, locations: vec![tab::Location::Path(desktop_dir())],
}; };
cosmic::app::run::<App>(settings, flags)?; cosmic::app::run::<App>(settings, flags)?;

View file

@ -497,7 +497,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
(false, true) => Ordering::Greater, (false, true) => Ordering::Greater,
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name), _ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
}); });
items.iter_mut().for_each(|mut item| { items.iter_mut().for_each(|item| {
if hidden_files if hidden_files
.iter() .iter()
.find(|hidden| &&item.name == hidden) .find(|hidden| &&item.name == hidden)