feat: refactor the settings page architecture
This commit is contained in:
parent
efdd934e62
commit
c015ad9948
55 changed files with 2212 additions and 1635 deletions
468
app/src/app.rs
Normal file
468
app/src/app.rs
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use apply::Apply;
|
||||
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
|
||||
use cosmic::{
|
||||
iced::widget::{self, column, container, horizontal_space, row},
|
||||
iced::{self, Application, Command, Length, Subscription},
|
||||
iced_native::{subscription, window},
|
||||
iced_winit::window::{close, drag, minimize, toggle_maximize},
|
||||
keyboard_nav,
|
||||
theme::Theme,
|
||||
widget::{
|
||||
header_bar, nav_bar, nav_bar_toggle, scrollable, search, segmented_button, settings,
|
||||
IconSource,
|
||||
},
|
||||
Element, ElementExt,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::{self, Config},
|
||||
pages::{desktop, sound, system, time},
|
||||
widget::{page_title, parent_page_button, search_header, sub_page_button},
|
||||
};
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct SettingsApp {
|
||||
pub active_page: page::Entity,
|
||||
pub config: Config,
|
||||
pub config_path: config::PathManager,
|
||||
pub debug: bool,
|
||||
pub is_condensed: bool,
|
||||
pub nav_bar_toggled_condensed: bool,
|
||||
pub nav_bar_toggled: bool,
|
||||
pub nav_bar: segmented_button::SingleSelectModel,
|
||||
pub pages: page::Binder<crate::pages::Message>,
|
||||
pub scaling_factor: f32,
|
||||
pub search: search::Model,
|
||||
pub search_selections: Vec<(page::Entity, section::Entity)>,
|
||||
pub show_maximize: bool,
|
||||
pub show_minimize: bool,
|
||||
pub theme: Theme,
|
||||
pub title: String,
|
||||
pub window_width: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
Close,
|
||||
Drag,
|
||||
KeyboardNav(keyboard_nav::Message),
|
||||
Maximize,
|
||||
Minimize,
|
||||
NavBar(segmented_button::Entity),
|
||||
None,
|
||||
Page(page::Entity),
|
||||
PageMessage(crate::pages::Message),
|
||||
Search(search::Message),
|
||||
ToggleNavBar,
|
||||
ToggleNavBarCondensed,
|
||||
WindowResize(u32, u32),
|
||||
}
|
||||
|
||||
impl Application for SettingsApp {
|
||||
type Executor = cosmic::executor::single::Executor;
|
||||
type Flags = ();
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
let mut config_path = config::PathManager::new();
|
||||
|
||||
let mut app = SettingsApp {
|
||||
active_page: page::Entity::default(),
|
||||
config: config_path.config("main", Config::deserialize),
|
||||
config_path,
|
||||
debug: false,
|
||||
is_condensed: false,
|
||||
nav_bar: segmented_button::Model::default(),
|
||||
nav_bar_toggled: true,
|
||||
nav_bar_toggled_condensed: false,
|
||||
pages: page::Binder::default(),
|
||||
title: crate::fl!("app"),
|
||||
scaling_factor: std::env::var("COSMIC_SCALE")
|
||||
.ok()
|
||||
.and_then(|scale| scale.parse::<f32>().ok())
|
||||
.unwrap_or(1.0),
|
||||
search: search::Model::default(),
|
||||
search_selections: Vec::default(),
|
||||
show_maximize: true,
|
||||
show_minimize: true,
|
||||
theme: Theme::Dark,
|
||||
window_width: 0,
|
||||
};
|
||||
|
||||
// app.insert_page::<wifi::Page>();
|
||||
// app.insert_page::<networking::Page>();
|
||||
// app.insert_page::<bluetooth::Page>();
|
||||
|
||||
let desktop_id = app.insert_page::<desktop::Page>().id();
|
||||
|
||||
// app.insert_page::<input::Page>();
|
||||
|
||||
// app.insert_page::<displays::Page>();
|
||||
// app.insert_page::<power::Page>();
|
||||
|
||||
app.insert_page::<sound::Page>();
|
||||
|
||||
// app.insert_page::<printers::Page>();
|
||||
// app.insert_page::<privacy::Page>();
|
||||
|
||||
app.insert_page::<system::Page>();
|
||||
app.insert_page::<time::Page>();
|
||||
|
||||
// app.insert_page::<accessibility::Page>();
|
||||
// app.insert_page::<applications::Page>();
|
||||
|
||||
let active_id = app
|
||||
.pages
|
||||
.info
|
||||
.iter()
|
||||
.find(|(_id, info)| info.id == *app.config.active_page)
|
||||
.map_or(desktop_id, |(id, _info)| id);
|
||||
|
||||
let command = app.activate_page(active_id);
|
||||
|
||||
(app, command)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
let window_break = subscription::events_with(|event, _| match event {
|
||||
iced::Event::Window(_window_id, window::Event::Resized { width, height }) => {
|
||||
Some(Message::WindowResize(width, height))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
Subscription::batch(vec![
|
||||
window_break,
|
||||
keyboard_nav::subscription().map(Message::KeyboardNav),
|
||||
])
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||
let mut ret = Command::none();
|
||||
match message {
|
||||
Message::WindowResize(width, _height) => {
|
||||
let break_point = (600.0 * self.scaling_factor) as u32;
|
||||
self.window_width = width;
|
||||
self.is_condensed = self.window_width < break_point;
|
||||
}
|
||||
Message::KeyboardNav(message) => match message {
|
||||
keyboard_nav::Message::Unfocus => ret = keyboard_nav::unfocus(),
|
||||
keyboard_nav::Message::FocusNext => ret = widget::focus_next(),
|
||||
keyboard_nav::Message::FocusPrevious => ret = widget::focus_previous(),
|
||||
keyboard_nav::Message::Escape => {
|
||||
if self.search.is_active() {
|
||||
self.search.state = search::State::Inactive;
|
||||
self.search_clear();
|
||||
}
|
||||
}
|
||||
keyboard_nav::Message::Search => {
|
||||
return self.search.focus();
|
||||
}
|
||||
},
|
||||
Message::Page(page) => return self.activate_page(page),
|
||||
Message::Drag => return drag(window::Id::new(0)),
|
||||
Message::Close => return close(window::Id::new(0)),
|
||||
Message::Minimize => return minimize(window::Id::new(0), true),
|
||||
Message::Maximize => return toggle_maximize(window::Id::new(0)),
|
||||
Message::NavBar(key) => {
|
||||
if let Some(page) = self.nav_bar.data::<page::Entity>(key).copied() {
|
||||
return self.activate_page(page);
|
||||
}
|
||||
}
|
||||
Message::ToggleNavBar => self.nav_bar_toggled = !self.nav_bar_toggled,
|
||||
Message::ToggleNavBarCondensed => {
|
||||
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed;
|
||||
}
|
||||
Message::Search(search::Message::Activate) => {
|
||||
return self.search.focus();
|
||||
}
|
||||
Message::Search(search::Message::Changed(phrase)) => {
|
||||
self.search_changed(phrase);
|
||||
}
|
||||
Message::Search(search::Message::Clear) => {
|
||||
self.search_clear();
|
||||
}
|
||||
Message::None | Message::Search(_) => {}
|
||||
Message::PageMessage(message) => match message {
|
||||
crate::pages::Message::About(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<system::about::Page>() {
|
||||
page.update(message);
|
||||
}
|
||||
}
|
||||
crate::pages::Message::DateAndTime(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<time::date::Page>() {
|
||||
page.update(message);
|
||||
}
|
||||
}
|
||||
crate::pages::Message::Desktop(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<desktop::Page>() {
|
||||
page.update(message);
|
||||
}
|
||||
}
|
||||
crate::pages::Message::External { .. } => {
|
||||
todo!("external plugins not supported yet");
|
||||
}
|
||||
},
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn view(&self) -> Element<Message> {
|
||||
let (nav_bar_message, nav_bar_toggled) = if self.is_condensed {
|
||||
(
|
||||
Message::ToggleNavBarCondensed,
|
||||
self.nav_bar_toggled_condensed,
|
||||
)
|
||||
} else {
|
||||
(Message::ToggleNavBar, self.nav_bar_toggled)
|
||||
};
|
||||
|
||||
let mut header = header_bar()
|
||||
.title("")
|
||||
.on_close(Message::Close)
|
||||
.on_drag(Message::Drag)
|
||||
.start(
|
||||
iced::widget::row!(
|
||||
nav_bar_toggle()
|
||||
.on_nav_bar_toggled(nav_bar_message)
|
||||
.nav_bar_active(nav_bar_toggled),
|
||||
search::search(&self.search, Message::Search)
|
||||
)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.into(),
|
||||
);
|
||||
|
||||
if self.show_maximize {
|
||||
header = header.on_maximize(Message::Maximize);
|
||||
}
|
||||
|
||||
if self.show_minimize {
|
||||
header = header.on_minimize(Message::Minimize);
|
||||
}
|
||||
|
||||
let header = Into::<Element<Message>>::into(header).debug(self.debug);
|
||||
|
||||
let mut widgets = Vec::with_capacity(2);
|
||||
|
||||
if nav_bar_toggled {
|
||||
let mut nav_bar = nav_bar(&self.nav_bar, Message::NavBar);
|
||||
|
||||
if !self.is_condensed {
|
||||
nav_bar = nav_bar.max_width(300);
|
||||
}
|
||||
|
||||
let nav_bar: Element<_> = nav_bar.into();
|
||||
widgets.push(nav_bar.debug(self.debug));
|
||||
}
|
||||
|
||||
if !(self.is_condensed && nav_bar_toggled) {
|
||||
widgets.push(
|
||||
scrollable(row![
|
||||
horizontal_space(Length::Fill),
|
||||
(if self.search.is_active() {
|
||||
self.search_view()
|
||||
} else if let Some(sub_pages) = self.pages.sub_pages(self.active_page) {
|
||||
self.sub_page_view(sub_pages)
|
||||
} else if let Some(content) = self.pages.content(self.active_page) {
|
||||
self.page_view(content)
|
||||
} else {
|
||||
panic!("page without sub-pages or content");
|
||||
})
|
||||
.debug(self.debug),
|
||||
horizontal_space(Length::Fill),
|
||||
])
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let content = container(row(widgets))
|
||||
.padding([0, 8, 8, 8])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into();
|
||||
|
||||
column(vec![header, content]).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
self.theme
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.scaling_factor as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsApp {
|
||||
/// Activates a page.
|
||||
fn activate_page(&mut self, page: page::Entity) -> Command<crate::Message> {
|
||||
self.nav_bar_toggled_condensed = false;
|
||||
let current_page = self.active_page;
|
||||
self.active_page = page;
|
||||
|
||||
if current_page != page {
|
||||
self.config.active_page = Box::from(&*self.pages.info[page].id);
|
||||
self.config_path
|
||||
.config("main", |path| self.config.serialize(path));
|
||||
}
|
||||
|
||||
self.search_clear();
|
||||
self.search.state = search::State::Inactive;
|
||||
self.activate_navbar(page);
|
||||
|
||||
self.pages
|
||||
.page_reload(page)
|
||||
.unwrap_or(Command::none())
|
||||
.map(Message::PageMessage)
|
||||
}
|
||||
|
||||
/// Activates the navbar item associated with a page.
|
||||
fn activate_navbar(&mut self, mut page: page::Entity) {
|
||||
if let Some(parent) = self.pages.info[page].parent {
|
||||
page = parent;
|
||||
}
|
||||
|
||||
if let Some(nav_id) = self.pages.data(page) {
|
||||
self.nav_bar.activate(*nav_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a main page to the settings application.
|
||||
fn insert_page<P: page::AutoBind<crate::pages::Message>>(
|
||||
&mut self,
|
||||
) -> page::Insert<crate::pages::Message> {
|
||||
let id = self.pages.register::<P>().id();
|
||||
self.navbar_insert(id);
|
||||
|
||||
page::Insert {
|
||||
model: &mut self.pages,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
fn navbar_insert(&mut self, id: page::Entity) -> segmented_button::SingleSelectEntityMut {
|
||||
let page = &self.pages.info[id];
|
||||
|
||||
self.nav_bar
|
||||
.insert()
|
||||
.text(page.title.clone())
|
||||
.icon(IconSource::from(page.icon_name.clone()))
|
||||
.data(id)
|
||||
.with_id(|nav_id| self.pages.data_set(id, nav_id))
|
||||
}
|
||||
|
||||
/// Displays the view of a page.
|
||||
fn page_view(&self, content: &[section::Entity]) -> cosmic::Element<Message> {
|
||||
let page = &self.pages.info[self.active_page];
|
||||
let mut column_widgets = Vec::with_capacity(1);
|
||||
|
||||
if let Some(parent) = page.parent {
|
||||
column_widgets.push(parent_page_button(
|
||||
&self.pages.info[parent],
|
||||
page,
|
||||
Message::Page(parent),
|
||||
));
|
||||
}
|
||||
|
||||
column_widgets.reserve_exact(1 + content.len());
|
||||
for id in content.iter().copied() {
|
||||
let section = &self.pages.sections[id];
|
||||
let model = &self.pages.page[self.active_page];
|
||||
|
||||
column_widgets.push(
|
||||
(section.view_fn)(&self.pages, model.as_ref(), section).map(Message::PageMessage),
|
||||
);
|
||||
}
|
||||
|
||||
settings::view_column(column_widgets).into()
|
||||
}
|
||||
|
||||
fn search_changed(&mut self, phrase: String) {
|
||||
// If the text was cleared, clear the search results too.
|
||||
if phrase.is_empty() {
|
||||
self.search_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a case-insensitive regular expression for the search function.
|
||||
let search_expression = regex::RegexBuilder::new(&phrase)
|
||||
.case_insensitive(true)
|
||||
.unicode(true)
|
||||
.ignore_whitespace(true)
|
||||
.size_limit(16 * 1024)
|
||||
.build();
|
||||
|
||||
if let Ok(expression) = search_expression {
|
||||
// With the new search expression, generate new search results.
|
||||
let results: Vec<_> = self.pages.search(&expression).collect();
|
||||
|
||||
// Use the results if results were found.
|
||||
if !results.is_empty() {
|
||||
self.search_selections = results;
|
||||
}
|
||||
}
|
||||
|
||||
self.search.phrase = phrase;
|
||||
}
|
||||
|
||||
/// Clears the search results so that the search page will not be shown.
|
||||
fn search_clear(&mut self) {
|
||||
self.search_selections.clear();
|
||||
self.search.phrase.clear();
|
||||
}
|
||||
|
||||
/// Displays the search view.
|
||||
fn search_view(&self) -> cosmic::Element<Message> {
|
||||
let mut sections: Vec<cosmic::Element<Message>> = Vec::new();
|
||||
|
||||
let mut current_page = page::Entity::default();
|
||||
for (page, section) in self.search_selections.iter().copied() {
|
||||
let section = &self.pages.sections[section];
|
||||
let model = &self.pages.page[page];
|
||||
|
||||
if page != current_page {
|
||||
current_page = page;
|
||||
sections.push(search_header(&self.pages, page));
|
||||
}
|
||||
|
||||
let section = (section.view_fn)(&self.pages, model.as_ref(), section)
|
||||
.map(Message::PageMessage)
|
||||
.apply(iced::widget::container)
|
||||
.padding([0, 0, 0, 48]);
|
||||
|
||||
sections.push(section.into());
|
||||
}
|
||||
|
||||
settings::view_column(sections).into()
|
||||
}
|
||||
|
||||
/// Displays the sub-pages view of a page.
|
||||
fn sub_page_view(&self, sub_pages: &[page::Entity]) -> cosmic::Element<Message> {
|
||||
let page = &self.pages.info[self.active_page];
|
||||
|
||||
let mut column_widgets = Vec::with_capacity(sub_pages.len());
|
||||
column_widgets.push(page_title(page));
|
||||
|
||||
for entity in sub_pages.iter().copied() {
|
||||
let sub_page = &self.pages.info[entity];
|
||||
column_widgets.push(sub_page_button(entity, sub_page));
|
||||
}
|
||||
|
||||
settings::view_column(column_widgets)
|
||||
.apply(Element::from)
|
||||
.map(Message::Page)
|
||||
}
|
||||
}
|
||||
100
app/src/config.rs
Normal file
100
app/src/config.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod path {
|
||||
use std::path::Path;
|
||||
|
||||
#[must_use]
|
||||
|
||||
pub struct Manager {
|
||||
base: String,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
let mut base = dirs::config_dir()
|
||||
.expect("XDG config directory missing")
|
||||
.join("cosmic-settings")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.expect("XDG config path is not UTF-8");
|
||||
|
||||
base.push('/');
|
||||
|
||||
if !Path::new(&base).exists() {
|
||||
let _res = std::fs::create_dir_all(&base);
|
||||
}
|
||||
|
||||
Self { base }
|
||||
}
|
||||
|
||||
pub fn config<T>(&mut self, page: &str, with: impl Fn(&Path) -> T) -> T {
|
||||
let truncate_length = self.base.len();
|
||||
|
||||
self.base.push_str(page);
|
||||
self.base.push_str(".config");
|
||||
|
||||
let output = with(Path::new(&self.base));
|
||||
self.base.truncate(truncate_length);
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use path::Manager as PathManager;
|
||||
|
||||
use bytecheck::CheckBytes;
|
||||
use rkyv::{ser::Serializer, Archive, Deserialize, Serialize};
|
||||
use std::{io::Read, path::Path};
|
||||
|
||||
#[must_use]
|
||||
#[repr(C)]
|
||||
#[derive(Archive, Debug, Deserialize, Serialize)]
|
||||
#[archive_attr(derive(CheckBytes, Debug))]
|
||||
pub struct Config {
|
||||
pub active_page: Box<str>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn deserialize(path: &Path) -> Self {
|
||||
let Ok(mut file) = std::fs::File::open(path) else {
|
||||
return Self::default();
|
||||
};
|
||||
|
||||
let mut buffer = Vec::with_capacity(128);
|
||||
if file.read_to_end(&mut buffer).is_err() {
|
||||
return Self::default();
|
||||
}
|
||||
|
||||
buffer.shrink_to_fit();
|
||||
|
||||
let Ok(archived) = rkyv::check_archived_root::<Self>(buffer.as_slice()) else {
|
||||
return Self::default();
|
||||
};
|
||||
|
||||
archived
|
||||
.deserialize(&mut rkyv::Infallible)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn serialize(&self, path: &Path) {
|
||||
let mut serializer = rkyv::ser::serializers::AllocSerializer::<0>::default();
|
||||
|
||||
if serializer.serialize_value(self).is_err() {
|
||||
return;
|
||||
};
|
||||
|
||||
let bytes = serializer.into_serializer().into_inner();
|
||||
|
||||
let _res = std::fs::write(path, &bytes);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active_page: Box::from("desktop"),
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/src/localize.rs
Normal file
40
app/src/localize.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use i18n_embed::{
|
||||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../i18n/"]
|
||||
struct Localizations;
|
||||
|
||||
pub static LANGUAGE_LOADER: Lazy<FluentLanguageLoader> = Lazy::new(|| {
|
||||
let loader: FluentLanguageLoader = fluent_language_loader!();
|
||||
|
||||
loader
|
||||
.load_fallback_language(&Localizations)
|
||||
.expect("Error while loading fallback language");
|
||||
|
||||
loader
|
||||
});
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fl {
|
||||
($message_id:literal) => {{
|
||||
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
|
||||
}};
|
||||
|
||||
($message_id:literal, $($args:expr),*) => {{
|
||||
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *)
|
||||
}};
|
||||
}
|
||||
|
||||
// Get the `Localizer` to be used for localizing this library.
|
||||
#[must_use]
|
||||
pub fn localizer() -> Box<dyn Localizer> {
|
||||
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
|
||||
}
|
||||
47
app/src/main.rs
Normal file
47
app/src/main.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(clippy::cast_precision_loss)]
|
||||
#![allow(clippy::cast_sign_loss)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(clippy::cast_lossless)]
|
||||
|
||||
pub mod app;
|
||||
pub use app::{Message, SettingsApp};
|
||||
|
||||
pub mod config;
|
||||
|
||||
#[macro_use]
|
||||
pub mod localize;
|
||||
|
||||
pub mod widget;
|
||||
|
||||
pub mod pages;
|
||||
|
||||
use cosmic::iced::Application;
|
||||
use i18n_embed::DesktopLanguageRequester;
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if iced fails to run the application.
|
||||
pub fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
if std::env::var("RUST_SPANTRACE").is_err() {
|
||||
std::env::set_var("RUST_SPANTRACE", "0");
|
||||
}
|
||||
|
||||
let localizer = crate::localize::localizer();
|
||||
let requested_languages = DesktopLanguageRequester::requested_languages();
|
||||
|
||||
if let Err(error) = localizer.select(&requested_languages) {
|
||||
eprintln!("error while loading fluent localizations: {error}");
|
||||
}
|
||||
|
||||
cosmic::settings::set_default_icon_theme("Pop");
|
||||
let mut settings = cosmic::settings();
|
||||
settings.window.min_size = Some((600, 300));
|
||||
SettingsApp::run(settings)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
26
app/src/pages/desktop/appearance.rs
Normal file
26
app/src/pages/desktop/appearance.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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("appearance", "preferences-pop-desktop-appearance-symbolic")
|
||||
.title(fl!("appearance"))
|
||||
.description(fl!("appearance", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
26
app/src/pages/desktop/dock.rs
Normal file
26
app/src/pages/desktop/dock.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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 {}
|
||||
113
app/src/pages/desktop/mod.rs
Normal file
113
app/src/pages/desktop/mod.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod appearance;
|
||||
pub mod dock;
|
||||
pub mod notifications;
|
||||
pub mod options;
|
||||
pub mod wallpaper;
|
||||
pub mod workspaces;
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Page {
|
||||
pub top_left_hot_corner: bool,
|
||||
pub show_workspaces_button: bool,
|
||||
pub show_applications_button: bool,
|
||||
pub show_minimize_button: bool,
|
||||
pub show_maximize_button: bool,
|
||||
pub slideshow: bool,
|
||||
pub same_background: bool,
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("desktop", "video-display-symbolic").title(fl!("desktop"))
|
||||
}
|
||||
}
|
||||
|
||||
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::<options::Page>()
|
||||
.sub_page::<wallpaper::Page>()
|
||||
.sub_page::<appearance::Page>()
|
||||
.sub_page::<dock::Page>()
|
||||
.sub_page::<workspaces::Page>()
|
||||
.sub_page::<notifications::Page>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Message {
|
||||
Slideshow(bool),
|
||||
SameBackground(bool),
|
||||
ShowWorkspacesButton(bool),
|
||||
ShowApplicationsButton(bool),
|
||||
ShowMinimizeButton(bool),
|
||||
ShowMaximizeButton(bool),
|
||||
TopLeftHotCorner(bool),
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::SameBackground(value) => self.same_background = value,
|
||||
Message::ShowApplicationsButton(value) => self.show_applications_button = value,
|
||||
Message::ShowMaximizeButton(value) => self.show_maximize_button = value,
|
||||
Message::ShowMinimizeButton(value) => self.show_minimize_button = value,
|
||||
Message::ShowWorkspacesButton(value) => self.show_workspaces_button = value,
|
||||
Message::Slideshow(value) => self.slideshow = value,
|
||||
Message::TopLeftHotCorner(value) => self.top_left_hot_corner = value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl From<page::Info> for Message {
|
||||
// fn from(page: page::Info) -> Message {
|
||||
// Message::page::Info(page)
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub enum Output {
|
||||
// page::Info(page::Info),
|
||||
// }
|
||||
|
||||
// impl Subpage::Info for Desktoppage::Info {
|
||||
// //TODO: translate
|
||||
// fn title(&self) -> &'static str {
|
||||
// use Desktoppage::Info::*;
|
||||
// match self {
|
||||
// Workspaces => "Workspaces",
|
||||
// Notifications => "Notifications",
|
||||
// }
|
||||
// }
|
||||
|
||||
// //TODO: translate
|
||||
// fn description(&self) -> &'static str {
|
||||
// use Desktoppage::Info::*;
|
||||
// match self {
|
||||
// Workspaces => "Set workspace number, behavior, and placement.",
|
||||
// Notifications => {
|
||||
// "Do Not Disturb, lockscreen notifications, and per-application settings."
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn icon_name(&self) -> &'static str {
|
||||
// use Desktoppage::Info::*;
|
||||
// match self {
|
||||
// Workspaces => "preferences-pop-desktop-workspaces-symbolic",
|
||||
// Notifications => "preferences-system-notifications-symbolic",
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn parent_page(&self) -> page::Info {
|
||||
// page::Info::Desktop(None)
|
||||
// }
|
||||
|
||||
// fn into_page(self) -> page::Info {
|
||||
// page::Info::Desktop(Some(self))
|
||||
// }
|
||||
// }
|
||||
26
app/src/pages/desktop/notifications.rs
Normal file
26
app/src/pages/desktop/notifications.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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("notifications", "preferences-system-notifications-symbolic")
|
||||
.title(fl!("notifications"))
|
||||
.description(fl!("notifications", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
161
app/src/pages/desktop/options.rs
Normal file
161
app/src/pages/desktop/options.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Message;
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::widget::horizontal_space,
|
||||
iced::Length,
|
||||
widget::{settings, toggler},
|
||||
Element,
|
||||
};
|
||||
|
||||
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 {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![
|
||||
sections.insert(super_key_action()),
|
||||
sections.insert(hot_corner()),
|
||||
sections.insert(top_panel()),
|
||||
sections.insert(window_controls()),
|
||||
])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("desktop-options", "video-display-symbolic")
|
||||
.title(fl!("desktop-options"))
|
||||
.description(fl!("desktop-options", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
pub fn hot_corner() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("hot-corner"))
|
||||
.descriptions(vec![fl!("hot-corner", "top-left-corner")])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let desktop = binder
|
||||
.page::<super::Page>()
|
||||
.expect("desktop page not found");
|
||||
|
||||
let descriptions = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(None, desktop.top_left_hot_corner, |value| {
|
||||
Message::TopLeftHotCorner(value)
|
||||
}),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Desktop)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn super_key_action() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("super-key-action"))
|
||||
.descriptions(vec![
|
||||
fl!("super-key-action", "launcher"),
|
||||
fl!("super-key-action", "workspaces"),
|
||||
fl!("super-key-action", "applications"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[2],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn top_panel() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("top-panel"))
|
||||
.descriptions(vec![
|
||||
fl!("top-panel", "workspaces"),
|
||||
fl!("top-panel", "applications"),
|
||||
])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let desktop = binder
|
||||
.page::<super::Page>()
|
||||
.expect("desktop page not found");
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_workspaces_button,
|
||||
Message::ShowWorkspacesButton,
|
||||
),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_applications_button,
|
||||
Message::ShowApplicationsButton,
|
||||
),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Desktop)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn window_controls() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("window-controls"))
|
||||
.descriptions(vec![
|
||||
fl!("window-controls", "minimize"),
|
||||
fl!("window-controls", "maximize"),
|
||||
])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let desktop = binder
|
||||
.page::<super::Page>()
|
||||
.expect("desktop page not found");
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_minimize_button,
|
||||
Message::ShowMinimizeButton,
|
||||
),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_maximize_button,
|
||||
Message::ShowMaximizeButton,
|
||||
),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Desktop)
|
||||
})
|
||||
}
|
||||
98
app/src/pages/desktop/wallpaper.rs
Normal file
98
app/src/pages/desktop/wallpaper.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Message;
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::widget::{column, container, horizontal_space, image, row, svg, text},
|
||||
iced::Length,
|
||||
theme,
|
||||
widget::{list_column, settings, toggler},
|
||||
Element,
|
||||
};
|
||||
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(settings())])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("wallpaper", "preferences-desktop-wallpaper-symbolic")
|
||||
.title(fl!("wallpaper"))
|
||||
.description(fl!("wallpaper", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
pub fn settings() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.descriptions(vec![
|
||||
fl!("wallpaper", "same"),
|
||||
fl!("wallpaper", "fit"),
|
||||
fl!("wallpaper", "slide"),
|
||||
fl!("wallpaper", "change"),
|
||||
])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let desktop = binder
|
||||
.page::<super::Page>()
|
||||
.expect("desktop page not found");
|
||||
let descriptions = §ion.descriptions;
|
||||
let image_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
|
||||
let mut image_column = Vec::with_capacity(image_paths.len() / 4);
|
||||
for chunk in image_paths.chunks(4) {
|
||||
let mut image_row = Vec::with_capacity(chunk.len());
|
||||
for image_path in chunk.iter() {
|
||||
image_row.push(if image_path.ends_with(".svg") {
|
||||
svg(svg::Handle::from_path(image_path))
|
||||
.width(Length::Units(150))
|
||||
.into()
|
||||
} else {
|
||||
image(image_path).width(Length::Units(150)).into()
|
||||
});
|
||||
}
|
||||
image_column.push(row(image_row).spacing(16).into());
|
||||
}
|
||||
|
||||
let children = vec![
|
||||
row!(
|
||||
horizontal_space(Length::Fill),
|
||||
container(
|
||||
image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png")
|
||||
.width(Length::Units(300))
|
||||
)
|
||||
.padding(4)
|
||||
.style(theme::Container::Box),
|
||||
horizontal_space(Length::Fill),
|
||||
)
|
||||
.into(),
|
||||
list_column()
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(None, desktop.same_background, Message::SameBackground),
|
||||
))
|
||||
.add(settings::item(&descriptions[1], text("TODO")))
|
||||
.add(settings::item(
|
||||
&descriptions[2],
|
||||
toggler(None, desktop.slideshow, Message::Slideshow),
|
||||
))
|
||||
.into(),
|
||||
column(image_column).spacing(16).into(),
|
||||
];
|
||||
|
||||
settings::view_column(children)
|
||||
.padding(0)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Desktop)
|
||||
})
|
||||
}
|
||||
76
app/src/pages/desktop/workspaces.rs
Normal file
76
app/src/pages/desktop/workspaces.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::iced::{widget::horizontal_space, Length};
|
||||
use cosmic::widget::settings;
|
||||
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(behavior()),
|
||||
sections.insert(multi_behavior()),
|
||||
])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("workspaces", "preferences-pop-desktop-workspaces-symbolic")
|
||||
.title(fl!("workspaces"))
|
||||
.description(fl!("workspaces", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
fn behavior() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("workspaces-behavior"))
|
||||
.descriptions(vec![
|
||||
fl!("workspaces-behavior", "dynamic"),
|
||||
fl!("workspaces-behavior", "fixed"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn multi_behavior() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("workspaces-multi-behavior"))
|
||||
.descriptions(vec![
|
||||
fl!("workspaces-multi-behavior", "span"),
|
||||
fl!("workspaces-multi-behavior", "separate"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
16
app/src/pages/mod.rs
Normal file
16
app/src/pages/mod.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod desktop;
|
||||
pub mod networking;
|
||||
pub mod sound;
|
||||
pub mod system;
|
||||
pub mod time;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
About(system::about::Message),
|
||||
DateAndTime(time::date::Message),
|
||||
Desktop(desktop::Message),
|
||||
External { id: String, message: Vec<u8> },
|
||||
}
|
||||
10
app/src/pages/networking/accounts.rs
Normal file
10
app/src/pages/networking/accounts.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
pub fn info() -> page::Info {
|
||||
page::Info::new("online-accounts", "goa-panel-symbolic")
|
||||
.title(fl!("online-accounts"))
|
||||
.description(fl!("online-accounts", "desc"))
|
||||
}
|
||||
5
app/src/pages/networking/mod.rs
Normal file
5
app/src/pages/networking/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod accounts;
|
||||
pub mod wired;
|
||||
10
app/src/pages/networking/wired.rs
Normal file
10
app/src/pages/networking/wired.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
pub fn info() -> page::Info {
|
||||
page::Info::new("wired", "network-workgroup-symbolic")
|
||||
.title(fl!("wired"))
|
||||
.description(fl!("wired", "desc"))
|
||||
}
|
||||
124
app/src/pages/sound.rs
Normal file
124
app/src/pages/sound.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{iced, widget::settings};
|
||||
use cosmic_settings_page::{self as page, section, 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(output()),
|
||||
sections.insert(input()),
|
||||
sections.insert(alerts()),
|
||||
sections.insert(applications()),
|
||||
])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("sound", "multimedia-volume-control-symbolic")
|
||||
.title(fl!("sound"))
|
||||
.description(fl!("sound", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
fn alerts() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("sound-alerts"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-alerts", "volume"),
|
||||
fl!("sound-alerts", "sound"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn applications() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("sound-applications"))
|
||||
.descriptions(vec![fl!("sound-applications", "desc")])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn input() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("sound-input"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-input", "volume"),
|
||||
fl!("sound-input", "device"),
|
||||
fl!("sound-input", "level"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[2],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn output() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("sound-output"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-output", "volume"),
|
||||
fl!("sound-output", "device"),
|
||||
fl!("sound-output", "level"),
|
||||
fl!("sound-output", "config"),
|
||||
fl!("sound-output", "balance"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[2],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[3],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
158
app/src/pages/system/about.rs
Normal file
158
app/src/pages/system/about.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
|
||||
use cosmic::{
|
||||
iced::{
|
||||
widget::{horizontal_space, row},
|
||||
Length,
|
||||
},
|
||||
widget::{icon, list_column, settings, text},
|
||||
};
|
||||
use cosmic_settings_system::about::Info;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
Info(Box<Info>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Page {
|
||||
info: Info,
|
||||
// support_page: page::Entity,
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for 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(distributor_logo()),
|
||||
sections.insert(device()),
|
||||
sections.insert(hardware()),
|
||||
sections.insert(os()),
|
||||
sections.insert(related()),
|
||||
])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("about", "help-about-symbolic")
|
||||
.title(fl!("about"))
|
||||
.description(fl!("about", "desc"))
|
||||
}
|
||||
|
||||
fn load(&self, _page: page::Entity) -> Option<page::Task<crate::pages::Message>> {
|
||||
Some(Box::pin(async move {
|
||||
crate::pages::Message::About(Message::Info(Box::new(Info::load())))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Info(info) => self.info = *info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.descriptions(vec![fl!("about-device"), fl!("about-device", "desc")])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
let desc = §ion.descriptions;
|
||||
let device_name = settings::item::builder(&desc[0])
|
||||
.description(&desc[1])
|
||||
.control(text(&page.info.device_name));
|
||||
|
||||
list_column().add(device_name).into()
|
||||
})
|
||||
}
|
||||
|
||||
fn distributor_logo() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.search_ignore()
|
||||
.view::<Page>(|_binder, _page, _section| {
|
||||
row!(
|
||||
horizontal_space(Length::Fill),
|
||||
icon("distributor-logo", 78),
|
||||
horizontal_space(Length::Fill),
|
||||
)
|
||||
// Add extra padding to reach 40px from the first section.
|
||||
.padding([0, 16, 0, 16])
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn hardware() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("about-hardware"))
|
||||
.descriptions(vec![
|
||||
fl!("about-hardware", "model"),
|
||||
fl!("about-hardware", "memory"),
|
||||
fl!("about-hardware", "processor"),
|
||||
fl!("about-hardware", "graphics"),
|
||||
fl!("about-hardware", "disk-capacity"),
|
||||
])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
let desc = §ion.descriptions;
|
||||
|
||||
let mut sections = settings::view_section(§ion.title)
|
||||
.add(settings::item(&desc[0], text(&page.info.hardware_model)))
|
||||
.add(settings::item(&desc[1], text(&page.info.memory)))
|
||||
.add(settings::item(&desc[2], text(&page.info.processor)));
|
||||
|
||||
for card in &page.info.graphics {
|
||||
sections = sections.add(settings::item(&desc[3], text(card.as_str())));
|
||||
}
|
||||
|
||||
sections
|
||||
.add(settings::item(&desc[4], text(&page.info.disk_capacity)))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn os() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("about-os"))
|
||||
.descriptions(vec![
|
||||
fl!("about-os", "os"),
|
||||
fl!("about-os", "os-architecture"),
|
||||
fl!("about-os", "desktop-environment"),
|
||||
fl!("about-os", "windowing-system"),
|
||||
])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
let desc = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(&desc[0], text(&page.info.operating_system)))
|
||||
.add(settings::item(&desc[1], text(&page.info.os_architecture)))
|
||||
.add(settings::item(
|
||||
&desc[2],
|
||||
text(&page.info.desktop_environment),
|
||||
))
|
||||
.add(settings::item(&desc[3], text(&page.info.windowing_system)))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn related() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("about-related"))
|
||||
.descriptions(vec![fl!("about-related", "support")])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(§ion.descriptions[0], text("TODO")))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
// fn page(app: &crate::SettingsApp) -> &Page {
|
||||
// app.pages
|
||||
// .resource::<Page>()
|
||||
// .expect("missing system->about page")
|
||||
// }
|
||||
26
app/src/pages/system/firmware.rs
Normal file
26
app/src/pages/system/firmware.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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("firmware", "firmware-manager-symbolic")
|
||||
.title(fl!("firmware"))
|
||||
.description(fl!("firmware", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
25
app/src/pages/system/mod.rs
Normal file
25
app/src/pages/system/mod.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod about;
|
||||
pub mod firmware;
|
||||
pub mod users;
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("system", "system-users-symbolic").title(fl!("system"))
|
||||
}
|
||||
}
|
||||
|
||||
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::<users::Page>()
|
||||
.sub_page::<about::Page>()
|
||||
.sub_page::<firmware::Page>()
|
||||
}
|
||||
}
|
||||
26
app/src/pages/system/users.rs
Normal file
26
app/src/pages/system/users.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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("users", "system-users-symbolic")
|
||||
.title(fl!("users"))
|
||||
.description(fl!("users", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
132
app/src/pages/time/date.rs
Normal file
132
app/src/pages/time/date.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::{widget::horizontal_space, Length},
|
||||
widget::settings,
|
||||
};
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
// use icu::calendar::{DateTime, Gregorian};
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Page {
|
||||
auto: bool,
|
||||
auto_timezone: bool,
|
||||
military_time: bool,
|
||||
// info: Option<cosmic_settings_time::Info>,
|
||||
}
|
||||
|
||||
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(date()),
|
||||
sections.insert(timezone()),
|
||||
sections.insert(format()),
|
||||
])
|
||||
}
|
||||
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("time-date", "preferences-system-time-symbolic")
|
||||
.title(fl!("time-date"))
|
||||
.description(fl!("time-date", "desc"))
|
||||
}
|
||||
|
||||
fn load(&self, _page: page::Entity) -> Option<page::Task<crate::pages::Message>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Automatic(enable) => self.auto = enable,
|
||||
Message::AutomaticTimezone(enable) => self.auto_timezone = enable,
|
||||
Message::MilitaryTime(enable) => self.military_time = enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Message {
|
||||
Automatic(bool),
|
||||
AutomaticTimezone(bool),
|
||||
MilitaryTime(bool),
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
fn date() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("time-date"))
|
||||
.descriptions(vec![fl!("time-date", "auto"), fl!("time-date")])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.toggler(page.auto, Message::Automatic),
|
||||
)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::pages::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
|
||||
fn format() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("time-format"))
|
||||
.descriptions(vec![
|
||||
fl!("time-format", "twenty-four"),
|
||||
fl!("time-format", "first"),
|
||||
])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
// 24-hour toggle
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.toggler(page.military_time, Message::MilitaryTime),
|
||||
)
|
||||
// First day of week
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::pages::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
|
||||
fn timezone() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("time-zone"))
|
||||
.descriptions(vec![
|
||||
fl!("time-zone", "auto"),
|
||||
fl!("time-zone", "auto-info"),
|
||||
fl!("time-zone"),
|
||||
])
|
||||
.view::<Page>(|_binder, page, section| {
|
||||
settings::view_section(§ion.title)
|
||||
// Automatic timezone toggle
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.description(§ion.descriptions[1])
|
||||
.toggler(page.auto_timezone, Message::AutomaticTimezone),
|
||||
)
|
||||
// Time zone select
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[2])
|
||||
.control(horizontal_space(Length::Fill)),
|
||||
)
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::pages::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
24
app/src/pages/time/mod.rs
Normal file
24
app/src/pages/time/mod.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
pub mod date;
|
||||
pub mod region;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("time", "preferences-system-time-symbolic")
|
||||
.title(fl!("time"))
|
||||
.description(fl!("time", "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::<date::Page>().sub_page::<region::Page>()
|
||||
}
|
||||
}
|
||||
26
app/src/pages/time/region.rs
Normal file
26
app/src/pages/time/region.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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("time-region", "preferences-desktop-locale-symbolic")
|
||||
.title(fl!("time-region"))
|
||||
.description(fl!("time-region", "desc"))
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
114
app/src/widget/mod.rs
Normal file
114
app/src/widget/mod.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use apply::Apply;
|
||||
use cosmic::iced::{
|
||||
self,
|
||||
widget::{button, column, container, horizontal_space, row, vertical_space, Button},
|
||||
Length,
|
||||
};
|
||||
use cosmic::widget::{divider, icon, list, settings, text};
|
||||
use cosmic::{theme, Element};
|
||||
use cosmic_settings_page as page;
|
||||
|
||||
#[must_use]
|
||||
pub fn search_header<Message>(
|
||||
pages: &page::Binder<Message>,
|
||||
page: page::Entity,
|
||||
) -> cosmic::Element<crate::Message> {
|
||||
let page_meta = &pages.info[page];
|
||||
|
||||
let mut column_children = Vec::with_capacity(4);
|
||||
|
||||
if let Some(parent) = page_meta.parent {
|
||||
let parent_meta = &pages.info[parent];
|
||||
|
||||
column_children.push(
|
||||
text(parent_meta.title.as_str())
|
||||
.size(16)
|
||||
.apply(container)
|
||||
.padding([0, 0, 0, 6])
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
column_children.push(
|
||||
crate::widget::search_page_link(&page_meta.title)
|
||||
.on_press(crate::Message::Page(page))
|
||||
.into(),
|
||||
);
|
||||
|
||||
column_children.push(vertical_space(Length::Units(8)).into());
|
||||
column_children.push(divider::horizontal::heavy().into());
|
||||
|
||||
column(column_children).into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn search_page_link<Message: 'static>(title: &str) -> Button<Message, cosmic::Renderer> {
|
||||
text(title)
|
||||
.size(32)
|
||||
.horizontal_alignment(iced::alignment::Horizontal::Left)
|
||||
.apply(button)
|
||||
.style(cosmic::theme::Button::Link)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn page_title<Message: 'static>(page: &page::Info) -> Element<Message> {
|
||||
row!(
|
||||
text(page.title.as_str()).size(32),
|
||||
horizontal_space(Length::Fill)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parent_page_button<'a, Message: Clone + 'static>(
|
||||
parent: &'a page::Info,
|
||||
sub_page: &'a page::Info,
|
||||
on_press: Message,
|
||||
) -> Element<'a, Message> {
|
||||
column!(
|
||||
button(row!(
|
||||
icon("go-previous-symbolic", 20).style(theme::Svg::SymbolicLink),
|
||||
text(parent.title.as_str()).size(20),
|
||||
))
|
||||
.padding(0)
|
||||
.style(theme::Button::Link)
|
||||
.on_press(on_press),
|
||||
row!(
|
||||
text(sub_page.title.as_str()).size(32),
|
||||
horizontal_space(Length::Fill),
|
||||
)
|
||||
.align_items(iced::alignment::Alignment::Center),
|
||||
)
|
||||
.spacing(6)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sub_page_button(entity: page::Entity, page: &page::Info) -> Element<page::Entity> {
|
||||
settings::item::builder(page.title.as_str())
|
||||
.description(page.description.as_str())
|
||||
.icon(icon(&*page.icon_name, 20).style(theme::Svg::Symbolic))
|
||||
.control(row!(
|
||||
horizontal_space(Length::Fill),
|
||||
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
||||
))
|
||||
.spacing(16)
|
||||
.apply(container)
|
||||
.padding([20, 24])
|
||||
.style(theme::Container::Custom(list::column::style))
|
||||
.apply(button)
|
||||
.padding(0)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(entity)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn unimplemented_page<Message: 'static>() -> Element<'static, Message> {
|
||||
settings::view_section("")
|
||||
.add(text("We haven't created that panel yet, and/or it is using a similar idea as current Pop! designs."))
|
||||
.into()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue