Make each monitor an independant tab
This commit is contained in:
parent
01f0b0967a
commit
cc2a62a14c
4 changed files with 112 additions and 58 deletions
137
src/app.rs
137
src/app.rs
|
|
@ -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
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
29
src/lib.rs
29
src/lib.rs
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue