mod appearance;
#[cfg(all(
feature = "package",
not(any(target_os = "ios", target_os = "android"))
))]
mod cli;
mod launcher;
mod layout;
mod rail;
mod scope;
pub use appearance::{AppearanceChange, AppearanceCtx, AppearancePage, AppearanceScope};
#[cfg(all(
feature = "package",
not(any(target_os = "ios", target_os = "android"))
))]
pub use cli::CliRow;
pub use launcher::LauncherRow;
pub use layout::{ListItem, SettingsLayout};
pub use rail::RailPage;
pub use scope::{ScopeTab, scope_tabs};
pub trait SettingsSection {
fn label(&self, ctx: &egui::Context) -> String;
fn ui(&mut self, ui: &mut egui::Ui);
fn dirty(&self) -> bool {
false
}
}
enum Heading {
None,
Message(&'static str),
Text(String),
}
pub struct SettingsGroup {
heading: Heading,
sections: Vec<Box<dyn SettingsSection>>,
}
impl SettingsGroup {
pub fn new(sections: Vec<Box<dyn SettingsSection>>) -> SettingsGroup {
SettingsGroup {
heading: Heading::None,
sections,
}
}
pub fn titled(mut self, id: &'static str) -> SettingsGroup {
self.heading = Heading::Message(id);
self
}
pub fn named(mut self, name: impl Into<String>) -> SettingsGroup {
self.heading = Heading::Text(name.into());
self
}
}
#[derive(Default)]
pub struct Settings {
groups: Vec<SettingsGroup>,
layout: SettingsLayout<(usize, usize)>,
}
impl Settings {
pub fn new() -> Settings {
Settings::default()
}
pub fn with_group(mut self, group: SettingsGroup) -> Settings {
self.groups.push(group);
self
}
pub fn push_group(&mut self, group: SettingsGroup) {
self.groups.push(group);
}
pub fn open(&mut self, group: usize, section: usize) {
self.layout.open(Some((group, section)));
}
pub fn current(&self) -> Option<(usize, usize)> {
self.layout.current().copied()
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
let items = self.items(ui.ctx());
let Settings { groups, layout } = self;
layout.ui(ui, &items, |ui, (g, s)| {
if let Some(section) = groups.get_mut(g).and_then(|g| g.sections.get_mut(s)) {
section.ui(ui);
}
});
}
fn items(&self, ctx: &egui::Context) -> Vec<ListItem<(usize, usize)>> {
let mut out = Vec::new();
for (g, group) in self.groups.iter().enumerate() {
match &group.heading {
Heading::None if g > 0 => out.push(ListItem::Gap),
Heading::None => {}
Heading::Message(id) => out.push(ListItem::Heading(crate::i18n::tr(ctx, id))),
Heading::Text(text) => out.push(ListItem::Heading(text.clone())),
}
for (s, section) in group.sections.iter().enumerate() {
out.push(ListItem::Entry {
page: (g, s),
label: section.label(ctx),
dirty: section.dirty(),
});
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::render;
struct Named(&'static str, bool);
impl SettingsSection for Named {
fn label(&self, _ctx: &egui::Context) -> String {
self.0.to_owned()
}
fn ui(&mut self, ui: &mut egui::Ui) {
ui.label(self.0);
}
fn dirty(&self) -> bool {
self.1
}
}
fn settings() -> Settings {
Settings::new()
.with_group(
SettingsGroup::new(vec![
Box::new(Named("Look", false)),
Box::new(Named("Sync", true)),
])
.named("Example"),
)
.with_group(
SettingsGroup::new(vec![Box::new(Named("Appearance", false))])
.titled("corundum-settings"),
)
.with_group(SettingsGroup::new(vec![Box::new(Named("About", false))]))
}
fn labels(settings: &Settings) -> Vec<String> {
let ctx = egui::Context::default();
crate::i18n::install(
&ctx,
&crate::i18n::LanguageSettings {
language: crate::i18n::Language::Explicit("en".into()),
},
);
settings
.items(&ctx)
.into_iter()
.map(|item| match item {
ListItem::Heading(h) => format!("# {h}"),
ListItem::Entry { label, dirty, .. } => layout::marked(&label, dirty),
ListItem::Gap => "-".into(),
})
.collect()
}
#[test]
fn groups_are_listed_in_order() {
let mut settings = settings();
settings
.push_group(SettingsGroup::new(vec![Box::new(Named("Later", false))]).named("Late"));
let labels = labels(&settings);
assert_eq!(
labels,
[
"# Example",
"Look",
"Sync •",
"# Settings",
"Appearance",
"-",
"About",
"# Late",
"Later",
]
);
}
#[test]
fn wide_it_opens_the_first_page() {
let mut settings = settings();
render([900.0, 600.0], |ui| settings.ui(ui));
assert_eq!(settings.current(), Some((0, 0)));
}
#[test]
fn compact_it_shows_the_list_then_the_page_opened() {
let mut settings = settings();
render([400.0, 700.0], |ui| settings.ui(ui));
assert_eq!(settings.current(), None);
settings.open(2, 0);
render([400.0, 700.0], |ui| settings.ui(ui));
assert_eq!(settings.current(), Some((2, 0)));
}
}