feat: dock settings pages
This commit is contained in:
parent
1c6ed73165
commit
87de348d86
11 changed files with 901 additions and 553 deletions
110
app/src/app.rs
110
app/src/app.rs
|
|
@ -28,15 +28,18 @@ use cosmic::{
|
||||||
},
|
},
|
||||||
Element, ElementExt,
|
Element, ElementExt,
|
||||||
};
|
};
|
||||||
|
use page::Page;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
pages::{
|
pages::{
|
||||||
desktop::{
|
desktop::{
|
||||||
self,
|
self,
|
||||||
|
dock::{self, applets::ADD_DOCK_APPLET_DIALOGUE_ID},
|
||||||
panel::{
|
panel::{
|
||||||
self,
|
self,
|
||||||
applets::{self, APPLET_DND_ICON_ID},
|
applets_inner::{self, AppletsPage, APPLET_DND_ICON_ID},
|
||||||
|
inner as _panel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
input::{self, keyboard},
|
input::{self, keyboard},
|
||||||
|
|
@ -125,8 +128,8 @@ impl Application for SettingsApp {
|
||||||
// app.insert_page::<bluetooth::Page>();
|
// app.insert_page::<bluetooth::Page>();
|
||||||
|
|
||||||
let desktop_id = app.insert_page::<desktop::Page>().id();
|
let desktop_id = app.insert_page::<desktop::Page>().id();
|
||||||
// app.insert_page::<panel::Page>();
|
app.insert_page::<panel::Page>();
|
||||||
// app.insert_page::<applets::Page>();
|
app.insert_page::<dock::Page>();
|
||||||
|
|
||||||
// app.insert_page::<input::Page>();
|
// app.insert_page::<input::Page>();
|
||||||
|
|
||||||
|
|
@ -175,13 +178,13 @@ impl Application for SettingsApp {
|
||||||
wayland::OutputEvent::Created(Some(info)),
|
wayland::OutputEvent::Created(Some(info)),
|
||||||
o,
|
o,
|
||||||
))) if info.name.is_some() => Some(Message::PageMessage(crate::pages::Message::Panel(
|
))) if info.name.is_some() => Some(Message::PageMessage(crate::pages::Message::Panel(
|
||||||
panel::Message::OutputAdded(info.name.unwrap(), o),
|
panel::Message(_panel::Message::OutputAdded(info.name.unwrap(), o)),
|
||||||
))),
|
))),
|
||||||
iced::Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::Output(
|
iced::Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::Output(
|
||||||
wayland::OutputEvent::Removed,
|
wayland::OutputEvent::Removed,
|
||||||
o,
|
o,
|
||||||
))) => Some(Message::PageMessage(crate::pages::Message::Panel(
|
))) => Some(Message::PageMessage(crate::pages::Message::Panel(
|
||||||
panel::Message::OutputRemoved(o),
|
panel::Message(_panel::Message::OutputRemoved(o)),
|
||||||
))),
|
))),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
@ -224,7 +227,9 @@ impl Application for SettingsApp {
|
||||||
return self.search.focus();
|
return self.search.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Message::Page(page) => return self.activate_page(page),
|
Message::Page(page) => {
|
||||||
|
return self.activate_page(page);
|
||||||
|
}
|
||||||
Message::Drag => return start_drag_window(window::Id(0)),
|
Message::Drag => return start_drag_window(window::Id(0)),
|
||||||
Message::Close => {
|
Message::Close => {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
|
|
@ -257,7 +262,6 @@ impl Application for SettingsApp {
|
||||||
Message::Search(search::Message::Clear) => {
|
Message::Search(search::Message::Clear) => {
|
||||||
self.search_clear();
|
self.search_clear();
|
||||||
}
|
}
|
||||||
Message::None | Message::Search(_) => {}
|
|
||||||
Message::PageMessage(message) => match message {
|
Message::PageMessage(message) => match message {
|
||||||
crate::pages::Message::About(message) => {
|
crate::pages::Message::About(message) => {
|
||||||
page::update!(self.pages, message, system::about::Page);
|
page::update!(self.pages, message, system::about::Page);
|
||||||
|
|
@ -285,8 +289,16 @@ impl Application for SettingsApp {
|
||||||
crate::pages::Message::Panel(message) => {
|
crate::pages::Message::Panel(message) => {
|
||||||
page::update!(self.pages, message, panel::Page);
|
page::update!(self.pages, message, panel::Page);
|
||||||
}
|
}
|
||||||
crate::pages::Message::Applet(message) => {
|
crate::pages::Message::PanelApplet(message) => {
|
||||||
if let Some(page) = self.pages.page_mut::<applets::Page>() {
|
if let Some(page) = self.pages.page_mut::<applets_inner::Page>() {
|
||||||
|
return page.update(message, applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::pages::Message::Dock(message) => {
|
||||||
|
page::update!(self.pages, message, dock::Page);
|
||||||
|
}
|
||||||
|
crate::pages::Message::DockApplet(message) => {
|
||||||
|
if let Some(page) = self.pages.page_mut::<dock::applets::Page>() {
|
||||||
return page.update(message);
|
return page.update(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,29 +307,54 @@ impl Application for SettingsApp {
|
||||||
self.sharp_corners = matches!(state, WindowState::Activated);
|
self.sharp_corners = matches!(state, WindowState::Activated);
|
||||||
}
|
}
|
||||||
Message::PanelConfig(config) if config.name.to_lowercase().contains("panel") => {
|
Message::PanelConfig(config) if config.name.to_lowercase().contains("panel") => {
|
||||||
if let Some(page) = self.pages.page_mut::<panel::Page>() {
|
page::update!(
|
||||||
page.update(panel::Message::PanelConfig(config.clone()));
|
self.pages,
|
||||||
}
|
panel::Message(_panel::Message::PanelConfig(config.clone())),
|
||||||
if let Some(page) = self.pages.page_mut::<applets::Page>() {
|
panel::Page
|
||||||
_ = page.update(applets::Message::PanelConfig(config));
|
);
|
||||||
|
|
||||||
|
if let Some(page) = self.pages.page_mut::<applets_inner::Page>() {
|
||||||
|
return page.update(
|
||||||
|
applets_inner::Message::PanelConfig(config),
|
||||||
|
applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::PanelConfig(_) => {} // ignore other config changes for now,
|
Message::PanelConfig(config) if config.name.to_lowercase().contains("dock") => {
|
||||||
|
page::update!(
|
||||||
|
self.pages,
|
||||||
|
dock::Message::Inner(_panel::Message::PanelConfig(config.clone(),)),
|
||||||
|
dock::Page
|
||||||
|
);
|
||||||
|
page::update!(
|
||||||
|
self.pages,
|
||||||
|
dock::applets::Message(applets_inner::Message::PanelConfig(config,)),
|
||||||
|
dock::applets::Page
|
||||||
|
);
|
||||||
|
}
|
||||||
Message::DesktopInfo => {
|
Message::DesktopInfo => {
|
||||||
if let Some(page) = self.pages.page_mut::<applets::Page>() {
|
let info_list: Vec<_> = freedesktop_desktop_entry::Iter::new(
|
||||||
// collect the potential applets
|
freedesktop_desktop_entry::default_paths(),
|
||||||
ret = page.update(applets::Message::Applets(
|
)
|
||||||
freedesktop_desktop_entry::Iter::new(
|
.filter_map(|p| applets_inner::Applet::try_from(Cow::from(p)).ok())
|
||||||
freedesktop_desktop_entry::default_paths(),
|
.collect();
|
||||||
)
|
|
||||||
.filter_map(|p| applets::Applet::try_from(Cow::from(p)).ok())
|
page::update!(
|
||||||
.collect(),
|
self.pages,
|
||||||
));
|
dock::applets::Message(applets_inner::Message::Applets(info_list.clone())),
|
||||||
|
dock::applets::Page
|
||||||
|
);
|
||||||
|
if let Some(page) = self.pages.page_mut::<applets_inner::Page>() {
|
||||||
|
return page.update(
|
||||||
|
applets_inner::Message::Applets(info_list),
|
||||||
|
applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ThemeChanged(theme) => {
|
Message::ThemeChanged(theme) => {
|
||||||
self.theme = theme;
|
self.theme = theme;
|
||||||
}
|
}
|
||||||
|
Message::PanelConfig(_) | Message::None | Message::Search(_) => {} // Ignored
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
@ -325,14 +362,21 @@ impl Application for SettingsApp {
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn view(&self, id: window::Id) -> Element<Message> {
|
fn view(&self, id: window::Id) -> Element<Message> {
|
||||||
if let Some(Some(page)) =
|
if let Some(Some(page)) =
|
||||||
(id == APPLET_DND_ICON_ID).then(|| self.pages.page::<applets::Page>())
|
(id == APPLET_DND_ICON_ID).then(|| self.pages.page::<applets_inner::Page>())
|
||||||
{
|
{
|
||||||
return page.dnd_icon();
|
return page.dnd_icon();
|
||||||
}
|
}
|
||||||
if let Some(Some(page)) =
|
if let Some(Some(page)) = (id == applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID)
|
||||||
(id == applets::ADD_APPLET_DIALOGUE_ID).then(|| self.pages.page::<applets::Page>())
|
.then(|| self.pages.page::<applets_inner::Page>())
|
||||||
{
|
{
|
||||||
return page.add_applet_view();
|
return page.add_applet_view(crate::pages::Message::PanelApplet);
|
||||||
|
}
|
||||||
|
if let Some(Some(page)) =
|
||||||
|
(id == ADD_DOCK_APPLET_DIALOGUE_ID).then(|| self.pages.page::<dock::applets::Page>())
|
||||||
|
{
|
||||||
|
return page.inner().add_applet_view(|msg| {
|
||||||
|
crate::pages::Message::DockApplet(dock::applets::Message(msg))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if let Some(Some(page)) =
|
if let Some(Some(page)) =
|
||||||
(id == keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID).then(|| self.pages.page::<input::Page>())
|
(id == keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID).then(|| self.pages.page::<input::Page>())
|
||||||
|
|
@ -439,10 +483,14 @@ impl Application for SettingsApp {
|
||||||
fn close_requested(&self, id: window::Id) -> Self::Message {
|
fn close_requested(&self, id: window::Id) -> Self::Message {
|
||||||
if id == window::Id(0) {
|
if id == window::Id(0) {
|
||||||
Message::Close
|
Message::Close
|
||||||
} else if id == applets::ADD_APPLET_DIALOGUE_ID {
|
} else if id == applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID {
|
||||||
Message::PageMessage(crate::pages::Message::Applet(
|
Message::PageMessage(crate::pages::Message::PanelApplet(
|
||||||
applets::Message::ClosedAppletDialogue,
|
applets_inner::Message::ClosedAppletDialogue,
|
||||||
))
|
))
|
||||||
|
} else if id == ADD_DOCK_APPLET_DIALOGUE_ID {
|
||||||
|
Message::PageMessage(crate::pages::Message::DockApplet(dock::applets::Message(
|
||||||
|
applets_inner::Message::ClosedAppletDialogue,
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
Message::None
|
Message::None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
use cosmic_settings_page::Section;
|
|
||||||
use cosmic_settings_page::{self as page, section};
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Page;
|
|
||||||
|
|
||||||
impl page::Page<crate::pages::Message> for Page {
|
|
||||||
fn content(
|
|
||||||
&self,
|
|
||||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
|
||||||
) -> Option<page::Content> {
|
|
||||||
Some(vec![sections.insert(Section::default())])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn info(&self) -> page::Info {
|
|
||||||
page::Info::new("dock", "preferences-pop-desktop-dock-symbolic")
|
|
||||||
.title(fl!("dock"))
|
|
||||||
.description(fl!("dock", "desc"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
|
||||||
82
app/src/pages/desktop/dock/applets.rs
Normal file
82
app/src/pages/desktop/dock/applets.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
use cosmic::{cosmic_config::CosmicConfigEntry, iced::window, iced_runtime::Command};
|
||||||
|
use cosmic_panel_config::CosmicPanelConfig;
|
||||||
|
use cosmic_settings_page::{self as page, section, Section};
|
||||||
|
use slotmap::SlotMap;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app,
|
||||||
|
pages::{
|
||||||
|
self,
|
||||||
|
desktop::panel::applets_inner::{self, lists, AppletsPage, ReorderWidgetState},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ADD_DOCK_APPLET_DIALOGUE_ID: window::Id = window::Id(1002);
|
||||||
|
|
||||||
|
pub(crate) struct Page {
|
||||||
|
inner: applets_inner::Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Page {
|
||||||
|
fn default() -> Self {
|
||||||
|
let config_helper = CosmicPanelConfig::cosmic_config("Dock").ok();
|
||||||
|
let current_config = config_helper.as_ref().and_then(|config_helper| {
|
||||||
|
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
||||||
|
// If the config is not present, it will be created with the default values and the name will not match
|
||||||
|
(panel_config.name == "Dock").then_some(panel_config)
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
inner: applets_inner::Page {
|
||||||
|
available_entries: freedesktop_desktop_entry::Iter::new(
|
||||||
|
freedesktop_desktop_entry::default_paths(),
|
||||||
|
)
|
||||||
|
.filter_map(|p| applets_inner::Applet::try_from(Cow::from(p)).ok())
|
||||||
|
.collect(),
|
||||||
|
config_helper,
|
||||||
|
current_config,
|
||||||
|
reorder_widget_state: ReorderWidgetState::default(),
|
||||||
|
search: String::new(),
|
||||||
|
has_dialogue: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppletsPage for Page {
|
||||||
|
fn inner(&self) -> &applets_inner::Page {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut applets_inner::Page {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Message(pub applets_inner::Message);
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub fn update(&mut self, message: Message) -> Command<app::Message> {
|
||||||
|
self.inner.update(message.0, ADD_DOCK_APPLET_DIALOGUE_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn content(
|
||||||
|
&self,
|
||||||
|
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||||
|
) -> Option<page::Content> {
|
||||||
|
Some(vec![sections.insert(lists::<Page, _>(|msg| {
|
||||||
|
pages::Message::DockApplet(Message(msg))
|
||||||
|
}))])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(&self) -> page::Info {
|
||||||
|
page::Info::new("dock_applets", "preferences-pop-desktop-dock-symbolic")
|
||||||
|
// .title(fl!("applets"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||||
166
app/src/pages/desktop/dock/mod.rs
Normal file
166
app/src/pages/desktop/dock/mod.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use apply::Apply;
|
||||||
|
use cosmic::{
|
||||||
|
cosmic_config::CosmicConfigEntry,
|
||||||
|
widget::{settings, text, toggler},
|
||||||
|
Element,
|
||||||
|
};
|
||||||
|
use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig};
|
||||||
|
use cosmic_settings_page::{self as page, section, Section};
|
||||||
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
|
use crate::pages::desktop::panel::inner::{add_panel, behavior_and_position, configuration, style};
|
||||||
|
|
||||||
|
use super::panel::inner::{self, PageInner, PanelPage};
|
||||||
|
|
||||||
|
pub mod applets;
|
||||||
|
|
||||||
|
pub struct Page {
|
||||||
|
inner: PageInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
EnableDock(bool),
|
||||||
|
Inner(inner::Message),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub fn update(&mut self, message: Message) {
|
||||||
|
match message {
|
||||||
|
Message::EnableDock(enabled) => {
|
||||||
|
let Some(container_config) = self.inner.container_config.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(panel_config) = self.inner.panel_config.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if enabled {
|
||||||
|
container_config.config_list.push(panel_config.clone());
|
||||||
|
} else {
|
||||||
|
container_config
|
||||||
|
.config_list
|
||||||
|
.retain(|c| c.name.as_str() != "Dock");
|
||||||
|
}
|
||||||
|
_ = container_config.write_entries();
|
||||||
|
}
|
||||||
|
Message::Inner(inner) => {
|
||||||
|
self.inner.update(inner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl page::AutoBind<crate::pages::Message> for Page {
|
||||||
|
fn sub_pages(page: page::Insert<crate::pages::Message>) -> page::Insert<crate::pages::Message> {
|
||||||
|
page.sub_page::<applets::Page>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelPage for Page {
|
||||||
|
fn inner(&self) -> &PageInner {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut PageInner {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autohide_label(&self) -> String {
|
||||||
|
fl!("panel-behavior-and-position", "dock-autohide")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gap_label(&self) -> String {
|
||||||
|
fl!("panel-style", "dock-anchor-gap")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_label(&self) -> String {
|
||||||
|
fl!("panel-style", "dock-extend")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_applets_label(&self) -> String {
|
||||||
|
fl!("panel-applets", "dock-desc")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn applets_page_id(&self) -> &'static str {
|
||||||
|
"dock_applets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Page {
|
||||||
|
fn default() -> Self {
|
||||||
|
// TODO CosmicPanelConfig should return its own version
|
||||||
|
let config_helper = CosmicPanelConfig::cosmic_config("Dock").ok();
|
||||||
|
let panel_config = config_helper.as_ref().and_then(|config_helper| {
|
||||||
|
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
||||||
|
// If the config is not present, it will be created with the default values and the name will not match
|
||||||
|
(panel_config.name == "Dock").then_some(panel_config)
|
||||||
|
});
|
||||||
|
let container_config = CosmicPanelContainerConfig::load().ok();
|
||||||
|
Self {
|
||||||
|
inner: PageInner {
|
||||||
|
config_helper,
|
||||||
|
panel_config,
|
||||||
|
container_config,
|
||||||
|
outputs: HashMap::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn enable() -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.descriptions(vec![fl!("dock")])
|
||||||
|
.view::<Page>(|_binder, page, section| {
|
||||||
|
let descriptions = §ion.descriptions;
|
||||||
|
let Some(container_config) = page.inner.container_config.as_ref() else {
|
||||||
|
return Element::from(text(fl!("unknown")));
|
||||||
|
};
|
||||||
|
settings::view_section(§ion.title)
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[0],
|
||||||
|
toggler(
|
||||||
|
None,
|
||||||
|
container_config
|
||||||
|
.config_list
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.name.as_str() == "Dock"),
|
||||||
|
Message::EnableDock,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(crate::pages::Message::Dock)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// TODO cleanup
|
||||||
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn content(
|
||||||
|
&self,
|
||||||
|
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||||
|
) -> Option<page::Content> {
|
||||||
|
Some(if self.inner.panel_config.is_some() {
|
||||||
|
vec![
|
||||||
|
sections.insert(enable()),
|
||||||
|
sections.insert(behavior_and_position::<Page, _>(self, |m| {
|
||||||
|
crate::pages::Message::Dock(Message::Inner(m))
|
||||||
|
})),
|
||||||
|
sections.insert(style::<Page, _>(self, |m| {
|
||||||
|
crate::pages::Message::Dock(Message::Inner(m))
|
||||||
|
})),
|
||||||
|
sections.insert(configuration::<Page>(self)),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![sections.insert(add_panel::<Page, _>(|m| {
|
||||||
|
crate::pages::Message::Dock(Message::Inner(m))
|
||||||
|
}))]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(&self) -> page::Info {
|
||||||
|
page::Info::new("dock", "preferences-pop-desktop-dock-symbolic")
|
||||||
|
.title(fl!("dock"))
|
||||||
|
.description(fl!("dock", "desc"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
use std::{
|
|
||||||
borrow::{Borrow, Cow},
|
|
||||||
fmt::Debug,
|
|
||||||
mem,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
cosmic_config::{self, Config, CosmicConfigEntry},
|
cosmic_config::{Config, CosmicConfigEntry},
|
||||||
iced::{
|
iced::{
|
||||||
alignment::{Horizontal, Vertical},
|
alignment::{Horizontal, Vertical},
|
||||||
event::{
|
event::{
|
||||||
|
|
@ -47,6 +38,14 @@ use cosmic::{
|
||||||
widget::{button, header_bar, icon, list_column},
|
widget::{button, header_bar, icon, list_column},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
use std::{
|
||||||
|
borrow::{Borrow, Cow},
|
||||||
|
fmt::Debug,
|
||||||
|
mem,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{app, pages};
|
use crate::{app, pages};
|
||||||
use cosmic_panel_config::CosmicPanelConfig;
|
use cosmic_panel_config::CosmicPanelConfig;
|
||||||
|
|
@ -70,28 +69,31 @@ const SPACING: f32 = 8.0;
|
||||||
const DRAG_START_DISTANCE_SQUARED: f32 = 64.0;
|
const DRAG_START_DISTANCE_SQUARED: f32 = 64.0;
|
||||||
|
|
||||||
pub const APPLET_DND_ICON_ID: window::Id = window::Id(1000);
|
pub const APPLET_DND_ICON_ID: window::Id = window::Id(1000);
|
||||||
pub const ADD_APPLET_DIALOGUE_ID: window::Id = window::Id(1001);
|
pub const ADD_PANEL_APPLET_DIALOGUE_ID: window::Id = window::Id(1001);
|
||||||
|
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
available_entries: Vec<Applet<'static>>,
|
pub(crate) available_entries: Vec<Applet<'static>>,
|
||||||
config_helper: Option<Config>,
|
pub(crate) config_helper: Option<Config>,
|
||||||
current_config: Option<CosmicPanelConfig>,
|
pub(crate) current_config: Option<CosmicPanelConfig>,
|
||||||
reorder_widget_state: ReorderWidgetState,
|
pub(crate) reorder_widget_state: ReorderWidgetState,
|
||||||
search: String,
|
pub(crate) search: String,
|
||||||
has_dialogue: bool,
|
pub(crate) has_dialogue: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Page {
|
impl Default for Page {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let config_helper = cosmic_config::Config::new("com.system76.CosmicPanel.Panel", 1).ok();
|
let config_helper = CosmicPanelConfig::cosmic_config("Panel").ok();
|
||||||
let current_config = config_helper.as_ref().and_then(|config_helper| {
|
let current_config = config_helper.as_ref().and_then(|config_helper| {
|
||||||
// TODO error handling...
|
|
||||||
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
||||||
// If the config is not present, it will be created with the default values and the name will not match
|
// If the config is not present, it will be created with the default values and the name will not match
|
||||||
(panel_config.name == "Panel").then_some(panel_config)
|
(panel_config.name == "Panel").then_some(panel_config)
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
available_entries: Vec::new(),
|
available_entries: freedesktop_desktop_entry::Iter::new(
|
||||||
|
freedesktop_desktop_entry::default_paths(),
|
||||||
|
)
|
||||||
|
.filter_map(|p| Applet::try_from(Cow::from(p)).ok())
|
||||||
|
.collect(),
|
||||||
config_helper,
|
config_helper,
|
||||||
current_config,
|
current_config,
|
||||||
reorder_widget_state: ReorderWidgetState::default(),
|
reorder_widget_state: ReorderWidgetState::default(),
|
||||||
|
|
@ -101,13 +103,31 @@ impl Default for Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait AppletsPage {
|
||||||
|
fn inner(&self) -> &Page;
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppletsPage for Page {
|
||||||
|
fn inner(&self) -> &Page {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut Page {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl page::Page<crate::pages::Message> for Page {
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn content(
|
fn content(
|
||||||
&self,
|
&self,
|
||||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||||
) -> Option<page::Content> {
|
) -> Option<page::Content> {
|
||||||
Some(vec![sections.insert(lists())])
|
Some(vec![
|
||||||
|
sections.insert(lists::<Page, _>(pages::Message::PanelApplet))
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self) -> page::Info {
|
fn info(&self) -> page::Info {
|
||||||
|
|
@ -141,7 +161,6 @@ pub enum Message {
|
||||||
DragAppletDialogue,
|
DragAppletDialogue,
|
||||||
Save,
|
Save,
|
||||||
Cancel,
|
Cancel,
|
||||||
Ignore,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Message {
|
impl Debug for Message {
|
||||||
|
|
@ -154,7 +173,6 @@ impl Debug for Message {
|
||||||
Message::PanelConfig(_) => write!(f, "PanelConfig"),
|
Message::PanelConfig(_) => write!(f, "PanelConfig"),
|
||||||
Message::StartDnd(_) => write!(f, "StartDnd"),
|
Message::StartDnd(_) => write!(f, "StartDnd"),
|
||||||
Message::DnDCommand(_) => write!(f, "DnDCommand"),
|
Message::DnDCommand(_) => write!(f, "DnDCommand"),
|
||||||
Message::Ignore => write!(f, "Ignore"),
|
|
||||||
Message::Save => write!(f, "ApplyReorder"),
|
Message::Save => write!(f, "ApplyReorder"),
|
||||||
Message::RemoveStart(_) => write!(f, "RemoveStart"),
|
Message::RemoveStart(_) => write!(f, "RemoveStart"),
|
||||||
Message::RemoveCenter(_) => write!(f, "RemoveCenter"),
|
Message::RemoveCenter(_) => write!(f, "RemoveCenter"),
|
||||||
|
|
@ -195,7 +213,10 @@ impl Page {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn add_applet_view(&self) -> Element<app::Message> {
|
pub fn add_applet_view<T: Fn(Message) -> crate::pages::Message + Copy + 'static>(
|
||||||
|
&self,
|
||||||
|
msg_map: T,
|
||||||
|
) -> Element<app::Message> {
|
||||||
let mut list_column = list_column();
|
let mut list_column = list_column();
|
||||||
let mut has_some = false;
|
let mut has_some = false;
|
||||||
for info in self
|
for info in self
|
||||||
|
|
@ -245,9 +266,9 @@ impl Page {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.padding(8.0)
|
.padding(8.0)
|
||||||
.on_press(app::Message::PageMessage(pages::Message::Applet(
|
.on_press(app::Message::PageMessage(msg_map(Message::AddApplet(
|
||||||
Message::AddApplet(info.clone())
|
info.clone()
|
||||||
))),
|
)))),
|
||||||
]
|
]
|
||||||
.padding([0, 32, 0, 32])
|
.padding([0, 32, 0, 32])
|
||||||
.spacing(12)
|
.spacing(12)
|
||||||
|
|
@ -264,10 +285,10 @@ impl Page {
|
||||||
column![
|
column![
|
||||||
header_bar()
|
header_bar()
|
||||||
.title(fl!("add-applet"))
|
.title(fl!("add-applet"))
|
||||||
.on_close(app::Message::PageMessage(pages::Message::Applet(
|
.on_close(app::Message::PageMessage(msg_map(
|
||||||
Message::CloseAppletDialogue
|
Message::CloseAppletDialogue
|
||||||
)))
|
)))
|
||||||
.on_drag(app::Message::PageMessage(pages::Message::Applet(
|
.on_drag(app::Message::PageMessage(msg_map(
|
||||||
Message::DragAppletDialogue
|
Message::DragAppletDialogue
|
||||||
))),
|
))),
|
||||||
container(
|
container(
|
||||||
|
|
@ -284,15 +305,11 @@ impl Page {
|
||||||
spacing: 12.0,
|
spacing: 12.0,
|
||||||
side: Side::Left,
|
side: Side::Left,
|
||||||
})
|
})
|
||||||
.on_input(|s| {
|
.on_input(move |s| {
|
||||||
app::Message::PageMessage(pages::Message::Applet(Message::Search(
|
app::Message::PageMessage(msg_map(Message::Search(s)))
|
||||||
s,
|
|
||||||
)))
|
|
||||||
})
|
})
|
||||||
.on_paste(|s| {
|
.on_paste(move |s| {
|
||||||
app::Message::PageMessage(pages::Message::Applet(Message::Search(
|
app::Message::PageMessage(msg_map(Message::Search(s)))
|
||||||
s,
|
|
||||||
)))
|
|
||||||
})
|
})
|
||||||
.width(Length::Fixed(312.0)),
|
.width(Length::Fixed(312.0)),
|
||||||
list_column
|
list_column
|
||||||
|
|
@ -312,7 +329,7 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn update(&mut self, message: Message) -> Command<app::Message> {
|
pub fn update(&mut self, message: Message, window_id: window::Id) -> Command<app::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::PanelConfig(c) => {
|
Message::PanelConfig(c) => {
|
||||||
self.current_config = Some(c);
|
self.current_config = Some(c);
|
||||||
|
|
@ -349,6 +366,7 @@ impl Page {
|
||||||
*list = end_list.into_iter().map(|a| a.id.into()).collect();
|
*list = end_list.into_iter().map(|a| a.id.into()).collect();
|
||||||
}
|
}
|
||||||
Message::Applets(applets) => {
|
Message::Applets(applets) => {
|
||||||
|
dbg!(&applets);
|
||||||
self.available_entries = applets;
|
self.available_entries = applets;
|
||||||
}
|
}
|
||||||
Message::StartDnd(state) => {
|
Message::StartDnd(state) => {
|
||||||
|
|
@ -358,7 +376,6 @@ impl Page {
|
||||||
Message::DnDCommand(action) => {
|
Message::DnDCommand(action) => {
|
||||||
return data_device_action(action());
|
return data_device_action(action());
|
||||||
}
|
}
|
||||||
Message::Ignore => {}
|
|
||||||
Message::Save => {
|
Message::Save => {
|
||||||
self.reorder_widget_state = ReorderWidgetState::default();
|
self.reorder_widget_state = ReorderWidgetState::default();
|
||||||
self.save();
|
self.save();
|
||||||
|
|
@ -429,12 +446,12 @@ impl Page {
|
||||||
|
|
||||||
list.push(applet.id.to_string());
|
list.push(applet.id.to_string());
|
||||||
self.save();
|
self.save();
|
||||||
return commands::window::close_window(ADD_APPLET_DIALOGUE_ID);
|
return commands::window::close_window(window_id);
|
||||||
}
|
}
|
||||||
Message::AddAppletDialogue => {
|
Message::AddAppletDialogue => {
|
||||||
self.has_dialogue = true;
|
self.has_dialogue = true;
|
||||||
let window_settings = SctkWindowSettings {
|
let window_settings = SctkWindowSettings {
|
||||||
window_id: ADD_APPLET_DIALOGUE_ID,
|
window_id,
|
||||||
app_id: Some("com.system76.CosmicSettings".to_string()),
|
app_id: Some("com.system76.CosmicSettings".to_string()),
|
||||||
title: Some(fl!("add-applet")),
|
title: Some(fl!("add-applet")),
|
||||||
parent: Some(window::Id(0)),
|
parent: Some(window::Id(0)),
|
||||||
|
|
@ -456,10 +473,10 @@ impl Page {
|
||||||
}
|
}
|
||||||
Message::CloseAppletDialogue => {
|
Message::CloseAppletDialogue => {
|
||||||
self.has_dialogue = false;
|
self.has_dialogue = false;
|
||||||
return commands::window::close_window(ADD_APPLET_DIALOGUE_ID);
|
return commands::window::close_window(window_id);
|
||||||
}
|
}
|
||||||
Message::DragAppletDialogue => {
|
Message::DragAppletDialogue => {
|
||||||
return commands::window::start_drag_window(ADD_APPLET_DIALOGUE_ID);
|
return commands::window::start_drag_window(window_id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -467,13 +484,20 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn lists() -> Section<crate::pages::Message> {
|
pub fn lists<
|
||||||
Section::default().view::<Page>(|_binder, page, _section| {
|
P: page::Page<crate::pages::Message> + AppletsPage,
|
||||||
|
T: Fn(Message) -> crate::pages::Message + Copy + 'static,
|
||||||
|
>(
|
||||||
|
msg_map: T,
|
||||||
|
) -> Section<crate::pages::Message> {
|
||||||
|
Section::default().view::<P>(move |_binder, page, _section| {
|
||||||
|
let page = page.inner();
|
||||||
let Some(config) = page.current_config.as_ref() else {
|
let Some(config) = page.current_config.as_ref() else {
|
||||||
return Element::from(
|
return Element::from(
|
||||||
text(fl!("unknown"))
|
text(fl!("unknown"))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = cosmic::iced::widget::button(text(fl!("add-applet")))
|
let button = cosmic::iced::widget::button(text(fl!("add-applet")))
|
||||||
.style(theme::Button::Secondary)
|
.style(theme::Button::Secondary)
|
||||||
.padding(8.0);
|
.padding(8.0);
|
||||||
|
|
@ -510,7 +534,7 @@ pub fn lists() -> Section<crate::pages::Message> {
|
||||||
Message::ReorderStart,
|
Message::ReorderStart,
|
||||||
Message::Save,
|
Message::Save,
|
||||||
Message::Cancel,
|
Message::Cancel,
|
||||||
page.reorder_widget_state.dragged_applet()
|
page.reorder_widget_state.dragged_applet().as_ref()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
.spacing(8.0),
|
.spacing(8.0),
|
||||||
|
|
@ -537,7 +561,7 @@ pub fn lists() -> Section<crate::pages::Message> {
|
||||||
Message::ReorderCenter,
|
Message::ReorderCenter,
|
||||||
Message::Save,
|
Message::Save,
|
||||||
Message::Cancel,
|
Message::Cancel,
|
||||||
page.reorder_widget_state.dragged_applet()
|
page.reorder_widget_state.dragged_applet().as_ref()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
.spacing(8.0),
|
.spacing(8.0),
|
||||||
|
|
@ -565,7 +589,7 @@ pub fn lists() -> Section<crate::pages::Message> {
|
||||||
Message::ReorderEnd,
|
Message::ReorderEnd,
|
||||||
Message::Save,
|
Message::Save,
|
||||||
Message::Cancel,
|
Message::Cancel,
|
||||||
page.reorder_widget_state.dragged_applet()
|
page.reorder_widget_state.dragged_applet().as_ref()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
.spacing(8.0),
|
.spacing(8.0),
|
||||||
|
|
@ -573,7 +597,7 @@ pub fn lists() -> Section<crate::pages::Message> {
|
||||||
.padding([0, 16, 0, 16])
|
.padding([0, 16, 0, 16])
|
||||||
.spacing(12.0)
|
.spacing(12.0)
|
||||||
.apply(Element::from)
|
.apply(Element::from)
|
||||||
.map(pages::Message::Applet)
|
.map(msg_map)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -587,6 +611,7 @@ pub struct Applet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Applet<'_> {
|
impl Applet<'_> {
|
||||||
|
#[must_use]
|
||||||
pub fn matches(&self, query: &str) -> bool {
|
pub fn matches(&self, query: &str) -> bool {
|
||||||
self.name.contains(query) || self.description.contains(query) || self.id.contains(query)
|
self.name.contains(query) || self.description.contains(query) || self.id.contains(query)
|
||||||
}
|
}
|
||||||
|
|
@ -667,9 +692,8 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> {
|
||||||
on_reorder: impl Fn(Vec<Applet<'static>>) -> Message + 'a,
|
on_reorder: impl Fn(Vec<Applet<'static>>) -> Message + 'a,
|
||||||
on_apply_reorder: Message,
|
on_apply_reorder: Message,
|
||||||
on_cancel: Message,
|
on_cancel: Message,
|
||||||
active_dnd: Option<Applet<'a>>, // state: Option<&State>,
|
active_dnd: Option<&Applet<'a>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// let dragged_path = state.and_then(|state| state.dragged_applet());
|
|
||||||
let applet_buttons = info
|
let applet_buttons = info
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -1057,7 +1081,6 @@ where
|
||||||
)));
|
)));
|
||||||
let data = match &state.dragging_state {
|
let data = match &state.dragging_state {
|
||||||
DraggingState::Dragging(a) => Some(a.clone()),
|
DraggingState::Dragging(a) => Some(a.clone()),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
shell.publish((self.on_dnd_command_produced.as_ref())(Box::new(
|
shell.publish((self.on_dnd_command_produced.as_ref())(Box::new(
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -1087,7 +1110,7 @@ where
|
||||||
shell.publish((self.on_reorder.as_ref())(
|
shell.publish((self.on_reorder.as_ref())(
|
||||||
filtered
|
filtered
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(pages::desktop::panel::applets::Applet::into_owned)
|
.map(pages::desktop::panel::applets_inner::Applet::into_owned)
|
||||||
.collect(),
|
.collect(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -1225,7 +1248,7 @@ where
|
||||||
shell.publish((self.on_reorder.as_ref())(
|
shell.publish((self.on_reorder.as_ref())(
|
||||||
reordered_list
|
reordered_list
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(pages::desktop::panel::applets::Applet::into_owned)
|
.map(pages::desktop::panel::applets_inner::Applet::into_owned)
|
||||||
.collect(),
|
.collect(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -1292,7 +1315,7 @@ where
|
||||||
shell.publish((self.on_reorder.as_ref())(
|
shell.publish((self.on_reorder.as_ref())(
|
||||||
filtered
|
filtered
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(pages::desktop::panel::applets::Applet::into_owned)
|
.map(pages::desktop::panel::applets_inner::Applet::into_owned)
|
||||||
.collect(),
|
.collect(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
393
app/src/pages/desktop/panel/inner.rs
Normal file
393
app/src/pages/desktop/panel/inner.rs
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
use cosmic::{
|
||||||
|
cosmic_config::{self, CosmicConfigEntry},
|
||||||
|
iced::widget::{button, container, horizontal_space, pick_list, row},
|
||||||
|
iced::Length,
|
||||||
|
iced_widget::slider,
|
||||||
|
sctk::reexports::client::{backend::ObjectId, protocol::wl_output::WlOutput, Proxy},
|
||||||
|
theme,
|
||||||
|
widget::{icon, list, settings, text, toggler},
|
||||||
|
Element,
|
||||||
|
};
|
||||||
|
|
||||||
|
use apply::Apply;
|
||||||
|
use cosmic_panel_config::{
|
||||||
|
AutoHide, CosmicPanelBackground, CosmicPanelConfig, CosmicPanelContainerConfig,
|
||||||
|
CosmicPanelOuput, PanelAnchor, PanelSize,
|
||||||
|
};
|
||||||
|
use cosmic_settings_page::{self as page, section, Section};
|
||||||
|
use std::{borrow::Cow, collections::HashMap};
|
||||||
|
|
||||||
|
pub struct PageInner {
|
||||||
|
pub(crate) config_helper: Option<cosmic_config::Config>,
|
||||||
|
pub(crate) panel_config: Option<CosmicPanelConfig>,
|
||||||
|
pub(crate) container_config: Option<CosmicPanelContainerConfig>,
|
||||||
|
// TODO move these into panel config
|
||||||
|
pub(crate) outputs: HashMap<ObjectId, (String, WlOutput)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PanelPage {
|
||||||
|
fn inner(&self) -> &PageInner;
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut PageInner;
|
||||||
|
|
||||||
|
fn autohide_label(&self) -> String;
|
||||||
|
|
||||||
|
fn gap_label(&self) -> String;
|
||||||
|
|
||||||
|
fn extend_label(&self) -> String;
|
||||||
|
|
||||||
|
fn configure_applets_label(&self) -> String;
|
||||||
|
|
||||||
|
fn applets_page_id(&self) -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn behavior_and_position<
|
||||||
|
P: page::Page<crate::pages::Message> + PanelPage,
|
||||||
|
T: Fn(Message) -> crate::pages::Message + Copy + 'static,
|
||||||
|
>(
|
||||||
|
p: &P,
|
||||||
|
msg_map: T,
|
||||||
|
) -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.title(fl!("panel-behavior-and-position"))
|
||||||
|
.descriptions(vec![
|
||||||
|
p.autohide_label(),
|
||||||
|
fl!("panel-behavior-and-position", "position"),
|
||||||
|
fl!("panel-behavior-and-position", "display"),
|
||||||
|
])
|
||||||
|
.view::<P>(move |_binder, page, section| {
|
||||||
|
let descriptions = §ion.descriptions;
|
||||||
|
let page = page.inner();
|
||||||
|
let Some(panel_config) = page.panel_config.as_ref() else {
|
||||||
|
return Element::from(text(fl!("unknown")));
|
||||||
|
};
|
||||||
|
settings::view_section(§ion.title)
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[0],
|
||||||
|
toggler(None, panel_config.autohide.is_some(), |value| {
|
||||||
|
Message::AutoHidePanel(value)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[1],
|
||||||
|
pick_list(
|
||||||
|
Cow::from(vec![
|
||||||
|
Anchor(PanelAnchor::Top),
|
||||||
|
Anchor(PanelAnchor::Left),
|
||||||
|
Anchor(PanelAnchor::Right),
|
||||||
|
Anchor(PanelAnchor::Bottom),
|
||||||
|
]),
|
||||||
|
Some(Anchor(panel_config.anchor)),
|
||||||
|
|a| Message::PanelAnchor(a.0),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[2],
|
||||||
|
pick_list(
|
||||||
|
Cow::from(
|
||||||
|
Some(fl!("all"))
|
||||||
|
.into_iter()
|
||||||
|
.chain(page.outputs.values().map(|(name, _)| name.clone()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
|
match &panel_config.output {
|
||||||
|
CosmicPanelOuput::All => Some(fl!("all")),
|
||||||
|
CosmicPanelOuput::Active => None,
|
||||||
|
CosmicPanelOuput::Name(ref name) => Some(name.clone()),
|
||||||
|
},
|
||||||
|
Message::Output,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(msg_map)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn style<
|
||||||
|
P: page::Page<crate::pages::Message> + PanelPage,
|
||||||
|
T: Fn(Message) -> crate::pages::Message + Copy + 'static,
|
||||||
|
>(
|
||||||
|
p: &P,
|
||||||
|
msg_map: T,
|
||||||
|
) -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.title(fl!("panel-style"))
|
||||||
|
.descriptions(vec![
|
||||||
|
p.gap_label(),
|
||||||
|
p.extend_label(),
|
||||||
|
fl!("panel-style", "appearance"),
|
||||||
|
fl!("panel-style", "size"),
|
||||||
|
fl!("panel-style", "background-opacity"),
|
||||||
|
])
|
||||||
|
.view::<P>(move |_binder, page, section| {
|
||||||
|
let descriptions = §ion.descriptions;
|
||||||
|
let inner = page.inner();
|
||||||
|
let Some(panel_config) = inner.panel_config.as_ref() else {
|
||||||
|
return Element::from(text(fl!("unknown")));
|
||||||
|
};
|
||||||
|
settings::view_section(§ion.title)
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[0],
|
||||||
|
toggler(None, panel_config.anchor_gap, |value| {
|
||||||
|
Message::AnchorGap(value)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[1],
|
||||||
|
toggler(None, panel_config.expand_to_edges, |value| {
|
||||||
|
Message::ExtendToEdge(value)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[2],
|
||||||
|
pick_list(
|
||||||
|
Cow::from(vec![Appearance::Match, Appearance::Light, Appearance::Dark]),
|
||||||
|
panel_config.background.clone().try_into().ok(),
|
||||||
|
Message::Appearance,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[3],
|
||||||
|
// TODO custom discrete slider variant
|
||||||
|
row![
|
||||||
|
text(fl!("small")),
|
||||||
|
slider(
|
||||||
|
0..=4,
|
||||||
|
match panel_config.size {
|
||||||
|
PanelSize::XS => 0,
|
||||||
|
PanelSize::S => 1,
|
||||||
|
PanelSize::M => 2,
|
||||||
|
PanelSize::L => 3,
|
||||||
|
PanelSize::XL => 4,
|
||||||
|
},
|
||||||
|
|v| {
|
||||||
|
if v == 0 {
|
||||||
|
Message::PanelSize(PanelSize::XS)
|
||||||
|
} else if v == 1 {
|
||||||
|
Message::PanelSize(PanelSize::S)
|
||||||
|
} else if v == 2 {
|
||||||
|
Message::PanelSize(PanelSize::M)
|
||||||
|
} else if v == 3 {
|
||||||
|
Message::PanelSize(PanelSize::L)
|
||||||
|
} else {
|
||||||
|
Message::PanelSize(PanelSize::XL)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
text(fl!("large"))
|
||||||
|
]
|
||||||
|
.spacing(12),
|
||||||
|
))
|
||||||
|
.add(settings::item(
|
||||||
|
&descriptions[4],
|
||||||
|
row![
|
||||||
|
text(fl!("number", HashMap::from_iter(vec![("number", 0)]))),
|
||||||
|
slider(0..=100, (panel_config.opacity * 100.0) as i32, |v| {
|
||||||
|
Message::Opacity(v as f32 / 100.0)
|
||||||
|
},),
|
||||||
|
text(fl!("number", HashMap::from_iter(vec![("number", 100)]))),
|
||||||
|
]
|
||||||
|
.spacing(12),
|
||||||
|
))
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(msg_map)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn configuration<P: page::Page<crate::pages::Message> + PanelPage>(
|
||||||
|
p: &P,
|
||||||
|
) -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.title(fl!("panel-applets"))
|
||||||
|
.descriptions(vec![p.configure_applets_label()])
|
||||||
|
.view::<P>(move |binder, page, section| {
|
||||||
|
let mut settings = settings::view_section(§ion.title);
|
||||||
|
let descriptions = §ion.descriptions;
|
||||||
|
settings = if let Some((panel_applets_entity, _panel_applets_info)) = binder
|
||||||
|
.info
|
||||||
|
.iter()
|
||||||
|
.find(|(_, v)| v.id == page.applets_page_id())
|
||||||
|
{
|
||||||
|
settings.add(
|
||||||
|
settings::item::builder(&descriptions[0])
|
||||||
|
.control(row!(
|
||||||
|
horizontal_space(Length::Fill),
|
||||||
|
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
||||||
|
))
|
||||||
|
.spacing(16)
|
||||||
|
.apply(container)
|
||||||
|
.style(theme::Container::custom(list::column::style))
|
||||||
|
.apply(button)
|
||||||
|
.padding(0)
|
||||||
|
.style(theme::Button::Transparent)
|
||||||
|
.on_press(crate::pages::Message::Page(panel_applets_entity)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
settings
|
||||||
|
};
|
||||||
|
|
||||||
|
Element::from(settings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub(crate) fn add_panel<
|
||||||
|
P: page::Page<crate::pages::Message> + PanelPage,
|
||||||
|
T: Fn(Message) -> crate::pages::Message + Copy + 'static,
|
||||||
|
>(
|
||||||
|
msg_map: T,
|
||||||
|
) -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.title(fl!("panel-missing"))
|
||||||
|
.descriptions(vec![
|
||||||
|
fl!("panel-missing", "desc"),
|
||||||
|
fl!("panel-missing", "fix"),
|
||||||
|
])
|
||||||
|
.view::<P>(move |_binder, page, section| {
|
||||||
|
// _descriptions = §ion.descriptions;
|
||||||
|
settings::view_section(§ion.title)
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(msg_map)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Anchor(PanelAnchor);
|
||||||
|
|
||||||
|
impl ToString for Anchor {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self.0 {
|
||||||
|
PanelAnchor::Top => fl!("panel-top"),
|
||||||
|
PanelAnchor::Bottom => fl!("panel-bottom"),
|
||||||
|
PanelAnchor::Left => fl!("panel-left"),
|
||||||
|
PanelAnchor::Right => fl!("panel-right"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Appearance {
|
||||||
|
Match,
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Appearance {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Appearance::Match => fl!("panel-appearance", "match"),
|
||||||
|
Appearance::Light => fl!("panel-appearance", "light"),
|
||||||
|
Appearance::Dark => fl!("panel-appearance", "dark"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<CosmicPanelBackground> for Appearance {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(value: CosmicPanelBackground) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
CosmicPanelBackground::ThemeDefault => Ok(Appearance::Match),
|
||||||
|
CosmicPanelBackground::Light => Ok(Appearance::Light),
|
||||||
|
CosmicPanelBackground::Dark => Ok(Appearance::Dark),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Appearance> for CosmicPanelBackground {
|
||||||
|
fn from(appearance: Appearance) -> Self {
|
||||||
|
match appearance {
|
||||||
|
Appearance::Match => CosmicPanelBackground::ThemeDefault,
|
||||||
|
Appearance::Light => CosmicPanelBackground::Light,
|
||||||
|
Appearance::Dark => CosmicPanelBackground::Dark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
// panel messages
|
||||||
|
AutoHidePanel(bool),
|
||||||
|
PanelAnchor(PanelAnchor),
|
||||||
|
Output(String),
|
||||||
|
AnchorGap(bool),
|
||||||
|
PanelSize(PanelSize),
|
||||||
|
Appearance(Appearance),
|
||||||
|
ExtendToEdge(bool),
|
||||||
|
Opacity(f32),
|
||||||
|
OutputAdded(String, WlOutput),
|
||||||
|
OutputRemoved(WlOutput),
|
||||||
|
PanelConfig(CosmicPanelConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageInner {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn update(&mut self, message: Message) {
|
||||||
|
let helper = self.config_helper.as_ref().unwrap();
|
||||||
|
let mut panel_config = self.panel_config.as_mut().unwrap();
|
||||||
|
|
||||||
|
match message {
|
||||||
|
Message::AutoHidePanel(enabled) => {
|
||||||
|
if enabled {
|
||||||
|
panel_config.exclusive_zone = false;
|
||||||
|
panel_config.autohide = Some(AutoHide {
|
||||||
|
wait_time: 1000,
|
||||||
|
transition_time: 200,
|
||||||
|
handle_size: 4,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
panel_config.exclusive_zone = true;
|
||||||
|
panel_config.autohide = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::PanelAnchor(anchor) => {
|
||||||
|
panel_config.anchor = anchor;
|
||||||
|
}
|
||||||
|
Message::Output(name) => {
|
||||||
|
panel_config.output = match name {
|
||||||
|
s if s == fl!("all") => CosmicPanelOuput::All,
|
||||||
|
_ => CosmicPanelOuput::Name(name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Message::AnchorGap(enabled) => {
|
||||||
|
panel_config.anchor_gap = enabled;
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
panel_config.margin = 4;
|
||||||
|
} else {
|
||||||
|
panel_config.margin = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::PanelSize(size) => {
|
||||||
|
panel_config.size = size;
|
||||||
|
}
|
||||||
|
Message::Appearance(a) => {
|
||||||
|
panel_config.background = a.into();
|
||||||
|
}
|
||||||
|
Message::ExtendToEdge(enabled) => {
|
||||||
|
panel_config.expand_to_edges = enabled;
|
||||||
|
}
|
||||||
|
Message::Opacity(opacity) => {
|
||||||
|
panel_config.opacity = opacity;
|
||||||
|
}
|
||||||
|
Message::OutputAdded(name, output) => {
|
||||||
|
self.outputs.insert(output.id(), (name, output));
|
||||||
|
}
|
||||||
|
Message::OutputRemoved(output) => {
|
||||||
|
self.outputs.remove(&output.id());
|
||||||
|
}
|
||||||
|
Message::PanelConfig(c) => {
|
||||||
|
self.panel_config = Some(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if panel_config.anchor_gap || !panel_config.expand_to_edges {
|
||||||
|
panel_config.border_radius = 8;
|
||||||
|
} else {
|
||||||
|
panel_config.border_radius = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = panel_config.write_entry(helper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,64 +1,107 @@
|
||||||
use cosmic::{
|
use std::collections::HashMap;
|
||||||
cosmic_config::{self, CosmicConfigEntry},
|
|
||||||
iced::widget::{button, container, horizontal_space, pick_list, row},
|
|
||||||
iced::Length,
|
|
||||||
iced_widget::slider,
|
|
||||||
sctk::reexports::client::{backend::ObjectId, protocol::wl_output::WlOutput, Proxy},
|
|
||||||
theme,
|
|
||||||
widget::{icon, list, settings, text, toggler},
|
|
||||||
Element,
|
|
||||||
};
|
|
||||||
|
|
||||||
use apply::Apply;
|
use cosmic::cosmic_config::CosmicConfigEntry;
|
||||||
use cosmic_panel_config::{
|
use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig};
|
||||||
AutoHide, CosmicPanelBackground, CosmicPanelConfig, CosmicPanelOuput, PanelAnchor, PanelSize,
|
|
||||||
};
|
|
||||||
use cosmic_settings_page::{self as page, section, Section};
|
use cosmic_settings_page::{self as page, section, Section};
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
use std::{borrow::Cow, collections::HashMap};
|
|
||||||
|
|
||||||
pub mod applets;
|
use crate::pages::desktop::panel::inner::{add_panel, behavior_and_position, configuration, style};
|
||||||
|
|
||||||
|
use self::inner::{PageInner, PanelPage};
|
||||||
|
|
||||||
|
pub mod applets_inner;
|
||||||
|
pub mod inner;
|
||||||
|
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
config_helper: Option<cosmic_config::Config>,
|
inner: PageInner,
|
||||||
panel_config: Option<CosmicPanelConfig>,
|
}
|
||||||
// TODO move these into panel config
|
|
||||||
pub outputs: HashMap<ObjectId, (String, WlOutput)>,
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Message(pub inner::Message);
|
||||||
|
|
||||||
|
impl Page {
|
||||||
|
pub fn update(&mut self, message: Message) {
|
||||||
|
self.inner.update(message.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl page::AutoBind<crate::pages::Message> for Page {
|
||||||
|
fn sub_pages(page: page::Insert<crate::pages::Message>) -> page::Insert<crate::pages::Message> {
|
||||||
|
page.sub_page::<applets_inner::Page>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelPage for Page {
|
||||||
|
fn inner(&self) -> &PageInner {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut PageInner {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autohide_label(&self) -> String {
|
||||||
|
fl!("panel-behavior-and-position", "autohide")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gap_label(&self) -> String {
|
||||||
|
fl!("panel-style", "anchor-gap")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_label(&self) -> String {
|
||||||
|
fl!("panel-style", "extend")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_applets_label(&self) -> String {
|
||||||
|
fl!("panel-applets", "desc")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn applets_page_id(&self) -> &'static str {
|
||||||
|
"panel_applets"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Page {
|
impl Default for Page {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// TODO CosmicPanelConfig should return its own version
|
let config_helper = CosmicPanelConfig::cosmic_config("Panel").ok();
|
||||||
let config_helper = cosmic_config::Config::new("com.system76.CosmicPanel.Panel", 1).ok();
|
|
||||||
let panel_config = config_helper.as_ref().and_then(|config_helper| {
|
let panel_config = config_helper.as_ref().and_then(|config_helper| {
|
||||||
// TODO error handling...
|
|
||||||
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?;
|
||||||
// If the config is not present, it will be created with the default values and the name will not match
|
// If the config is not present, it will be created with the default values and the name will not match
|
||||||
(panel_config.name == "Panel").then_some(panel_config)
|
(panel_config.name == "Panel").then_some(panel_config)
|
||||||
});
|
});
|
||||||
|
let container_config = CosmicPanelContainerConfig::load().ok();
|
||||||
Self {
|
Self {
|
||||||
config_helper,
|
inner: PageInner {
|
||||||
panel_config,
|
config_helper,
|
||||||
outputs: HashMap::new(),
|
panel_config,
|
||||||
|
container_config,
|
||||||
|
outputs: HashMap::new(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO cleanup
|
||||||
impl page::Page<crate::pages::Message> for Page {
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn content(
|
fn content(
|
||||||
&self,
|
&self,
|
||||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||||
) -> Option<page::Content> {
|
) -> Option<page::Content> {
|
||||||
Some(if self.panel_config.is_some() {
|
Some(if self.inner.panel_config.is_some() {
|
||||||
vec![
|
vec![
|
||||||
sections.insert(behavior_and_position()),
|
sections.insert(behavior_and_position::<Page, _>(self, |m| {
|
||||||
sections.insert(style()),
|
crate::pages::Message::Panel(Message(m))
|
||||||
sections.insert(configuration()),
|
})),
|
||||||
|
sections.insert(style::<Page, _>(self, |m| {
|
||||||
|
crate::pages::Message::Panel(Message(m))
|
||||||
|
})),
|
||||||
|
sections.insert(configuration::<Page>(self)),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
vec![sections.insert(add_panel())]
|
vec![sections.insert(add_panel::<Page, _>(|m| {
|
||||||
|
crate::pages::Message::Panel(Message(m))
|
||||||
|
}))]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,392 +111,3 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
.description(fl!("panel", "desc"))
|
.description(fl!("panel", "desc"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl page::AutoBind<crate::pages::Message> for Page {
|
|
||||||
fn sub_pages(page: page::Insert<crate::pages::Message>) -> page::Insert<crate::pages::Message> {
|
|
||||||
page.sub_page::<applets::Page>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn behavior_and_position() -> Section<crate::pages::Message> {
|
|
||||||
Section::default()
|
|
||||||
.title(fl!("panel-behavior-and-position"))
|
|
||||||
.descriptions(vec![
|
|
||||||
fl!("panel-behavior-and-position", "autohide"),
|
|
||||||
fl!("panel-behavior-and-position", "position"),
|
|
||||||
fl!("panel-behavior-and-position", "display"),
|
|
||||||
])
|
|
||||||
.view::<Page>(|_binder, page, section| {
|
|
||||||
let descriptions = §ion.descriptions;
|
|
||||||
let Some(panel_config) = page.panel_config.as_ref() else {
|
|
||||||
return Element::from(text(fl!("unknown")));
|
|
||||||
};
|
|
||||||
settings::view_section(§ion.title)
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[0],
|
|
||||||
toggler(None, panel_config.autohide.is_some(), |value| {
|
|
||||||
Message::AutoHidePanel(value)
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[1],
|
|
||||||
pick_list(
|
|
||||||
Cow::from(vec![
|
|
||||||
Anchor(PanelAnchor::Top),
|
|
||||||
Anchor(PanelAnchor::Left),
|
|
||||||
Anchor(PanelAnchor::Right),
|
|
||||||
Anchor(PanelAnchor::Bottom),
|
|
||||||
]),
|
|
||||||
Some(Anchor(panel_config.anchor)),
|
|
||||||
|a| Message::PanelAnchor(a.0),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[2],
|
|
||||||
pick_list(
|
|
||||||
Cow::from(
|
|
||||||
Some(fl!("all"))
|
|
||||||
.into_iter()
|
|
||||||
.chain(page.outputs.values().map(|(name, _)| name.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
),
|
|
||||||
match &panel_config.output {
|
|
||||||
CosmicPanelOuput::All => Some(fl!("all")),
|
|
||||||
CosmicPanelOuput::Active => None,
|
|
||||||
CosmicPanelOuput::Name(ref name) => Some(name.clone()),
|
|
||||||
},
|
|
||||||
Message::Output,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.apply(Element::from)
|
|
||||||
.map(crate::pages::Message::Panel)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style() -> Section<crate::pages::Message> {
|
|
||||||
Section::default()
|
|
||||||
.title(fl!("panel-style"))
|
|
||||||
.descriptions(vec![
|
|
||||||
fl!("panel-style", "anchor-gap"),
|
|
||||||
fl!("panel-style", "extend"),
|
|
||||||
fl!("panel-style", "appearance"),
|
|
||||||
fl!("panel-style", "size"),
|
|
||||||
fl!("panel-style", "background-opacity"),
|
|
||||||
])
|
|
||||||
.view::<Page>(|_binder, page, section| {
|
|
||||||
let descriptions = §ion.descriptions;
|
|
||||||
let Some(panel_config) = page.panel_config.as_ref() else {
|
|
||||||
return Element::from(text(fl!("unknown")));
|
|
||||||
};
|
|
||||||
settings::view_section(§ion.title)
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[0],
|
|
||||||
toggler(None, panel_config.anchor_gap, |value| {
|
|
||||||
Message::AnchorGap(value)
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[1],
|
|
||||||
toggler(None, panel_config.expand_to_edges, |value| {
|
|
||||||
Message::ExtendToEdge(value)
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[2],
|
|
||||||
pick_list(
|
|
||||||
Cow::from(vec![Appearance::Match, Appearance::Light, Appearance::Dark]),
|
|
||||||
panel_config.background.clone().try_into().ok(),
|
|
||||||
Message::Appearance,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[3],
|
|
||||||
// TODO custom discrete slider variant
|
|
||||||
row![
|
|
||||||
text(fl!("small")),
|
|
||||||
slider(
|
|
||||||
0..=4,
|
|
||||||
match panel_config.size {
|
|
||||||
PanelSize::XS => 0,
|
|
||||||
PanelSize::S => 1,
|
|
||||||
PanelSize::M => 2,
|
|
||||||
PanelSize::L => 3,
|
|
||||||
PanelSize::XL => 4,
|
|
||||||
},
|
|
||||||
|v| {
|
|
||||||
if v == 0 {
|
|
||||||
Message::PanelSize(PanelSize::XS)
|
|
||||||
} else if v == 1 {
|
|
||||||
Message::PanelSize(PanelSize::S)
|
|
||||||
} else if v == 2 {
|
|
||||||
Message::PanelSize(PanelSize::M)
|
|
||||||
} else if v == 3 {
|
|
||||||
Message::PanelSize(PanelSize::L)
|
|
||||||
} else {
|
|
||||||
Message::PanelSize(PanelSize::XL)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
text(fl!("large"))
|
|
||||||
]
|
|
||||||
.spacing(12),
|
|
||||||
))
|
|
||||||
.add(settings::item(
|
|
||||||
&descriptions[4],
|
|
||||||
row![
|
|
||||||
text(fl!("number", HashMap::from_iter(vec![("number", 0)]))),
|
|
||||||
slider(0..=100, (panel_config.opacity * 100.0) as i32, |v| {
|
|
||||||
Message::Opacity(v as f32 / 100.0)
|
|
||||||
},),
|
|
||||||
text(fl!("number", HashMap::from_iter(vec![("number", 100)]))),
|
|
||||||
]
|
|
||||||
.spacing(12),
|
|
||||||
))
|
|
||||||
.apply(Element::from)
|
|
||||||
.map(crate::pages::Message::Panel)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configuration() -> Section<crate::pages::Message> {
|
|
||||||
Section::default()
|
|
||||||
.title(fl!("panel-applets"))
|
|
||||||
.descriptions(vec![fl!("panel-applets", "desc")])
|
|
||||||
.view::<Page>(|binder, _page, section| {
|
|
||||||
let mut settings = settings::view_section(§ion.title);
|
|
||||||
settings = if let Some((panel_applets_entity, _panel_applets_info)) =
|
|
||||||
binder.info.iter().find(|(_, v)| v.id == "panel_applets")
|
|
||||||
{
|
|
||||||
settings.add(
|
|
||||||
settings::item::builder(fl!("panel-applets", "desc"))
|
|
||||||
.control(row!(
|
|
||||||
horizontal_space(Length::Fill),
|
|
||||||
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
|
||||||
))
|
|
||||||
.spacing(16)
|
|
||||||
.apply(container)
|
|
||||||
.style(theme::Container::custom(list::column::style))
|
|
||||||
.apply(button)
|
|
||||||
.padding(0)
|
|
||||||
.style(theme::Button::Transparent)
|
|
||||||
.on_press(crate::pages::Message::Page(panel_applets_entity)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
settings
|
|
||||||
};
|
|
||||||
|
|
||||||
Element::from(settings)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub fn panel_dock_links() -> Section<crate::pages::Message> {
|
|
||||||
Section::default()
|
|
||||||
.title(fl!("desktop-panels-and-applets"))
|
|
||||||
.view::<Page>(|binder, _page, section| {
|
|
||||||
// TODO probably a way of getting the entity and its info
|
|
||||||
let mut settings = settings::view_section(§ion.title);
|
|
||||||
settings = if let Some((panel_entity, panel_info)) =
|
|
||||||
binder.info.iter().find(|(_, v)| v.id == "panel")
|
|
||||||
{
|
|
||||||
settings.add(
|
|
||||||
settings::item::builder(panel_info.title.clone())
|
|
||||||
.description(panel_info.description.clone())
|
|
||||||
.control(row!(
|
|
||||||
horizontal_space(Length::Fill),
|
|
||||||
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
|
||||||
))
|
|
||||||
.spacing(16)
|
|
||||||
.apply(container)
|
|
||||||
.style(theme::Container::custom(list::column::style))
|
|
||||||
.apply(button)
|
|
||||||
.padding(0)
|
|
||||||
.style(theme::Button::Transparent)
|
|
||||||
.on_press(crate::pages::Message::Page(panel_entity)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
settings
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = if let Some((dock_entity, dock_info)) =
|
|
||||||
binder.info.iter().find(|(_, v)| v.id == "dock")
|
|
||||||
{
|
|
||||||
settings.add(
|
|
||||||
settings::item::builder(dock_info.title.clone())
|
|
||||||
.description(dock_info.description.clone())
|
|
||||||
.control(row!(
|
|
||||||
horizontal_space(Length::Fill),
|
|
||||||
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
|
||||||
))
|
|
||||||
.spacing(16)
|
|
||||||
.apply(container)
|
|
||||||
.style(theme::Container::custom(list::column::style))
|
|
||||||
.apply(button)
|
|
||||||
.padding(0)
|
|
||||||
.style(theme::Button::Transparent)
|
|
||||||
.on_press(crate::pages::Message::Page(dock_entity)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
settings
|
|
||||||
};
|
|
||||||
|
|
||||||
Element::from(settings)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub fn add_panel() -> Section<crate::pages::Message> {
|
|
||||||
Section::default()
|
|
||||||
.title(fl!("panel-missing"))
|
|
||||||
.descriptions(vec![
|
|
||||||
fl!("panel-missing", "desc"),
|
|
||||||
fl!("panel-missing", "fix"),
|
|
||||||
])
|
|
||||||
.view::<Page>(|_binder, _page, section| {
|
|
||||||
// _descriptions = §ion.descriptions;
|
|
||||||
settings::view_section(§ion.title)
|
|
||||||
.apply(Element::from)
|
|
||||||
.map(crate::pages::Message::Desktop)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Anchor(PanelAnchor);
|
|
||||||
|
|
||||||
impl ToString for Anchor {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self.0 {
|
|
||||||
PanelAnchor::Top => fl!("panel-top"),
|
|
||||||
PanelAnchor::Bottom => fl!("panel-bottom"),
|
|
||||||
PanelAnchor::Left => fl!("panel-left"),
|
|
||||||
PanelAnchor::Right => fl!("panel-right"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Appearance {
|
|
||||||
Match,
|
|
||||||
Light,
|
|
||||||
Dark,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for Appearance {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Appearance::Match => fl!("panel-appearance", "match"),
|
|
||||||
Appearance::Light => fl!("panel-appearance", "light"),
|
|
||||||
Appearance::Dark => fl!("panel-appearance", "dark"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<CosmicPanelBackground> for Appearance {
|
|
||||||
type Error = ();
|
|
||||||
fn try_from(value: CosmicPanelBackground) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
CosmicPanelBackground::ThemeDefault => Ok(Appearance::Match),
|
|
||||||
CosmicPanelBackground::Light => Ok(Appearance::Light),
|
|
||||||
CosmicPanelBackground::Dark => Ok(Appearance::Dark),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Appearance> for CosmicPanelBackground {
|
|
||||||
fn from(appearance: Appearance) -> Self {
|
|
||||||
match appearance {
|
|
||||||
Appearance::Match => CosmicPanelBackground::ThemeDefault,
|
|
||||||
Appearance::Light => CosmicPanelBackground::Light,
|
|
||||||
Appearance::Dark => CosmicPanelBackground::Dark,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Message {
|
|
||||||
// panel messages
|
|
||||||
AutoHidePanel(bool),
|
|
||||||
PanelAnchor(PanelAnchor),
|
|
||||||
Output(String),
|
|
||||||
AnchorGap(bool),
|
|
||||||
PanelSize(PanelSize),
|
|
||||||
Appearance(Appearance),
|
|
||||||
ExtendToEdge(bool),
|
|
||||||
Opacity(f32),
|
|
||||||
Applets,
|
|
||||||
OutputAdded(String, WlOutput),
|
|
||||||
OutputRemoved(WlOutput),
|
|
||||||
PanelConfig(CosmicPanelConfig),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Page {
|
|
||||||
pub fn update(&mut self, message: Message) {
|
|
||||||
let helper = self.config_helper.as_ref().unwrap();
|
|
||||||
let mut panel_config = self.panel_config.as_mut().unwrap();
|
|
||||||
match message {
|
|
||||||
Message::AutoHidePanel(enabled) => {
|
|
||||||
if enabled {
|
|
||||||
panel_config.exclusive_zone = false;
|
|
||||||
panel_config.autohide = Some(AutoHide {
|
|
||||||
wait_time: 1000,
|
|
||||||
transition_time: 200,
|
|
||||||
handle_size: 4,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
panel_config.exclusive_zone = true;
|
|
||||||
panel_config.autohide = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::PanelAnchor(anchor) => {
|
|
||||||
panel_config.anchor = anchor;
|
|
||||||
}
|
|
||||||
Message::Output(name) => {
|
|
||||||
panel_config.output = match name {
|
|
||||||
s if s == fl!("all") => CosmicPanelOuput::All,
|
|
||||||
_ => CosmicPanelOuput::Name(name),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Message::AnchorGap(enabled) => {
|
|
||||||
panel_config.anchor_gap = enabled;
|
|
||||||
|
|
||||||
if enabled {
|
|
||||||
panel_config.margin = 4;
|
|
||||||
} else {
|
|
||||||
panel_config.margin = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Message::PanelSize(size) => {
|
|
||||||
panel_config.size = size;
|
|
||||||
}
|
|
||||||
Message::Appearance(a) => {
|
|
||||||
panel_config.background = a.into();
|
|
||||||
}
|
|
||||||
Message::ExtendToEdge(enabled) => {
|
|
||||||
panel_config.expand_to_edges = enabled;
|
|
||||||
}
|
|
||||||
Message::Opacity(opacity) => {
|
|
||||||
panel_config.opacity = opacity;
|
|
||||||
}
|
|
||||||
Message::Applets => todo!(),
|
|
||||||
|
|
||||||
Message::OutputAdded(name, output) => {
|
|
||||||
self.outputs.insert(output.id(), (name, output));
|
|
||||||
}
|
|
||||||
Message::OutputRemoved(output) => {
|
|
||||||
self.outputs.remove(&output.id());
|
|
||||||
}
|
|
||||||
Message::PanelConfig(c) => {
|
|
||||||
self.panel_config = Some(c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if panel_config.anchor_gap || !panel_config.expand_to_edges {
|
|
||||||
panel_config.border_radius = 8;
|
|
||||||
} else {
|
|
||||||
panel_config.border_radius = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = panel_config.write_entry(helper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ pub enum Message {
|
||||||
DateAndTime(time::date::Message),
|
DateAndTime(time::date::Message),
|
||||||
Desktop(desktop::Message),
|
Desktop(desktop::Message),
|
||||||
Panel(desktop::panel::Message),
|
Panel(desktop::panel::Message),
|
||||||
|
Dock(desktop::dock::Message),
|
||||||
DesktopWallpaper(desktop::wallpaper::Message),
|
DesktopWallpaper(desktop::wallpaper::Message),
|
||||||
Applet(desktop::panel::applets::Message),
|
PanelApplet(desktop::panel::applets_inner::Message),
|
||||||
|
DockApplet(desktop::dock::applets::Message),
|
||||||
Input(input::Message),
|
Input(input::Message),
|
||||||
External { id: String, message: Vec<u8> },
|
External { id: String, message: Vec<u8> },
|
||||||
Page(Entity),
|
Page(Entity),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use cosmic::iced::subscription;
|
use cosmic::{
|
||||||
|
iced::subscription,
|
||||||
|
iced_futures::futures::{self, SinkExt},
|
||||||
|
};
|
||||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
@ -21,19 +24,20 @@ pub enum DesktopFileEvent {
|
||||||
|
|
||||||
pub fn desktop_files<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
pub fn desktop_files<I: 'static + Hash + Copy + Send + Sync + Debug>(
|
||||||
id: I,
|
id: I,
|
||||||
) -> cosmic::iced::Subscription<(I, DesktopFileEvent)> {
|
) -> cosmic::iced::Subscription<DesktopFileEvent> {
|
||||||
subscription::unfold(id, State::Ready, move |mut state| async move {
|
subscription::channel(id, 50, move |mut output| async move {
|
||||||
|
let mut state = State::Ready;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (event, new_state) = start_watching(id, state).await;
|
state = start_watching(state, &mut output).await;
|
||||||
state = new_state;
|
|
||||||
if let Some(event) = event {
|
|
||||||
return (event, state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_watching<I: Copy>(id: I, state: State) -> (Option<(I, DesktopFileEvent)>, State) {
|
async fn start_watching(
|
||||||
|
state: State,
|
||||||
|
output: &mut futures::channel::mpsc::Sender<DesktopFileEvent>,
|
||||||
|
) -> State {
|
||||||
match state {
|
match state {
|
||||||
State::Ready => {
|
State::Ready => {
|
||||||
let paths = freedesktop_desktop_entry::default_paths();
|
let paths = freedesktop_desktop_entry::default_paths();
|
||||||
|
|
@ -42,22 +46,19 @@ async fn start_watching<I: Copy>(id: I, state: State) -> (Option<(I, DesktopFile
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let _ = watcher.watch(path.as_ref(), RecursiveMode::Recursive);
|
let _ = watcher.watch(path.as_ref(), RecursiveMode::Recursive);
|
||||||
}
|
}
|
||||||
(
|
|
||||||
Some((id, DesktopFileEvent::Changed)),
|
_ = output.send(DesktopFileEvent::Changed).await;
|
||||||
State::Waiting { watcher, rx },
|
State::Waiting { watcher, rx }
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(None, State::Finished)
|
State::Finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Waiting { watcher, rx } => {
|
State::Waiting { watcher, rx } => {
|
||||||
if let Some(rx) = async_watch(rx).await {
|
if let Some(rx) = async_watch(rx).await {
|
||||||
(
|
_ = output.send(DesktopFileEvent::Changed).await;
|
||||||
Some((id, DesktopFileEvent::Changed)),
|
State::Waiting { watcher, rx }
|
||||||
State::Waiting { watcher, rx },
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(None, State::Finished)
|
State::Finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Finished => cosmic::iced::futures::future::pending().await,
|
State::Finished => cosmic::iced::futures::future::pending().await,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
];
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
systemdMinimal
|
systemdMinimal
|
||||||
|
bashInteractive
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
freetype
|
freetype
|
||||||
fontconfig
|
fontconfig
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ panel = Panel
|
||||||
|
|
||||||
panel-behavior-and-position = Behavior and Positions
|
panel-behavior-and-position = Behavior and Positions
|
||||||
.autohide = Automatically hide panel
|
.autohide = Automatically hide panel
|
||||||
|
.dock-autohide = Automatically hide dock
|
||||||
.position = Position on screen
|
.position = Position on screen
|
||||||
.display = Show on display
|
.display = Show on display
|
||||||
|
|
||||||
|
|
@ -67,7 +68,9 @@ panel-appearance = Appearance
|
||||||
|
|
||||||
panel-style = Style
|
panel-style = Style
|
||||||
.anchor-gap = Gap between panel and screen edges
|
.anchor-gap = Gap between panel and screen edges
|
||||||
|
.dock-anchor-gap = Gap between dock and screen edges
|
||||||
.extend = Extend panel to screen edges
|
.extend = Extend panel to screen edges
|
||||||
|
.dock-extend = Extend dock to screen edges
|
||||||
.appearance = Appearance
|
.appearance = Appearance
|
||||||
.size = Size
|
.size = Size
|
||||||
.background-opacity = Background opacity
|
.background-opacity = Background opacity
|
||||||
|
|
@ -76,6 +79,7 @@ small = Small
|
||||||
large = Large
|
large = Large
|
||||||
|
|
||||||
panel-applets = Configuration
|
panel-applets = Configuration
|
||||||
|
.dock-desc = Configure dock applets.
|
||||||
.desc = Configure panel applets.
|
.desc = Configure panel applets.
|
||||||
|
|
||||||
panel-missing = Panel Configuration is Missing
|
panel-missing = Panel Configuration is Missing
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue