Made compact and standard table view into separate widgets
This commit is contained in:
parent
2305e25fa8
commit
91c35e88ad
7 changed files with 570 additions and 396 deletions
|
|
@ -6,9 +6,10 @@
|
||||||
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;
|
||||||
|
use cosmic::prelude::*;
|
||||||
use cosmic::widget::table;
|
use cosmic::widget::table;
|
||||||
use cosmic::widget::{self, nav_bar};
|
use cosmic::widget::{self, nav_bar};
|
||||||
use cosmic::{executor, iced, Element};
|
use cosmic::{executor, iced};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
|
|
@ -194,15 +195,15 @@ impl cosmic::Application for App {
|
||||||
fn view(&self) -> Element<Self::Message> {
|
fn view(&self) -> Element<Self::Message> {
|
||||||
cosmic::widget::responsive(|size| {
|
cosmic::widget::responsive(|size| {
|
||||||
if size.width < 600.0 {
|
if size.width < 600.0 {
|
||||||
widget::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)
|
.on_category_select(Message::CategorySelect)
|
||||||
.element_compact()
|
.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)
|
||||||
.element_standard()
|
.apply(Element::from)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ pub use spin_button::{spin_button, vertical as vertical_spin_button, SpinButton}
|
||||||
pub mod tab_bar;
|
pub mod tab_bar;
|
||||||
|
|
||||||
pub mod table;
|
pub mod table;
|
||||||
pub use table::table;
|
pub use table::{compact_table, table};
|
||||||
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ pub use model::{
|
||||||
Entity, Model,
|
Entity, Model,
|
||||||
};
|
};
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
pub use widget::TableView;
|
pub use widget::compact::CompactTableView;
|
||||||
|
pub use widget::standard::TableView;
|
||||||
|
|
||||||
pub type SingleSelectTableView<'a, Item, Category, Message> =
|
pub type SingleSelectTableView<'a, Item, Category, Message> =
|
||||||
TableView<'a, SingleSelect, Item, Category, Message>;
|
TableView<'a, SingleSelect, Item, Category, Message>;
|
||||||
|
|
@ -31,3 +32,16 @@ where
|
||||||
{
|
{
|
||||||
TableView::new(model)
|
TableView::new(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compact_table<'a, SelectionMode, Item, Category, Message>(
|
||||||
|
model: &'a Model<SelectionMode, Item, Category>,
|
||||||
|
) -> CompactTableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
Message: Clone,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: model::selection::Selectable,
|
||||||
|
{
|
||||||
|
CompactTableView::new(model)
|
||||||
|
}
|
||||||
|
|
|
||||||
249
src/widget/table/widget/compact.rs
Normal file
249
src/widget/table/widget/compact.rs
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
use derive_setters::Setters;
|
||||||
|
|
||||||
|
use crate::widget::table::model::{
|
||||||
|
category::{ItemCategory, ItemInterface},
|
||||||
|
selection::Selectable,
|
||||||
|
Entity, Model,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
theme,
|
||||||
|
widget::{self, container, menu},
|
||||||
|
Apply, Element,
|
||||||
|
};
|
||||||
|
use iced::{Alignment, Border, Padding};
|
||||||
|
|
||||||
|
#[derive(Setters)]
|
||||||
|
#[must_use]
|
||||||
|
pub struct CompactTableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) element_padding: Padding,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) item_padding: Padding,
|
||||||
|
pub(super) item_spacing: u16,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) divider_padding: Padding,
|
||||||
|
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||||
|
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
#[setters(skip)]
|
||||||
|
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>
|
||||||
|
From<CompactTableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
|
||||||
|
where
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self {
|
||||||
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
val.model
|
||||||
|
.iter()
|
||||||
|
.map(|entity| {
|
||||||
|
let item = val.model.item(entity).unwrap();
|
||||||
|
let selected = val.model.is_active(entity);
|
||||||
|
widget::column()
|
||||||
|
.spacing(val.item_spacing)
|
||||||
|
.push(
|
||||||
|
widget::divider::horizontal::default()
|
||||||
|
.apply(container)
|
||||||
|
.padding(val.divider_padding),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::row()
|
||||||
|
.spacing(space_xxxs)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.push_maybe(
|
||||||
|
item.get_icon(Category::default()).map(|icon| icon.size(48)),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::column()
|
||||||
|
.push(widget::text::body(item.get_text(Category::default())))
|
||||||
|
.push({
|
||||||
|
let mut elements = val
|
||||||
|
.model
|
||||||
|
.categories
|
||||||
|
.iter()
|
||||||
|
.skip_while(|cat| **cat != Category::default())
|
||||||
|
.map(|category| {
|
||||||
|
vec![
|
||||||
|
widget::text::caption(item.get_text(*category))
|
||||||
|
.apply(Element::from),
|
||||||
|
widget::text::caption("-").apply(Element::from),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<Element<'a, Message>>>();
|
||||||
|
elements.pop();
|
||||||
|
elements
|
||||||
|
.apply(widget::row::with_children)
|
||||||
|
.spacing(space_xxxs)
|
||||||
|
.wrap()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.apply(container)
|
||||||
|
.padding(val.item_padding)
|
||||||
|
.width(iced::Length::Fill)
|
||||||
|
.class(theme::Container::custom(move |theme| {
|
||||||
|
widget::container::Style {
|
||||||
|
icon_color: if selected {
|
||||||
|
Some(theme.cosmic().on_accent_color().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
text_color: if selected {
|
||||||
|
Some(theme.cosmic().on_accent_color().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
background: if selected {
|
||||||
|
Some(iced::Background::Color(
|
||||||
|
theme.cosmic().accent_color().into(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
border: Border {
|
||||||
|
radius: theme.cosmic().radius_xs().into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
shadow: Default::default(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.apply(widget::mouse_area)
|
||||||
|
.apply(|ma| {
|
||||||
|
if let Some(on_item_select) = &val.on_item_select {
|
||||||
|
ma.on_press((on_item_select)(entity))
|
||||||
|
} else {
|
||||||
|
ma
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.apply(Element::from)
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
|
.apply(widget::column::with_children)
|
||||||
|
.spacing(val.item_spacing)
|
||||||
|
.padding(val.element_padding)
|
||||||
|
.apply(Element::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
|
CompactTableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
SelectionMode: Default,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxxs,
|
||||||
|
space_xxs,
|
||||||
|
..
|
||||||
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
model,
|
||||||
|
element_padding: Padding::from(0),
|
||||||
|
|
||||||
|
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
||||||
|
|
||||||
|
item_padding: Padding::from(space_xxs).into(),
|
||||||
|
item_spacing: 0,
|
||||||
|
|
||||||
|
on_item_select: None,
|
||||||
|
on_item_context: None,
|
||||||
|
item_context_tree: None,
|
||||||
|
|
||||||
|
on_category_select: None,
|
||||||
|
on_category_context: None,
|
||||||
|
category_context_tree: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_item_select<F>(mut self, on_select: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_select = Some(Box::new(on_select));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_item_context<F>(mut self, on_select: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_context = Some(Box::new(on_select));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_context(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
||||||
|
where
|
||||||
|
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<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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,382 +1,2 @@
|
||||||
mod state;
|
pub mod compact;
|
||||||
use derive_setters::Setters;
|
pub mod standard;
|
||||||
use state::State;
|
|
||||||
|
|
||||||
use super::model::{
|
|
||||||
category::{ItemCategory, ItemInterface},
|
|
||||||
selection::Selectable,
|
|
||||||
Entity, Model,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
ext::CollectionWidget,
|
|
||||||
theme,
|
|
||||||
widget::{self, container, divider, menu},
|
|
||||||
Apply, Element,
|
|
||||||
};
|
|
||||||
use iced::{Alignment, Border, Length, Padding};
|
|
||||||
|
|
||||||
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
|
|
||||||
|
|
||||||
#[derive(Setters)]
|
|
||||||
#[must_use]
|
|
||||||
pub struct TableView<'a, SelectionMode, Item, Category, Message>
|
|
||||||
where
|
|
||||||
Category: ItemCategory,
|
|
||||||
Item: ItemInterface<Category>,
|
|
||||||
Model<SelectionMode, Item, Category>: Selectable,
|
|
||||||
SelectionMode: Default,
|
|
||||||
Message: Clone + 'static,
|
|
||||||
{
|
|
||||||
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
|
||||||
|
|
||||||
#[setters(into)]
|
|
||||||
pub(super) element_padding: Padding,
|
|
||||||
|
|
||||||
#[setters(into)]
|
|
||||||
pub(super) item_padding: Padding,
|
|
||||||
pub(super) item_spacing: u16,
|
|
||||||
|
|
||||||
#[setters(into)]
|
|
||||||
pub(super) divider_padding: Padding,
|
|
||||||
|
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
|
||||||
|
|
||||||
#[setters(skip)]
|
|
||||||
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
|
||||||
#[setters(skip)]
|
|
||||||
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>
|
|
||||||
TableView<'a, SelectionMode, Item, Category, Message>
|
|
||||||
where
|
|
||||||
SelectionMode: Default,
|
|
||||||
Model<SelectionMode, Item, Category>: Selectable,
|
|
||||||
Category: ItemCategory,
|
|
||||||
Item: ItemInterface<Category>,
|
|
||||||
Message: Clone + 'static,
|
|
||||||
{
|
|
||||||
pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
|
|
||||||
let cosmic_theme::Spacing {
|
|
||||||
space_xxxs,
|
|
||||||
space_xxs,
|
|
||||||
..
|
|
||||||
} = theme::active().cosmic().spacing;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
model,
|
|
||||||
item_spacing: 0,
|
|
||||||
element_padding: Padding::from(0),
|
|
||||||
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
|
||||||
item_padding: Padding::from(space_xxs).into(),
|
|
||||||
on_item_select: None,
|
|
||||||
on_item_context: None,
|
|
||||||
item_context_tree: None,
|
|
||||||
on_category_select: None,
|
|
||||||
on_category_context: None,
|
|
||||||
category_context_tree: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_item_select<F>(mut self, on_select: F) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Entity) -> Message + 'a,
|
|
||||||
{
|
|
||||||
self.on_item_select = Some(Box::new(on_select));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_item_context<F>(mut self, on_select: F) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Entity) -> Message + 'a,
|
|
||||||
{
|
|
||||||
self.on_item_context = Some(Box::new(on_select));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn item_context(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
|
||||||
where
|
|
||||||
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<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
|
|
||||||
}
|
|
||||||
|
|
||||||
/*#[must_use]
|
|
||||||
pub fn element_standard(&self) -> Element<'a, Message> {
|
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
|
||||||
|
|
||||||
let header_row = self
|
|
||||||
.model
|
|
||||||
.categories
|
|
||||||
.iter()
|
|
||||||
.map(|category| {
|
|
||||||
widget::row()
|
|
||||||
.spacing(space_xxxs)
|
|
||||||
.push(widget::text::heading(category.to_string()))
|
|
||||||
.push_maybe(if let Some(sort) = self.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
|
|
||||||
})
|
|
||||||
.apply(container)
|
|
||||||
.padding(
|
|
||||||
Padding::default()
|
|
||||||
.left(self.item_padding.left)
|
|
||||||
.right(self.item_padding.right),
|
|
||||||
)
|
|
||||||
.width(category.width())
|
|
||||||
.apply(widget::mouse_area)
|
|
||||||
.apply(|mouse_area| {
|
|
||||||
if let Some(ref on_category_select) = self.on_category_select {
|
|
||||||
mouse_area.on_press((on_category_select)(
|
|
||||||
*category,
|
|
||||||
if let Some(sort) = self.model.sort {
|
|
||||||
if sort.0 == *category {
|
|
||||||
!sort.1
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
mouse_area
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.apply(Element::from)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
|
||||||
.apply(widget::row::with_children)
|
|
||||||
.apply(Element::from);
|
|
||||||
let items_full = if self.model.items.is_empty() {
|
|
||||||
vec![divider::horizontal::default()
|
|
||||||
.apply(container)
|
|
||||||
.padding(self.divider_padding)
|
|
||||||
.apply(Element::from)]
|
|
||||||
} else {
|
|
||||||
self.model
|
|
||||||
.iter()
|
|
||||||
.map(|entity| {
|
|
||||||
let item = self.model.item(entity).unwrap();
|
|
||||||
let categories = &self.model.categories;
|
|
||||||
let selected = self.model.is_active(entity);
|
|
||||||
|
|
||||||
vec![
|
|
||||||
divider::horizontal::default()
|
|
||||||
.apply(container)
|
|
||||||
.padding(self.divider_padding)
|
|
||||||
.apply(Element::from),
|
|
||||||
categories
|
|
||||||
.iter()
|
|
||||||
.map(|category| {
|
|
||||||
widget::row()
|
|
||||||
.spacing(space_xxxs)
|
|
||||||
.push_maybe(item.get_icon(*category).map(|icon| icon.size(24)))
|
|
||||||
.push(widget::text::body(item.get_text(*category)))
|
|
||||||
.align_y(Alignment::Center)
|
|
||||||
.apply(container)
|
|
||||||
.width(category.width())
|
|
||||||
.align_y(Alignment::Center)
|
|
||||||
.apply(Element::from)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
|
||||||
.apply(widget::row::with_children)
|
|
||||||
.apply(container)
|
|
||||||
.padding(self.item_padding)
|
|
||||||
.class(theme::Container::custom(move |theme| {
|
|
||||||
widget::container::Style {
|
|
||||||
icon_color: if selected {
|
|
||||||
Some(theme.cosmic().on_accent_color().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
text_color: if selected {
|
|
||||||
Some(theme.cosmic().on_accent_color().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
background: if selected {
|
|
||||||
Some(iced::Background::Color(
|
|
||||||
theme.cosmic().accent_color().into(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
border: Border {
|
|
||||||
radius: theme.cosmic().radius_xs().into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
shadow: Default::default(),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.apply(widget::mouse_area)
|
|
||||||
.apply(|mouse_area| {
|
|
||||||
if let Some(ref on_item_select) = self.on_item_select {
|
|
||||||
mouse_area.on_press((on_item_select)(entity))
|
|
||||||
} else {
|
|
||||||
mouse_area
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.apply(Element::from),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
|
||||||
};
|
|
||||||
vec![vec![header_row], items_full]
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
|
||||||
.apply(widget::column::with_children)
|
|
||||||
.spacing(self.item_spacing)
|
|
||||||
.padding(self.element_padding)
|
|
||||||
.apply(Element::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn element_compact(&self) -> Element<'a, Message> {
|
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
|
||||||
self.model
|
|
||||||
.iter()
|
|
||||||
.map(|entity| {
|
|
||||||
let item = self.model.item(entity).unwrap();
|
|
||||||
let selected = self.model.is_active(entity);
|
|
||||||
widget::column()
|
|
||||||
.spacing(self.item_spacing)
|
|
||||||
.push(
|
|
||||||
widget::divider::horizontal::default()
|
|
||||||
.apply(container)
|
|
||||||
.padding(self.divider_padding),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
widget::row()
|
|
||||||
.spacing(space_xxxs)
|
|
||||||
.align_y(Alignment::Center)
|
|
||||||
.push_maybe(
|
|
||||||
item.get_icon(Category::default()).map(|icon| icon.size(48)),
|
|
||||||
)
|
|
||||||
.push(
|
|
||||||
widget::column()
|
|
||||||
.push(widget::text::body(item.get_text(Category::default())))
|
|
||||||
.push({
|
|
||||||
let mut elements = self
|
|
||||||
.model
|
|
||||||
.categories
|
|
||||||
.iter()
|
|
||||||
.skip_while(|cat| **cat != Category::default())
|
|
||||||
.map(|category| {
|
|
||||||
vec![
|
|
||||||
widget::text::caption(item.get_text(*category))
|
|
||||||
.apply(Element::from),
|
|
||||||
widget::text::caption("-").apply(Element::from),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<Element<'a, Message>>>();
|
|
||||||
elements.pop();
|
|
||||||
elements
|
|
||||||
.apply(widget::row::with_children)
|
|
||||||
.spacing(space_xxxs)
|
|
||||||
.wrap()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.apply(container)
|
|
||||||
.padding(self.item_padding)
|
|
||||||
.width(iced::Length::Fill)
|
|
||||||
.class(theme::Container::custom(move |theme| {
|
|
||||||
widget::container::Style {
|
|
||||||
icon_color: if selected {
|
|
||||||
Some(theme.cosmic().on_accent_color().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
text_color: if selected {
|
|
||||||
Some(theme.cosmic().on_accent_color().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
background: if selected {
|
|
||||||
Some(iced::Background::Color(
|
|
||||||
theme.cosmic().accent_color().into(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
border: Border {
|
|
||||||
radius: theme.cosmic().radius_xs().into(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
shadow: Default::default(),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.apply(widget::mouse_area)
|
|
||||||
.apply(|ma| {
|
|
||||||
if let Some(on_item_select) = &self.on_item_select {
|
|
||||||
ma.on_press((on_item_select)(entity))
|
|
||||||
} else {
|
|
||||||
ma
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.apply(Element::from)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
|
||||||
.apply(widget::column::with_children)
|
|
||||||
.spacing(self.item_spacing)
|
|
||||||
.padding(self.element_padding)
|
|
||||||
.apply(Element::from)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
|
||||||
298
src/widget/table/widget/standard.rs
Normal file
298
src/widget/table/widget/standard.rs
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
use derive_setters::Setters;
|
||||||
|
|
||||||
|
use crate::widget::table::model::{
|
||||||
|
category::{ItemCategory, ItemInterface},
|
||||||
|
selection::Selectable,
|
||||||
|
Entity, Model,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
ext::CollectionWidget,
|
||||||
|
theme,
|
||||||
|
widget::{self, container, divider, menu},
|
||||||
|
Apply, Element,
|
||||||
|
};
|
||||||
|
use iced::{Alignment, Border, Length, Padding};
|
||||||
|
|
||||||
|
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
|
||||||
|
|
||||||
|
#[derive(Setters)]
|
||||||
|
#[must_use]
|
||||||
|
pub struct TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) element_padding: Padding,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) item_padding: Padding,
|
||||||
|
pub(super) item_spacing: u16,
|
||||||
|
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) divider_padding: Padding,
|
||||||
|
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||||
|
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
#[setters(skip)]
|
||||||
|
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>
|
||||||
|
From<TableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
|
||||||
|
where
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
|
||||||
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
|
let header_row = val
|
||||||
|
.model
|
||||||
|
.categories
|
||||||
|
.iter()
|
||||||
|
.map(|category| {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
.apply(container)
|
||||||
|
.padding(
|
||||||
|
Padding::default()
|
||||||
|
.left(val.item_padding.left)
|
||||||
|
.right(val.item_padding.right),
|
||||||
|
)
|
||||||
|
.width(category.width())
|
||||||
|
.apply(widget::mouse_area)
|
||||||
|
.apply(|mouse_area| {
|
||||||
|
if let Some(ref on_category_select) = val.on_category_select {
|
||||||
|
mouse_area.on_press((on_category_select)(
|
||||||
|
*category,
|
||||||
|
if let Some(sort) = val.model.sort {
|
||||||
|
if sort.0 == *category {
|
||||||
|
!sort.1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
mouse_area
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.apply(Element::from)
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
|
.apply(widget::row::with_children)
|
||||||
|
.apply(Element::from);
|
||||||
|
let items_full = if val.model.items.is_empty() {
|
||||||
|
vec![divider::horizontal::default()
|
||||||
|
.apply(container)
|
||||||
|
.padding(val.divider_padding)
|
||||||
|
.apply(Element::from)]
|
||||||
|
} else {
|
||||||
|
val.model
|
||||||
|
.iter()
|
||||||
|
.map(|entity| {
|
||||||
|
let item = val.model.item(entity).unwrap();
|
||||||
|
let categories = &val.model.categories;
|
||||||
|
let selected = val.model.is_active(entity);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
divider::horizontal::default()
|
||||||
|
.apply(container)
|
||||||
|
.padding(val.divider_padding)
|
||||||
|
.apply(Element::from),
|
||||||
|
categories
|
||||||
|
.iter()
|
||||||
|
.map(|category| {
|
||||||
|
widget::row()
|
||||||
|
.spacing(space_xxxs)
|
||||||
|
.push_maybe(item.get_icon(*category).map(|icon| icon.size(24)))
|
||||||
|
.push(widget::text::body(item.get_text(*category)))
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.apply(container)
|
||||||
|
.width(category.width())
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.apply(Element::from)
|
||||||
|
})
|
||||||
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
|
.apply(widget::row::with_children)
|
||||||
|
.apply(container)
|
||||||
|
.padding(val.item_padding)
|
||||||
|
.class(theme::Container::custom(move |theme| {
|
||||||
|
widget::container::Style {
|
||||||
|
icon_color: if selected {
|
||||||
|
Some(theme.cosmic().on_accent_color().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
text_color: if selected {
|
||||||
|
Some(theme.cosmic().on_accent_color().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
background: if selected {
|
||||||
|
Some(iced::Background::Color(
|
||||||
|
theme.cosmic().accent_color().into(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
border: Border {
|
||||||
|
radius: theme.cosmic().radius_xs().into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
shadow: Default::default(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.apply(widget::mouse_area)
|
||||||
|
.apply(|mouse_area| {
|
||||||
|
if let Some(ref on_item_select) = val.on_item_select {
|
||||||
|
mouse_area.on_press((on_item_select)(entity))
|
||||||
|
} else {
|
||||||
|
mouse_area
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.apply(Element::from),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
|
};
|
||||||
|
vec![vec![header_row], items_full]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
|
.apply(widget::column::with_children)
|
||||||
|
.spacing(val.item_spacing)
|
||||||
|
.padding(val.element_padding)
|
||||||
|
.apply(Element::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
|
TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
SelectionMode: Default,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
Category: ItemCategory,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_xxxs,
|
||||||
|
space_xxs,
|
||||||
|
..
|
||||||
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
model,
|
||||||
|
element_padding: Padding::from(0),
|
||||||
|
|
||||||
|
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
||||||
|
|
||||||
|
item_padding: Padding::from(space_xxs).into(),
|
||||||
|
item_spacing: 0,
|
||||||
|
|
||||||
|
on_item_select: None,
|
||||||
|
on_item_context: None,
|
||||||
|
item_context_tree: None,
|
||||||
|
|
||||||
|
on_category_select: None,
|
||||||
|
on_category_context: None,
|
||||||
|
category_context_tree: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_item_select<F>(mut self, on_select: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_select = Some(Box::new(on_select));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_item_context<F>(mut self, on_select: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_context = Some(Box::new(on_select));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_context(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
||||||
|
where
|
||||||
|
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<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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
use slotmap::SecondaryMap;
|
|
||||||
|
|
||||||
use crate::widget::table::Entity;
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
pub(super) num_items: usize,
|
|
||||||
pub(super) selected: SecondaryMap<Entity, bool>,
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue