improv(keyboard): shortcuts UI improvements
This commit is contained in:
parent
bcd8293c3e
commit
48e14d4add
20 changed files with 703 additions and 497 deletions
|
|
@ -9,7 +9,6 @@ regex = "1.11.1"
|
|||
slotmap = "1.0.7"
|
||||
libcosmic = { workspace = true }
|
||||
downcast-rs = "1.2.1"
|
||||
once_cell = "1.20.3"
|
||||
tokio.workspace = true
|
||||
url = "2.5.4"
|
||||
slab = "0.4.9"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub struct Binder<Message> {
|
|||
}
|
||||
|
||||
impl<Message> Default for Binder<Message> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: SparseSecondaryMap::new(),
|
||||
|
|
@ -42,12 +43,14 @@ impl<Message> Default for Binder<Message> {
|
|||
impl<Message: 'static> Binder<Message> {
|
||||
/// Check if a page exists in the model.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn contains_item(&self, id: crate::Entity) -> bool {
|
||||
self.info.contains_key(id)
|
||||
}
|
||||
|
||||
/// Returns the content of a page, if it has any.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn content(&self, page: crate::Entity) -> Option<&[section::Entity]> {
|
||||
self.content.get(page).map(Vec::as_slice)
|
||||
}
|
||||
|
|
@ -87,6 +90,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn find_page_by_id(&self, id: &str) -> Option<(crate::Entity, &Info)> {
|
||||
self.info.iter().find(|(_id, info)| info.id == id)
|
||||
}
|
||||
|
|
@ -117,22 +121,26 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn model(&self, id: crate::Entity) -> Option<&dyn Page<Message>> {
|
||||
self.page.get(id).map(AsRef::as_ref)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn model_mut(&mut self, id: crate::Entity) -> Option<&mut dyn Page<Message>> {
|
||||
self.page.get_mut(id).map(AsMut::as_mut)
|
||||
}
|
||||
|
||||
/// Get entity ID of page by its type ID.
|
||||
#[inline]
|
||||
pub fn page_id<P: Page<Message>>(&self) -> Option<crate::Entity> {
|
||||
self.typed_page_ids.get(&TypeId::of::<P>()).copied()
|
||||
}
|
||||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn page<P: Page<Message>>(&self) -> Option<&P> {
|
||||
let page = self.page.get(self.page_id::<P>()?)?;
|
||||
page.downcast_ref::<P>()
|
||||
|
|
@ -140,6 +148,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Create a context drawer for the given page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn context_drawer(&self, id: crate::Entity) -> Option<Element<'_, Message>> {
|
||||
let page = self.page.get(id)?;
|
||||
page.context_drawer()
|
||||
|
|
@ -147,6 +156,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Create a dialog for the given page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn dialog(&self, id: crate::Entity) -> Option<Element<'_, Message>> {
|
||||
let page = self.page.get(id)?;
|
||||
page.dialog()
|
||||
|
|
@ -154,12 +164,23 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn page_mut<P: Page<Message>>(&mut self) -> Option<&mut P> {
|
||||
let page = self.page.get_mut(self.page_id::<P>()?)?;
|
||||
page.downcast_mut::<P>()
|
||||
}
|
||||
|
||||
/// Returns a Task when a context drawer is closed.
|
||||
#[inline]
|
||||
pub fn on_context_drawer_close(&mut self, id: crate::Entity) -> Option<Task<Message>> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return Some(page.on_context_drawer_close());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a Task when a page is left
|
||||
#[inline]
|
||||
pub fn on_leave(&mut self, id: crate::Entity) -> Option<Task<Message>> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return Some(page.on_leave());
|
||||
|
|
@ -168,6 +189,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
/// Calls a page's load function to refresh its data.
|
||||
#[inline]
|
||||
pub fn on_enter(&mut self, id: crate::Entity) -> Task<Message> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return page.on_enter();
|
||||
|
|
@ -204,13 +226,14 @@ impl<Message: 'static> Binder<Message> {
|
|||
) -> impl Iterator<Item = (crate::Entity, section::Entity)> + 'a {
|
||||
self.content.iter().flat_map(move |(page, sections)| {
|
||||
sections
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter(|&id| self.sections[*id].search_matches(rule))
|
||||
.map(move |&id| (page, id))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the sub-pages of a page, if it has any.
|
||||
#[inline]
|
||||
pub fn sub_pages(&self, page: crate::Entity) -> Option<&[crate::Entity]> {
|
||||
self.sub_pages.get(page).map(AsRef::as_ref)
|
||||
}
|
||||
|
|
@ -219,6 +242,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
pub trait AutoBind<Message: 'static>: Page<Message> + Default + 'static {
|
||||
/// Attaches sub-pages to the page.
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
#[inline]
|
||||
fn sub_pages(page: crate::Insert<Message>) -> crate::Insert<Message> {
|
||||
page
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ pub struct Insert<'a, Message> {
|
|||
pub id: Entity,
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> Insert<'a, Message> {
|
||||
impl<Message: 'static> Insert<'_, Message> {
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn id(self) -> Entity {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn content(self, content: Content) -> Self {
|
||||
self.model.content.insert(self.id, content);
|
||||
self
|
||||
|
|
@ -26,7 +28,11 @@ impl<'a, Message: 'static> Insert<'a, Message> {
|
|||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn sub_page<P: AutoBind<Message>>(self) -> Self {
|
||||
let sub_page = self.model.register::<P>().id();
|
||||
self.sub_page_inner(sub_page)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn sub_page_inner(self, sub_page: Entity) -> Self {
|
||||
self.model.info[sub_page].parent = Some(self.id);
|
||||
|
||||
self.model
|
||||
|
|
@ -43,7 +49,11 @@ impl<'a, Message: 'static> Insert<'a, Message> {
|
|||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn sub_page_with_id<P: AutoBind<Message>>(&mut self) -> Entity {
|
||||
let sub_page = self.model.register::<P>().id();
|
||||
self.sub_page_with_id_inner(sub_page)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn sub_page_with_id_inner(&mut self, sub_page: Entity) -> Entity {
|
||||
self.model.info[sub_page].parent = Some(self.id);
|
||||
|
||||
self.model
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub use binder::{AutoBind, Binder};
|
|||
|
||||
mod insert;
|
||||
use cosmic::{Element, Task};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use downcast_rs::{Downcast, impl_downcast};
|
||||
pub use insert::Insert;
|
||||
|
||||
pub mod section;
|
||||
|
|
@ -30,6 +30,7 @@ pub trait Page<Message: 'static>: Downcast {
|
|||
|
||||
/// Initialize the sections used by this page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn content(
|
||||
&self,
|
||||
_sections: &mut SlotMap<section::Entity, Section<Message>>,
|
||||
|
|
@ -39,46 +40,62 @@ pub trait Page<Message: 'static>: Downcast {
|
|||
|
||||
/// Display a context drawer for the page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn context_drawer(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set a custom page header
|
||||
#[inline]
|
||||
fn header(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Display an inner app dialog for the page.
|
||||
#[inline]
|
||||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Response from a file chooser dialog request.
|
||||
#[inline]
|
||||
fn file_chooser(&mut self, _selected: Vec<url::Url>) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Alter the contents of the page's header view.
|
||||
#[inline]
|
||||
fn header_view(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Emit on the context drawer being closed
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn on_context_drawer_close(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Reload page metadata via a Task.
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn on_enter(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Emit a command when the page is left
|
||||
#[inline]
|
||||
fn on_leave(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Assigns the entity ID of the page to the page.
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn set_id(&mut self, entity: Entity) {}
|
||||
|
||||
/// The title to display in the page header.
|
||||
#[inline]
|
||||
fn title(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
|
@ -112,6 +129,7 @@ pub struct Info {
|
|||
}
|
||||
|
||||
impl Info {
|
||||
#[inline]
|
||||
pub fn new(id: impl Into<Cow<'static, str>>, icon_name: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ impl<Message: 'static> Default for Section<Message> {
|
|||
|
||||
impl<Message: 'static> Section<Message> {
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn search_matches(&self, rule: &Regex) -> bool {
|
||||
if self.search_ignore {
|
||||
return false;
|
||||
|
|
@ -72,6 +73,7 @@ impl<Message: 'static> Section<Message> {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_while<Model: Page<Message>>(
|
||||
mut self,
|
||||
func: impl for<'a> Fn(&'a Model) -> bool + 'static,
|
||||
|
|
@ -92,6 +94,7 @@ impl<Message: 'static> Section<Message> {
|
|||
/// # 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(
|
||||
|
|
@ -116,6 +119,7 @@ impl<Message: 'static> Section<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn unimplemented<'a, Message: 'static>(
|
||||
_binder: &'a Binder<Message>,
|
||||
_page: &'a dyn Page<Message>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue