From ea4e6c6cde5eaa1dccb758fcd577394a0ceabdd9 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Fri, 10 Jan 2025 22:44:42 -0800 Subject: [PATCH] Added item contexts and changed the context menu functions to take builder functions --- examples/table-view/src/main.rs | 87 ++++++++++++++++++++++++++++- src/widget/table/widget/compact.rs | 62 ++++---------------- src/widget/table/widget/standard.rs | 87 ++++++++++++++--------------- 3 files changed, 139 insertions(+), 97 deletions(-) diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index 23df06a2..d7dd8ae8 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -3,6 +3,8 @@ //! Table API example +use std::collections::HashMap; + use chrono::Datelike; use cosmic::app::{Core, Settings, Task}; use cosmic::iced_core::Size; @@ -100,6 +102,7 @@ fn main() -> Result<(), Box> { pub enum Message { ItemSelect(table::Entity), CategorySelect(Category, bool), + NoOp, } /// The [`App`] stores application-specific state. @@ -187,6 +190,7 @@ impl cosmic::Application for App { Message::CategorySelect(category, descending) => { self.table_model.sort(category, descending) } + Message::NoOp => {} } Task::none() } @@ -197,15 +201,96 @@ impl cosmic::Application for App { if size.width < 600.0 { widget::compact_table(&self.table_model) .on_item_select(Message::ItemSelect) - .on_category_select(Message::CategorySelect) + .item_context(|item| { + Some(widget::menu::items( + &HashMap::new(), + vec![widget::menu::Item::Button( + format!("Action on {}", item.name), + None, + Action::None, + )], + )) + }) .apply(Element::from) } else { widget::table(&self.table_model) .on_item_select(Message::ItemSelect) .on_category_select(Message::CategorySelect) + .item_context(|item| { + Some(widget::menu::items( + &HashMap::new(), + vec![widget::menu::Item::Button( + format!("Action on {}", item.name), + None, + Action::None, + )], + )) + }) + .category_context(|category| { + Some(match category { + Category::Name => widget::menu::items( + &HashMap::new(), + vec![ + widget::menu::Item::Button( + "Action on Name Category", + None, + Action::None, + ), + widget::menu::Item::Button( + "Other action on Name", + None, + Action::None, + ), + ], + ), + Category::Date => widget::menu::items( + &HashMap::new(), + vec![ + widget::menu::Item::Button( + "Action on Date Category", + None, + Action::None, + ), + widget::menu::Item::Button( + "Other action on Date", + None, + Action::None, + ), + ], + ), + Category::Size => widget::menu::items( + &HashMap::new(), + vec![ + widget::menu::Item::Button( + "Action on Size Category", + None, + Action::None, + ), + widget::menu::Item::Button( + "Other action on Size", + None, + Action::None, + ), + ], + ), + }) + }) .apply(Element::from) } }) .into() } } + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Action { + None, +} + +impl widget::menu::Action for Action { + type Message = Message; + + fn message(&self) -> Self::Message { + Message::NoOp + } +} diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 62891fca..71b88cbb 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -30,23 +30,18 @@ where #[setters(into)] pub(super) item_padding: Padding, pub(super) item_spacing: u16, + pub(super) icon_size: u16, #[setters(into)] pub(super) divider_padding: Padding, #[setters(skip)] - pub(super) item_context_tree: Option>>, - #[setters(skip)] - pub(super) category_context_tree: Option>>, + pub(super) item_context_builder: Box Option>>>, #[setters(skip)] pub(super) on_item_select: Option Message + 'a>>, #[setters(skip)] pub(super) on_item_context: Option Message + 'a>>, - #[setters(skip)] - pub(super) on_category_select: Option Message + 'a>>, - #[setters(skip)] - pub(super) on_category_context: Option Message + 'a>>, } impl<'a, SelectionMode, Item, Category, Message> @@ -65,6 +60,8 @@ where .map(|entity| { let item = val.model.item(entity).unwrap(); let selected = val.model.is_active(entity); + let context_menu = (val.item_context_builder)(&item); + widget::column() .spacing(val.item_spacing) .push( @@ -140,7 +137,8 @@ where } else { ma } - }), + }) + .apply(|ma| widget::context_menu(ma, context_menu)), ) .apply(Element::from) }) @@ -176,14 +174,11 @@ where item_padding: Padding::from(space_xxs).into(), item_spacing: 0, + icon_size: 48, + item_context_builder: Box::new(|_| None), on_item_select: None, on_item_context: None, - item_context_tree: None, - - on_category_select: None, - on_category_context: None, - category_context_tree: None, } } @@ -203,47 +198,12 @@ where self } - pub fn item_context(mut self, context_menu: Option>>) -> Self + pub fn item_context(mut self, context_menu_builder: F) -> Self where + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { - self.item_context_tree = - context_menu.map(|menus| vec![menu::Tree::with_children(widget::row(), menus)]); - - if let Some(ref mut context_menu) = self.item_context_tree { - context_menu.iter_mut().for_each(menu::Tree::set_index); - } - - self - } - - pub fn category_context(mut self, context_menu: Option>>) -> Self - where - Message: 'static, - { - self.category_context_tree = - context_menu.map(|menus| vec![menu::Tree::with_children(widget::row(), menus)]); - - if let Some(ref mut context_menu) = self.category_context_tree { - context_menu.iter_mut().for_each(menu::Tree::set_index); - } - - self - } - - pub fn on_category_select(mut self, on_select: F) -> Self - where - F: Fn(Category, bool) -> Message + 'a, - { - self.on_category_select = Some(Box::new(on_select)); - self - } - - pub fn on_category_context(mut self, on_select: F) -> Self - where - F: Fn(Category) -> Message + 'a, - { - self.on_category_context = Some(Box::new(on_select)); + self.item_context_builder = Box::new(context_menu_builder); self } } diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index bf86153c..3a1eafa0 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -33,15 +33,15 @@ where #[setters(into)] pub(super) item_padding: Padding, pub(super) item_spacing: u16, + pub(super) icon_size: u16, #[setters(into)] pub(super) divider_padding: Padding, #[setters(skip)] - pub(super) item_context_tree: Option>>, + pub(super) item_context_builder: Box Option>>>, #[setters(skip)] - pub(super) category_context_trees: - std::collections::HashMap>>>, + pub(super) category_contexts: Box Option>>>, #[setters(skip)] pub(super) on_item_select: Option Message + 'a>>, @@ -65,29 +65,34 @@ where fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self { let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing; - let mut category_contexts = val.category_context_trees.into_values(); - let header_row = val .model .categories .iter() + .cloned() .map(|category| { - let cat_context_tree = category_contexts.next().unwrap(); + let cat_context_tree = (val.category_contexts)(category); + let mut sort_state = 0; + + if let Some(sort) = val.model.sort { + if sort.0 == category { + if sort.1 { + sort_state = 1; + } else { + sort_state = 2; + } + } + }; + + // Build the category header widget::row() .spacing(space_xxxs) .push(widget::text::heading(category.to_string())) - .push_maybe(if let Some(sort) = val.model.sort { - if sort.0 == *category { - match sort.1 { - true => Some(widget::icon::from_name("pan-up-symbolic").icon()), - false => Some(widget::icon::from_name("pan-down-symbolic").icon()), - } - } else { - None - } - } else { - None + .push_maybe(match sort_state { + 1 => Some(widget::icon::from_name("pan-up-symbolic").icon()), + 2 => Some(widget::icon::from_name("pan-down-symbolic").icon()), + _ => None, }) .apply(container) .padding( @@ -100,9 +105,9 @@ where .apply(|mouse_area| { if let Some(ref on_category_select) = val.on_category_select { mouse_area.on_press((on_category_select)( - *category, + category, if let Some(sort) = val.model.sort { - if sort.0 == *category { + if sort.0 == category { !sort.1 } else { false @@ -121,6 +126,7 @@ where .collect::>>() .apply(widget::row::with_children) .apply(Element::from); + // Build the items let items_full = if val.model.items.is_empty() { vec![divider::horizontal::default() .apply(container) @@ -129,10 +135,11 @@ where } else { val.model .iter() - .map(|entity| { + .map(move |entity| { let item = val.model.item(entity).unwrap(); let categories = &val.model.categories; let selected = val.model.is_active(entity); + let item_context = (val.item_context_builder)(&item); vec![ divider::horizontal::default() @@ -144,7 +151,10 @@ where .map(|category| { widget::row() .spacing(space_xxxs) - .push_maybe(item.get_icon(*category).map(|icon| icon.size(24))) + .push_maybe( + item.get_icon(*category) + .map(|icon| icon.size(val.icon_size)), + ) .push(widget::text::body(item.get_text(*category))) .align_y(Alignment::Center) .apply(container) @@ -190,6 +200,7 @@ where mouse_area } }) + .apply(|mouse_area| widget::context_menu(mouse_area, item_context)) .apply(Element::from), ] }) @@ -223,7 +234,7 @@ where .. } = theme::active().cosmic().spacing; - let mut result = Self { + Self { model, element_padding: Padding::from(0), @@ -231,21 +242,16 @@ where item_padding: Padding::from(space_xxs).into(), item_spacing: 0, + icon_size: 24, on_item_select: None, on_item_context: None, - item_context_tree: None, + item_context_builder: Box::new(|_| None), on_category_select: None, on_category_context: None, - category_context_trees: std::collections::HashMap::new(), - }; - - for category in model.categories.iter().cloned() { - result.category_context_trees.insert(category, None); + category_contexts: Box::new(|_| None), } - - result } pub fn on_item_select(mut self, on_select: F) -> Self @@ -264,30 +270,21 @@ where self } - pub fn item_context(mut self, context_menu: Option>>) -> Self + pub fn item_context(mut self, context_menu_builder: F) -> Self where + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { - self.item_context_tree = - context_menu.map(|menus| vec![menu::Tree::with_children(widget::row(), menus)]); - - if let Some(ref mut context_menu) = self.item_context_tree { - context_menu.iter_mut().for_each(menu::Tree::set_index); - } - + self.item_context_builder = Box::new(context_menu_builder); self } - pub fn category_context(mut self, category: Category, context_menu: Option>>) -> Self + pub fn category_context(mut self, context_menu_builder: F) -> Self where + F: Fn(Category) -> Option>> + 'static, Message: 'static, { - *self.category_context_trees.get_mut(&category).unwrap() = - context_menu.map(|menus| vec![menu::Tree::with_children(widget::row(), menus)]); - if let Some(ref mut context_menu) = self.category_context_trees.get_mut(&category).unwrap() { - context_menu.iter_mut().for_each(menu::Tree::set_index); - } - + self.category_contexts = Box::new(context_menu_builder); self }