Merge pull request #231 from pop-os/iced-rebase

Iced rebase
This commit is contained in:
Jeremy Soller 2026-04-01 09:45:56 -06:00 committed by GitHub
commit 069337e2ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 2007 additions and 1688 deletions

3428
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -30,15 +30,14 @@ env_logger = "0.10"
log = "0.4" log = "0.4"
[dependencies.iced_video_player] [dependencies.iced_video_player]
git = "https://github.com/jackpot51/iced_video_player.git" git = "https://github.com/wash2/iced_video_player.git"
branch = "prev-cosmic" branch = "iced-rebase"
default-features = false default-features = false
[dependencies.libcosmic] [dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git" git = "https://github.com/pop-os/libcosmic.git"
branch = "prev-master"
default-features = false default-features = false
features = ["tokio", "winit"] features = ["tokio", "winit", "multi-window"]
[dependencies.mpris-server] [dependencies.mpris-server]
version = "0.8.1" version = "0.8.1"
@ -56,15 +55,13 @@ wgpu = ["iced_video_player/wgpu", "libcosmic/wgpu"]
inherits = "release" inherits = "release"
debug = true debug = true
# Keep cosmic-text at version compatible with prev-master of libcosmic # [patch.'https://github.com/wash2/iced_video_player']
[patch.'https://github.com/pop-os/cosmic-text'.cosmic-text] # iced_video_player = { path = "../iced_video_player" }
git = "https://github.com/pop-os/cosmic-text//"
rev = "166b59f560c551dab391a864f7c1f503c1e18446"
# [patch.'https://github.com/jackpot51/iced_video_player']
# iced_video_player = { path = "../../iced_video_player" }
# [patch.'https://github.com/pop-os/libcosmic'] # [patch.'https://github.com/pop-os/libcosmic']
# libcosmic = { path = "../libcosmic" } # libcosmic = { path = "../libcosmic" }
# cosmic-config = { path = "../libcosmic/cosmic-config" } # cosmic-config = { path = "../libcosmic/cosmic-config" }
# cosmic-theme = { path = "../libcosmic/cosmic-theme" } # 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" }

View file

@ -24,7 +24,7 @@ pub fn key_binds() -> HashMap<KeyBind, Action> {
//TODO: key bindings //TODO: key bindings
bind!([], Key::Character("f".into()), Fullscreen); bind!([], Key::Character("f".into()), Fullscreen);
bind!([Alt], Key::Named(Named::Enter), Fullscreen); bind!([Alt], Key::Named(Named::Enter), Fullscreen);
bind!([], Key::Named(Named::Space), PlayPause); bind!([], Key::Character(" ".into()), PlayPause);
bind!([], Key::Named(Named::ArrowLeft), SeekBackward); bind!([], Key::Named(Named::ArrowLeft), SeekBackward);
bind!([], Key::Named(Named::ArrowRight), SeekForward); bind!([], Key::Named(Named::ArrowRight), SeekForward);

View file

@ -2,17 +2,19 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::{
app::{command, message, Command, Core, Settings}, action::{self, app},
app::{Core, Settings, Task},
command::set_theme,
cosmic_config::{self, CosmicConfigEntry}, cosmic_config::{self, CosmicConfigEntry},
cosmic_theme, executor, font, cosmic_theme, executor, font,
iced::{ iced::{
event::{self, Event}, event::{self, Event},
keyboard::{Event as KeyEvent, Key, Modifiers}, keyboard::{Event as KeyEvent, Key, Modifiers},
mouse::{Event as MouseEvent, ScrollDelta}, mouse::{Event as MouseEvent, ScrollDelta},
subscription::Subscription, window::{self, set_mode},
window, Alignment, Background, Border, Color, ContentFit, Length, Limits, Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription,
}, },
iced_style, theme, theme,
widget::{self, menu::action::MenuAction, nav_bar, segmented_button, Slider}, widget::{self, menu::action::MenuAction, nav_bar, segmented_button, Slider},
Application, ApplicationExt, Element, Application, ApplicationExt, Element,
}; };
@ -26,7 +28,12 @@ use std::{
ffi::{CStr, CString}, ffi::{CStr, CString},
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process, thread, process,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -34,6 +41,7 @@ use tokio::sync::mpsc;
use crate::{ use crate::{
config::{Config, ConfigState, CONFIG_VERSION, RepeatState}, config::{Config, ConfigState, CONFIG_VERSION, RepeatState},
key_bind::{key_binds, KeyBind}, key_bind::{key_binds, KeyBind},
mpris::subscription,
project::ProjectNode, project::ProjectNode,
}; };
@ -254,7 +262,6 @@ impl AsRef<str> for TextCode {
/// Messages that are used specifically by our [`App`]. /// Messages that are used specifically by our [`App`].
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
None,
Config(Config), Config(Config),
ConfigState(ConfigState), ConfigState(ConfigState),
DropdownToggle(DropdownKind), DropdownToggle(DropdownKind),
@ -351,15 +358,15 @@ impl App {
was_open was_open
} }
fn load(&mut self) -> Command<Message> { fn load(&mut self) -> Task<Message> {
if self.close() { if self.close() {
// Allow a redraw before trying to load again, to prevent deadlock // Allow a redraw before trying to load again, to prevent deadlock
return Command::perform(async { message::app(Message::Reload) }, |x| x); return Task::perform(async { action::app(Message::Reload) }, |x| x);
} }
let url = match &self.flags.url_opt { let url = match &self.flags.url_opt {
Some(some) => some.clone(), Some(some) => some.clone(),
None => return Command::none(), None => return Task::none(),
}; };
log::info!("Loading {}", url); log::info!("Loading {}", url);
@ -607,8 +614,8 @@ impl App {
self.update_mpris_state(); self.update_mpris_state();
} }
fn update_config(&mut self) -> Command<Message> { fn update_config(&mut self) -> Task<Message> {
cosmic::app::command::set_theme(self.flags.config.app_theme.theme()) set_theme(self.flags.config.app_theme.theme())
} }
fn update_flags(&mut self) { fn update_flags(&mut self) {
@ -800,10 +807,10 @@ impl App {
self.nav_model.activate(active_id); self.nav_model.activate(active_id);
} }
fn update_title(&mut self) -> Command<Message> { fn update_title(&mut self) -> Task<Message> {
//TODO: filename? //TODO: filename?
let title = "COSMIC Media Player"; let title = "COSMIC Media Player";
self.set_window_title(title.to_string()) self.set_window_title(title.to_string(), self.core.main_window_id().unwrap())
} }
/// Allow screen to dim or turn off if there is no input from the user. /// Allow screen to dim or turn off if there is no input from the user.
@ -850,7 +857,7 @@ impl Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// 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.content_container = false; core.window.content_container = false;
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
@ -905,10 +912,13 @@ impl Application for App {
.as_ref() .as_ref()
.and_then(|url| url.to_file_path().ok()); .and_then(|url| url.to_file_path().ok());
let command = match (app.flags.urls.take(), maybe_path) { let command = match (app.flags.urls.take(), maybe_path) {
(Some(urls), _) => command::message::app(Message::MultipleLoad(urls)), (Some(urls), _) => {
(None, Some(path)) if path.is_dir() => command::message::app(Message::FolderLoad(path)), cosmic::task::message(cosmic::action::app(Message::MultipleLoad(urls)))
_ => app.load(), //If there is no url args, we execute load for nothing? }
//If only one file is loaded, nothing is added to the navbar. (None, Some(path)) if path.is_dir() => {
cosmic::task::message(cosmic::action::app(Message::FolderLoad(path)))
}
_ => app.load(),
}; };
(app, command) (app, command)
} }
@ -917,15 +927,15 @@ impl Application for App {
Some(&self.nav_model) Some(&self.nav_model)
} }
fn on_escape(&mut self) -> Command<Self::Message> { fn on_escape(&mut self) -> Task<Self::Message> {
if self.fullscreen { if self.fullscreen {
return self.update(Message::Fullscreen); return self.update(Message::Fullscreen);
} else { } else {
Command::none() Task::none()
} }
} }
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Message> { fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Message> {
// Toggle open state and get clone of node data // Toggle open state and get clone of node data
let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) { let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) {
Some(node) => { Some(node) => {
@ -966,13 +976,13 @@ impl Application for App {
// folder in condensed mode. // folder in condensed mode.
self.core_mut().nav_bar_set_toggled(true); self.core_mut().nav_bar_set_toggled(true);
Command::none() Task::none()
} }
ProjectNode::File { path, .. } => match url::Url::from_file_path(&path) { ProjectNode::File { path, .. } => match url::Url::from_file_path(&path) {
Ok(url) => self.update(Message::FileLoad(url)), Ok(url) => self.update(Message::FileLoad(url)),
Err(()) => { Err(()) => {
log::warn!("failed to convert {:?} to url", path); log::warn!("failed to convert {:?} to url", path);
Command::none() Task::none()
} }
}, },
} }
@ -984,21 +994,21 @@ impl Application for App {
} }
} }
fn style(&self) -> Option<theme::Application> { fn style(&self) -> Option<cosmic::iced_core::theme::Style> {
// This ensures we have a solid background color even when using no content container // This ensures we have a solid background color even when using no content container
Some(theme::Application::Custom(Box::new(|theme| {
iced_style::application::Appearance { let theme = self.core.system_theme();
background_color: theme.cosmic().bg_color().into(),
icon_color: theme.cosmic().on_bg_color().into(), Some(cosmic::iced_core::theme::Style {
text_color: theme.cosmic().on_bg_color().into(), background_color: theme.cosmic().bg_color().into(),
} icon_color: theme.cosmic().on_bg_color().into(),
}))) text_color: theme.cosmic().on_bg_color().into(),
})
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Command<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::None => {}
Message::Config(config) => { Message::Config(config) => {
if config != self.flags.config { if config != self.flags.config {
log::info!("update config"); log::info!("update config");
@ -1031,17 +1041,17 @@ impl Application for App {
Message::FileOpen => { Message::FileOpen => {
//TODO: embed cosmic-files dialog (after libcosmic rebase works) //TODO: embed cosmic-files dialog (after libcosmic rebase works)
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
return Command::perform( return Task::perform(
async move { async move {
let dialog = cosmic::dialog::file_chooser::open::Dialog::new() let dialog = cosmic::dialog::file_chooser::open::Dialog::new()
.title(fl!("open-media")); .title(fl!("open-media"));
match dialog.open_file().await { match dialog.open_file().await {
Ok(response) => { Ok(response) => {
message::app(Message::FileLoad(response.url().to_owned())) action::app(Message::FileLoad(response.url().to_owned()))
} }
Err(err) => { Err(err) => {
log::warn!("failed to open file: {}", err); log::warn!("failed to open file: {}", err);
message::none() action::none()
} }
} }
}, },
@ -1095,7 +1105,7 @@ impl Application for App {
Message::FolderOpen => { Message::FolderOpen => {
//TODO: embed cosmic-files dialog (after libcosmic rebase works) //TODO: embed cosmic-files dialog (after libcosmic rebase works)
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
return Command::perform( return Task::perform(
async move { async move {
let dialog = cosmic::dialog::file_chooser::open::Dialog::new() let dialog = cosmic::dialog::file_chooser::open::Dialog::new()
.title(fl!("open-media-folder")); .title(fl!("open-media-folder"));
@ -1103,16 +1113,16 @@ impl Application for App {
Ok(response) => { Ok(response) => {
let url = response.url(); let url = response.url();
match url.to_file_path() { match url.to_file_path() {
Ok(path) => message::app(Message::FolderLoad(path)), Ok(path) => action::app(Message::FolderLoad(path)),
Err(()) => { Err(()) => {
log::warn!("unsupported folder URL {:?}", url); log::warn!("unsupported folder URL {:?}", url);
message::none() action::none()
} }
} }
} }
Err(err) => { Err(err) => {
log::warn!("failed to open folder: {}", err); log::warn!("failed to open folder: {}", err);
message::none() action::none()
} }
} }
}, },
@ -1156,17 +1166,18 @@ impl Application for App {
//TODO: cleanest way to close dropdowns //TODO: cleanest way to close dropdowns
self.dropdown_opt = None; self.dropdown_opt = None;
self.fullscreen = !self.fullscreen; if let Some(window_id) = self.core.main_window_id() {
self.core.window.show_headerbar = !self.fullscreen; self.fullscreen = !self.fullscreen;
self.controls = !self.fullscreen; self.core.window.show_headerbar = !self.fullscreen;
return window::change_mode( return set_mode(
window::Id::MAIN, window_id,
if self.fullscreen { if self.fullscreen {
window::Mode::Fullscreen window::Mode::Fullscreen
} else { } else {
window::Mode::Windowed window::Mode::Windowed
}, },
); );
}
} }
Message::Key(modifiers, key) => { Message::Key(modifiers, key) => {
for (key_bind, action) in self.key_binds.iter() { for (key_bind, action) in self.key_binds.iter() {
@ -1337,7 +1348,7 @@ impl Application for App {
if self.flags.config_state.player_state.repeat == RepeatState::Track { if self.flags.config_state.player_state.repeat == RepeatState::Track {
// we hook Message::PlayNext to the EOS signal. iced_video_player always emits EOS regardless of // we hook Message::PlayNext to the EOS signal. iced_video_player always emits EOS regardless of
// looping state, so do nothing if repeat is set. // looping state, so do nothing if repeat is set.
return Command::none(); return Task::none();
} }
//first we get info about current media id & position in nav_bar //first we get info about current media id & position in nav_bar
@ -1385,7 +1396,7 @@ impl Application for App {
if let Some(video) = &mut self.video_opt { if let Some(video) = &mut self.video_opt {
video.set_paused(true); video.set_paused(true);
} }
return Command::perform( return Task::perform(
async move { async move {
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
match gst_pbutils::MissingPluginMessage::parse(&element) { match gst_pbutils::MissingPluginMessage::parse(&element) {
@ -1419,7 +1430,7 @@ impl Application for App {
"gstreamer registry update: {:?}", "gstreamer registry update: {:?}",
gst::Registry::update() gst::Registry::update()
); );
return message::app(Message::Reload); return action::app(Message::Reload);
}, },
_ => { _ => {
log::warn!("failed to install plugins: {status}"); log::warn!("failed to install plugins: {status}");
@ -1433,7 +1444,7 @@ impl Application for App {
log::warn!("failed to parse missing plugin message: {err}"); log::warn!("failed to parse missing plugin message: {err}");
} }
} }
message::none() action::none()
}) })
.await .await
.unwrap() .unwrap()
@ -1467,7 +1478,7 @@ impl Application for App {
process::exit(0); process::exit(0);
} }
} }
Command::none() Task::none()
} }
fn header_start(&self) -> Vec<Element<'_, Self::Message>> { fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
@ -1501,25 +1512,25 @@ impl Application for App {
let Some(video) = &self.video_opt else { let Some(video) = &self.video_opt else {
//TODO: use space variables //TODO: use space variables
let column = widget::column::with_capacity(4) let column = widget::column::with_capacity(4)
.align_items(Alignment::Center) .align_x(Alignment::Center)
.spacing(24) .spacing(24)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.push(widget::vertical_space(Length::Fill)) .push(widget::space::vertical())
.push( .push(
widget::column::with_capacity(2) widget::column::with_capacity(2)
.align_items(Alignment::Center) .align_x(Alignment::Center)
.spacing(8) .spacing(8)
.push(widget::icon::from_name("folder-symbolic").size(64)) .push(widget::icon::from_name("folder-symbolic").size(64))
.push(widget::text::body(fl!("no-video-or-audio-file-open"))), .push(widget::text::body(fl!("no-video-or-audio-file-open"))),
) )
.push(widget::button::suggested(fl!("open-file")).on_press(Message::FileOpen)) .push(widget::button::suggested(fl!("open-file")).on_press(Message::FileOpen))
.push(widget::vertical_space(Length::Fill)); .push(widget::space::vertical());
return widget::container(column) return widget::container(column)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.style(theme::Container::WindowBackground) .class(theme::Container::WindowBackground)
.into(); .into();
}; };
@ -1534,6 +1545,7 @@ impl Application for App {
.on_new_frame(Message::NewFrame) .on_new_frame(Message::NewFrame)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.id(cosmic::widget::Id::new("video-player".to_string()))
.into(); .into();
let mut background_color = Color::BLACK; let mut background_color = Color::BLACK;
@ -1543,7 +1555,7 @@ impl Application for App {
text_color_opt = Some(Color::from(theme.cosmic().on_bg_component_color())); text_color_opt = Some(Color::from(theme.cosmic().on_bg_component_color()));
let mut col = widget::column(); let mut col = widget::column();
col = col.push(widget::vertical_space(Length::Fill)); col = col.push(widget::space::vertical());
if let Some(album_art) = &self.album_art_opt { if let Some(album_art) = &self.album_art_opt {
col = col.push( col = col.push(
widget::image(widget::image::Handle::from_path(album_art.path())) widget::image(widget::image::Handle::from_path(album_art.path()))
@ -1553,7 +1565,7 @@ impl Application for App {
} else { } else {
col = col.push(widget::icon::from_name("audio-x-generic-symbolic").size(256)); col = col.push(widget::icon::from_name("audio-x-generic-symbolic").size(256));
} }
col = col.push(widget::vertical_space(space_s)); col = col.push(widget::space::vertical().height(space_s));
if self.mpris_meta.title.is_empty() { if self.mpris_meta.title.is_empty() {
col = col.push(widget::text::title4(fl!("untitled"))); col = col.push(widget::text::title4(fl!("untitled")));
} else { } else {
@ -1566,7 +1578,7 @@ impl Application for App {
col = col.push(widget::text::body(artist)); col = col.push(widget::text::body(artist));
} }
} }
col = col.push(widget::vertical_space(space_s)); col = col.push(widget::space::vertical().height(space_s));
if !self.mpris_meta.album.is_empty() { if !self.mpris_meta.album.is_empty() {
col = col.push(widget::text::body(fl!( col = col.push(widget::text::body(fl!(
"album", "album",
@ -1576,7 +1588,7 @@ impl Application for App {
if let Some(year) = &self.mpris_meta.album_year_opt { if let Some(year) = &self.mpris_meta.album_year_opt {
col = col.push(widget::text::body(format!("{}", year))); col = col.push(widget::text::body(format!("{}", year)));
} }
col = col.push(widget::vertical_space(Length::Fill)); col = col.push(widget::space::vertical());
// Space to keep from going under control overlay // Space to keep from going under control overlay
let mut control_height = space_xxs + 32 + space_xxs; let mut control_height = space_xxs + 32 + space_xxs;
@ -1586,11 +1598,11 @@ impl Application for App {
// This is a hack to have the video player running but not visible (since the controls will cover it as an overlay) // This is a hack to have the video player running but not visible (since the controls will cover it as an overlay)
video_player = widget::row::with_children(vec![ video_player = widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(), widget::space::horizontal().into(),
widget::container(col.push(widget::container(video_player).height(control_height))) widget::container(col.push(widget::container(video_player).height(control_height)))
.width(320) .width(320)
.into(), .into(),
widget::horizontal_space(Length::Fill).into(), widget::space::horizontal().into(),
]) ])
.into(); .into();
} }
@ -1629,7 +1641,7 @@ impl Application for App {
.step(0.01) .step(0.01)
.into(), .into(),
]) ])
.align_items(Alignment::Center) .align_y(Alignment::Center)
.into(), .into(),
); );
} }
@ -1668,14 +1680,14 @@ impl Application for App {
popup_items.push( popup_items.push(
widget::row::with_children(vec![ widget::row::with_children(vec![
widget::horizontal_space(Length::Fill).into(), widget::space::horizontal().into(),
widget::container(column) widget::container(column)
.padding(1) .padding(1)
//TODO: move style to libcosmic //TODO: move style to libcosmic
.style(theme::Container::custom(|theme| { .class(theme::Container::custom(|theme| {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let component = &cosmic.background.component; let component = &cosmic.background.component;
widget::container::Appearance { widget::container::Style {
icon_color: Some(component.on.into()), icon_color: Some(component.on.into()),
text_color: Some(component.on.into()), text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())), background: Some(Background::Color(component.base.into())),
@ -1694,8 +1706,8 @@ impl Application for App {
); );
} }
if self.controls { if self.controls {
let mut row = widget::row::with_capacity(8) let mut row = widget::row::with_capacity(7)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.spacing(space_xxs) .spacing(space_xxs)
.push( .push(
widget::button::icon( widget::button::icon(
@ -1721,14 +1733,14 @@ impl Application for App {
RepeatState::Track => RepeatState::Disabled, RepeatState::Track => RepeatState::Disabled,
}, },
)), )),
match self.flags.config_state.player_state.repeat { widget::text(match self.flags.config_state.player_state.repeat {
RepeatState::Disabled => fl!("repeat-disabled"), RepeatState::Disabled => fl!("repeat-disabled"),
RepeatState::Track => fl!("repeat-track"), RepeatState::Track => fl!("repeat-track"),
}, }),
widget::tooltip::Position::Top, widget::tooltip::Position::Top,
)); ));
if self.core.is_condensed() { if self.core.is_condensed() {
row = row.push(widget::horizontal_space(Length::Fill)); row = row.push(widget::space::horizontal());
} else { } else {
row = row row = row
.push(widget::text(format_time(self.position)).font(font::mono())) .push(widget::text(format_time(self.position)).font(font::mono()))
@ -1777,7 +1789,7 @@ impl Application for App {
popup_items.push( popup_items.push(
widget::container(row) widget::container(row)
.padding([space_xxs, space_xs]) .padding([space_xxs, space_xs])
.style(theme::Container::WindowBackground) .class(theme::Container::WindowBackground)
.into(), .into(),
); );
@ -1785,7 +1797,7 @@ impl Application for App {
popup_items.push( popup_items.push(
widget::container( widget::container(
widget::row::with_capacity(3) widget::row::with_capacity(3)
.align_items(Alignment::Center) .align_y(Alignment::Center)
.spacing(space_xxs) .spacing(space_xxs)
.push(widget::text(format_time(self.position)).font(font::mono())) .push(widget::text(format_time(self.position)).font(font::mono()))
.push( .push(
@ -1799,7 +1811,7 @@ impl Application for App {
), ),
) )
.padding([space_xxs, space_xs]) .padding([space_xxs, space_xs])
.style(theme::Container::WindowBackground) .class(theme::Container::WindowBackground)
.into(), .into(),
); );
} }
@ -1811,9 +1823,9 @@ impl Application for App {
widget::container(popover) widget::container(popover)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.style(theme::Container::Custom(Box::new(move |_theme| { .class(theme::Container::Custom(Box::new(move |_theme| {
let mut appearance = let mut appearance =
widget::container::Appearance::default().with_background(background_color); widget::container::Style::default().background(background_color);
if let Some(text_color) = text_color_opt { if let Some(text_color) = text_color_opt {
appearance.text_color = Some(text_color); appearance.text_color = Some(text_color);
} }
@ -1828,7 +1840,7 @@ impl Application for App {
struct ThemeSubscription; struct ThemeSubscription;
let mut subscriptions = vec![ 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, .. }) => { Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => {
Some(Message::Key(modifiers, key)) Some(Message::Key(modifiers, key))
} }

View file

@ -1,8 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::{
Element, theme, theme,
widget::menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind}, widget::{
menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
RcElementWrapper,
},
Element,
}; };
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
@ -39,6 +43,7 @@ pub fn menu_bar<'a>(
for (i, path) in config_state.recent_files.iter().enumerate() { for (i, path) in config_state.recent_files.iter().enumerate() {
recent_files.push(menu::Item::Button( recent_files.push(menu::Item::Button(
format_url(path), format_url(path),
None,
Action::FileOpenRecent(i), Action::FileOpenRecent(i),
)); ));
} }
@ -46,6 +51,7 @@ pub fn menu_bar<'a>(
recent_files.push(menu::Item::Divider); recent_files.push(menu::Item::Divider);
recent_files.push(menu::Item::Button( recent_files.push(menu::Item::Button(
fl!("clear-recent"), fl!("clear-recent"),
None,
Action::FileClearRecents, Action::FileClearRecents,
)); ));
} }
@ -59,6 +65,7 @@ pub fn menu_bar<'a>(
for (i, path) in config_state.recent_projects.iter().enumerate() { for (i, path) in config_state.recent_projects.iter().enumerate() {
recent_projects.push(menu::Item::Button( recent_projects.push(menu::Item::Button(
format_path(path), format_path(path),
None,
Action::FolderOpenRecent(i), Action::FolderOpenRecent(i),
)); ));
} }
@ -66,6 +73,7 @@ pub fn menu_bar<'a>(
recent_projects.push(menu::Item::Divider); recent_projects.push(menu::Item::Divider);
recent_projects.push(menu::Item::Button( recent_projects.push(menu::Item::Button(
fl!("clear-recent"), fl!("clear-recent"),
None,
Action::FolderClearRecents, Action::FolderClearRecents,
)); ));
} }
@ -74,24 +82,25 @@ pub fn menu_bar<'a>(
for (folder_i, (name, _path)) in projects.iter().enumerate() { for (folder_i, (name, _path)) in projects.iter().enumerate() {
close_projects.push(menu::Item::Button( close_projects.push(menu::Item::Button(
name.clone(), name.clone(),
None,
Action::FolderClose(folder_i), Action::FolderClose(folder_i),
)); ));
} }
MenuBar::new(vec![menu::Tree::with_children( MenuBar::new(vec![menu::Tree::with_children(
menu::root(fl!("file")), RcElementWrapper::new(Element::from(menu::root(fl!("file")))),
menu::items( menu::items(
key_binds, key_binds,
vec![ vec![
menu::Item::Button(fl!("open-media"), Action::FileOpen), menu::Item::Button(fl!("open-media"), None, Action::FileOpen),
menu::Item::Folder(fl!("open-recent-media"), recent_files), menu::Item::Folder(fl!("open-recent-media"), recent_files),
menu::Item::Button(fl!("close-file"), Action::FileClose), menu::Item::Button(fl!("close-file"), None, Action::FileClose),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Button(fl!("open-media-folder"), Action::FolderOpen), menu::Item::Button(fl!("open-media-folder"), None, Action::FolderOpen),
menu::Item::Folder(fl!("open-recent-media-folder"), recent_projects), menu::Item::Folder(fl!("open-recent-media-folder"), recent_projects),
menu::Item::Folder(fl!("close-media-folder"), close_projects), menu::Item::Folder(fl!("close-media-folder"), close_projects),
menu::Item::Divider, menu::Item::Divider,
menu::Item::Button(fl!("quit"), Action::WindowClose), menu::Item::Button(fl!("quit"), None, Action::WindowClose),
], ],
), ),
)]) )])

View file

@ -1,6 +1,9 @@
use cosmic::iced::{ use cosmic::{
futures::{self, SinkExt}, iced::{
subscription::{self, Subscription}, futures::{self, SinkExt, Stream},
Subscription,
},
iced_futures::stream,
}; };
use mpris_server::{ use mpris_server::{
LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface, LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
@ -377,15 +380,13 @@ impl PlaylistsInterface for Player {
} }
*/ */
pub fn subscription() -> Subscription<Message> { fn watcher_stream() -> impl Stream<Item = Message> {
struct MprisSubscription; stream::channel(
subscription::channel( 5,
TypeId::of::<MprisSubscription>(), move |mut msg_tx: futures::channel::mpsc::Sender<Message>| async move {
16,
move |mut msg_tx| async move {
let (event_tx, mut event_rx) = mpsc::unbounded_channel(); let (event_tx, mut event_rx) = mpsc::unbounded_channel();
let meta = MprisMeta::default(); let meta: MprisMeta = MprisMeta::default();
let state = MprisState::default(); let state: MprisState = MprisState::default();
msg_tx msg_tx
.send(Message::MprisChannel(meta.clone(), state.clone(), event_tx)) .send(Message::MprisChannel(meta.clone(), state.clone(), event_tx))
.await .await
@ -455,3 +456,9 @@ pub fn subscription() -> Subscription<Message> {
}, },
) )
} }
#[cold]
pub fn subscription() -> Subscription<Message> {
struct MprisSubscription;
Subscription::run_with(TypeId::of::<MprisSubscription>(), |_| watcher_stream())
}

View file

@ -1,4 +1,3 @@
use cosmic::iced_core::image::Data;
use iced_video_player::Position; use iced_video_player::Position;
use image::{DynamicImage, ImageFormat, RgbaImage}; use image::{DynamicImage, ImageFormat, RgbaImage};
use std::{error::Error, num::NonZero, path::Path, time::Duration}; use std::{error::Error, num::NonZero, path::Path, time::Duration};
@ -29,9 +28,10 @@ pub fn main(
}; };
video.thumbnails([position], NonZero::new(1).unwrap())? video.thumbnails([position], NonZero::new(1).unwrap())?
}; };
//TODO: do not require clone of pixels data // TODO: do not require clone of pixels data
match thumbnails[0].data() { match &thumbnails[0] {
Data::Rgba { cosmic::widget::image::Handle::Rgba {
id: _,
width, width,
height, height,
pixels, pixels,

View file

@ -4,7 +4,7 @@ use iced_video_player::{
gst_app, gst_pbutils, gst_app, gst_pbutils,
}; };
use cosmic::app::{Command, message}; use cosmic::{action, app::Task};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct VideoSettings { pub struct VideoSettings {
@ -14,7 +14,7 @@ pub struct VideoSettings {
pub fn new_video( pub fn new_video(
url: &url::Url, url: &url::Url,
settings: VideoSettings, settings: VideoSettings,
) -> Result<Video, cosmic::Command<cosmic::app::Message<super::Message>>> { ) -> Result<Video, cosmic::Task<cosmic::Action<super::Message>>> {
//TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error //TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error
//TODO: remove unwraps and enable playback of files with only audio. //TODO: remove unwraps and enable playback of files with only audio.
gst::init().unwrap(); gst::init().unwrap();
@ -68,8 +68,8 @@ pub fn new_video(
match msg.view() { match msg.view() {
gst::MessageView::Element(element) => { gst::MessageView::Element(element) => {
if gst_pbutils::MissingPluginMessage::is(&element) { if gst_pbutils::MissingPluginMessage::is(&element) {
commands.push(Command::perform( commands.push(Task::perform(
async { message::app(super::Message::MissingPlugin(msg)) }, async { action::app(super::Message::MissingPlugin(msg)) },
|x| x, |x| x,
)); ));
// Do one codec install at a time // Do one codec install at a time
@ -80,7 +80,7 @@ pub fn new_video(
} }
} }
pipeline.set_state(gst::State::Null).unwrap(); pipeline.set_state(gst::State::Null).unwrap();
Err(Command::batch(commands)) Err(Task::batch(commands))
} }
} }
} }