Update libcosmic/iced

This commit is contained in:
Jeremy Soller 2024-10-21 13:51:10 -06:00
parent d29baf1a8f
commit 390673c70f
No known key found for this signature in database
GPG key ID: D02FD439211AF56F
10 changed files with 1056 additions and 1083 deletions

1220
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -63,7 +63,7 @@ uzers = "0.12.0"
[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
default-features = false
features = ["a11y", "clipboard", "multi-window", "tokio"]
features = ["a11y", "multi-window", "tokio"]
[dependencies.smol_str]
version = "0.2.1"

View file

@ -13,17 +13,17 @@ use cosmic::iced::{
Limits,
};
use cosmic::{
app::{self, message, Command, Core},
app::{self, message, Core, Task},
cosmic_config, cosmic_theme, executor,
iced::{
clipboard::dnd::DndAction,
event,
futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers},
subscription::{self, Subscription},
stream,
widget::scrollable,
window::{self, Event as WindowEvent, Id as WindowId},
Alignment, Event, Length, Size,
Alignment, Event, Length, Size, Subscription,
},
iced_runtime::clipboard,
style, theme,
@ -619,7 +619,7 @@ impl App {
location: Location,
activate: bool,
selection_path: Option<PathBuf>,
) -> (Entity, Command<Message>) {
) -> (Entity, Task<Message>) {
let mut tab = Tab::new(location.clone(), self.config.tab);
tab.mode = match self.mode {
Mode::App => tab::Mode::App,
@ -643,7 +643,7 @@ impl App {
(
entity,
Command::batch([
Task::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, location, selection_path),
@ -656,7 +656,7 @@ impl App {
location: Location,
activate: bool,
selection_path: Option<PathBuf>,
) -> Command<Message> {
) -> Task<Message> {
self.open_tab_entity(location, activate, selection_path).1
}
@ -681,10 +681,10 @@ impl App {
entity: Entity,
location: Location,
selection_path: Option<PathBuf>,
) -> Command<Message> {
) -> Task<Message> {
log::info!("rescan_tab {entity:?} {location:?} {selection_path:?}");
let icon_sizes = self.config.tab.icon_sizes;
Command::perform(
Task::perform(
async move {
let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
@ -705,7 +705,7 @@ impl App {
)
}
fn rescan_trash(&mut self) -> Command<Message> {
fn rescan_trash(&mut self) -> Task<Message> {
let mut needs_reload = Vec::new();
for entity in self.tab_model.iter() {
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
@ -719,14 +719,14 @@ impl App {
for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location, None));
}
Command::batch(commands)
Task::batch(commands)
}
fn search(&mut self) -> Command<Message> {
fn search(&mut self) -> Task<Message> {
if let Some(term) = self.search_get() {
self.search_set(Some(term.to_string()))
} else {
Command::none()
Task::none()
}
}
@ -739,7 +739,7 @@ impl App {
}
}
fn search_set(&mut self, term_opt: Option<String>) -> Command<Message> {
fn search_set(&mut self, term_opt: Option<String>) -> Task<Message> {
let entity = self.tab_model.active();
let mut title_location_opt = None;
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
@ -768,18 +768,18 @@ impl App {
}
if let Some((title, location, focus_search)) = title_location_opt {
self.tab_model.text_set(entity, title);
return Command::batch([
return Task::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, location, None),
if focus_search {
widget::text_input::focus(self.search_id.clone())
} else {
Command::none()
Task::none()
},
]);
}
Command::none()
Task::none()
}
fn selected_paths(&self, entity_opt: Option<Entity>) -> Vec<PathBuf> {
@ -795,7 +795,7 @@ impl App {
paths
}
fn update_config(&mut self) -> Command<Message> {
fn update_config(&mut self) -> Task<Message> {
self.update_nav_model();
// Tabs are collected first to placate the borrowck
let tabs: Vec<_> = self.tab_model.iter().collect();
@ -810,10 +810,10 @@ impl App {
))
}))
.collect();
Command::batch(commands)
Task::batch(commands)
}
fn update_desktop(&mut self) -> Command<Message> {
fn update_desktop(&mut self) -> Task<Message> {
let mut needs_reload = Vec::new();
for entity in self.tab_model.iter() {
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
@ -826,7 +826,7 @@ impl App {
for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location, None));
}
Command::batch(commands)
Task::batch(commands)
}
fn activate_nav_model_location(&mut self, location: &Location) {
@ -941,12 +941,12 @@ impl App {
}
}
fn update_notification(&mut self) -> Command<Message> {
fn update_notification(&mut self) -> Task<Message> {
// Handle closing notification if there are no operations
if self.pending_operations.is_empty() {
#[cfg(feature = "notify")]
if let Some(notification_arc) = self.notification_opt.take() {
return Command::perform(
return Task::perform(
async move {
tokio::task::spawn_blocking(move || {
//TODO: this is nasty
@ -963,18 +963,22 @@ impl App {
}
}
Command::none()
Task::none()
}
fn update_title(&mut self) -> Command<Message> {
fn update_title(&mut self) -> Task<Message> {
let window_title = match self.tab_model.text(self.tab_model.active()) {
Some(tab_title) => format!("{tab_title}{}", fl!("cosmic-files")),
None => fl!("cosmic-files"),
};
self.set_window_title(window_title, window::Id::MAIN)
if let Some(window_id) = &self.window_id_opt {
self.set_window_title(window_title, *window_id)
} else {
Task::none()
}
}
fn update_watcher(&mut self) -> Command<Message> {
fn update_watcher(&mut self) -> Task<Message> {
if let Some((mut watcher, old_paths)) = self.watcher_opt.take() {
let mut new_paths = HashSet::new();
for entity in self.tab_model.iter() {
@ -1021,7 +1025,7 @@ impl App {
}
//TODO: should any of this run in a command?
Command::none()
Task::none()
}
fn about(&self) -> Element<Message> {
@ -1051,7 +1055,7 @@ impl App {
.padding(0)
.into(),
])
.align_items(Alignment::Center)
.align_x(Alignment::Center)
.spacing(space_xxs)
.into()
}
@ -1093,11 +1097,8 @@ impl App {
text_input.into(),
widget::text(fl!("network-drive-description")).into(),
table.into(),
widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(),
button.into(),
])
.into(),
widget::row::with_children(vec![widget::horizontal_space().into(), button.into()])
.into(),
])
.spacing(space_m)
.into()
@ -1322,7 +1323,7 @@ impl Application for App {
}
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>) {
core.window.context_is_overlay = false;
match flags.mode {
Mode::App => {
@ -1346,6 +1347,9 @@ impl Application for App {
Mode::Desktop => tab::Mode::Desktop,
});
let window_id_opt = core.main_window_id();
println!("WINDOW ID {:?}", window_id_opt);
let mut app = App {
core,
nav_bar_context_id: segmented_button::Entity::null(),
@ -1376,7 +1380,7 @@ impl Application for App {
surface_names: HashMap::new(),
toasts: widget::toaster::Toasts::new(Message::CloseToast),
watcher_opt: None,
window_id_opt: Some(window::Id::MAIN),
window_id_opt,
windows: HashMap::new(),
nav_dnd_hover: None,
tab_dnd_hover: None,
@ -1398,11 +1402,7 @@ impl Application for App {
}
}
(app, Command::batch(commands))
}
fn main_window_id(&self) -> window::Id {
self.window_id_opt.unwrap_or(window::Id::MAIN)
(app, Task::batch(commands))
}
fn nav_bar(&self) -> Option<Element<message::Message<Self::Message>>> {
@ -1504,7 +1504,7 @@ impl Application for App {
}
}
fn on_nav_select(&mut self, entity: Entity) -> Command<Self::Message> {
fn on_nav_select(&mut self, entity: Entity) -> Task<Self::Message> {
self.nav_model.activate(entity);
if let Some(location) = self.nav_model.data::<Location>(entity) {
let message = Message::TabMessage(None, tab::Message::Location(location.clone()));
@ -1516,7 +1516,7 @@ impl Application for App {
return mounter.mount(data.1.clone()).map(|_| message::none());
}
}
Command::none()
Task::none()
}
fn on_app_exit(&mut self) -> Option<Message> {
@ -1527,7 +1527,7 @@ impl Application for App {
Some(Message::WindowCloseRequested(id))
}
fn on_context_drawer(&mut self) -> Command<Self::Message> {
fn on_context_drawer(&mut self) -> Task<Self::Message> {
match self.context_page {
ContextPage::Preview(..) => {
// Persist state of preview page
@ -1537,22 +1537,22 @@ impl Application for App {
}
_ => {}
}
Command::none()
Task::none()
}
fn on_escape(&mut self) -> Command<Self::Message> {
fn on_escape(&mut self) -> Task<Self::Message> {
let entity = self.tab_model.active();
// Close dialog if open
if self.dialog_pages.pop_front().is_some() {
return Command::none();
return Task::none();
}
// Close gallery mode if open
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if tab.gallery {
tab.gallery = false;
return Command::none();
return Task::none();
}
}
@ -1562,7 +1562,7 @@ impl Application for App {
// of closing everything on one press
if self.core.window.show_context {
self.set_show_context(false);
return Command::none();
return Task::none();
}
if self.search_get().is_some() {
// Close search if open
@ -1571,12 +1571,12 @@ impl Application for App {
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if tab.context_menu.is_some() {
tab.context_menu = None;
return Command::none();
return Task::none();
}
if tab.edit_location.is_some() {
tab.edit_location = None;
return Command::none();
return Task::none();
}
let had_focused_button = tab.select_focus_id().is_some();
@ -1585,15 +1585,15 @@ impl Application for App {
// Unfocus if there was a focused button
return widget::button::focus(widget::Id::unique());
}
return Command::none();
return Task::none();
}
}
Command::none()
Task::none()
}
/// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
// Helper for updating config values efficiently
macro_rules! config_set {
($name: ident, $value: expr) => {
@ -1709,9 +1709,9 @@ impl Application for App {
"com.system76.CosmicFilesDialog".to_string();
}
let (id, command) = window::spawn(settings);
let (id, command) = window::open(settings);
self.windows.insert(id, WindowKind::DesktopViewOptions);
return command;
return command.map(|_id| message::none());
}
Message::DialogCancel => {
self.dialog_pages.pop_front();
@ -1755,7 +1755,7 @@ impl Application for App {
auth,
auth_tx,
} => {
return Command::perform(
return Task::perform(
async move {
auth_tx.send(auth).await.unwrap();
message::none()
@ -1769,7 +1769,7 @@ impl Application for App {
error: _,
} => {
//TODO: re-use mounter_key?
return Command::batch([
return Task::batch([
self.update(Message::NetworkDriveInput(uri)),
self.update(Message::NetworkDriveSubmit),
]);
@ -1841,7 +1841,7 @@ impl Application for App {
}
}
Message::DialogUpdateComplete(dialog_page) => {
return Command::batch([
return Task::batch([
self.update(Message::DialogUpdate(dialog_page)),
self.update(Message::DialogComplete),
]);
@ -1951,7 +1951,7 @@ impl Application for App {
// Update desktop tabs
commands.push(self.update_desktop());
return Command::batch(commands);
return Task::batch(commands);
}
Message::MountResult(mounter_key, item, res) => match res {
Ok(true) => {
@ -2099,7 +2099,7 @@ impl Application for App {
for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location, None));
}
return Command::batch(commands);
return Task::batch(commands);
}
Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take()
{
@ -2152,7 +2152,7 @@ impl Application for App {
}
}
Message::OpenInNewTab(entity_opt) => {
return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map(
return Task::batch(self.selected_paths(entity_opt).into_iter().filter_map(
|path| {
if path.is_dir() {
Some(self.open_tab(Location::Path(path), false, None))
@ -2178,7 +2178,7 @@ impl Application for App {
}
},
Message::OpenItemLocation(entity_opt) => {
return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map(
return Task::batch(self.selected_paths(entity_opt).into_iter().filter_map(
|path| {
if let Some(parent) = path.parent() {
Some(self.open_tab(
@ -2259,7 +2259,7 @@ impl Application for App {
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
if let Some(path) = tab.location.path_opt() {
let to = path.clone();
return clipboard::read_data::<ClipboardPaste, _>(move |contents_opt| {
return clipboard::read_data::<ClipboardPaste>().map(move |contents_opt| {
match contents_opt {
Some(contents) => {
message::app(Message::PasteContents(to.clone(), contents))
@ -2340,7 +2340,7 @@ impl Application for App {
commands.push(self.rescan_trash());
// if search is active, update "search" tab view
commands.push(self.search());
return Command::batch(commands);
return Task::batch(commands);
}
Message::PendingError(id, err) => {
if let Some((op, _)) = self.pending_operations.remove(&id) {
@ -2384,7 +2384,7 @@ impl Application for App {
"com.system76.CosmicFilesDialog".to_string();
}
let (id, command) = window::spawn(settings);
let (id, command) = window::open(settings);
self.windows.insert(
id,
WindowKind::Preview(
@ -2392,9 +2392,9 @@ impl Application for App {
PreviewKind::Location(Location::Path(path)),
),
);
commands.push(command);
commands.push(command.map(|_id| message::none()));
}
return Command::batch(commands);
return Task::batch(commands);
}
}
}
@ -2411,7 +2411,7 @@ impl Application for App {
.icon_set(entity, widget::icon::icon(tab::trash_icon_symbolic(16)));
}
return Command::batch([self.rescan_trash(), self.update_desktop()]);
return Task::batch([self.rescan_trash(), self.update_desktop()]);
}
Message::Rename(entity_opt) => {
@ -2452,7 +2452,7 @@ impl Application for App {
if let Some(dialog_page) = self.dialog_pages.pop_front() {
match dialog_page {
DialogPage::Replace { tx, .. } => {
return Command::perform(
return Task::perform(
async move {
let _ = tx.send(replace_result).await;
message::none()
@ -2572,10 +2572,12 @@ impl Application for App {
// If that was the last tab, close window
if self.tab_model.iter().next().is_none() {
return window::close(window::Id::MAIN);
if let Some(window_id) = &self.window_id_opt {
return window::close(*window_id);
}
}
return Command::batch([self.update_title(), self.update_watcher()]);
return Task::batch([self.update_title(), self.update_watcher()]);
}
Message::TabConfig(config) => {
if config != self.config.tab {
@ -2591,7 +2593,7 @@ impl Application for App {
Message::TabMessage(entity_opt, tab_message) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
//TODO: move to Command?
//TODO: move to Task?
if let tab::Message::ContextMenu(_point_opt) = tab_message {
// Disable side context page
self.set_show_context(false);
@ -2626,7 +2628,7 @@ impl Application for App {
self.activate_nav_model_location(&tab_path);
self.tab_model.text_set(entity, tab_title);
commands.push(Command::batch([
commands.push(Task::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, tab_path, selection_path),
@ -2639,7 +2641,7 @@ impl Application for App {
self.dialog_pages.push_back(DialogPage::EmptyTrash);
}
tab::Command::Iced(iced_command) => {
commands.push(iced_command.map(move |tab_message| {
commands.push(iced_command.0.map(move |tab_message| {
message::app(Message::TabMessage(Some(entity), tab_message))
}));
}
@ -2678,14 +2680,18 @@ impl Application for App {
self.set_context_title(self.context_page.title());
}
tab::Command::WindowDrag => {
commands.push(window::drag(self.main_window_id()));
if let Some(window_id) = &self.window_id_opt {
commands.push(window::drag(*window_id));
}
}
tab::Command::WindowToggleMaximize => {
commands.push(window::toggle_maximize(self.main_window_id()));
if let Some(window_id) = &self.window_id_opt {
commands.push(window::toggle_maximize(*window_id));
}
}
}
}
return Command::batch(commands);
return Task::batch(commands);
}
Message::TabNew => {
let active = self.tab_model.active();
@ -2766,9 +2772,9 @@ impl Application for App {
}
Message::WindowClose => {
if let Some(window_id) = self.window_id_opt.take() {
return Command::batch([
return Task::batch([
window::close(window_id),
Command::perform(async move { message::app(Message::MaybeExit) }, |x| x),
Task::perform(async move { message::app(Message::MaybeExit) }, |x| x),
]);
}
}
@ -2849,8 +2855,8 @@ impl Application for App {
if let Some(location) = self.nav_model.data::<Location>(entity) {
self.nav_dnd_hover = Some((location.clone(), Instant::now()));
let location = location.clone();
return Command::perform(tokio::time::sleep(HOVER_DURATION), move |_| {
cosmic::app::Message::App(Message::DndHoverLocTimeout(location))
return Task::perform(tokio::time::sleep(HOVER_DURATION), move |_| {
cosmic::app::Message::App(Message::DndHoverLocTimeout(location.clone()))
});
}
}
@ -2874,11 +2880,11 @@ impl Application for App {
)),
Location::Trash if matches!(action, DndAction::Move) => {
self.operation(Operation::Delete { paths: data.paths });
Command::none()
Task::none()
}
_ => {
log::warn!("Copy to trash is not supported.");
Command::none()
Task::none()
}
};
return ret;
@ -2901,7 +2907,7 @@ impl Application for App {
};
if let Some(title) = title_opt {
self.tab_model.text_set(entity, title);
return Command::batch([
return Task::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(entity, location, None),
@ -2911,7 +2917,7 @@ impl Application for App {
}
Message::DndEnterTab(entity) => {
self.tab_dnd_hover = Some((entity, Instant::now()));
return Command::perform(tokio::time::sleep(HOVER_DURATION), move |_| {
return Task::perform(tokio::time::sleep(HOVER_DURATION), move |_| {
cosmic::app::Message::App(Message::DndHoverTabTimeout(entity))
});
}
@ -2935,11 +2941,11 @@ impl Application for App {
)),
Location::Trash if matches!(action, DndAction::Move) => {
self.operation(Operation::Delete { paths: data.paths });
Command::none()
Task::none()
}
_ => {
log::warn!("Copy to trash is not supported.");
Command::none()
Task::none()
}
};
return ret;
@ -3131,7 +3137,7 @@ impl Application for App {
None,
);
self.windows.insert(surface_id, WindowKind::Desktop(entity));
return Command::batch([
return Task::batch([
command,
get_layer_surface(SctkLayerSurfaceSettings {
id: surface_id,
@ -3173,12 +3179,12 @@ impl Application for App {
}
Message::Cosmic(cosmic) => {
// Forward cosmic messages
return Command::perform(async move { cosmic }, |cosmic| message::cosmic(cosmic));
return Task::perform(async move { cosmic }, |cosmic| message::cosmic(cosmic));
}
Message::None => {}
}
Command::none()
Task::none()
}
fn context_drawer(&self) -> Option<Element<Message>> {
@ -3288,7 +3294,7 @@ impl Application for App {
})
.into(),
])
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
.into(),
])
@ -3396,18 +3402,19 @@ impl Application for App {
//TODO: what should submit do?
//TODO: button for showing password
controls.push(
widget::checkbox(fl!("remember-password"), *remember, move |value| {
Message::DialogUpdate(DialogPage::NetworkAuth {
mounter_key: *mounter_key,
uri: uri.clone(),
auth: MounterAuth {
remember_opt: Some(value),
..auth.clone()
},
auth_tx: auth_tx.clone(),
widget::checkbox(fl!("remember-password"), *remember)
.on_toggle(move |value| {
Message::DialogUpdate(DialogPage::NetworkAuth {
mounter_key: *mounter_key,
uri: uri.clone(),
auth: MounterAuth {
remember_opt: Some(value),
..auth.clone()
},
auth_tx: auth_tx.clone(),
})
})
})
.into(),
.into(),
);
}
@ -3540,21 +3547,21 @@ impl Application for App {
} else {
widget::text(app.name.to_string()).into()
},
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().into(),
if *selected == i {
widget::icon::from_name("checkbox-checked-symbolic")
.size(16)
.into()
} else {
widget::horizontal_space(Length::Fixed(16.0)).into()
widget::Space::with_width(Length::Fixed(16.0)).into()
},
])
.spacing(space_s)
.height(Length::Fixed(32.0))
.align_items(Alignment::Center),
.align_y(Alignment::Center),
)
.width(Length::Fill)
.style(theme::Button::MenuItem)
.class(theme::Button::MenuItem)
.on_press(Message::OpenWithSelection(i)),
);
}
@ -3668,19 +3675,19 @@ impl Application for App {
));
if *multiple {
dialog
.control(widget::checkbox(
fl!("apply-to-all"),
*apply_to_all,
|apply_to_all| {
Message::DialogUpdate(DialogPage::Replace {
from: from.clone(),
to: to.clone(),
multiple: *multiple,
apply_to_all,
tx: tx.clone(),
})
},
))
.control(
widget::checkbox(fl!("apply-to-all"), *apply_to_all).on_toggle(
|apply_to_all| {
Message::DialogUpdate(DialogPage::Replace {
from: from.clone(),
to: to.clone(),
multiple: *multiple,
apply_to_all,
tx: tx.clone(),
})
},
),
)
.secondary_action(
widget::button::standard(fl!("skip")).on_press(Message::ReplaceResult(
ReplaceResult::Skip(*apply_to_all),
@ -3710,12 +3717,12 @@ impl Application for App {
widget::dialog(fl!("set-executable-and-launch"))
.primary_action(
widget::button::text(fl!("set-and-launch"))
.style(theme::Button::Suggested)
.class(theme::Button::Suggested)
.on_press(Message::DialogComplete),
)
.secondary_action(
widget::button::text(fl!("cancel"))
.style(theme::Button::Standard)
.class(theme::Button::Standard)
.on_press(Message::DialogCancel),
)
.control(widget::text::text(fl!(
@ -3818,7 +3825,7 @@ impl Application for App {
})
.drag_id(self.tab_drag_id),
)
.style(style::Container::Background)
.class(style::Container::Background)
.width(Length::Fill)
.padding([0, space_s]),
);
@ -3838,10 +3845,7 @@ impl Application for App {
}
// 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),
));
tab_column = tab_column.push(widget::toaster(&self.toasts, widget::horizontal_space()));
let content: Element<_> = tab_column.into();
@ -3859,7 +3863,7 @@ impl Application for App {
Some(tab) => tab
.view(&self.key_binds)
.map(move |message| Message::TabMessage(Some(*entity), message)),
None => widget::vertical_space(Length::Fill).into(),
None => widget::vertical_space().into(),
};
let mut popover = widget::popover(tab_view);
@ -3869,10 +3873,8 @@ impl Application for App {
tab_column = tab_column.push(popover);
// 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),
));
tab_column =
tab_column.push(widget::toaster(&self.toasts, widget::horizontal_space()));
return tab_column.into();
}
@ -3895,13 +3897,13 @@ impl Application for App {
widget::container(
widget::scrollable(widget::row::with_children(vec![
content,
widget::horizontal_space(Length::Fixed(
widget::Space::with_width(Length::Fixed(
(scrollbar_width + scrollbar_margin).into(),
))
.into(),
]))
.direction(scrollable::Direction::Vertical(
scrollable::Properties::new()
scrollable::Scrollbar::new()
.width(scrollbar_width)
.scroller_width(scrollbar_width),
)),
@ -3914,7 +3916,7 @@ impl Application for App {
space_l,
space_l,
])
.style(theme::Container::WindowBackground)
.class(theme::Container::WindowBackground)
.into()
}
@ -3924,7 +3926,7 @@ impl Application for App {
struct TrashWatcherSubscription;
let mut subscriptions = vec![
event::listen_with(|event, status| match event {
event::listen_with(|event, status, _window_id| match event {
Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => match status {
event::Status::Ignored => Some(Message::Key(modifiers, key)),
event::Status::Captured => None,
@ -3932,7 +3934,7 @@ impl Application for App {
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
Some(Message::Modifiers(modifiers))
}
Event::Window(_id, WindowEvent::CloseRequested) => Some(Message::WindowClose),
Event::Window(WindowEvent::CloseRequested) => Some(Message::WindowClose),
#[cfg(feature = "wayland")]
Event::PlatformSpecific(event::PlatformSpecific::Wayland(wayland_event)) => {
match wayland_event {
@ -3969,10 +3971,9 @@ impl Application for App {
}
Message::SystemThemeModeChange(update.config)
}),
subscription::channel(
Subscription::run_with_id(
TypeId::of::<WatcherSubscription>(),
100,
|mut output| async move {
stream::channel(100, |mut output| async move {
let watcher_res = {
let mut output = output.clone();
new_debouncer(
@ -4044,12 +4045,11 @@ impl Application for App {
}
std::future::pending().await
},
}),
),
subscription::channel(
Subscription::run_with_id(
TypeId::of::<TrashWatcherSubscription>(),
25,
|mut output| async move {
stream::channel(25, |mut output| async move {
let watcher_res = new_debouncer(
time::Duration::from_millis(250),
Some(time::Duration::from_millis(250)),
@ -4110,7 +4110,7 @@ impl Application for App {
}
std::future::pending().await
},
}),
),
];
@ -4137,10 +4137,9 @@ impl Application for App {
#[cfg(feature = "notify")]
{
struct NotificationSubscription;
subscriptions.push(subscription::channel(
subscriptions.push(Subscription::run_with_id(
TypeId::of::<NotificationSubscription>(),
1,
move |msg_tx| async move {
stream::channel(1, move |msg_tx| async move {
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
tokio::task::spawn_blocking(move || {
match notify_rust::Notification::new()
@ -4168,7 +4167,7 @@ impl Application for App {
.unwrap();
std::future::pending().await
},
}),
));
}
}
@ -4178,23 +4177,26 @@ impl Application for App {
//TODO: use recipe?
let id = *id;
let pending_operation = pending_operation.clone();
subscriptions.push(subscription::channel(id, 16, move |msg_tx| async move {
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
match pending_operation.perform(id, &msg_tx).await {
Ok(()) => {
let _ = msg_tx.lock().await.send(Message::PendingComplete(id)).await;
subscriptions.push(Subscription::run_with_id(
id,
stream::channel(16, move |msg_tx| async move {
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
match pending_operation.perform(id, &msg_tx).await {
Ok(()) => {
let _ = msg_tx.lock().await.send(Message::PendingComplete(id)).await;
}
Err(err) => {
let _ = msg_tx
.lock()
.await
.send(Message::PendingError(id, err.to_string()))
.await;
}
}
Err(err) => {
let _ = msg_tx
.lock()
.await
.send(Message::PendingError(id, err.to_string()))
.await;
}
}
std::future::pending().await
}));
std::future::pending().await
}),
));
}
for entity in self.tab_model.iter() {

View file

@ -4,7 +4,7 @@ use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
use cosmic::{
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
iced::subscription::Subscription,
iced::Subscription,
theme, Application,
};
use serde::{Deserialize, Serialize};

View file

@ -1,19 +1,14 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "winit")]
use cosmic::iced::multi_window::Application as IcedApplication;
#[cfg(feature = "wayland")]
use cosmic::iced::Application as IcedApplication;
use cosmic::{
app::{self, cosmic::Cosmic, message, Command, Core},
app::{self, cosmic::Cosmic, message, Core, Task},
cosmic_config, cosmic_theme, executor,
iced::{
event,
futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers},
subscription::{self, Subscription},
window, Alignment, Event, Length, Size,
stream, window, Alignment, Event, Length, Size, Subscription,
},
theme,
widget::{
@ -156,7 +151,7 @@ impl<M: Send + 'static> Dialog<M> {
path_opt: Option<PathBuf>,
mapper: fn(DialogMessage) -> M,
on_result: impl Fn(DialogResult) -> M + 'static,
) -> (Self, Command<M>) {
) -> (Self, Task<M>) {
//TODO: only do this once somehow?
crate::localize::localize();
@ -175,7 +170,7 @@ impl<M: Send + 'static> Dialog<M> {
settings.platform_specific.application_id = App::APP_ID.to_string();
}
let (window_id, window_command) = window::spawn(settings);
//let (window_id, window_command) = window::open(settings);
let core = Core::default();
let flags = Flags {
@ -189,11 +184,13 @@ impl<M: Send + 'static> Dialog<M> {
None
}
}),
window_id,
//TODO
window_id: window::Id::NONE,
config_handler,
config,
};
let (cosmic, cosmic_command) = <Cosmic<App> as IcedApplication>::new((core, flags));
let (cosmic, cosmic_command) = Cosmic::<App>::init((core, flags, settings));
(
Self {
@ -201,13 +198,13 @@ impl<M: Send + 'static> Dialog<M> {
mapper,
on_result: Box::new(on_result),
},
Command::batch([window_command, cosmic_command])
cosmic_command
.map(DialogMessage)
.map(move |message| app::Message::App(mapper(message))),
)
}
pub fn set_title(&mut self, title: impl Into<String>) -> Command<M> {
pub fn set_title(&mut self, title: impl Into<String>) -> Task<M> {
let mapper = self.mapper;
self.cosmic.app.title = title.into();
self.cosmic
@ -237,7 +234,7 @@ impl<M: Send + 'static> Dialog<M> {
&mut self,
filters: impl Into<Vec<DialogFilter>>,
filter_selected: Option<usize>,
) -> Command<M> {
) -> Task<M> {
let mapper = self.mapper;
self.cosmic.app.filters = filters.into();
self.cosmic.app.filter_selected = filter_selected;
@ -255,7 +252,7 @@ impl<M: Send + 'static> Dialog<M> {
.map(self.mapper)
}
pub fn update(&mut self, message: DialogMessage) -> Command<M> {
pub fn update(&mut self, message: DialogMessage) -> Task<M> {
let mapper = self.mapper;
let command = self
.cosmic
@ -264,9 +261,9 @@ impl<M: Send + 'static> Dialog<M> {
.map(move |message| app::Message::App(mapper(message)));
if let Some(result) = self.cosmic.app.result_opt.take() {
let on_result_message = (self.on_result)(result);
Command::batch([
Task::batch([
command,
Command::perform(async move { app::Message::App(on_result_message) }, |x| x),
Task::perform(async move { app::Message::App(on_result_message) }, |x| x),
])
} else {
command
@ -281,7 +278,7 @@ impl<M: Send + 'static> Dialog<M> {
}
pub fn window_id(&self) -> window::Id {
self.cosmic.app.main_window_id()
self.cosmic.app.flags.window_id
}
}
@ -421,7 +418,7 @@ impl App {
let mut row = widget::row::with_capacity(
if !self.filters.is_empty() { 1 } else { 0 } + self.choices.len() * 2 + 3,
)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs);
if !self.filters.is_empty() {
row = row.push(widget::dropdown(
@ -433,7 +430,7 @@ impl App {
for (choice_i, choice) in self.choices.iter().enumerate() {
match choice {
DialogChoice::CheckBox { label, value, .. } => {
row = row.push(widget::checkbox(label, *value, move |checked| {
row = row.push(widget::checkbox(label, *value).on_toggle(move |checked| {
Message::Choice(choice_i, if checked { 1 } else { 0 })
}));
}
@ -450,7 +447,7 @@ impl App {
}
}
}
row = row.push(widget::horizontal_space(Length::Fill));
row = row.push(widget::horizontal_space());
row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel));
row = row.push(if self.flags.kind.save() {
widget::button::suggested(&self.accept_label).on_press(Message::Save(false))
@ -505,10 +502,10 @@ impl App {
widget::settings::view_column(children).into()
}
fn rescan_tab(&self) -> Command<Message> {
fn rescan_tab(&self) -> Task<Message> {
let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes;
Command::perform(
Task::perform(
async move {
let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
@ -532,7 +529,7 @@ impl App {
}
}
fn search_set(&mut self, term_opt: Option<String>) -> Command<Message> {
fn search_set(&mut self, term_opt: Option<String>) -> Task<Message> {
let location_opt = match term_opt {
Some(term) => match &self.tab.location {
Location::Path(path) | Location::Search(path, ..) => Some((
@ -553,23 +550,23 @@ impl App {
};
if let Some((location, focus_search)) = location_opt {
self.tab.change_location(&location, None);
return Command::batch([
return Task::batch([
self.update_title(),
self.update_watcher(),
self.rescan_tab(),
if focus_search {
widget::text_input::focus(self.search_id.clone())
} else {
Command::none()
Task::none()
},
]);
}
Command::none()
Task::none()
}
fn update_config(&mut self) -> Command<Message> {
fn update_config(&mut self) -> Task<Message> {
self.update_nav_model();
Command::none()
Task::none()
}
fn activate_nav_model_location(&mut self, location: &Location) {
@ -657,12 +654,12 @@ impl App {
self.activate_nav_model_location(&self.tab.location.clone());
}
fn update_title(&mut self) -> Command<Message> {
fn update_title(&mut self) -> Task<Message> {
self.set_header_title(self.title.clone());
self.set_window_title(self.title.clone(), self.main_window_id())
self.set_window_title(self.title.clone(), self.flags.window_id)
}
fn update_watcher(&mut self) -> Command<Message> {
fn update_watcher(&mut self) -> Task<Message> {
if let Some((mut watcher, old_paths)) = self.watcher_opt.take() {
let mut new_paths = HashSet::new();
if let Some(path) = &self.tab.location.path_opt() {
@ -705,7 +702,7 @@ impl App {
}
//TODO: should any of this run in a command?
Command::none()
Task::none()
}
}
@ -732,7 +729,7 @@ impl Application for App {
}
/// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Message>) {
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Message>) {
core.window.context_is_overlay = false;
core.window.show_close = false;
core.window.show_maximize = false;
@ -785,7 +782,7 @@ impl Application for App {
watcher_opt: None,
};
let commands = Command::batch([
let commands = Task::batch([
app.update_config(),
app.update_title(),
app.update_watcher(),
@ -795,10 +792,6 @@ impl Application for App {
(app, commands)
}
fn main_window_id(&self) -> window::Id {
self.flags.window_id
}
fn context_drawer(&self) -> Option<Element<Message>> {
if !self.core.window.show_context {
return None;
@ -822,7 +815,7 @@ impl Application for App {
widget::container(self.button_view())
.width(Length::Fill)
.padding(space_xxs)
.style(theme::Container::WindowBackground)
.class(theme::Container::WindowBackground)
.into(),
])
.into(),
@ -1004,7 +997,7 @@ impl Application for App {
None
}
fn on_nav_select(&mut self, entity: segmented_button::Entity) -> Command<Message> {
fn on_nav_select(&mut self, entity: segmented_button::Entity) -> Task<Message> {
self.nav_model.activate(entity);
if let Some(location) = self.nav_model.data::<Location>(entity) {
let message = Message::TabMessage(tab::Message::Location(location.clone()));
@ -1016,14 +1009,14 @@ impl Application for App {
return mounter.mount(data.1.clone()).map(|_| message::none());
}
}
Command::none()
Task::none()
}
fn on_escape(&mut self) -> Command<Message> {
fn on_escape(&mut self) -> Task<Message> {
if self.tab.gallery {
// Close gallery if open
self.tab.gallery = false;
return Command::none();
return Task::none();
}
if self.search_get().is_some() {
@ -1033,13 +1026,13 @@ impl Application for App {
if self.tab.context_menu.is_some() {
self.tab.context_menu = None;
return Command::none();
return Task::none();
}
if self.tab.edit_location.is_some() {
// Close location editing if enabled
self.tab.edit_location = None;
return Command::none();
return Task::none();
}
let had_focused_button = self.tab.select_focus_id().is_some();
@ -1048,19 +1041,19 @@ impl Application for App {
// Unfocus if there was a focused button
return widget::button::focus(widget::Id::unique());
}
return Command::none();
return Task::none();
}
self.update(Message::Cancel)
}
/// Handle application events here.
fn update(&mut self, message: Message) -> Command<Message> {
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::None => {}
Message::Cancel => {
self.result_opt = Some(DialogResult::Cancel);
return window::close(self.main_window_id());
return window::close(self.flags.window_id);
}
Message::Choice(choice_i, option_i) => {
if let Some(choice) = self.choices.get_mut(choice_i) {
@ -1187,7 +1180,7 @@ impl Application for App {
//TODO: this could change favorites IDs while they are in use
self.update_nav_model();
return Command::batch(commands);
return Task::batch(commands);
}
Message::NewFolder => {
if let Some(path) = self.tab.location.path_opt() {
@ -1293,7 +1286,7 @@ impl Application for App {
return self.update(message);
} else {
// Otherwise, this is not a legal selection
return Command::none();
return Task::none();
}
}
}
@ -1301,7 +1294,7 @@ impl Application for App {
// If there are proper matching items, return them
if !paths.is_empty() {
self.result_opt = Some(DialogResult::Open(paths));
return window::close(self.main_window_id());
return window::close(self.flags.window_id);
}
// If we are in directory mode, return the current directory
@ -1309,7 +1302,7 @@ impl Application for App {
match &self.tab.location {
Location::Path(tab_path) => {
self.result_opt = Some(DialogResult::Open(vec![tab_path.clone()]));
return window::close(self.main_window_id());
return window::close(self.flags.window_id);
}
_ => {}
}
@ -1341,7 +1334,7 @@ impl Application for App {
});
} else {
self.result_opt = Some(DialogResult::Open(vec![path]));
return window::close(self.main_window_id());
return window::close(self.flags.window_id);
}
}
}
@ -1388,12 +1381,11 @@ impl Application for App {
commands.push(self.update(Message::from(action.message())));
}
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
commands
.push(Command::batch([self.update_watcher(), self.rescan_tab()]));
commands.push(Task::batch([self.update_watcher(), self.rescan_tab()]));
}
tab::Command::Iced(iced_command) => {
commands.push(
iced_command.map(|tab_message| {
iced_command.0.map(|tab_message| {
message::app(Message::TabMessage(tab_message))
}),
);
@ -1411,17 +1403,17 @@ impl Application for App {
self.set_context_title(self.context_page.title());
}
tab::Command::WindowDrag => {
commands.push(window::drag(self.main_window_id()));
commands.push(window::drag(self.flags.window_id));
}
tab::Command::WindowToggleMaximize => {
commands.push(window::toggle_maximize(self.main_window_id()));
commands.push(window::toggle_maximize(self.flags.window_id));
}
unsupported => {
log::warn!("{unsupported:?} not supported in dialog mode");
}
}
}
return Command::batch(commands);
return Task::batch(commands);
}
Message::TabRescan(location, parent_item_opt, mut items) => {
if location == self.tab.location {
@ -1554,7 +1546,7 @@ impl Application for App {
}
}
Command::none()
Task::none()
}
/// Creates a view after each update.
@ -1590,7 +1582,7 @@ impl Application for App {
fn subscription(&self) -> Subscription<Message> {
struct WatcherSubscription;
let mut subscriptions = vec![
event::listen_with(|event, status| match event {
event::listen_with(|event, status, _window_id| match event {
Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => match status {
event::Status::Ignored => Some(Message::Key(modifiers, key)),
event::Status::Captured => None,
@ -1610,10 +1602,9 @@ impl Application for App {
}
Message::Config(update.config)
}),
subscription::channel(
Subscription::run_with_id(
TypeId::of::<WatcherSubscription>(),
100,
|mut output| async move {
stream::channel(100, |mut output| async move {
let watcher_res = {
let mut output = output.clone();
new_debouncer(
@ -1683,7 +1674,7 @@ impl Application for App {
}
std::future::pending().await
},
}),
),
self.tab.subscription().map(Message::TabMessage),
];

View file

@ -27,11 +27,11 @@ macro_rules! menu_button {
vec![$(Element::from($x)),+]
)
.height(Length::Fixed(24.0))
.align_items(Alignment::Center)
.align_y(Alignment::Center)
)
.padding([theme::active().cosmic().spacing.space_xxxs, 16])
.width(Length::Fill)
.style(theme::Button::MenuItem)
.class(theme::Button::MenuItem)
);
}
@ -62,12 +62,8 @@ pub fn context_menu<'a>(
let menu_item = |label, action| {
let key = find_key(&action);
menu_button!(
text::body(label),
horizontal_space(Length::Fill),
text::body(key)
)
.on_press(tab::Message::ContextAction(action))
menu_button!(text::body(label), horizontal_space(), text::body(key))
.on_press(tab::Message::ContextAction(action))
};
let (sort_name, sort_direction, _) = tab.sort_options();
@ -291,10 +287,10 @@ pub fn context_menu<'a>(
container(column::with_children(children))
.padding(1)
//TODO: move style to libcosmic
.style(theme::Container::custom(|theme| {
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
container::Appearance {
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
@ -305,7 +301,7 @@ pub fn context_menu<'a>(
},
..Default::default()
}
}))
})
.width(Length::Fixed(260.0))
.into()
}
@ -641,10 +637,10 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess
container(column::with_children(children))
.padding(1)
.style(theme::Container::custom(|theme| {
.style(|theme| {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
container::Appearance {
container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
@ -655,7 +651,7 @@ pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Mess
},
..Default::default()
}
}))
})
.width(Length::Fixed(240.0))
.into()
}

View file

@ -1,6 +1,6 @@
use cosmic::{
iced::{futures::SinkExt, subscription},
widget, Command,
iced::{futures::SinkExt, stream, Subscription},
widget, Task,
};
use gio::{glib, prelude::*};
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
@ -460,9 +460,9 @@ impl Mounter for Gvfs {
items_rx.blocking_recv()
}
fn mount(&self, item: MounterItem) -> Command<()> {
fn mount(&self, item: MounterItem) -> Task<()> {
let command_tx = self.command_tx.clone();
Command::perform(
Task::perform(
async move {
command_tx.send(Cmd::Mount(item)).unwrap();
()
@ -471,9 +471,9 @@ impl Mounter for Gvfs {
)
}
fn network_drive(&self, uri: String) -> Command<()> {
fn network_drive(&self, uri: String) -> Task<()> {
let command_tx = self.command_tx.clone();
Command::perform(
Task::perform(
async move {
command_tx.send(Cmd::NetworkDrive(uri)).unwrap();
()
@ -490,9 +490,9 @@ impl Mounter for Gvfs {
items_rx.blocking_recv()
}
fn unmount(&self, item: MounterItem) -> Command<()> {
fn unmount(&self, item: MounterItem) -> Task<()> {
let command_tx = self.command_tx.clone();
Command::perform(
Task::perform(
async move {
command_tx.send(Cmd::Unmount(item)).unwrap();
()
@ -501,30 +501,35 @@ impl Mounter for Gvfs {
)
}
fn subscription(&self) -> subscription::Subscription<MounterMessage> {
fn subscription(&self) -> Subscription<MounterMessage> {
let command_tx = self.command_tx.clone();
let event_rx = self.event_rx.clone();
subscription::channel(TypeId::of::<Self>(), 1, |mut output| async move {
command_tx.send(Cmd::Rescan).unwrap();
while let Some(event) = event_rx.lock().await.recv().await {
match event {
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
Event::Items(items) => output.send(MounterMessage::Items(items)).await.unwrap(),
Event::MountResult(item, res) => output
.send(MounterMessage::MountResult(item, res))
.await
.unwrap(),
Event::NetworkAuth(uri, auth, auth_tx) => output
.send(MounterMessage::NetworkAuth(uri, auth, auth_tx))
.await
.unwrap(),
Event::NetworkResult(uri, res) => output
.send(MounterMessage::NetworkResult(uri, res))
.await
.unwrap(),
Subscription::run_with_id(
TypeId::of::<Self>(),
stream::channel(1, |mut output| async move {
command_tx.send(Cmd::Rescan).unwrap();
while let Some(event) = event_rx.lock().await.recv().await {
match event {
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
Event::Items(items) => {
output.send(MounterMessage::Items(items)).await.unwrap()
}
Event::MountResult(item, res) => output
.send(MounterMessage::MountResult(item, res))
.await
.unwrap(),
Event::NetworkAuth(uri, auth, auth_tx) => output
.send(MounterMessage::NetworkAuth(uri, auth, auth_tx))
.await
.unwrap(),
Event::NetworkResult(uri, res) => output
.send(MounterMessage::NetworkResult(uri, res))
.await
.unwrap(),
}
}
}
pending().await
})
pending().await
}),
)
}
}

View file

@ -1,4 +1,4 @@
use cosmic::{iced::subscription, widget, Command};
use cosmic::{iced::Subscription, widget, Task};
use once_cell::sync::Lazy;
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
use tokio::sync::mpsc;
@ -93,11 +93,11 @@ pub enum MounterMessage {
pub trait Mounter: Send + Sync {
fn items(&self, sizes: IconSizes) -> Option<MounterItems>;
//TODO: send result
fn mount(&self, item: MounterItem) -> Command<()>;
fn network_drive(&self, uri: String) -> Command<()>;
fn mount(&self, item: MounterItem) -> Task<()>;
fn network_drive(&self, uri: String) -> Task<()>;
fn network_scan(&self, uri: &str, sizes: IconSizes) -> Option<Result<Vec<tab::Item>, String>>;
fn unmount(&self, item: MounterItem) -> Command<()>;
fn subscription(&self) -> subscription::Subscription<MounterMessage>;
fn unmount(&self, item: MounterItem) -> Task<()>;
fn subscription(&self) -> Subscription<MounterMessage>;
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

View file

@ -17,8 +17,8 @@ use cosmic::{
overlay,
renderer::{self, Quad, Renderer as _},
touch,
widget::{tree, Operation, OperationOutputWrapper, Tree},
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Widget,
widget::{tree, Operation, Tree},
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
},
widget::Id,
Element, Renderer, Theme,
@ -218,15 +218,21 @@ impl State {
let new = if let Some((prev_click, prev_time)) = self.prev_click.take() {
if now.duration_since(prev_time) < DOUBLE_CLICK_DURATION {
match prev_click.kind() {
mouse::click::Kind::Single => mouse::Click::new(pos, Some(prev_click)),
mouse::click::Kind::Double => mouse::Click::new(pos, Some(prev_click)),
mouse::click::Kind::Triple => mouse::Click::new(pos, Some(prev_click)),
mouse::click::Kind::Single => {
mouse::Click::new(pos, mouse::Button::Left, Some(prev_click))
}
mouse::click::Kind::Double => {
mouse::Click::new(pos, mouse::Button::Left, Some(prev_click))
}
mouse::click::Kind::Triple => {
mouse::Click::new(pos, mouse::Button::Left, Some(prev_click))
}
}
} else {
mouse::Click::new(pos, None)
mouse::Click::new(pos, mouse::Button::Left, None)
}
} else {
mouse::Click::new(pos, None)
mouse::Click::new(pos, mouse::Button::Left, None)
};
self.prev_click = Some((new.clone(), now));
new
@ -300,7 +306,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
operation: &mut dyn Operation,
) {
self.content
.as_widget()
@ -406,10 +412,11 @@ where
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content
.as_widget_mut()
.overlay(&mut tree.children[0], layout, renderer)
.overlay(&mut tree.children[0], layout, renderer, translation)
}
fn drag_destinations(

View file

@ -11,10 +11,10 @@ use cosmic::{
futures,
futures::SinkExt,
keyboard::Modifiers,
subscription::{self, Subscription},
stream,
//TODO: export in cosmic::widget
widget::{
container, horizontal_rule,
container, horizontal_rule, rule,
scrollable::{self, AbsoluteOffset, Viewport},
},
Alignment,
@ -25,16 +25,16 @@ use cosmic::{
Point,
Rectangle,
Size,
Subscription,
},
iced_core::{mouse::ScrollDelta, widget::tree},
iced_style::rule,
theme,
widget::{
self,
menu::{action::MenuAction, key_bind::KeyBind},
vertical_space, DndDestination, DndSource, Id, Widget,
DndDestination, DndSource, Id, Space, Widget,
},
Element, Theme,
Element,
};
use chrono::{DateTime, Utc};
@ -122,9 +122,9 @@ fn button_appearance(
accent: bool,
condensed_radius: bool,
desktop: bool,
) -> widget::button::Appearance {
) -> widget::button::Style {
let cosmic = theme.cosmic();
let mut appearance = widget::button::Appearance::new();
let mut appearance = widget::button::Style::new();
if selected {
if accent {
appearance.background = Some(Color::from(cosmic.accent_color()).into());
@ -938,6 +938,20 @@ impl Location {
}
}
pub struct TaskWrapper(pub cosmic::Task<Message>);
impl From<cosmic::Task<Message>> for TaskWrapper {
fn from(task: cosmic::Task<Message>) -> Self {
Self(task)
}
}
impl fmt::Debug for TaskWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TaskWrapper").finish()
}
}
#[derive(Debug)]
pub enum Command {
Action(Action),
@ -946,7 +960,7 @@ pub enum Command {
ChangeLocation(String, Location, Option<PathBuf>),
DropFiles(PathBuf, ClipboardPaste),
EmptyTrash,
Iced(cosmic::Command<Message>),
Iced(TaskWrapper),
MoveToTrash(Vec<PathBuf>),
OpenFile(PathBuf),
OpenInNewTab(PathBuf),
@ -1127,7 +1141,7 @@ impl ItemThumbnail {
let thumbnail =
image.thumbnail(thumbnail_size, thumbnail_size).into_rgba8();
return ItemThumbnail::Image(
widget::image::Handle::from_pixels(
widget::image::Handle::from_rgba(
thumbnail.width(),
thumbnail.height(),
thumbnail.into_raw(),
@ -1186,7 +1200,7 @@ impl ItemThumbnail {
Ok(reader) => match reader.decode().map(|image| image.into_rgba8()) {
Ok(image) => {
return ItemThumbnail::Image(
widget::image::Handle::from_pixels(
widget::image::Handle::from_rgba(
image.width(),
image.height(),
image.into_raw(),
@ -1315,9 +1329,9 @@ impl Item {
}
column = column.push(widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().into(),
self.preview(sizes),
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().into(),
]));
let mut details = widget::column().spacing(space_xxxs);
@ -2223,23 +2237,25 @@ impl Tab {
self.select_rect(rect, mod_ctrl, mod_shift);
if self.select_focus.take().is_some() {
// Unfocus currently focused button
commands.push(Command::Iced(widget::button::focus(widget::Id::unique())));
commands.push(Command::Iced(
widget::button::focus(widget::Id::unique()).into(),
));
}
}
None => {}
},
Message::EditLocation(edit_location) => {
if self.edit_location.is_none() && edit_location.is_some() {
commands.push(Command::Iced(widget::text_input::focus(
self.edit_location_id.clone(),
)));
commands.push(Command::Iced(
widget::text_input::focus(self.edit_location_id.clone()).into(),
));
}
self.edit_location = edit_location;
}
Message::EditLocationEnable => {
commands.push(Command::Iced(widget::text_input::focus(
self.edit_location_id.clone(),
)));
commands.push(Command::Iced(
widget::text_input::focus(self.edit_location_id.clone()).into(),
));
self.edit_location = Some(self.location.clone());
}
Message::OpenInNewTab(path) => {
@ -2281,13 +2297,12 @@ impl Tab {
self.select_position(row, col, mod_shift);
}
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
if let Some(id) = self.select_focus_id() {
commands.push(Command::Iced(widget::button::focus(id)));
commands.push(Command::Iced(widget::button::focus(id).into()));
}
}
Message::GalleryToggle => {
@ -2342,13 +2357,12 @@ impl Tab {
self.select_position(0, 0, mod_shift);
}
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
if let Some(id) = self.select_focus_id() {
commands.push(Command::Iced(widget::button::focus(id)));
commands.push(Command::Iced(widget::button::focus(id).into()));
}
}
}
@ -2396,13 +2410,12 @@ impl Tab {
self.select_position(0, 0, mod_shift);
}
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
if let Some(id) = self.select_focus_id() {
commands.push(Command::Iced(widget::button::focus(id)));
commands.push(Command::Iced(widget::button::focus(id).into()));
}
}
}
@ -2433,13 +2446,12 @@ impl Tab {
self.select_position(0, 0, mod_shift);
}
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
if let Some(id) = self.select_focus_id() {
commands.push(Command::Iced(widget::button::focus(id)));
commands.push(Command::Iced(widget::button::focus(id).into()));
}
}
}
@ -2472,13 +2484,12 @@ impl Tab {
self.select_position(0, 0, mod_shift);
}
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
if let Some(id) = self.select_focus_id() {
commands.push(Command::Iced(widget::button::focus(id)));
commands.push(Command::Iced(widget::button::focus(id).into()));
}
}
}
@ -2587,10 +2598,9 @@ impl Tab {
}
Message::ScrollToFocus => {
if let Some(offset) = self.select_focus_scroll() {
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
}
Message::SearchContext(location, context) => {
@ -2653,7 +2663,9 @@ impl Tab {
self.select_all();
if self.select_focus.take().is_some() {
// Unfocus currently focused button
commands.push(Command::Iced(widget::button::focus(widget::Id::unique())));
commands.push(Command::Iced(
widget::button::focus(widget::Id::unique()).into(),
));
}
}
Message::SetSort(heading_option, dir) => {
@ -2756,13 +2768,16 @@ impl Tab {
Message::DndEnter(loc) => {
self.dnd_hovered = Some((loc.clone(), Instant::now()));
if loc != self.location {
commands.push(Command::Iced(cosmic::Command::perform(
async move {
tokio::time::sleep(HOVER_DURATION).await;
Message::DndHover(loc)
},
|x| x,
)));
commands.push(Command::Iced(
cosmic::Task::perform(
async move {
tokio::time::sleep(HOVER_DURATION).await;
Message::DndHover(loc)
},
|x| x,
)
.into(),
));
}
}
Message::DndLeave(loc) => {
@ -2788,10 +2803,9 @@ impl Tab {
if self.scroll_opt.is_none() {
let offset = AbsoluteOffset { x: 0.0, y: 0.0 };
self.scroll_opt = Some(offset);
commands.push(Command::Iced(scrollable::scroll_to(
self.scrollable_id.clone(),
offset,
)));
commands.push(Command::Iced(
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
));
}
// Change directory if requested
@ -2940,7 +2954,7 @@ impl Tab {
let location2 = location.clone();
let location3 = location.clone();
let is_dnd_hovered = self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&location);
widget::container(
let mut container = widget::container(
DndDestination::for_data::<ClipboardPaste>(element, move |data, action| {
if let Some(mut data) = data {
if action == DndAction::Copy {
@ -2958,13 +2972,10 @@ impl Tab {
})
.on_enter(move |_, _, _| Message::DndEnter(location2.clone()))
.on_leave(move || Message::DndLeave(location3.clone())),
)
.style(if is_dnd_hovered {
theme::Container::custom(|t| {
let mut a = cosmic::iced_style::container::StyleSheet::appearance(
t,
&theme::Container::default(),
);
);
if is_dnd_hovered {
container = container.style(|t| {
let mut a = widget::container::Style::default();
let t = t.cosmic();
// todo use theme drop target color
let mut bg = t.accent_color();
@ -2976,11 +2987,9 @@ impl Tab {
radius: t.radius_s().into(),
};
a
})
} else {
theme::Container::default()
})
.into()
});
}
container.into()
}
pub fn gallery_view(&self) -> Element<Message> {
@ -3040,20 +3049,20 @@ impl Tab {
}
let mut column = widget::column::with_capacity(2);
column = column.push(widget::vertical_space(Length::Fixed(space_m.into())));
column = column.push(widget::Space::with_height(Length::Fixed(space_m.into())));
{
let mut row = widget::row::with_capacity(5).align_items(Alignment::Center);
row = row.push(widget::horizontal_space(Length::Fill));
let mut row = widget::row::with_capacity(5).align_y(Alignment::Center);
row = row.push(widget::horizontal_space());
if let Some(name) = name_opt {
row = row.push(name);
}
row = row.push(widget::horizontal_space(Length::Fill));
row = row.push(widget::horizontal_space());
row = row.push(
widget::button::icon(widget::icon::from_name("window-close-symbolic"))
.style(theme::Button::Standard)
.class(theme::Button::Standard)
.on_press(Message::Gallery(false)),
);
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
row = row.push(widget::Space::with_width(Length::Fixed(space_m.into())));
// This mouse area provides window drag while the header bar is hidden
let mouse_area = mouse_area::MouseArea::new(row)
.on_drag(|_| Message::WindowDrag)
@ -3061,44 +3070,44 @@ impl Tab {
column = column.push(mouse_area);
}
{
let mut row = widget::row::with_capacity(7).align_items(Alignment::Center);
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
let mut row = widget::row::with_capacity(7).align_y(Alignment::Center);
row = row.push(widget::Space::with_width(Length::Fixed(space_m.into())));
row = row.push(
widget::button::icon(widget::icon::from_name("go-previous-symbolic"))
.padding(space_xs)
.style(theme::Button::Standard)
.class(theme::Button::Standard)
.on_press(Message::GalleryPrevious),
);
row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into())));
row = row.push(widget::Space::with_width(Length::Fixed(space_xxs.into())));
if let Some(element) = element_opt {
row = row.push(element);
} else {
//TODO: what to do when no image?
row = row.push(widget::Space::new(Length::Fill, Length::Fill));
}
row = row.push(widget::horizontal_space(Length::Fixed(space_xxs.into())));
row = row.push(widget::Space::with_width(Length::Fixed(space_xxs.into())));
row = row.push(
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
.padding(space_xs)
.style(theme::Button::Standard)
.class(theme::Button::Standard)
.on_press(Message::GalleryNext),
);
row = row.push(widget::horizontal_space(Length::Fixed(space_m.into())));
row = row.push(widget::Space::with_width(Length::Fixed(space_m.into())));
column = column.push(row);
}
widget::container(column)
.width(Length::Fill)
.height(Length::Fill)
.style(theme::Container::Custom(Box::new(|theme| {
.style(|theme| {
let cosmic = theme.cosmic();
let mut bg = cosmic.bg_color();
bg.alpha = 0.75;
widget::container::Appearance {
widget::container::Style {
background: Some(Color::from(bg).into()),
..Default::default()
}
})))
})
.into()
}
@ -3110,7 +3119,7 @@ impl Tab {
font_size: f32,
line_height: f32,
) -> f32 {
let text: text::Text<'a, font::Font> = text::Text {
let text: text::Text<&'a str, font::Font> = text::Text {
content,
bounds: Size::INFINITY,
size: font_size.into(),
@ -3119,7 +3128,7 @@ impl Tab {
horizontal_alignment: Horizontal::Left,
vertical_alignment: Vertical::Top,
shaping: text::Shaping::default(),
wrap: text::Wrap::None,
wrapping: text::Wrapping::None,
};
graphics::text::Paragraph::with_text(text)
.min_bounds()
@ -3144,14 +3153,14 @@ impl Tab {
let size = self.size_opt.get().unwrap_or(Size::new(0.0, 0.0));
let mut row = widget::row::with_capacity(5)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.padding([space_xxxs, 0]);
let mut w = 0.0;
let mut prev_button =
widget::button::custom(widget::icon::from_name("go-previous-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon);
.class(theme::Button::Icon);
if self.history_i > 0 && !self.history.is_empty() {
prev_button = prev_button.on_press(Message::GoPrevious);
}
@ -3161,14 +3170,14 @@ impl Tab {
let mut next_button =
widget::button::custom(widget::icon::from_name("go-next-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon);
.class(theme::Button::Icon);
if self.history_i + 1 < self.history.len() {
next_button = next_button.on_press(Message::GoNext);
}
row = row.push(next_button);
w += 16.0 + 2.0 * space_xxs as f32;
row = row.push(widget::horizontal_space(Length::Fixed(space_s.into())));
row = row.push(widget::Space::with_width(Length::Fixed(space_s.into())));
w += space_s as f32;
//TODO: allow resizing?
@ -3180,7 +3189,7 @@ impl Tab {
let (sort_name, sort_direction, _) = self.sort_options();
let heading_item = |name, width, msg| {
let mut row = widget::row::with_capacity(2)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
.width(width);
row = row.push(widget::text::heading(name));
@ -3216,7 +3225,7 @@ impl Tab {
},
heading_item(fl!("size"), Length::Fixed(size_width), HeadingOptions::Size),
])
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.height(Length::Fixed((space_m + 4).into()))
.padding([0, space_xxs])
.spacing(space_xxs);
@ -3230,7 +3239,7 @@ impl Tab {
)
.on_press(Message::EditLocation(None))
.padding(space_xxs)
.style(theme::Button::Icon),
.class(theme::Button::Icon),
);
row = row.push(
widget::text_input("", path.to_string_lossy())
@ -3243,8 +3252,8 @@ impl Tab {
);
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
column = column.push(row);
column = column.push(horizontal_rule(1).style(theme::Rule::Custom(Box::new(
|theme: &Theme| rule::Appearance {
column = column.push(horizontal_rule(1).class(theme::Rule::Custom(Box::new(
|theme| rule::Style {
color: theme.cosmic().accent_color().into(),
width: 1,
radius: 0.0.into(),
@ -3262,7 +3271,7 @@ impl Tab {
crate::mouse_area::MouseArea::new(
widget::button::custom(widget::icon::from_name("edit-symbolic").size(16))
.padding(space_xxs)
.style(theme::Button::Icon)
.class(theme::Button::Icon)
.on_press(Message::EditLocation(Some(self.location.clone()))),
)
.on_middle_press(move |_| Message::OpenInNewTab(path.clone())),
@ -3280,7 +3289,7 @@ impl Tab {
let (name_width, name_text) = if children.is_empty() {
(
text_width_heading(&name),
widget::text::heading(name).wrap(text::Wrap::None),
widget::text::heading(name).wrapping(text::Wrapping::None),
)
} else {
children.push(
@ -3292,7 +3301,7 @@ impl Tab {
w += 16.0;
(
text_width_body(&name),
widget::text::body(name).wrap(text::Wrap::None),
widget::text::body(name).wrapping(text::Wrapping::None),
)
};
@ -3300,7 +3309,7 @@ impl Tab {
w += 2.0 * space_xxxs as f32;
let mut row = widget::row::with_capacity(2)
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxxs);
//TODO: figure out why this hardcoded offset is needed after the first item is ellipsed
let overflow_offset = 64.0;
@ -3317,7 +3326,7 @@ impl Tab {
let mut mouse_area = crate::mouse_area::MouseArea::new(
widget::button::custom(row)
.padding(space_xxxs)
.style(theme::Button::Link)
.class(theme::Button::Link)
.on_press(Message::Location(location.clone())),
);
@ -3351,7 +3360,7 @@ impl Tab {
widget::button::custom(widget::text::heading(fl!("trash")))
.padding(space_xxxs)
.on_press(Message::Location(Location::Trash))
.style(theme::Button::Text)
.class(theme::Button::Text)
.into(),
);
}
@ -3360,7 +3369,7 @@ impl Tab {
widget::button::custom(widget::text::heading(fl!("recents")))
.padding(space_xxxs)
.on_press(Message::Location(Location::Recents))
.style(theme::Button::Text)
.class(theme::Button::Text)
.into(),
);
}
@ -3372,7 +3381,7 @@ impl Tab {
uri.clone(),
display_name.clone(),
)))
.style(theme::Button::Text)
.class(theme::Button::Text)
.into(),
);
}
@ -3383,14 +3392,14 @@ impl Tab {
}
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
column = column.push(row);
column = column.push(horizontal_rule(1).style(theme::Rule::Custom(Box::new(
|theme: &Theme| rule::Appearance {
column = column.push(
horizontal_rule(1).class(theme::Rule::Custom(Box::new(|theme| rule::Style {
color: theme.cosmic().accent_color().into(),
width: 1,
radius: 0.0.into(),
fill_mode: rule::FillMode::Full,
},
))));
}))),
);
if self.config.view == View::List && !condensed {
column = column.push(heading_row);
@ -3435,7 +3444,7 @@ impl Tab {
],
Mode::Desktop => Vec::new(),
})
.align_items(Alignment::Center)
.align_x(Alignment::Center)
.spacing(space_xxs),
)
.align_x(Horizontal::Center)
@ -3446,13 +3455,7 @@ impl Tab {
.into()
}
pub fn grid_view(
&self,
) -> (
Option<Element<'static, cosmic::app::Message<crate::app::Message>>>,
Element<Message>,
bool,
) {
pub fn grid_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) {
let cosmic_theme::Spacing {
space_m,
space_xxs,
@ -3543,26 +3546,26 @@ impl Tab {
.size(icon_sizes.grid()),
)
.padding(space_xxxs)
.style(button_style(item.selected, false, false, false))
.class(button_style(item.selected, false, false, false))
.into(),
widget::tooltip(
widget::button::custom(widget::text::body(&item.display_name))
.id(item.button_id.clone())
.padding([0, space_xxxs])
.style(button_style(
.class(button_style(
item.selected,
true,
true,
matches!(self.mode, Mode::Desktop),
)),
&item.name,
widget::text::body(&item.name),
widget::tooltip::Position::Bottom,
)
.into(),
];
let mut column = widget::column::with_capacity(buttons.len())
.align_items(Alignment::Center)
.align_x(Alignment::Center)
.height(Length::Fixed(item_height as f32))
.width(Length::Fixed(item_width as f32));
for button in buttons {
@ -3660,7 +3663,7 @@ impl Tab {
let spacer_height = height.checked_sub(max_bottom + top_deduct).unwrap_or(0);
if spacer_height > 0 {
children.push(
widget::container(vertical_space(Length::Fixed(spacer_height as f32)))
widget::container(Space::with_height(Length::Fixed(spacer_height as f32)))
.into(),
)
}
@ -3690,7 +3693,7 @@ impl Tab {
)
.on_press(Message::Click(Some(*i)))
.padding(space_xxxs)
.style(button_style(
.class(button_style(
item.selected,
false,
false,
@ -3700,11 +3703,11 @@ impl Tab {
.id(item.button_id.clone())
.on_press(Message::Click(Some(*i)))
.padding([0, space_xxxs])
.style(button_style(item.selected, true, true, false)),
.class(button_style(item.selected, true, true, false)),
];
let mut column = widget::column::with_capacity(buttons.len())
.align_items(Alignment::Center)
.align_x(Alignment::Center)
.height(Length::Fixed(item_height as f32))
.width(Length::Fixed(item_width as f32));
for button in buttons {
@ -3715,14 +3718,13 @@ impl Tab {
dnd_item_i += 1;
} else {
dnd_grid = dnd_grid.push(
widget::container(vertical_space(item_width as f32))
widget::container(Space::with_height(item_width as f32))
.height(Length::Fixed(item_height as f32)),
);
}
}
}
Element::from(dnd_grid)
.map(|m| cosmic::app::Message::App(crate::app::Message::TabMessage(None, m)))
}),
mouse_area::MouseArea::new(
widget::container(widget::column::with_children(children)).width(Length::Fill),
@ -3737,13 +3739,7 @@ impl Tab {
)
}
pub fn list_view(
&self,
) -> (
Option<Element<'static, cosmic::app::Message<crate::app::Message>>>,
Element<Message>,
bool,
) {
pub fn list_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) {
let cosmic_theme::Spacing {
space_m,
space_s,
@ -3863,7 +3859,7 @@ impl Tab {
.into(),
])
.height(Length::Fixed(row_height as f32))
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
} else if is_search {
widget::row::with_children(vec![
@ -3889,7 +3885,7 @@ impl Tab {
.into(),
])
.height(Length::Fixed(row_height as f32))
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
} else {
widget::row::with_children(vec![
@ -3908,7 +3904,7 @@ impl Tab {
.into(),
])
.height(Length::Fixed(row_height as f32))
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
};
@ -3918,7 +3914,7 @@ impl Tab {
.width(Length::Fill)
.id(item.button_id.clone())
.padding([0, space_xxs])
.style(button_style(item.selected, true, false, false)),
.class(button_style(item.selected, true, false, false)),
)
.on_press(move |_| Message::Click(Some(i)))
.on_double_click(move |_| Message::DoubleClick(Some(i)))
@ -3944,7 +3940,7 @@ impl Tab {
if item.selected || !drag_items.is_empty() {
let dnd_row = if !item.selected {
Element::from(vertical_space(Length::Fixed(row_height as f32)))
Element::from(Space::with_height(Length::Fixed(row_height as f32)))
} else if condensed {
widget::row::with_children(vec![
widget::icon::icon(item.icon_handle_list_condensed.clone())
@ -3958,7 +3954,7 @@ impl Tab {
])
.into(),
])
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
.into()
} else if is_search {
@ -3984,7 +3980,7 @@ impl Tab {
.width(Length::Fixed(size_width))
.into(),
])
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
.into()
} else {
@ -4003,7 +3999,7 @@ impl Tab {
.width(Length::Fixed(size_width))
.into(),
])
.align_items(Alignment::Center)
.align_y(Alignment::Center)
.spacing(space_xxs)
.into()
};
@ -4039,14 +4035,13 @@ impl Tab {
let spacer_height = size.height - y as f32 - top_deduct as f32;
if spacer_height > 0. {
children
.push(widget::container(vertical_space(Length::Fixed(spacer_height))).into());
children.push(
widget::container(Space::with_height(Length::Fixed(spacer_height))).into(),
);
}
}
let drag_col = (!drag_items.is_empty()).then(|| {
Element::from(widget::column::with_children(drag_items))
.map(|m| cosmic::app::Message::App(crate::app::Message::TabMessage(None, m)))
});
let drag_col = (!drag_items.is_empty())
.then(|| Element::from(widget::column::with_children(drag_items)));
(
drag_col,
@ -4099,22 +4094,19 @@ impl Tab {
.collect::<Vec<PathBuf>>()
})
.unwrap_or_default();
let item_view = DndSource::<_, cosmic::app::Message<app::Message>, ClipboardCopy>::with_id(
item_view,
Id::new("tab-view"),
);
let item_view =
DndSource::<Message, ClipboardCopy>::with_id(item_view, Id::new("tab-view"));
let item_view = match drag_list {
Some(drag_list) if self.selected_clicked => {
let drag_list = ArcElementWrapper(Arc::new(Mutex::new(drag_list)));
let drag_list = ArcElementWrapper::<Message>(Arc::new(Mutex::new(drag_list)));
item_view
.drag_content(move || {
ClipboardCopy::new(crate::clipboard::ClipboardKind::Copy, &files)
})
.drag_icon(move || {
let state: tree::State =
Widget::<cosmic::app::Message<app::Message>, _, _>::state(&drag_list);
(drag_list.clone().into(), state)
let state: tree::State = Widget::<Message, _, _>::state(&drag_list);
(Element::from(drag_list.clone()).map(|_m| ()), state)
})
}
_ => item_view,
@ -4164,7 +4156,7 @@ impl Tab {
if !items.is_empty() {
tab_column = tab_column.push(
widget::layer_container(widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().into(),
widget::button::standard(fl!("empty-trash"))
.on_press(Message::EmptyTrash)
.into(),
@ -4178,7 +4170,7 @@ impl Tab {
Location::Network(uri, _display_name) if uri == "network:///" => {
tab_column = tab_column.push(
widget::layer_container(widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(),
widget::horizontal_space().into(),
widget::button::standard(fl!("add-network-drive"))
.on_press(Message::AddNetworkDrive)
.into(),
@ -4194,11 +4186,8 @@ impl Tab {
.width(Length::Fill);
if self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location) {
tab_view = tab_view.style(cosmic::theme::Container::custom(|t| {
let mut a = cosmic::iced_style::container::StyleSheet::appearance(
t,
&cosmic::theme::Container::default(),
);
tab_view = tab_view.style(|t| {
let mut a = widget::container::Style::default();
let c = t.cosmic();
a.border = cosmic::iced_core::Border {
color: (c.accent_color()).into(),
@ -4206,7 +4195,7 @@ impl Tab {
radius: c.radius_0().into(),
};
a
}));
});
}
let tab_location_2 = self.location.clone();
@ -4287,10 +4276,9 @@ impl Tab {
continue;
};
let mime = item.mime.clone();
subscriptions.push(subscription::channel(
subscriptions.push(Subscription::run_with_id(
path.clone(),
1,
|mut output| async move {
stream::channel(1, |mut output| async move {
let message = {
let path = path.clone();
tokio::task::spawn_blocking(move || {
@ -4312,7 +4300,7 @@ impl Tab {
}
std::future::pending().await
},
}),
));
if subscriptions.len() >= jobs {
@ -4327,10 +4315,9 @@ impl Tab {
let term = term.clone();
let show_hidden = *show_hidden;
let start = start.clone();
subscriptions.push(subscription::channel(
subscriptions.push(Subscription::run_with_id(
location.clone(),
2,
move |mut output| async move {
stream::channel(2, move |mut output| async move {
//TODO: optimal size?
let (results_tx, results_rx) = mpsc::channel(65536);
@ -4408,7 +4395,7 @@ impl Tab {
let _ = output.lock().await.send(Message::SearchReady(true)).await;
std::future::pending().await
},
}),
));
}
@ -4846,7 +4833,7 @@ impl<M> Widget<M, cosmic::Theme, cosmic::Renderer> for ArcElementWrapper<M> {
state: &mut tree::Tree,
layout: cosmic::iced_core::Layout<'_>,
renderer: &cosmic::Renderer,
operation: &mut dyn widget::Operation<cosmic::iced_core::widget::OperationOutputWrapper<M>>,
operation: &mut dyn widget::Operation,
) {
self.0
.lock()
@ -4891,6 +4878,7 @@ impl<M> Widget<M, cosmic::Theme, cosmic::Renderer> for ArcElementWrapper<M> {
_state: &'a mut tree::Tree,
_layout: cosmic::iced_core::Layout<'_>,
_renderer: &cosmic::Renderer,
_translation: cosmic::iced_core::Vector,
) -> Option<cosmic::iced_core::overlay::Element<'a, M, cosmic::Theme, cosmic::Renderer>> {
// TODO
None