feat: memorize last active page between instances

This commit is contained in:
Michael Aaron Murphy 2023-01-27 03:10:54 +01:00
parent 2709dcfee5
commit 9d3c3405a2
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
23 changed files with 252 additions and 87 deletions

84
Cargo.lock generated
View file

@ -179,6 +179,27 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytecheck"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f"
dependencies = [
"bytecheck_derive",
"ptr_meta",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bytemuck"
version = "1.13.0"
@ -397,12 +418,15 @@ name = "cosmic-settings"
version = "0.1.0"
dependencies = [
"apply",
"bytecheck",
"derive_setters",
"dirs",
"i18n-embed",
"i18n-embed-fl",
"libcosmic",
"once_cell",
"regex",
"rkyv",
"rust-embed",
"slotmap",
]
@ -2545,6 +2569,26 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df"
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quick-xml"
version = "0.23.1"
@ -2719,6 +2763,15 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rend"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95"
dependencies = [
"bytecheck",
]
[[package]]
name = "renderdoc-sys"
version = "0.7.1"
@ -2750,6 +2803,31 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "rkyv"
version = "0.7.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15"
dependencies = [
"bytecheck",
"hashbrown",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
]
[[package]]
name = "rkyv_derive"
version = "0.7.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ron"
version = "0.8.0"
@ -2901,6 +2979,12 @@ dependencies = [
"tiny-skia 0.7.0",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "self_cell"
version = "0.10.2"

View file

@ -13,6 +13,9 @@ once_cell = "1.17.0"
regex = "1.7.1"
rust-embed = "6.4.2"
slotmap = "1.0.6"
dirs = "4.0.0"
rkyv = { version = "0.7.39", features = ["validation"]}
bytecheck = "0.6.9"
[dependencies.i18n-embed]
version = "0.13.8"
@ -22,4 +25,4 @@ features = ["fluent-system", "desktop-requester"]
git = "https://github.com/pop-os/libcosmic"
branch = "settings-improv"
default-features = false
features = ["debug", "winit", "dyrend"]
features = ["debug", "winit", "dyrend"]

View file

@ -18,6 +18,7 @@ use cosmic::{
};
use crate::{
config::{self, Config},
page::{self, desktop, section, sound, system, time, Page},
widget::{page_title, parent_page_button, search_header, sub_page_button},
};
@ -27,6 +28,7 @@ use crate::{
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,
@ -43,11 +45,6 @@ pub struct SettingsApp {
pub window_width: u32,
}
#[derive(Debug, Default)]
pub struct Config {
nav_page: &'static str,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum Message {
@ -73,11 +70,12 @@ impl Application for SettingsApp {
type Theme = Theme;
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let mut config_path = config::PathManager::new();
let mut app = SettingsApp {
active_page: page::Entity::default(),
config: Config {
nav_page: "desktop",
},
config: config_path.config("main", Config::deserialize),
config_path,
debug: false,
is_condensed: false,
nav_bar: segmented_button::Model::default(),
@ -119,7 +117,12 @@ impl Application for SettingsApp {
// app.insert_page::<accessibility::Page>();
// app.insert_page::<applications::Page>();
app.activate_navbar(app.active_page);
for (id, info) in app.pages.pages.iter() {
if info.id == &*app.config.active_page {
app.activate_page(id);
break;
}
}
(app, Command::none())
}
@ -299,8 +302,15 @@ impl SettingsApp {
/// Activates a page.
fn activate_page(&mut self, page: page::Entity) {
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.pages[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);
@ -320,13 +330,8 @@ impl SettingsApp {
/// Adds a main page to the settings application.
fn insert_page<P: Page>(&mut self) -> page::Insert {
let id = self.pages.register::<P>().id();
self.navbar_insert(id);
if P::PERSISTENT_ID == self.config.nav_page {
self.active_page = id;
}
page::Insert {
model: &mut self.pages,
id,

100
src/config.rs Normal file
View 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"),
}
}
}

View file

@ -9,6 +9,8 @@
pub mod app;
pub use app::{Message, SettingsApp};
pub mod config;
#[macro_use]
pub mod localize;

View file

@ -9,13 +9,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "appearance";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("appearance", "preferences-pop-desktop-appearance-symbolic")
.title(fl!("appearance"))
.description(fl!("appearance", "desc"))
.icon_name("preferences-pop-desktop-appearance-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -10,13 +10,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "dock";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("dock", "preferences-pop-desktop-dock-symbolic")
.title(fl!("dock"))
.description(fl!("dock", "desc"))
.icon_name("preferences-pop-desktop-dock-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -15,12 +15,8 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "desktop";
fn page() -> page::Meta {
page::Meta::default()
.title(fl!("desktop"))
.icon_name("video-display-symbolic")
page::Meta::new("desktop", "video-display-symbolic").title(fl!("desktop"))
}
fn sub_pages(page: page::Insert) -> page::Insert {

View file

@ -10,13 +10,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "notifications";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("notifications", "preferences-system-notifications-symbolic")
.title(fl!("notifications"))
.description(fl!("notifications", "desc"))
.icon_name("preferences-system-notifications-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -19,13 +19,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "desktop-options";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("desktop-options", "video-display-symbolic")
.title(fl!("desktop-options"))
.description(fl!("desktop-options", "desc"))
.icon_name("video-display-symbolic")
}
#[allow(clippy::too_many_lines)]

View file

@ -19,13 +19,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "wallpaper";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("wallpaper", "preferences-desktop-wallpaper-symbolic")
.title(fl!("wallpaper"))
.description(fl!("wallpaper", "desc"))
.icon_name("preferences-desktop-wallpaper-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -12,13 +12,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "workspaces";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("workspaces", "preferences-pop-desktop-workspaces-symbolic")
.title(fl!("workspaces"))
.description(fl!("workspaces", "desc"))
.icon_name("preferences-pop-desktop-workspaces-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -24,9 +24,6 @@ slotmap::new_key_type! {
pub trait Page {
type Model: Default + 'static;
/// A unique identity that is the same between application runs.
const PERSISTENT_ID: &'static str;
fn page() -> Meta;
#[must_use]
@ -41,17 +38,40 @@ pub trait Page {
}
}
#[derive(Default, Setters)]
#[derive(Setters)]
#[must_use]
pub struct Meta {
/// A unique identity that is the same between application runs.
#[setters(skip)]
pub id: &'static str,
/// The icon associated with the page.
#[setters(skip)]
pub icon_name: &'static str,
/// The title of the page.
#[setters(into)]
pub title: String,
#[setters(into)]
pub icon_name: &'static str,
/// A description of the page.
#[setters(into)]
pub description: String,
/// The parent of the page.
#[setters(strip_option)]
pub parent: Option<Entity>,
}
impl Meta {
pub const fn new(id: &'static str, icon_name: &'static str) -> Self {
Self {
title: String::new(),
icon_name,
id,
description: String::new(),
parent: None,
}
}
}
pub type Content = Vec<section::Entity>;

View file

@ -4,8 +4,7 @@
use crate::page;
pub fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("online-accounts", "goa-panel-symbolic")
.title(fl!("online-accounts"))
.description(fl!("online-accounts", "desc"))
.icon_name("goa-panel-symbolic")
}

View file

@ -4,8 +4,7 @@
use crate::page;
pub fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("wired", "network-workgroup-symbolic")
.title(fl!("wired"))
.description(fl!("wired", "desc"))
.icon_name("network-workgroup-symbolic")
}

View file

@ -15,13 +15,10 @@ pub struct Page;
impl page::Page for Page {
type Model = Sound;
const PERSISTENT_ID: &'static str = "sound";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("sound", "multimedia-volume-control-symbolic")
.title(fl!("sound"))
.description(fl!("sound", "desc"))
.icon_name("multimedia-volume-control-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -16,13 +16,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "about";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("about", "help-about-symbolic")
.title(fl!("about"))
.description(fl!("about", "desc"))
.icon_name("help-about-symbolic")
}
fn content(sections: &mut SlotMap<page::section::Entity, Section>) -> Option<Content> {

View file

@ -10,13 +10,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "firmware";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("firmware", "firmware-manager-symbolic")
.title(fl!("firmware"))
.description(fl!("firmware", "desc"))
.icon_name("firmware-manager-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -12,12 +12,8 @@ pub struct Page;
impl page::Page for Page {
type Model = Model;
const PERSISTENT_ID: &'static str = "system";
fn page() -> page::Meta {
page::Meta::default()
.title(fl!("system"))
.icon_name("system-users-symbolic")
page::Meta::new("system", "system-users-symbolic").title(fl!("system"))
}
fn sub_pages(page: page::Insert) -> page::Insert {

View file

@ -10,13 +10,10 @@ pub struct Page;
impl page::Page for Page {
type Model = super::Model;
const PERSISTENT_ID: &'static str = "users";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("users", "system-users-symbolic")
.title(fl!("users"))
.description(fl!("users", "desc"))
.icon_name("system-users-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -38,13 +38,10 @@ pub struct Page;
impl page::Page for Page {
type Model = Model;
const PERSISTENT_ID: &'static str = "time-date";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("time-date", "preferences-system-time-symbolic")
.title(fl!("time-date"))
.description(fl!("time-date", "desc"))
.icon_name("preferences-system-time-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {

View file

@ -11,13 +11,10 @@ pub struct Page;
impl page::Page for Page {
type Model = ();
const PERSISTENT_ID: &'static str = "time";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("time", "preferences-system-time-symbolic")
.title(fl!("time"))
.description(fl!("time", "desc"))
.icon_name("preferences-system-time-symbolic")
}
fn sub_pages(page: page::Insert) -> page::Insert {

View file

@ -9,13 +9,10 @@ pub struct Page;
impl page::Page for Page {
type Model = ();
const PERSISTENT_ID: &'static str = "time-region";
fn page() -> page::Meta {
page::Meta::default()
page::Meta::new("time-region", "preferences-desktop-locale-symbolic")
.title(fl!("time-region"))
.description(fl!("time-region", "desc"))
.icon_name("preferences-desktop-locale-symbolic")
}
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {