Added item contexts and changed the context menu functions to take builder functions
This commit is contained in:
parent
0b0e143fe4
commit
ea4e6c6cde
3 changed files with 139 additions and 97 deletions
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
//! Table API example
|
//! Table API example
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use cosmic::app::{Core, Settings, Task};
|
use cosmic::app::{Core, Settings, Task};
|
||||||
use cosmic::iced_core::Size;
|
use cosmic::iced_core::Size;
|
||||||
|
|
@ -100,6 +102,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
ItemSelect(table::Entity),
|
ItemSelect(table::Entity),
|
||||||
CategorySelect(Category, bool),
|
CategorySelect(Category, bool),
|
||||||
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -187,6 +190,7 @@ impl cosmic::Application for App {
|
||||||
Message::CategorySelect(category, descending) => {
|
Message::CategorySelect(category, descending) => {
|
||||||
self.table_model.sort(category, descending)
|
self.table_model.sort(category, descending)
|
||||||
}
|
}
|
||||||
|
Message::NoOp => {}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -197,15 +201,96 @@ impl cosmic::Application for App {
|
||||||
if size.width < 600.0 {
|
if size.width < 600.0 {
|
||||||
widget::compact_table(&self.table_model)
|
widget::compact_table(&self.table_model)
|
||||||
.on_item_select(Message::ItemSelect)
|
.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)
|
.apply(Element::from)
|
||||||
} else {
|
} else {
|
||||||
widget::table(&self.table_model)
|
widget::table(&self.table_model)
|
||||||
.on_item_select(Message::ItemSelect)
|
.on_item_select(Message::ItemSelect)
|
||||||
.on_category_select(Message::CategorySelect)
|
.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)
|
.apply(Element::from)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,23 +30,18 @@ where
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub(super) item_padding: Padding,
|
pub(super) item_padding: Padding,
|
||||||
pub(super) item_spacing: u16,
|
pub(super) item_spacing: u16,
|
||||||
|
pub(super) icon_size: u16,
|
||||||
|
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub(super) divider_padding: Padding,
|
pub(super) divider_padding: Padding,
|
||||||
|
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>,
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
|
||||||
|
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) on_item_context: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
pub(super) on_item_context: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) on_category_select: Option<Box<dyn Fn(Category, bool) -> Message + 'a>>,
|
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) on_category_context: Option<Box<dyn Fn(Category) -> Message + 'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, SelectionMode, Item, Category, Message>
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
|
|
@ -65,6 +60,8 @@ where
|
||||||
.map(|entity| {
|
.map(|entity| {
|
||||||
let item = val.model.item(entity).unwrap();
|
let item = val.model.item(entity).unwrap();
|
||||||
let selected = val.model.is_active(entity);
|
let selected = val.model.is_active(entity);
|
||||||
|
let context_menu = (val.item_context_builder)(&item);
|
||||||
|
|
||||||
widget::column()
|
widget::column()
|
||||||
.spacing(val.item_spacing)
|
.spacing(val.item_spacing)
|
||||||
.push(
|
.push(
|
||||||
|
|
@ -140,7 +137,8 @@ where
|
||||||
} else {
|
} else {
|
||||||
ma
|
ma
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
|
.apply(|ma| widget::context_menu(ma, context_menu)),
|
||||||
)
|
)
|
||||||
.apply(Element::from)
|
.apply(Element::from)
|
||||||
})
|
})
|
||||||
|
|
@ -176,14 +174,11 @@ where
|
||||||
|
|
||||||
item_padding: Padding::from(space_xxs).into(),
|
item_padding: Padding::from(space_xxs).into(),
|
||||||
item_spacing: 0,
|
item_spacing: 0,
|
||||||
|
icon_size: 48,
|
||||||
|
|
||||||
|
item_context_builder: Box::new(|_| None),
|
||||||
on_item_select: None,
|
on_item_select: None,
|
||||||
on_item_context: 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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_context(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
pub fn item_context<F>(mut self, context_menu_builder: F) -> Self
|
||||||
where
|
where
|
||||||
|
F: Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>> + 'static,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
self.item_context_tree =
|
self.item_context_builder = Box::new(context_menu_builder);
|
||||||
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<Vec<menu::Tree<'a, Message>>>) -> 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<F>(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<F>(mut self, on_select: F) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Category) -> Message + 'a,
|
|
||||||
{
|
|
||||||
self.on_category_context = Some(Box::new(on_select));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,15 @@ where
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub(super) item_padding: Padding,
|
pub(super) item_padding: Padding,
|
||||||
pub(super) item_spacing: u16,
|
pub(super) item_spacing: u16,
|
||||||
|
pub(super) icon_size: u16,
|
||||||
|
|
||||||
#[setters(into)]
|
#[setters(into)]
|
||||||
pub(super) divider_padding: Padding,
|
pub(super) divider_padding: Padding,
|
||||||
|
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>>>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) category_context_trees:
|
pub(super) category_contexts: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>>>,
|
||||||
std::collections::HashMap<Category, Option<Vec<menu::Tree<'a, Message>>>>,
|
|
||||||
|
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
|
@ -65,29 +65,34 @@ where
|
||||||
fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
|
fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut category_contexts = val.category_context_trees.into_values();
|
|
||||||
|
|
||||||
let header_row = val
|
let header_row = val
|
||||||
.model
|
.model
|
||||||
.categories
|
.categories
|
||||||
.iter()
|
.iter()
|
||||||
|
.cloned()
|
||||||
.map(|category| {
|
.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()
|
widget::row()
|
||||||
.spacing(space_xxxs)
|
.spacing(space_xxxs)
|
||||||
.push(widget::text::heading(category.to_string()))
|
.push(widget::text::heading(category.to_string()))
|
||||||
.push_maybe(if let Some(sort) = val.model.sort {
|
.push_maybe(match sort_state {
|
||||||
if sort.0 == *category {
|
1 => Some(widget::icon::from_name("pan-up-symbolic").icon()),
|
||||||
match sort.1 {
|
2 => Some(widget::icon::from_name("pan-down-symbolic").icon()),
|
||||||
true => Some(widget::icon::from_name("pan-up-symbolic").icon()),
|
_ => None,
|
||||||
false => Some(widget::icon::from_name("pan-down-symbolic").icon()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.padding(
|
.padding(
|
||||||
|
|
@ -100,9 +105,9 @@ where
|
||||||
.apply(|mouse_area| {
|
.apply(|mouse_area| {
|
||||||
if let Some(ref on_category_select) = val.on_category_select {
|
if let Some(ref on_category_select) = val.on_category_select {
|
||||||
mouse_area.on_press((on_category_select)(
|
mouse_area.on_press((on_category_select)(
|
||||||
*category,
|
category,
|
||||||
if let Some(sort) = val.model.sort {
|
if let Some(sort) = val.model.sort {
|
||||||
if sort.0 == *category {
|
if sort.0 == category {
|
||||||
!sort.1
|
!sort.1
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -121,6 +126,7 @@ where
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
.apply(widget::row::with_children)
|
.apply(widget::row::with_children)
|
||||||
.apply(Element::from);
|
.apply(Element::from);
|
||||||
|
// Build the items
|
||||||
let items_full = if val.model.items.is_empty() {
|
let items_full = if val.model.items.is_empty() {
|
||||||
vec![divider::horizontal::default()
|
vec![divider::horizontal::default()
|
||||||
.apply(container)
|
.apply(container)
|
||||||
|
|
@ -129,10 +135,11 @@ where
|
||||||
} else {
|
} else {
|
||||||
val.model
|
val.model
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entity| {
|
.map(move |entity| {
|
||||||
let item = val.model.item(entity).unwrap();
|
let item = val.model.item(entity).unwrap();
|
||||||
let categories = &val.model.categories;
|
let categories = &val.model.categories;
|
||||||
let selected = val.model.is_active(entity);
|
let selected = val.model.is_active(entity);
|
||||||
|
let item_context = (val.item_context_builder)(&item);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
divider::horizontal::default()
|
divider::horizontal::default()
|
||||||
|
|
@ -144,7 +151,10 @@ where
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
widget::row()
|
widget::row()
|
||||||
.spacing(space_xxxs)
|
.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)))
|
.push(widget::text::body(item.get_text(*category)))
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.apply(container)
|
.apply(container)
|
||||||
|
|
@ -190,6 +200,7 @@ where
|
||||||
mouse_area
|
mouse_area
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.apply(|mouse_area| widget::context_menu(mouse_area, item_context))
|
||||||
.apply(Element::from),
|
.apply(Element::from),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -223,7 +234,7 @@ where
|
||||||
..
|
..
|
||||||
} = theme::active().cosmic().spacing;
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
let mut result = Self {
|
Self {
|
||||||
model,
|
model,
|
||||||
element_padding: Padding::from(0),
|
element_padding: Padding::from(0),
|
||||||
|
|
||||||
|
|
@ -231,21 +242,16 @@ where
|
||||||
|
|
||||||
item_padding: Padding::from(space_xxs).into(),
|
item_padding: Padding::from(space_xxs).into(),
|
||||||
item_spacing: 0,
|
item_spacing: 0,
|
||||||
|
icon_size: 24,
|
||||||
|
|
||||||
on_item_select: None,
|
on_item_select: None,
|
||||||
on_item_context: None,
|
on_item_context: None,
|
||||||
item_context_tree: None,
|
item_context_builder: Box::new(|_| None),
|
||||||
|
|
||||||
on_category_select: None,
|
on_category_select: None,
|
||||||
on_category_context: None,
|
on_category_context: None,
|
||||||
category_context_trees: std::collections::HashMap::new(),
|
category_contexts: Box::new(|_| None),
|
||||||
};
|
|
||||||
|
|
||||||
for category in model.categories.iter().cloned() {
|
|
||||||
result.category_context_trees.insert(category, None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_item_select<F>(mut self, on_select: F) -> Self
|
pub fn on_item_select<F>(mut self, on_select: F) -> Self
|
||||||
|
|
@ -264,30 +270,21 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_context(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
pub fn item_context<F>(mut self, context_menu_builder: F) -> Self
|
||||||
where
|
where
|
||||||
|
F: Fn(&Item) -> Option<Vec<menu::Tree<'a, Message>>> + 'static,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
self.item_context_tree =
|
self.item_context_builder = Box::new(context_menu_builder);
|
||||||
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn category_context(mut self, category: Category, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
pub fn category_context<F>(mut self, context_menu_builder: F) -> Self
|
||||||
where
|
where
|
||||||
|
F: Fn(Category) -> Option<Vec<menu::Tree<'a, Message>>> + 'static,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
*self.category_context_trees.get_mut(&category).unwrap() =
|
self.category_contexts = Box::new(context_menu_builder);
|
||||||
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue