feat: rebase libcosmic onto iced 0.14
This commit is contained in:
parent
03988df2dc
commit
360973175c
11 changed files with 2142 additions and 2014 deletions
2301
Cargo.lock
generated
2301
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -103,8 +103,8 @@ default = [
|
|||
"io-uring",
|
||||
"lzma-rust2",
|
||||
"notify",
|
||||
"wgpu",
|
||||
"wayland",
|
||||
"wgpu",
|
||||
]
|
||||
dbus-config = ["libcosmic/dbus-config"]
|
||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
||||
|
|
@ -146,6 +146,10 @@ tokio = { version = "1", features = ["rt", "macros"] }
|
|||
# libcosmic = { path = "../libcosmic" }
|
||||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||
|
||||
|
||||
# [patch.'https://github.com/pop-os/smithay-clipboard']
|
||||
# smithay-clipboard = { path = "../smithay-clipboard" }
|
||||
|
|
|
|||
532
src/app.rs
532
src/app.rs
|
|
@ -33,16 +33,16 @@ use cosmic::{
|
|||
window::{self, Event as WindowEvent, Id as WindowId},
|
||||
},
|
||||
iced_runtime::clipboard,
|
||||
iced_widget::button::focus,
|
||||
iced_widget::{button::focus, scrollable::AbsoluteOffset},
|
||||
style, surface, theme,
|
||||
widget::{
|
||||
self,
|
||||
about::About,
|
||||
dnd_destination::DragId,
|
||||
horizontal_space, icon,
|
||||
icon,
|
||||
menu::{action::MenuAction, key_bind::KeyBind},
|
||||
segmented_button::{self, Entity, ReorderEvent},
|
||||
vertical_space,
|
||||
space,
|
||||
},
|
||||
};
|
||||
use mime_guess::Mime;
|
||||
|
|
@ -1900,7 +1900,7 @@ impl App {
|
|||
section = section.add(widget::column::with_children([
|
||||
widget::row::with_children([
|
||||
widget::progress_bar(0.0..=1.0, progress)
|
||||
.height(progress_bar_height)
|
||||
.girth(progress_bar_height)
|
||||
.into(),
|
||||
if controller.is_paused() {
|
||||
widget::tooltip(
|
||||
|
|
@ -2400,8 +2400,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Some(Element::from(
|
||||
// XXX both must be shrink to avoid flex layout from ignoring it
|
||||
nav.width(Length::Shrink).height(Length::Shrink),
|
||||
nav.width(Length::Shrink).height(Length::Fill),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -3213,7 +3212,10 @@ impl Application for App {
|
|||
if let Some(offset) = tab.select_focus_scroll() {
|
||||
return scrollable::scroll_to(
|
||||
tab.scrollable_id.clone(),
|
||||
offset,
|
||||
AbsoluteOffset {
|
||||
x: Some(offset.x),
|
||||
y: Some(offset.y),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4199,7 +4201,13 @@ impl Application for App {
|
|||
//Restore scroll
|
||||
//TODO: why do scrollers with different IDs get the same scroll position?
|
||||
let scroll = tab.scroll_opt.unwrap_or_default();
|
||||
tasks.push(scrollable::scroll_to(tab.scrollable_id.clone(), scroll));
|
||||
tasks.push(scrollable::scroll_to(
|
||||
tab.scrollable_id.clone(),
|
||||
AbsoluteOffset {
|
||||
x: Some(scroll.x),
|
||||
y: Some(scroll.y),
|
||||
},
|
||||
));
|
||||
}
|
||||
self.activate_nav_model_location(&tab.location.clone());
|
||||
}
|
||||
|
|
@ -5229,7 +5237,7 @@ impl Application for App {
|
|||
.title(fl!("add-network-drive"))
|
||||
.header(text_input)
|
||||
.footer(widget::row::with_children([
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
button.into(),
|
||||
]))
|
||||
}
|
||||
|
|
@ -5251,7 +5259,7 @@ impl Application for App {
|
|||
_ => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| widget::horizontal_space().into());
|
||||
.unwrap_or_else(|| widget::space::horizontal().into());
|
||||
context_drawer::context_drawer(
|
||||
self.preview(entity_opt, kind, true)
|
||||
.map(move |x| Message::TabMessage(Some(entity), x)),
|
||||
|
|
@ -5534,8 +5542,9 @@ impl Application for App {
|
|||
//TODO: what should submit do?
|
||||
//TODO: button for showing password
|
||||
controls = controls.push(
|
||||
widget::checkbox(fl!("remember-password"), *remember).on_toggle(
|
||||
move |value| {
|
||||
widget::checkbox(*remember)
|
||||
.label(fl!("remember-password"))
|
||||
.on_toggle(move |value| {
|
||||
Message::DialogUpdate(DialogPage::NetworkAuth {
|
||||
mounter_key: *mounter_key,
|
||||
uri: uri.clone(),
|
||||
|
|
@ -5545,8 +5554,7 @@ impl Application for App {
|
|||
},
|
||||
auth_tx: auth_tx.clone(),
|
||||
})
|
||||
},
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -5710,11 +5718,13 @@ impl Application for App {
|
|||
} else {
|
||||
widget::text::body(app.name.clone()).into()
|
||||
},
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
if *selected == i {
|
||||
icon::from_name("checkbox-checked-symbolic").size(16).into()
|
||||
} else {
|
||||
widget::Space::with_width(Length::Fixed(16.0)).into()
|
||||
widget::space::horizontal()
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
},
|
||||
])
|
||||
.spacing(space_s)
|
||||
|
|
@ -5919,20 +5929,18 @@ impl Application for App {
|
|||
if *multiple {
|
||||
dialog
|
||||
.control(
|
||||
widget::checkbox(
|
||||
format!("{} ({})", fl!("apply-to-all"), *conflict_count),
|
||||
*apply_to_all,
|
||||
)
|
||||
.on_toggle(|apply_to_all| {
|
||||
Message::DialogUpdate(DialogPage::Replace {
|
||||
from: from.clone(),
|
||||
to: to.clone(),
|
||||
multiple: *multiple,
|
||||
apply_to_all,
|
||||
conflict_count: *conflict_count,
|
||||
tx: tx.clone(),
|
||||
})
|
||||
}),
|
||||
widget::checkbox(*apply_to_all)
|
||||
.label(format!("{} ({})", fl!("apply-to-all"), *conflict_count))
|
||||
.on_toggle(|apply_to_all| {
|
||||
Message::DialogUpdate(DialogPage::Replace {
|
||||
from: from.clone(),
|
||||
to: to.clone(),
|
||||
multiple: *multiple,
|
||||
apply_to_all,
|
||||
conflict_count: *conflict_count,
|
||||
tx: tx.clone(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.secondary_action(
|
||||
widget::button::standard(fl!("skip")).on_press(Message::ReplaceResult(
|
||||
|
|
@ -6053,7 +6061,7 @@ impl Application for App {
|
|||
//TODO: get height from theme?
|
||||
let progress_bar_height = Length::Fixed(4.0);
|
||||
let progress_bar =
|
||||
widget::progress_bar(0.0..=1.0, total_progress).height(progress_bar_height);
|
||||
widget::progress_bar(0.0..=1.0, total_progress).girth(progress_bar_height);
|
||||
|
||||
let container = widget::layer_container(widget::column::with_children([
|
||||
widget::row::with_children([
|
||||
|
|
@ -6089,14 +6097,14 @@ impl Application for App {
|
|||
.align_y(Alignment::Center)
|
||||
.into(),
|
||||
widget::text::body(title).into(),
|
||||
widget::Space::with_height(space_s).into(),
|
||||
widget::space::vertical().height(space_s).into(),
|
||||
widget::row::with_children([
|
||||
widget::button::link(fl!("details"))
|
||||
.on_press(Message::ToggleContextPage(ContextPage::EditHistory))
|
||||
.padding(0)
|
||||
.trailing_icon(true)
|
||||
.into(),
|
||||
widget::horizontal_space().into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::button::standard(fl!("dismiss"))
|
||||
.on_press(Message::PendingDismiss)
|
||||
.into(),
|
||||
|
|
@ -6218,7 +6226,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()));
|
||||
tab_column = tab_column.push(widget::toaster(&self.toasts, widget::space::horizontal()));
|
||||
|
||||
let content: Element<_> = tab_column.into();
|
||||
|
||||
|
|
@ -6257,27 +6265,27 @@ impl Application for App {
|
|||
self.clipboard_has_content(),
|
||||
)
|
||||
.map(move |message| Message::TabMessage(Some(*entity), message)),
|
||||
None => widget::vertical_space().into(),
|
||||
None => widget::space::vertical().into(),
|
||||
};
|
||||
|
||||
tab_column = tab_column.push(tab_view);
|
||||
|
||||
// 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()));
|
||||
tab_column.push(widget::toaster(&self.toasts, widget::space::horizontal()));
|
||||
return if let Some(margin) = self.margin.get(&id) {
|
||||
if margin.0 >= 0. || margin.2 >= 0. {
|
||||
tab_column = widget::column::with_children([
|
||||
vertical_space().height(margin.0).into(),
|
||||
space::vertical().height(margin.0).into(),
|
||||
tab_column.into(),
|
||||
vertical_space().height(margin.2).into(),
|
||||
space::vertical().height(margin.2).into(),
|
||||
]);
|
||||
}
|
||||
if margin.1 >= 0. || margin.3 >= 0. {
|
||||
Element::from(widget::row::with_children([
|
||||
horizontal_space().width(margin.1).into(),
|
||||
space::horizontal().width(margin.1).into(),
|
||||
tab_column.into(),
|
||||
horizontal_space().width(margin.3).into(),
|
||||
space::horizontal().width(margin.3).into(),
|
||||
]))
|
||||
} else {
|
||||
tab_column.into()
|
||||
|
|
@ -6289,7 +6297,7 @@ impl Application for App {
|
|||
WindowKind::DesktopViewOptions => self.desktop_view_options(),
|
||||
WindowKind::Dialogs(id) => match self.dialog() {
|
||||
Some(element) => return widget::autosize::autosize(element, id.clone()).into(),
|
||||
None => widget::horizontal_space().into(),
|
||||
None => widget::space::horizontal().into(),
|
||||
},
|
||||
WindowKind::Preview(entity_opt, kind) => self
|
||||
.preview(entity_opt, kind, false)
|
||||
|
|
@ -6309,11 +6317,14 @@ impl Application for App {
|
|||
}
|
||||
};
|
||||
|
||||
widget::container(widget::scrollable(content))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.class(theme::Container::WindowBackground)
|
||||
.into()
|
||||
widget::container(widget::id_container(
|
||||
widget::scrollable(content),
|
||||
widget::Id::new("main container for files"),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.class(theme::Container::WindowBackground)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn system_theme_update(
|
||||
|
|
@ -6403,20 +6414,21 @@ impl Application for App {
|
|||
}
|
||||
Message::TimeConfigChange(update.config)
|
||||
}),
|
||||
Subscription::run_with_id(
|
||||
TypeId::of::<WatcherSubscription>(),
|
||||
stream::channel(100, |mut output| async move {
|
||||
let watcher_res = {
|
||||
let mut output = output.clone();
|
||||
new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |events_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match events_res {
|
||||
Ok(mut events) => {
|
||||
log::debug!("{events:?}");
|
||||
Subscription::run_with(TypeId::of::<WatcherSubscription>(), |_| {
|
||||
stream::channel(
|
||||
100,
|
||||
|mut output: futures::channel::mpsc::Sender<Message>| async move {
|
||||
let watcher_res = {
|
||||
let mut output = output.clone();
|
||||
new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |events_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match events_res {
|
||||
Ok(mut events) => {
|
||||
log::debug!("{events:?}");
|
||||
|
||||
events.retain(|event| {
|
||||
events.retain(|event| {
|
||||
match &event.kind {
|
||||
notify::EventKind::Access(_) => {
|
||||
// Data not mutated
|
||||
|
|
@ -6435,190 +6447,196 @@ impl Application for App {
|
|||
}
|
||||
});
|
||||
|
||||
if !events.is_empty() {
|
||||
match futures::executor::block_on(async {
|
||||
output.send(Message::NotifyEvents(events)).await
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to send notify events: {err:?}"
|
||||
);
|
||||
if !events.is_empty() {
|
||||
match futures::executor::block_on(async {
|
||||
output.send(Message::NotifyEvents(events)).await
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to send notify events: {err:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch files: {err:?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
match watcher_res {
|
||||
Ok(watcher) => {
|
||||
match output
|
||||
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||
watcher_opt: Some(watcher),
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch files: {err:?}");
|
||||
log::warn!("failed to send notify watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create file watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
},
|
||||
)
|
||||
}),
|
||||
Subscription::run_with(TypeId::of::<TrashWatcherSubscription>(), |_| {
|
||||
stream::channel(
|
||||
1,
|
||||
|mut output: futures::channel::mpsc::Sender<Message>| async move {
|
||||
let watcher_res = new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |event_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match event_res {
|
||||
Ok(events) => {
|
||||
// Rescan on any event. We don't need to evaluate each event
|
||||
// because as long as the trash changed in any way we need to
|
||||
// rescan.
|
||||
let should_rescan =
|
||||
events.iter().any(|event| !event.kind.is_access());
|
||||
|
||||
if should_rescan
|
||||
&& let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanTrash).await
|
||||
})
|
||||
{
|
||||
log::warn!(
|
||||
"trash needs to be rescanned but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to watch trash bin for changes: {e:?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
match watcher_res {
|
||||
Ok(watcher) => {
|
||||
match output
|
||||
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||
watcher_opt: Some(watcher),
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to send notify watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create file watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
}),
|
||||
),
|
||||
Subscription::run_with_id(
|
||||
TypeId::of::<TrashWatcherSubscription>(),
|
||||
stream::channel(1, |mut output| async move {
|
||||
let watcher_res = new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |event_res: notify_debouncer_full::DebounceEventResult| match event_res
|
||||
{
|
||||
Ok(events) => {
|
||||
// Rescan on any event. We don't need to evaluate each event
|
||||
// because as long as the trash changed in any way we need to
|
||||
// rescan.
|
||||
let should_rescan =
|
||||
events.iter().any(|event| !event.kind.is_access());
|
||||
|
||||
if should_rescan
|
||||
&& let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanTrash).await
|
||||
})
|
||||
{
|
||||
log::warn!(
|
||||
"trash needs to be rescanned but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to watch trash bin for changes: {e:?}");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: Trash watching support for Windows, macOS, and other OSes
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
match (watcher_res, trash::os_limited::trash_folders()) {
|
||||
(Ok(mut watcher), Ok(trash_bins)) => {
|
||||
// Watch the "bins" themselves as well as the files folder where
|
||||
// trashed items are placed. This allows us to avoid recursively
|
||||
// watching the trash which is slow but also properly get events.
|
||||
let trash_paths = trash_bins
|
||||
.into_iter()
|
||||
.flat_map(|path| [path.join("files"), path]);
|
||||
for path in trash_paths {
|
||||
if let Err(e) =
|
||||
watcher.watch(&path, notify::RecursiveMode::NonRecursive)
|
||||
{
|
||||
log::warn!(
|
||||
"failed to add trash bin `{}` to watcher: {e:?}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't drop the watcher
|
||||
std::future::pending().await
|
||||
}
|
||||
(Err(e), _) => {
|
||||
log::warn!("failed to create new watcher for trash bin: {e:?}");
|
||||
}
|
||||
(_, Err(e)) => {
|
||||
log::warn!("could not find any valid trash bins to watch: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(all(
|
||||
not(feature = "desktop-applet"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
if self.config.show_recents {
|
||||
subscriptions.push(Subscription::run_with_id(
|
||||
TypeId::of::<RecentsWatcherSubscription>(),
|
||||
stream::channel(1, |mut output| async move {
|
||||
let Some(recents_path) = recently_used_xbel::dir() else {
|
||||
log::warn!(
|
||||
"failed to watch recents changes: .recently_used.xbel does not exist"
|
||||
);
|
||||
return std::future::pending().await;
|
||||
};
|
||||
|
||||
let watcher_res = new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |event_res: notify_debouncer_full::DebounceEventResult| match event_res
|
||||
{
|
||||
Ok(events) => {
|
||||
// Programs differ in how they modify the recents file so the
|
||||
// rescan is triggered on any event but access.
|
||||
if events.iter().any(|event| {
|
||||
let kind = event.kind;
|
||||
kind.is_create()
|
||||
|| kind.is_modify()
|
||||
|| kind.is_remove()
|
||||
|| kind.is_other()
|
||||
}) && let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanRecents).await
|
||||
}) {
|
||||
// TODO: Trash watching support for Windows, macOS, and other OSes
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
match (watcher_res, trash::os_limited::trash_folders()) {
|
||||
(Ok(mut watcher), Ok(trash_bins)) => {
|
||||
// Watch the "bins" themselves as well as the files folder where
|
||||
// trashed items are placed. This allows us to avoid recursively
|
||||
// watching the trash which is slow but also properly get events.
|
||||
let trash_paths = trash_bins
|
||||
.into_iter()
|
||||
.flat_map(|path| [path.join("files"), path]);
|
||||
for path in trash_paths {
|
||||
if let Err(e) =
|
||||
watcher.watch(&path, notify::RecursiveMode::NonRecursive)
|
||||
{
|
||||
log::warn!(
|
||||
"failed to add trash bin `{}` to watcher: {e:?}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't drop the watcher
|
||||
std::future::pending().await
|
||||
}
|
||||
(Err(e), _) => {
|
||||
log::warn!("failed to create new watcher for trash bin: {e:?}");
|
||||
}
|
||||
(_, Err(e)) => {
|
||||
log::warn!("could not find any valid trash bins to watch: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
},
|
||||
)
|
||||
}),
|
||||
#[cfg(all(
|
||||
not(feature = "desktop-applet"),
|
||||
not(target_os = "ios"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
Subscription::run_with(TypeId::of::<RecentsWatcherSubscription>(), |_| {
|
||||
stream::channel(
|
||||
1,
|
||||
|mut output: futures::channel::mpsc::Sender<Message>| async move {
|
||||
let Some(recents_path) = recently_used_xbel::dir() else {
|
||||
log::warn!(
|
||||
"failed to watch recents changes: .recently_used.xbel does not exist"
|
||||
);
|
||||
return std::future::pending().await;
|
||||
};
|
||||
|
||||
let watcher_res = new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |event_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match event_res {
|
||||
Ok(events) => {
|
||||
// Programs differ in how they modify the recents file so the
|
||||
// rescan is triggered on any event but access.
|
||||
if events.iter().any(|event| {
|
||||
let kind = event.kind;
|
||||
kind.is_create()
|
||||
|| kind.is_modify()
|
||||
|| kind.is_remove()
|
||||
|| kind.is_other()
|
||||
}) && let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanRecents).await
|
||||
}) {
|
||||
log::warn!(
|
||||
"open recents tabs need to be updated but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"failed to watch recents file for changes: {e:?}"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
match watcher_res {
|
||||
Ok(mut watcher) => {
|
||||
if let Err(e) = watcher
|
||||
.watch(&recents_path, notify::RecursiveMode::NonRecursive)
|
||||
{
|
||||
log::warn!(
|
||||
"open recents tabs need to be updated but sending message failed: {e:?}"
|
||||
"failed to add recents file `{}` to watcher: {}",
|
||||
recents_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Don't drop the watcher.
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to watch recents file for changes: {e:?}")
|
||||
log::warn!("failed to create new watcher for recents file: {e:?}")
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
match watcher_res {
|
||||
Ok(mut watcher) => {
|
||||
if let Err(e) =
|
||||
watcher.watch(&recents_path, notify::RecursiveMode::NonRecursive)
|
||||
{
|
||||
log::warn!(
|
||||
"failed to add recents file `{}` to watcher: {}",
|
||||
recents_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Don't drop the watcher.
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to create new watcher for recents file: {e:?}")
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
}),
|
||||
));
|
||||
}
|
||||
std::future::pending().await
|
||||
},
|
||||
)
|
||||
}),
|
||||
];
|
||||
|
||||
if let Some(scroll_speed) = self.auto_scroll_speed {
|
||||
subscriptions.push(
|
||||
|
|
@ -6663,38 +6681,44 @@ impl Application for App {
|
|||
// Handle notification when window is closed and operations are in progress
|
||||
#[cfg(feature = "notify")]
|
||||
{
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct NotificationSubscription;
|
||||
subscriptions.push(Subscription::run_with_id(
|
||||
subscriptions.push(Subscription::run_with(
|
||||
TypeId::of::<NotificationSubscription>(),
|
||||
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()
|
||||
.summary(&fl!("notification-in-progress"))
|
||||
.timeout(notify_rust::Timeout::Never)
|
||||
.show()
|
||||
{
|
||||
Ok(notification) => {
|
||||
let _ = futures::executor::block_on(async {
|
||||
msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::Notification(Arc::new(Mutex::new(
|
||||
notification,
|
||||
))))
|
||||
.await
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create notification: {err}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|_| {
|
||||
stream::channel(
|
||||
1,
|
||||
move |msg_tx: futures::channel::mpsc::Sender<_>| async move {
|
||||
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
|
||||
tokio::task::spawn_blocking(move || {
|
||||
match notify_rust::Notification::new()
|
||||
.summary(&fl!("notification-in-progress"))
|
||||
.timeout(notify_rust::Timeout::Never)
|
||||
.show()
|
||||
{
|
||||
Ok(notification) => {
|
||||
let _ = futures::executor::block_on(async {
|
||||
msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::Notification(Arc::new(
|
||||
Mutex::new(notification),
|
||||
)))
|
||||
.await
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create notification: {err}");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
std::future::pending().await
|
||||
}),
|
||||
std::future::pending().await
|
||||
},
|
||||
)
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
120
src/dialog.rs
120
src/dialog.rs
|
|
@ -16,6 +16,7 @@ use cosmic::{
|
|||
window,
|
||||
},
|
||||
iced_core::widget::operation,
|
||||
iced_widget::scrollable::AbsoluteOffset,
|
||||
iced_winit::{self, SurfaceIdWrapper},
|
||||
theme,
|
||||
widget::{
|
||||
|
|
@ -202,7 +203,8 @@ impl<T: AsRef<str>> From<T> for DialogLabel {
|
|||
|
||||
impl<'a, M: Clone + 'static> From<&'a DialogLabel> for Element<'a, M> {
|
||||
fn from(label: &'a DialogLabel) -> Self {
|
||||
let mut iced_spans = Vec::with_capacity(label.spans.len());
|
||||
let mut iced_spans: Vec<cosmic::iced_core::text::Span<'_, ()>> =
|
||||
Vec::with_capacity(label.spans.len());
|
||||
for span in &label.spans {
|
||||
iced_spans.push(cosmic::iced::widget::span(&span.text).underline(span.underline));
|
||||
}
|
||||
|
|
@ -615,10 +617,13 @@ impl App {
|
|||
for (choice_i, choice) in self.choices.iter().enumerate() {
|
||||
match choice {
|
||||
DialogChoice::CheckBox { label, value, .. } => {
|
||||
row =
|
||||
row.push(widget::checkbox(label, *value).on_toggle(move |checked| {
|
||||
Message::Choice(choice_i, usize::from(checked))
|
||||
}));
|
||||
row = row.push(
|
||||
widget::checkbox(*value)
|
||||
.label(label)
|
||||
.on_toggle(move |checked| {
|
||||
Message::Choice(choice_i, usize::from(checked))
|
||||
}),
|
||||
);
|
||||
}
|
||||
DialogChoice::ComboBox {
|
||||
label,
|
||||
|
|
@ -640,7 +645,7 @@ impl App {
|
|||
.align_y(Alignment::Center)
|
||||
.spacing(space_xxs);
|
||||
}
|
||||
row = row.push(widget::horizontal_space());
|
||||
row = row.push(widget::space::horizontal());
|
||||
row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel));
|
||||
|
||||
let mut has_selected = false;
|
||||
|
|
@ -1103,7 +1108,7 @@ impl Application for App {
|
|||
.find(|item| item.selected)
|
||||
.map(|item| item.preview_actions().map(Message::TabMessage))
|
||||
})
|
||||
.unwrap_or_else(|| widget::horizontal_space().into());
|
||||
.unwrap_or_else(|| widget::space::horizontal().into());
|
||||
Some(
|
||||
context_drawer::context_drawer(
|
||||
self.preview(kind).map(Message::TabMessage),
|
||||
|
|
@ -1289,8 +1294,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Some(Element::from(
|
||||
// XXX both must be shrink to avoid flex layout from ignoring it
|
||||
nav.width(Length::Shrink).height(Length::Shrink),
|
||||
nav.width(Length::Shrink).height(Length::Fill),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -1506,7 +1510,10 @@ impl Application for App {
|
|||
if let Some(offset) = self.tab.select_focus_scroll() {
|
||||
return scrollable::scroll_to(
|
||||
self.tab.scrollable_id.clone(),
|
||||
offset,
|
||||
AbsoluteOffset {
|
||||
x: Some(offset.x),
|
||||
y: Some(offset.y),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2076,18 +2083,18 @@ impl Application for App {
|
|||
}
|
||||
Message::TimeConfigChange(update.config)
|
||||
}),
|
||||
Subscription::run_with_id(
|
||||
TypeId::of::<WatcherSubscription>(),
|
||||
stream::channel(100, |mut output| async move {
|
||||
let watcher_res = {
|
||||
let mut output = output.clone();
|
||||
new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |events_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match events_res {
|
||||
Ok(mut events) => {
|
||||
events.retain(|event| {
|
||||
Subscription::run_with(TypeId::of::<WatcherSubscription>(), |_| {
|
||||
stream::channel(100, {
|
||||
|mut output: futures::channel::mpsc::Sender<_>| async move {
|
||||
let watcher_res = {
|
||||
let mut output = output.clone();
|
||||
new_debouncer(
|
||||
time::Duration::from_millis(250),
|
||||
Some(time::Duration::from_millis(250)),
|
||||
move |events_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match events_res {
|
||||
Ok(mut events) => {
|
||||
events.retain(|event| {
|
||||
match &event.kind {
|
||||
notify::EventKind::Access(_) => {
|
||||
// Data not mutated
|
||||
|
|
@ -2106,49 +2113,50 @@ impl Application for App {
|
|||
}
|
||||
});
|
||||
|
||||
if !events.is_empty() {
|
||||
match futures::executor::block_on(async {
|
||||
output.send(Message::NotifyEvents(events)).await
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to send notify events: {err:?}"
|
||||
);
|
||||
if !events.is_empty() {
|
||||
match futures::executor::block_on(async {
|
||||
output.send(Message::NotifyEvents(events)).await
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to send notify events: {err:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch files: {err:?}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to watch files: {err:?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
match watcher_res {
|
||||
Ok(watcher) => {
|
||||
match output
|
||||
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||
watcher_opt: Some(watcher),
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to send notify watcher: {err:?}");
|
||||
match watcher_res {
|
||||
Ok(watcher) => {
|
||||
match output
|
||||
.send(Message::NotifyWatcher(WatcherWrapper {
|
||||
watcher_opt: Some(watcher),
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to send notify watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create file watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to create file watcher: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
std::future::pending().await
|
||||
}),
|
||||
),
|
||||
std::future::pending().await
|
||||
}
|
||||
})
|
||||
}),
|
||||
self.tab
|
||||
.subscription(
|
||||
self.core.window.show_context
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
|
|||
}
|
||||
|
||||
// Common keys
|
||||
bind!([], Key::Named(Named::Space), Gallery);
|
||||
bind!([], Key::Named(Named::ArrowDown), ItemDown);
|
||||
bind!([], Key::Named(Named::ArrowLeft), ItemLeft);
|
||||
bind!([], Key::Named(Named::ArrowRight), ItemRight);
|
||||
|
|
@ -40,7 +39,9 @@ pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
|
|||
bind!([Shift], Key::Named(Named::End), SelectLast);
|
||||
bind!([Ctrl, Shift], Key::Character("n".into()), NewFolder);
|
||||
bind!([], Key::Named(Named::Enter), Open);
|
||||
bind!([Ctrl], Key::Named(Named::Space), Preview);
|
||||
bind!([Ctrl], Key::Character(" ".into()), Preview);
|
||||
bind!([], Key::Character(" ".into()), Gallery);
|
||||
|
||||
bind!([Ctrl], Key::Character("h".into()), ToggleShowHidden);
|
||||
bind!([Ctrl], Key::Character("a".into()), SelectAll);
|
||||
bind!([Ctrl], Key::Character("=".into()), ZoomIn);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub mod config;
|
|||
pub mod dialog;
|
||||
mod key_bind;
|
||||
pub(crate) mod large_image;
|
||||
pub(crate) mod load_image;
|
||||
mod localize;
|
||||
mod menu;
|
||||
mod mime_app;
|
||||
|
|
|
|||
219
src/load_image.rs
Normal file
219
src/load_image.rs
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
use cosmic::{iced_core, iced_widget};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
|
||||
pub fn loaded_image<'a, Message: 'static, Theme>(
|
||||
handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle,
|
||||
) -> LoadedImage<'a, Message, Theme, cosmic::Renderer>
|
||||
where
|
||||
Theme: iced_widget::container::Catalog,
|
||||
<Theme as iced_widget::container::Catalog>::Class<'a>: From<cosmic::theme::Container<'a>>,
|
||||
{
|
||||
LoadedImage::new(handle)
|
||||
}
|
||||
|
||||
/// Forces the wrapped image to be loaded before drawing.
|
||||
///
|
||||
/// May cause a dropped frame if the image is not already in the cache.
|
||||
/// This is useful when you want to ensure an image is loaded before it is drawn, for example when swapping out a placeholder.
|
||||
/// Otherwise, the image may be blank until the next redraw.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct LoadedImage<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + iced_core::image::Renderer,
|
||||
{
|
||||
handle: <Renderer as iced_core::image::Renderer>::Handle,
|
||||
content: cosmic::iced::Element<'a, Message, Theme, Renderer>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> LoadedImage<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + iced_core::image::Renderer,
|
||||
<Renderer as iced_core::image::Renderer>::Handle: 'a,
|
||||
{
|
||||
/// Creates an empty [`LoadedImage`].
|
||||
pub(crate) fn new(handle: <Renderer as iced_core::image::Renderer>::Handle) -> Self {
|
||||
LoadedImage {
|
||||
handle: handle.clone(),
|
||||
content: cosmic::widget::Image::new(handle).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for LoadedImage<'_, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + iced_core::image::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let node = self
|
||||
.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
let size = node.size();
|
||||
layout::Node::with_children(size, vec![node])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(None, layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
content_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
|
||||
// forces image to be loaded before drawing
|
||||
_ = renderer.load_image(&self.handle);
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
renderer_style,
|
||||
content_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
&self,
|
||||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().drag_destinations(
|
||||
&state.children[0],
|
||||
content_layout.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
dnd_rectangles,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<LoadedImage<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + iced_core::Renderer + iced_core::image::Renderer,
|
||||
Theme: 'a,
|
||||
{
|
||||
fn from(c: LoadedImage<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
|
||||
Element::new(c)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,9 @@ use cosmic::{
|
|||
},
|
||||
theme,
|
||||
widget::{
|
||||
self, Row, button, column, container, divider, horizontal_space,
|
||||
self, Row, button, column, container, divider,
|
||||
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||
responsive_menu_bar, text,
|
||||
responsive_menu_bar, space, text,
|
||||
},
|
||||
};
|
||||
use i18n_embed::LanguageLoader;
|
||||
|
|
@ -88,7 +88,7 @@ pub fn context_menu<'a>(
|
|||
let key = find_key(&action);
|
||||
menu_button!(
|
||||
text::body(label),
|
||||
horizontal_space(),
|
||||
space::horizontal(),
|
||||
text::body(key).class(theme::Text::Custom(key_style))
|
||||
)
|
||||
.on_press(tab::Message::ContextAction(action))
|
||||
|
|
@ -98,7 +98,7 @@ pub fn context_menu<'a>(
|
|||
let key = find_key(&action);
|
||||
menu_button!(
|
||||
text::body(label).class(theme::Text::Custom(disabled_style)),
|
||||
horizontal_space(),
|
||||
space::horizontal(),
|
||||
text::body(key).class(theme::Text::Custom(disabled_style))
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use cosmic::{
|
|||
widget,
|
||||
};
|
||||
use gio::{glib, prelude::*};
|
||||
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
|
||||
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||
|
|
@ -668,32 +668,56 @@ impl Mounter for Gvfs {
|
|||
fn subscription(&self) -> Subscription<MounterMessage> {
|
||||
let command_tx = self.command_tx.clone();
|
||||
let event_rx = self.event_rx.clone();
|
||||
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();
|
||||
struct Wrapper {
|
||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
||||
}
|
||||
impl Hash for Wrapper {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
TypeId::of::<Self>().hash(state);
|
||||
}
|
||||
}
|
||||
Subscription::run_with(
|
||||
Wrapper {
|
||||
command_tx,
|
||||
event_rx,
|
||||
},
|
||||
|Wrapper {
|
||||
command_tx,
|
||||
event_rx,
|
||||
}| {
|
||||
let command_tx = command_tx.clone();
|
||||
let event_rx = event_rx.clone();
|
||||
stream::channel(
|
||||
1,
|
||||
move |mut output: cosmic::iced::futures::channel::mpsc::Sender<
|
||||
MounterMessage,
|
||||
>| 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(),
|
||||
}
|
||||
}
|
||||
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
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use cosmic::{
|
|||
iced_core::{
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
border::Border,
|
||||
event::{self, Event},
|
||||
event::Event,
|
||||
layout,
|
||||
mouse::{self, click},
|
||||
overlay,
|
||||
|
|
@ -345,51 +345,52 @@ where
|
|||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
if self.content.as_widget_mut().on_event(
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
) == event::Status::Captured
|
||||
{
|
||||
return event::Status::Captured;
|
||||
);
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
update(
|
||||
|
|
@ -400,7 +401,7 @@ where
|
|||
shell,
|
||||
tree.state.downcast_mut::<State>(),
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -468,13 +469,18 @@ where
|
|||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
layout: Layout<'b>,
|
||||
renderer: &Renderer,
|
||||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut tree.children[0], layout, renderer, translation)
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
@ -522,7 +528,7 @@ fn update<Message: Clone>(
|
|||
shell: &mut Shell<'_, Message>,
|
||||
state: &mut State,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
) {
|
||||
let offset = layout.virtual_offset();
|
||||
let layout_bounds = layout.bounds();
|
||||
|
||||
|
|
@ -590,7 +596,7 @@ fn update<Message: Clone>(
|
|||
}
|
||||
|
||||
if state.drag_initiated.is_none() && !cursor.is_over(layout_bounds) {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
|
|
@ -620,7 +626,8 @@ fn update<Message: Clone>(
|
|||
}
|
||||
|
||||
if widget.on_press.is_some() {
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -653,13 +660,14 @@ fn update<Message: Clone>(
|
|||
{
|
||||
if !recent_click {
|
||||
state.prev_click = None;
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
state.drag_initiated = None;
|
||||
if let Some(message) = widget.on_release.as_ref() {
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -681,9 +689,10 @@ fn update<Message: Clone>(
|
|||
shell.publish(message(point_opt));
|
||||
|
||||
if widget.on_right_press_no_capture {
|
||||
return event::Status::Ignored;
|
||||
return;
|
||||
}
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_release.as_ref()
|
||||
|
|
@ -694,7 +703,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_press.as_ref()
|
||||
|
|
@ -705,7 +715,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_release.as_ref()
|
||||
|
|
@ -716,7 +727,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_back_press.as_ref()
|
||||
|
|
@ -727,7 +739,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_back_release.as_ref()
|
||||
|
|
@ -738,7 +751,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_forward_press.as_ref()
|
||||
|
|
@ -749,7 +763,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_forward_release.as_ref()
|
||||
|
|
@ -760,7 +775,8 @@ fn update<Message: Clone>(
|
|||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(on_scroll) = widget.on_scroll.as_ref()
|
||||
|
|
@ -768,7 +784,8 @@ fn update<Message: Clone>(
|
|||
&& let Some(message) = on_scroll(*delta)
|
||||
{
|
||||
shell.publish(message);
|
||||
return event::Status::Captured;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
|
||||
|
|
@ -780,6 +797,4 @@ fn update<Message: Clone>(
|
|||
},
|
||||
)));
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
|
|
|||
803
src/tab.rs
803
src/tab.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue