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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 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]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.13.0" version = "1.13.0"
@ -397,12 +418,15 @@ name = "cosmic-settings"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"apply", "apply",
"bytecheck",
"derive_setters", "derive_setters",
"dirs",
"i18n-embed", "i18n-embed",
"i18n-embed-fl", "i18n-embed-fl",
"libcosmic", "libcosmic",
"once_cell", "once_cell",
"regex", "regex",
"rkyv",
"rust-embed", "rust-embed",
"slotmap", "slotmap",
] ]
@ -2545,6 +2569,26 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" 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]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.23.1" version = "0.23.1"
@ -2719,6 +2763,15 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rend"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95"
dependencies = [
"bytecheck",
]
[[package]] [[package]]
name = "renderdoc-sys" name = "renderdoc-sys"
version = "0.7.1" version = "0.7.1"
@ -2750,6 +2803,31 @@ dependencies = [
"bytemuck", "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]] [[package]]
name = "ron" name = "ron"
version = "0.8.0" version = "0.8.0"
@ -2901,6 +2979,12 @@ dependencies = [
"tiny-skia 0.7.0", "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]] [[package]]
name = "self_cell" name = "self_cell"
version = "0.10.2" version = "0.10.2"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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