Compact view implemented
This commit is contained in:
parent
8021271524
commit
8132f8f3e8
4 changed files with 351 additions and 180 deletions
|
|
@ -43,7 +43,7 @@ where
|
|||
pub(super) selection: SelectionMode,
|
||||
|
||||
/// What category to sort by and whether it's ascending or not
|
||||
pub(super) sort: (Category, bool),
|
||||
pub(super) sort: Option<(Category, bool)>,
|
||||
|
||||
/// Application-managed data associated with each item
|
||||
pub(super) storage: Storage,
|
||||
|
|
@ -62,7 +62,7 @@ where
|
|||
indents: SecondaryMap::default(),
|
||||
order: VecDeque::new(),
|
||||
selection: SelectionMode::default(),
|
||||
sort: (Category::default(), false),
|
||||
sort: None,
|
||||
storage: Storage::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -342,12 +342,18 @@ where
|
|||
|
||||
/// Sorts items in the model, this should be called before it is drawn after all items have been added for the view
|
||||
pub fn sort(&mut self, category: Category, ascending: bool) {
|
||||
self.sort = (category, ascending);
|
||||
self.sort = Some((category, ascending));
|
||||
let mut order: Vec<Entity> = self.order.iter().cloned().collect();
|
||||
order.sort_by(|entity_a, entity_b| {
|
||||
self.item(*entity_a)
|
||||
.unwrap()
|
||||
.compare(self.item(*entity_b).unwrap(), category)
|
||||
if ascending {
|
||||
self.item(*entity_a)
|
||||
.unwrap()
|
||||
.compare(self.item(*entity_b).unwrap(), category)
|
||||
} else {
|
||||
self.item(*entity_b)
|
||||
.unwrap()
|
||||
.compare(self.item(*entity_a).unwrap(), category)
|
||||
}
|
||||
});
|
||||
self.order = order.into();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
use super::model::{
|
||||
category::{ItemCategory, ItemInterface},
|
||||
selection::Selectable,
|
||||
Entity, Model,
|
||||
};
|
||||
use crate::{
|
||||
ext::CollectionWidget,
|
||||
theme,
|
||||
widget::{self, container, divider},
|
||||
Apply, Element,
|
||||
};
|
||||
use iced::{Alignment, Padding};
|
||||
use iced_widget::container::Catalog;
|
||||
|
||||
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
|
||||
|
||||
#[must_use]
|
||||
pub struct TableView<'a, SelectionMode, Item, Category, Message>
|
||||
where
|
||||
Category: ItemCategory,
|
||||
Item: ItemInterface<Category>,
|
||||
Model<SelectionMode, Item, Category>: Selectable,
|
||||
SelectionMode: Default,
|
||||
{
|
||||
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
||||
|
||||
pub(super) spacing: u16,
|
||||
pub(super) padding: Padding,
|
||||
pub(super) list_item_padding: Padding,
|
||||
pub(super) divider_padding: Padding,
|
||||
pub(super) style: theme::Container<'a>,
|
||||
|
||||
pub(super) on_selected: Box<dyn Fn(Entity) -> Message + 'a>,
|
||||
pub(super) on_category_select: Box<dyn Fn(Category, bool) -> Message + 'a>,
|
||||
pub(super) on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||
}
|
||||
|
||||
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>,
|
||||
{
|
||||
pub fn new(
|
||||
model: &'a Model<SelectionMode, Item, Category>,
|
||||
on_selected: impl Fn(Entity) -> Message + 'a,
|
||||
on_category_select: impl Fn(Category, bool) -> Message + 'a,
|
||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||
) -> Self {
|
||||
let cosmic_theme::Spacing {
|
||||
space_xxxs,
|
||||
space_xxs,
|
||||
..
|
||||
} = theme::active().cosmic().spacing;
|
||||
Self {
|
||||
model,
|
||||
spacing: 0,
|
||||
padding: Padding::from(0),
|
||||
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
||||
list_item_padding: Padding::from(space_xxs).into(),
|
||||
style: theme::Container::Background,
|
||||
|
||||
on_selected: Box::new(on_selected),
|
||||
on_category_select: Box::new(on_category_select),
|
||||
on_option_hovered,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spacing(mut self, spacing: u16) -> Self {
|
||||
self.spacing = spacing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style variant of this [`Circular`].
|
||||
pub fn style(mut self, style: <crate::Theme as Catalog>::Class<'a>) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn divider_padding(mut self, padding: u16) -> Self {
|
||||
self.divider_padding = Padding::from(0).left(padding).right(padding);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list_item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.list_item_padding = padding.into();
|
||||
self
|
||||
}
|
||||
#[must_use]
|
||||
pub fn into_element(self) -> Element<'a, Message> {
|
||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
crate::widget::column()
|
||||
.push(widget::row::with_children(
|
||||
self.model
|
||||
.categories
|
||||
.iter()
|
||||
.map(|category| {
|
||||
container(
|
||||
widget::row()
|
||||
.spacing(space_xxxs)
|
||||
.push(widget::text::heading(category.to_string()))
|
||||
.push_maybe(if self.model.sort.0 == *category {
|
||||
match self.model.sort.1 {
|
||||
true => {
|
||||
Some(widget::icon::from_name("pan-up-symbolic").icon())
|
||||
}
|
||||
false => Some(
|
||||
widget::icon::from_name("pan-down-symbolic").icon(),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
)
|
||||
.padding(
|
||||
Padding::from(0)
|
||||
.left(self.list_item_padding.left)
|
||||
.right(self.list_item_padding.right),
|
||||
)
|
||||
.width(category.width())
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
.append(&mut if self.model.items.is_empty() {
|
||||
vec![container(divider::horizontal::default()).padding(self.divider_padding)]
|
||||
} else {
|
||||
self.model
|
||||
.order
|
||||
.iter()
|
||||
.map(|entity| {
|
||||
let item = self.model.item(*entity).unwrap();
|
||||
let categories = &self.model.categories;
|
||||
|
||||
vec![
|
||||
container(divider::horizontal::default()).padding(self.divider_padding),
|
||||
container(widget::row::with_children(
|
||||
categories
|
||||
.iter()
|
||||
.map(|category| {
|
||||
container(
|
||||
widget::row()
|
||||
.push_maybe(item.get_icon(*category))
|
||||
.push(widget::text::body(item.get_text(*category))),
|
||||
)
|
||||
.width(category.width())
|
||||
.align_y(Alignment::Center)
|
||||
.padding(self.list_item_padding)
|
||||
.apply(Element::from)
|
||||
})
|
||||
.collect(),
|
||||
)),
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
})
|
||||
.spacing(self.spacing)
|
||||
.padding(self.padding)
|
||||
.apply(container)
|
||||
.padding([self.spacing, 0])
|
||||
.class(self.style)
|
||||
.width(iced::Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
331
src/widget/table/widget/mod.rs
Normal file
331
src/widget/table/widget/mod.rs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
mod state;
|
||||
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
|
||||
|
||||
#[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>,
|
||||
|
||||
pub(super) item_spacing: u16,
|
||||
pub(super) element_padding: Padding,
|
||||
pub(super) item_padding: Padding,
|
||||
pub(super) divider_padding: Padding,
|
||||
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
||||
|
||||
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||
pub(super) on_item_context: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||
pub(super) on_category_select: Option<Box<dyn Fn(Category, bool) -> Message + 'a>>,
|
||||
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 item_spacing(mut self, spacing: u16) -> Self {
|
||||
self.item_spacing = spacing;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn element_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.element_padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn divider_padding(mut self, padding: u16) -> Self {
|
||||
self.divider_padding = Padding::from(0).left(padding).right(padding);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn item_padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
self.item_padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
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 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
|
||||
.order
|
||||
.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()
|
||||
.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)
|
||||
.padding(self.item_padding)
|
||||
.apply(Element::from)
|
||||
})
|
||||
.collect::<Vec<Element<'a, Message>>>()
|
||||
.apply(widget::row::with_children)
|
||||
.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(container)
|
||||
.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(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)
|
||||
.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();
|
||||
widget::column()
|
||||
.push(
|
||||
widget::divider::horizontal::default()
|
||||
.apply(container)
|
||||
.padding(self.divider_padding),
|
||||
)
|
||||
.push(
|
||||
widget::row()
|
||||
.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),
|
||||
)
|
||||
.apply(Element::from)
|
||||
})
|
||||
.collect::<Vec<Element<'a, Message>>>()
|
||||
.apply(widget::column::with_children)
|
||||
.apply(Element::from)
|
||||
}
|
||||
}
|
||||
8
src/widget/table/widget/state.rs
Normal file
8
src/widget/table/widget/state.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
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