Merge tag 'epoch-1.7.0' into local/libcosmic-path-patch

Epoch 1.7.0

# Conflicts:
#	Cargo.lock
#	src/app.rs
This commit is contained in:
Lionel DARNIS 2026-08-27 07:52:59 +02:00
commit deb6000c10
13 changed files with 773 additions and 218 deletions

View file

@ -37,7 +37,7 @@ use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::segmented_button::{self, Entity, ReorderEvent};
use cosmic::widget::{self, icon, settings, space};
use cosmic::{Application, ApplicationExt, Element, cosmic_theme, executor, style, surface, theme};
use cosmic::{Application, ApplicationExt, Element, cosmic_theme, executor, surface, theme};
use mime_guess::Mime;
use notify_debouncer_full::notify::{self, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
@ -428,6 +428,7 @@ pub enum NavMenuAction {
Preview(segmented_button::Entity),
RunContextAction(segmented_button::Entity, usize),
RemoveFromSidebar(segmented_button::Entity),
ChangeSidebarLabel(segmented_button::Entity),
}
impl MenuAction for NavMenuAction {
@ -503,6 +504,7 @@ pub enum Message {
OpenWithBrowse,
OpenWithDialog(Option<Entity>),
OpenWithSelection(usize),
OpenWithSearchClear,
OpenWithToggleDefault(bool),
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
Overlap(window::Id, OverlapNotifyEvent),
@ -691,6 +693,7 @@ pub enum DialogPage {
mime: mime_guess::Mime,
selected: usize,
store_opt: Option<Arc<MimeApp>>,
search_app_name: String,
/// When true, the chosen app is written to mimeapps.list as the
/// default handler for `mime` after it spawns.
set_default: bool,
@ -701,6 +704,10 @@ pub enum DialogPage {
DeleteTrash {
items: Vec<TrashItem>,
},
ChangeSidebarLabel {
entity: Entity,
label: String,
},
RenameItem {
from: PathBuf,
parent: PathBuf,
@ -1956,15 +1963,9 @@ impl App {
for (favorite_i, favorite) in self.config.favorites.iter().enumerate() {
if let Some(path) = favorite.path_opt() {
let name = if matches!(favorite, Favorite::Home) {
fl!("home")
} else if let Favorite::Network { name, .. } = favorite {
name.clone()
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
file_name.to_string()
} else {
fl!("filesystem")
};
let name = favorite
.display_name()
.unwrap_or_else(|| fl!("filesystem"));
nav_model = nav_model.insert(move |b| {
b.text(name.clone())
.icon(
@ -2601,15 +2602,33 @@ impl App {
.favorites
.iter()
.map(|favorite| {
if let Favorite::Path(path) = favorite {
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref())) {
if path.starts_with(from)
&& let Ok(relative) = path.strip_prefix(from)
match favorite {
Favorite::Path(path) => {
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref()))
{
favorites_changed = true;
return Favorite::from_path(to.join(relative));
if path.starts_with(from)
&& let Ok(relative) = path.strip_prefix(from)
{
favorites_changed = true;
return Favorite::from_path(to.join(relative));
}
}
}
Favorite::Named { path, name } => {
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref()))
{
if path.starts_with(from)
&& let Ok(relative) = path.strip_prefix(from)
{
favorites_changed = true;
return Favorite::Named {
path: to.join(relative),
name: name.clone(),
};
}
}
}
_ => {}
}
favorite.clone()
})
@ -2925,6 +2944,11 @@ impl Application for App {
}
items.push(cosmic::widget::menu::Item::Divider);
if favorite_index_opt.is_some() {
items.push(cosmic::widget::menu::Item::Button(
fl!("change-sidebar-label"),
None,
NavMenuAction::ChangeSidebarLabel(entity),
));
items.push(cosmic::widget::menu::Item::Button(
fl!("remove-from-sidebar"),
None,
@ -3216,7 +3240,8 @@ impl Application for App {
} else {
Favorite::from_path(path)
};
if !favorites.contains(&favorite) {
let favorite_path = favorite.path_opt();
if !favorites.iter().any(|f| f.path_opt() == favorite_path) {
favorites.push(favorite);
}
}
@ -3523,10 +3548,18 @@ impl Application for App {
path,
mime,
selected,
search_app_name,
set_default,
..
} => {
let available_apps = self.mime_app_cache.get_apps_for_mime(&mime, true);
let mut available_apps =
self.mime_app_cache.get_apps_for_mime(&mime, true);
available_apps.retain(|(app, _)| {
app.name
.to_lowercase()
.trim()
.contains(search_app_name.to_lowercase().as_str().trim())
});
if let Some((app, _)) = available_apps.get(selected) {
if let Some(mut command) =
@ -3572,6 +3605,18 @@ impl Application for App {
DialogPage::DeleteTrash { items } => {
tasks.push(self.operation(Operation::DeleteTrash { items }));
}
DialogPage::ChangeSidebarLabel { entity, label } => {
if let Some(FavoriteIndex(favorite_i)) =
self.nav_model.data::<FavoriteIndex>(entity)
{
let mut favorites = self.config.favorites.clone();
if let Some(favorite) = favorites.get_mut(*favorite_i) {
*favorite = favorite.with_label(label.trim());
config_set!(favorites, favorites);
tasks.push(self.update_config());
}
}
}
DialogPage::RenameItem {
from, parent, name, ..
} => {
@ -4173,21 +4218,25 @@ impl Application for App {
let Some(path) = item.path_opt() else {
continue;
};
return self.push_dialog(
DialogPage::OpenWith {
path: path.clone(),
mime: item.mime.clone(),
selected: 0,
store_opt: "x-scheme-handler/mime"
.parse::<mime_guess::Mime>()
.ok()
.and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned()
}),
set_default: false,
},
Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()),
);
return Task::batch([
self.push_dialog(
DialogPage::OpenWith {
path: path.clone(),
mime: item.mime.clone(),
selected: 0,
store_opt: "x-scheme-handler/mime"
.parse::<mime_guess::Mime>()
.ok()
.and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned()
}),
search_app_name: String::new(),
set_default: false,
},
Some(CONFIRM_OPEN_WITH_BUTTON_ID.clone()),
),
widget::text_input::focus(self.dialog_text_input.clone()),
]);
}
}
}
@ -4203,6 +4252,14 @@ impl Application for App {
*set_default = enabled;
}
}
Message::OpenWithSearchClear => {
if let Some(DialogPage::OpenWith {
search_app_name, ..
}) = self.dialog_pages.front_mut()
{
*search_app_name = String::new();
}
}
Message::Paste(entity_opt) => {
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
@ -4888,7 +4945,8 @@ impl Application for App {
tab::Command::AddToSidebar(path) => {
let mut favorites = self.config.favorites.clone();
let favorite = Favorite::from_path(path);
if !favorites.contains(&favorite) {
let favorite_path = favorite.path_opt();
if !favorites.iter().any(|f| f.path_opt() == favorite_path) {
favorites.push(favorite);
}
config_set!(favorites, favorites);
@ -5537,6 +5595,7 @@ impl Application for App {
.and_then(|mime| {
self.mime_app_cache.get(&mime).first().cloned()
}),
search_app_name: String::new(),
set_default: false,
},
None,
@ -5672,6 +5731,26 @@ impl Application for App {
return self.update_config();
}
}
NavMenuAction::ChangeSidebarLabel(entity) => {
if let Some(favorite) = self
.nav_model
.data::<FavoriteIndex>(entity)
.and_then(|FavoriteIndex(favorite_i)| {
self.config.favorites.get(*favorite_i)
})
{
let label = favorite
.display_name()
.unwrap_or_else(|| fl!("filesystem"));
return Task::batch([
self.dialog_pages
.push_back(DialogPage::ChangeSidebarLabel { entity, label }),
widget::text_input::focus(self.dialog_text_input.clone()),
widget::text_input::select_all(self.dialog_text_input.clone()),
]);
}
}
},
Message::Recents => {
if self.config.show_recents {
@ -6394,7 +6473,9 @@ impl Application for App {
mime,
selected,
store_opt,
search_app_name,
set_default,
..
} => {
let name = match path.file_name() {
Some(file_name) => file_name.to_str(),
@ -6402,7 +6483,13 @@ impl Application for App {
};
let mut column = widget::list_column();
let available_apps = self.mime_app_cache.get_apps_for_mime(mime, true);
let mut available_apps = self.mime_app_cache.get_apps_for_mime(mime, true);
available_apps.retain(|(app, _)| {
app.name
.to_lowercase()
.trim()
.contains(search_app_name.to_lowercase().as_str().trim())
});
let item_height = 32.0;
let mut displayed_default = false;
let mut last_kind = MimeAppMatch::Exact;
@ -6466,6 +6553,25 @@ impl Application for App {
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
)
.control(
widget::text_input::search_input(
fl!("search-application"),
search_app_name,
)
.id(self.dialog_text_input.clone())
.on_clear(Message::OpenWithSearchClear)
.on_input(move |search_app_name| {
Message::DialogUpdate(DialogPage::OpenWith {
path: path.clone(),
mime: mime.clone(),
selected: *selected,
store_opt: store_opt.clone(),
search_app_name,
set_default: *set_default,
})
})
.on_submit(|_| Message::DialogComplete),
)
.control(widget::scrollable(column).height({
let max_size = self
.size
@ -6554,6 +6660,40 @@ impl Application for App {
target = target
)))
}
DialogPage::ChangeSidebarLabel { entity, label } => {
let entity = *entity;
let complete_maybe = if label.trim().is_empty() {
None
} else {
Some(Message::DialogComplete)
};
widget::dialog()
.title(fl!("change-sidebar-label"))
.primary_action(
widget::button::suggested(fl!("save"))
.on_press_maybe(complete_maybe.clone()),
)
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
)
.control(
widget::column::with_children([
widget::text::body(fl!("sidebar-label")).into(),
widget::text_input("", label.as_str())
.id(self.dialog_text_input.clone())
.on_input(move |label| {
Message::DialogUpdate(DialogPage::ChangeSidebarLabel {
entity,
label,
})
})
.on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone()))
.into(),
])
.spacing(space_xxs),
)
}
DialogPage::RenameItem {
from,
parent,