129 lines
3.3 KiB
Rust
129 lines
3.3 KiB
Rust
// Copyright 2023 System76 <info@system76.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use derive_setters::Setters;
|
|
use regex::Regex;
|
|
use slab::Slab;
|
|
|
|
use crate::{Binder, Page};
|
|
|
|
slotmap::new_key_type! {
|
|
/// The unique ID of a section of page.
|
|
pub struct Entity;
|
|
}
|
|
|
|
pub type ShowWhileFn<Message> = Box<dyn for<'a> Fn(&'a dyn Page<Message>) -> bool>;
|
|
|
|
pub type ViewFn<Message> = Box<
|
|
dyn for<'a> Fn(
|
|
&'a Binder<Message>,
|
|
&'a dyn Page<Message>,
|
|
&'a Section<Message>,
|
|
) -> cosmic::Element<'a, Message>,
|
|
>;
|
|
|
|
/// A searchable sub-component of a page.
|
|
///
|
|
/// Searches can group multiple sections together.
|
|
#[derive(Setters)]
|
|
#[must_use]
|
|
pub struct Section<Message> {
|
|
#[setters(into)]
|
|
pub title: String,
|
|
#[setters(into)]
|
|
pub descriptions: Slab<String>,
|
|
#[setters(skip)]
|
|
pub show_while: Option<ShowWhileFn<Message>>,
|
|
#[setters(skip)]
|
|
pub view_fn: ViewFn<Message>,
|
|
#[setters(bool)]
|
|
pub search_ignore: bool,
|
|
}
|
|
|
|
impl<Message: Clone + 'static> Default for Section<Message> {
|
|
fn default() -> Self {
|
|
Self {
|
|
title: String::new(),
|
|
descriptions: Slab::new(),
|
|
show_while: None,
|
|
view_fn: Box::new(unimplemented),
|
|
search_ignore: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Message: Clone + 'static> Section<Message> {
|
|
#[must_use]
|
|
#[inline]
|
|
pub fn search_matches(&self, rule: &Regex) -> bool {
|
|
if self.search_ignore {
|
|
return false;
|
|
}
|
|
|
|
if rule.is_match(self.title.as_str()) {
|
|
return true;
|
|
}
|
|
|
|
for (_, description) in &self.descriptions {
|
|
if rule.is_match(description) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[inline]
|
|
pub fn show_while<Model: Page<Message>>(
|
|
mut self,
|
|
func: impl for<'a> Fn(&'a Model) -> bool + 'static,
|
|
) -> Self {
|
|
self.show_while = Some(Box::new(move |model: &dyn Page<Message>| {
|
|
let model = model.downcast_ref::<Model>().unwrap_or_else(|| {
|
|
panic!(
|
|
"page model type mismatch: expected {}",
|
|
std::any::type_name::<Model>()
|
|
)
|
|
});
|
|
|
|
func(model)
|
|
}));
|
|
self
|
|
}
|
|
|
|
/// # Panics
|
|
///
|
|
/// Will panic if the `Model` type does not match the page type.
|
|
#[inline]
|
|
pub fn view<Model: Page<Message>>(
|
|
mut self,
|
|
func: impl for<'a> Fn(
|
|
&'a Binder<Message>,
|
|
&'a Model,
|
|
&'a Section<Message>,
|
|
) -> cosmic::Element<'a, Message>
|
|
+ 'static,
|
|
) -> Self {
|
|
self.view_fn = Box::new(move |binder, model: &dyn Page<Message>, section| {
|
|
let model = model.downcast_ref::<Model>().unwrap_or_else(|| {
|
|
panic!(
|
|
"page model type mismatch: expected {}",
|
|
std::any::type_name::<Model>()
|
|
)
|
|
});
|
|
|
|
func(binder, model, section)
|
|
});
|
|
self
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
#[inline]
|
|
pub fn unimplemented<'a, Message: Clone + 'static>(
|
|
_binder: &'a Binder<Message>,
|
|
_page: &'a dyn Page<Message>,
|
|
_section: &'a Section<Message>,
|
|
) -> cosmic::Element<'a, Message> {
|
|
cosmic::widget::settings::view_column(vec![cosmic::widget::settings::section().into()]).into()
|
|
}
|