Added custom widget version of table
Currently does not have mouse interaction, in the demo the normal one is right and custom is left
This commit is contained in:
parent
5c96696f9c
commit
825792e367
4 changed files with 765 additions and 91 deletions
|
|
@ -10,7 +10,7 @@ use cosmic::widget::nav_bar;
|
||||||
use cosmic::widget::table;
|
use cosmic::widget::table;
|
||||||
use cosmic::{executor, iced, Element};
|
use cosmic::{executor, iced, Element};
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
#[default]
|
#[default]
|
||||||
Name,
|
Name,
|
||||||
|
|
@ -29,11 +29,11 @@ impl std::fmt::Display for Category {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl table::ItemCategory for Category {
|
impl table::ItemCategory for Category {
|
||||||
fn width(&self) -> iced::Length {
|
fn width(&self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
Self::Name => iced::Length::Fill,
|
Self::Name => 300,
|
||||||
Self::Date => iced::Length::Fixed(200.0),
|
Self::Date => 200,
|
||||||
Self::Size => iced::Length::Fixed(150.0),
|
Self::Size => 150,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,11 +63,11 @@ impl table::ItemInterface<Category> for Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_text(&self, category: Category) -> std::borrow::Cow<'static, str> {
|
fn get_text(&self, category: Category) -> Option<String> {
|
||||||
match category {
|
match category {
|
||||||
Category::Name => self.name.clone().into(),
|
Category::Name => Some(self.name.clone()),
|
||||||
Category::Date => self.date.format("%Y/%m/%d").to_string().into(),
|
Category::Date => Some(self.date.format("%Y/%m/%d").to_string()),
|
||||||
Category::Size => format!("{} items", self.size).into(),
|
Category::Size => Some(format!("{} items", self.size)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,19 +192,30 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
/// Creates a view after each update.
|
/// Creates a view after each update.
|
||||||
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 {
|
||||||
|
// table::SingleSelectTableView::new(&self.table_model)
|
||||||
|
// .on_item_select(Message::ItemSelect)
|
||||||
|
// .on_category_select(Message::CategorySelect)
|
||||||
|
// .element_compact()
|
||||||
|
// } else {
|
||||||
|
// table::SingleSelectTableView::new(&self.table_model)
|
||||||
|
// .on_item_select(Message::ItemSelect)
|
||||||
|
// .on_category_select(Message::CategorySelect)
|
||||||
|
// .element_standard()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .into()
|
||||||
|
cosmic::widget::row()
|
||||||
|
.push(Element::new(
|
||||||
table::SingleSelectTableView::new(&self.table_model)
|
table::SingleSelectTableView::new(&self.table_model)
|
||||||
.on_item_select(Message::ItemSelect)
|
.on_item_left(Message::ItemSelect),
|
||||||
.on_category_select(Message::CategorySelect)
|
))
|
||||||
.element_compact()
|
.push(
|
||||||
} else {
|
|
||||||
table::SingleSelectTableView::new(&self.table_model)
|
table::SingleSelectTableView::new(&self.table_model)
|
||||||
.on_item_select(Message::ItemSelect)
|
.on_item_left(Message::ItemSelect)
|
||||||
.on_category_select(Message::CategorySelect)
|
.element_standard(),
|
||||||
.element_standard()
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ use crate::widget::Icon;
|
||||||
/// Ideally, this is implemented on an enum.
|
/// Ideally, this is implemented on an enum.
|
||||||
pub trait ItemCategory: Default + std::fmt::Display + Clone + Copy + PartialEq + Eq {
|
pub trait ItemCategory: Default + std::fmt::Display + Clone + Copy + PartialEq + Eq {
|
||||||
/// Function that gets the width of the data
|
/// Function that gets the width of the data
|
||||||
fn width(&self) -> iced::Length;
|
fn width(&self) -> u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ItemInterface<Category: ItemCategory>: Default {
|
pub trait ItemInterface<Category: ItemCategory>: Default {
|
||||||
fn get_icon(&self, category: Category) -> Option<Icon>;
|
fn get_icon(&self, category: Category) -> Option<Icon>;
|
||||||
fn get_text(&self, category: Category) -> Cow<'static, str>;
|
fn get_text(&self, category: Category) -> Option<String>;
|
||||||
|
|
||||||
fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering;
|
fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,24 @@
|
||||||
mod state;
|
mod state;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
use iced_core::{
|
||||||
|
image::{self, Renderer as ImageRenderer},
|
||||||
|
Layout,
|
||||||
|
};
|
||||||
|
use iced_core::{layout, text::Renderer as TextRenderer};
|
||||||
|
use iced_core::{renderer::Quad, Renderer as IcedRenderer};
|
||||||
|
|
||||||
|
use derive_setters::Setters;
|
||||||
|
use iced_core::{
|
||||||
|
text::{LineHeight, Shaping, Wrapping},
|
||||||
|
widget::{tree, Tree},
|
||||||
|
Text, Widget,
|
||||||
|
};
|
||||||
|
use palette::named::BLACK;
|
||||||
|
use slotmap::SecondaryMap;
|
||||||
use state::State;
|
use state::State;
|
||||||
|
|
||||||
use super::model::{
|
use super::model::{
|
||||||
|
|
@ -9,46 +29,216 @@ use super::model::{
|
||||||
use crate::{
|
use crate::{
|
||||||
ext::CollectionWidget,
|
ext::CollectionWidget,
|
||||||
theme,
|
theme,
|
||||||
widget::{self, container, divider, menu},
|
widget::{
|
||||||
|
self, container, divider,
|
||||||
|
dnd_destination::DragId,
|
||||||
|
menu::{self, menu_roots_children, menu_roots_diff, MenuBarState},
|
||||||
|
},
|
||||||
Apply, Element,
|
Apply, Element,
|
||||||
};
|
};
|
||||||
use iced::{Alignment, Border, Length, Padding};
|
use iced::{
|
||||||
|
alignment,
|
||||||
|
clipboard::{dnd::DndAction, mime::AllowedMimeTypes},
|
||||||
|
Alignment, Background, Border, Length, Padding, Point, Rectangle, Size, Vector,
|
||||||
|
};
|
||||||
|
|
||||||
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
|
// THIS IS A PLACEHOLDER UNTIL A MORE SOPHISTICATED WIDGET CAN BE DEVELOPED
|
||||||
|
|
||||||
|
#[derive(Setters)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct TableView<'a, SelectionMode, Item, Category, Message>
|
pub struct TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
where
|
where
|
||||||
Category: ItemCategory,
|
Category: ItemCategory + Hash + 'static,
|
||||||
Item: ItemInterface<Category>,
|
Item: ItemInterface<Category>,
|
||||||
Model<SelectionMode, Item, Category>: Selectable,
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
SelectionMode: Default,
|
SelectionMode: Default,
|
||||||
Message: Clone + 'static,
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
|
/// iced widget ID
|
||||||
|
pub(super) id: widget::Id,
|
||||||
|
/// The table model
|
||||||
|
#[setters(skip)]
|
||||||
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
pub(super) model: &'a Model<SelectionMode, Item, Category>,
|
||||||
|
|
||||||
|
// === Element Layout ===
|
||||||
|
/// Desired width of the widget.
|
||||||
|
pub(super) width: Length,
|
||||||
|
/// Desired height of the widget.
|
||||||
|
pub(super) height: Length,
|
||||||
|
/// Spacing between items and the dividers
|
||||||
pub(super) item_spacing: u16,
|
pub(super) item_spacing: u16,
|
||||||
|
/// Spacing between text and icons in items
|
||||||
|
pub(super) icon_spacing: u16,
|
||||||
|
/// Size of the icon
|
||||||
|
pub(super) icon_size: u16,
|
||||||
|
/// The size of a single indent
|
||||||
|
pub(super) indent_spacing: u16,
|
||||||
|
/// The padding for the entire table
|
||||||
|
#[setters(into)]
|
||||||
pub(super) element_padding: Padding,
|
pub(super) element_padding: Padding,
|
||||||
|
/// The padding for each item
|
||||||
|
#[setters(into)]
|
||||||
pub(super) item_padding: Padding,
|
pub(super) item_padding: Padding,
|
||||||
|
/// the horizontal padding for each divider
|
||||||
|
#[setters(into)]
|
||||||
pub(super) divider_padding: Padding,
|
pub(super) divider_padding: Padding,
|
||||||
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
/// Size of the font.
|
||||||
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message>>>,
|
pub(super) font_size: f32,
|
||||||
|
|
||||||
pub(super) on_item_select: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
/// The context tree to show on right clicking an item
|
||||||
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>>,
|
pub(super) item_context_tree: Option<Vec<menu::Tree<'a, Message, crate::Renderer>>>,
|
||||||
|
/// The context tree to show on right clicking a category
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) category_context_tree: Option<Vec<menu::Tree<'a, Message, crate::Renderer>>>,
|
||||||
|
|
||||||
|
// === Item Mouse Events ===
|
||||||
|
/// Message to emit when the user left clicks on a table item
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_left: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
/// Message to emit when the user double clicks on a table item
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_double: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
/// Message to emit when the user middle clicks on a table item
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_middle: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
/// Message to emit when the user right clicks on a table item
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_item_right: Option<Box<dyn Fn(Entity) -> Message + 'a>>,
|
||||||
|
|
||||||
|
// === Category Mouse Events ===
|
||||||
|
/// Message to emit when the user clicks on a category
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_category_select: Option<Box<dyn Fn(Category) -> Message + 'a>>,
|
||||||
|
/// Message to emit when the user right clicks on a category
|
||||||
|
#[setters(skip)]
|
||||||
pub(super) on_category_context: Option<Box<dyn Fn(Category) -> Message + 'a>>,
|
pub(super) on_category_context: Option<Box<dyn Fn(Category) -> Message + 'a>>,
|
||||||
|
|
||||||
|
// === Drag n Drop ===
|
||||||
|
/// Message to emit on the DND drop event
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_drop:
|
||||||
|
Option<Box<dyn Fn(Entity, Vec<u8>, String, DndAction) -> Message + 'static>>,
|
||||||
|
/// MIME Types for the Drag n Drop
|
||||||
|
pub(super) mimes: Vec<String>,
|
||||||
|
/// Message to emit on the DND enter event
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_enter: Option<Box<dyn Fn(Entity, Vec<String>) -> Message + 'static>>,
|
||||||
|
/// Message to emit on the DND leave event
|
||||||
|
#[setters(skip)]
|
||||||
|
pub(super) on_dnd_leave: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
||||||
|
/// The Drag ID of the table
|
||||||
|
#[setters(strip_option)]
|
||||||
|
pub(super) drag_id: Option<DragId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PRIVATE INTERFACE
|
||||||
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
|
TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
Category: ItemCategory + Hash + 'static,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
SelectionMode: Default,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
fn max_item_dimensions(
|
||||||
|
&self,
|
||||||
|
state: &mut State<Category>,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
) -> (f32, f32) {
|
||||||
|
let mut width = 0.0f32;
|
||||||
|
let mut height = 0.0f32;
|
||||||
|
let font = renderer.default_font();
|
||||||
|
|
||||||
|
for key in self.model.iter() {
|
||||||
|
let (button_width, button_height) = self.item_dimensions(state, font, key);
|
||||||
|
|
||||||
|
state
|
||||||
|
.item_layout
|
||||||
|
.push(Size::new(button_width, button_height));
|
||||||
|
|
||||||
|
height = height.max(button_height);
|
||||||
|
width = width.max(button_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
for size in &mut state.item_layout {
|
||||||
|
size.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn item_dimensions(
|
||||||
|
&self,
|
||||||
|
state: &mut State<Category>,
|
||||||
|
font: crate::font::Font,
|
||||||
|
id: Entity,
|
||||||
|
) -> (f32, f32) {
|
||||||
|
let mut width = 0f32;
|
||||||
|
let mut height = 0f32;
|
||||||
|
let mut icon_spacing = 0f32;
|
||||||
|
|
||||||
|
if let Some((item, paragraphs)) = self.model.item(id).zip(state.paragraphs.entry(id)) {
|
||||||
|
let paragraphs = paragraphs.or_default();
|
||||||
|
for category in self.model.categories.iter().copied() {
|
||||||
|
if let Some(text) = item.get_text(category) {
|
||||||
|
if !text.is_empty() {
|
||||||
|
icon_spacing = f32::from(self.icon_spacing);
|
||||||
|
let paragraph = if let Some(entry) = paragraphs.get(&category) {
|
||||||
|
entry.clone()
|
||||||
|
} else {
|
||||||
|
crate::Plain::new(Text {
|
||||||
|
content: text.as_str(),
|
||||||
|
size: iced::Pixels(self.font_size),
|
||||||
|
bounds: Size::INFINITY
|
||||||
|
.apply(|inf| Size::new(category.width() as f32, inf.height)),
|
||||||
|
font,
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
shaping: Shaping::Advanced,
|
||||||
|
wrapping: Wrapping::default(),
|
||||||
|
line_height: LineHeight::default(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = paragraph.min_bounds();
|
||||||
|
width += size.width;
|
||||||
|
height = height.max(size.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add icon to measurement if icon was given.
|
||||||
|
if let Some(_) = item.get_icon(category) {
|
||||||
|
width += f32::from(self.icon_size) + icon_spacing;
|
||||||
|
height = height.max(f32::from(self.icon_size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add indent to measurement if found.
|
||||||
|
if let Some(indent) = self.model.indent(id) {
|
||||||
|
width = f32::from(indent).mul_add(f32::from(self.indent_spacing), width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add button padding to the max size found
|
||||||
|
width += f32::from(self.item_padding.left) + f32::from(self.item_padding.right);
|
||||||
|
height += f32::from(self.item_padding.top) + f32::from(self.item_padding.top);
|
||||||
|
|
||||||
|
(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUBLIC INTERFACE
|
||||||
impl<'a, SelectionMode, Item, Category, Message>
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
TableView<'a, SelectionMode, Item, Category, Message>
|
TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
where
|
where
|
||||||
SelectionMode: Default,
|
SelectionMode: Default,
|
||||||
Model<SelectionMode, Item, Category>: Selectable,
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
Category: ItemCategory,
|
Category: ItemCategory + Hash + 'static,
|
||||||
Item: ItemInterface<Category>,
|
Item: ItemInterface<Category>,
|
||||||
Message: Clone + 'static,
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
|
/// Creates a new table view with the given model
|
||||||
pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
|
pub fn new(model: &'a Model<SelectionMode, Item, Category>) -> Self {
|
||||||
let cosmic_theme::Spacing {
|
let cosmic_theme::Spacing {
|
||||||
space_xxxs,
|
space_xxxs,
|
||||||
|
|
@ -57,53 +247,67 @@ where
|
||||||
} = theme::active().cosmic().spacing;
|
} = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
id: widget::Id::unique(),
|
||||||
model,
|
model,
|
||||||
|
width: Length::Fill,
|
||||||
|
height: Length::Shrink,
|
||||||
item_spacing: 0,
|
item_spacing: 0,
|
||||||
element_padding: Padding::from(0),
|
element_padding: Padding::from(0),
|
||||||
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
|
||||||
item_padding: Padding::from(space_xxs).into(),
|
item_padding: Padding::from(space_xxs).into(),
|
||||||
on_item_select: None,
|
icon_spacing: space_xxxs,
|
||||||
on_item_context: None,
|
icon_size: 24,
|
||||||
|
indent_spacing: space_xxs,
|
||||||
|
font_size: 14.0,
|
||||||
item_context_tree: None,
|
item_context_tree: None,
|
||||||
|
category_context_tree: None,
|
||||||
on_category_select: None,
|
on_category_select: None,
|
||||||
on_category_context: None,
|
on_category_context: None,
|
||||||
category_context_tree: None,
|
on_item_left: None,
|
||||||
|
on_item_double: None,
|
||||||
|
on_item_middle: None,
|
||||||
|
on_item_right: None,
|
||||||
|
on_dnd_drop: None,
|
||||||
|
mimes: Vec::new(),
|
||||||
|
on_dnd_enter: None,
|
||||||
|
on_dnd_leave: None,
|
||||||
|
drag_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn item_spacing(mut self, spacing: u16) -> Self {
|
/// Sets the message to be emitted on left click
|
||||||
self.item_spacing = spacing;
|
pub fn on_item_left<F>(mut self, on_left: F) -> Self
|
||||||
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
|
where
|
||||||
F: Fn(Entity) -> Message + 'a,
|
F: Fn(Entity) -> Message + 'a,
|
||||||
{
|
{
|
||||||
self.on_item_select = Some(Box::new(on_select));
|
self.on_item_left = Some(Box::new(on_left));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_item_context<F>(mut self, on_select: F) -> Self
|
/// Sets the message to be emitted on double click
|
||||||
|
pub fn on_item_double<F>(mut self, on_double: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(Entity) -> Message + 'a,
|
F: Fn(Entity) -> Message + 'a,
|
||||||
{
|
{
|
||||||
self.on_item_context = Some(Box::new(on_select));
|
self.on_item_double = Some(Box::new(on_double));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the message to be emitted on middle click
|
||||||
|
pub fn on_item_middle<F>(mut self, on_middle: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_middle = Some(Box::new(on_middle));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the message to be emitted on right click
|
||||||
|
pub fn on_item_right<F>(mut self, on_right: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Entity) -> Message + 'a,
|
||||||
|
{
|
||||||
|
self.on_item_right = Some(Box::new(on_right));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +327,7 @@ where
|
||||||
|
|
||||||
pub fn on_category_select<F>(mut self, on_select: F) -> Self
|
pub fn on_category_select<F>(mut self, on_select: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(Category, bool) -> Message + 'a,
|
F: Fn(Category) -> Message + 'a,
|
||||||
{
|
{
|
||||||
self.on_category_select = Some(Box::new(on_select));
|
self.on_category_select = Some(Box::new(on_select));
|
||||||
self
|
self
|
||||||
|
|
@ -137,6 +341,448 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd drop event.
|
||||||
|
pub fn on_dnd_drop<D: AllowedMimeTypes>(
|
||||||
|
mut self,
|
||||||
|
dnd_drop_handler: impl Fn(Entity, Option<D>, DndAction) -> Message + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_dnd_drop = Some(Box::new(move |entity, data, mime, action| {
|
||||||
|
dnd_drop_handler(entity, D::try_from((data, mime)).ok(), action)
|
||||||
|
}));
|
||||||
|
self.mimes = D::allowed().iter().cloned().collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd enter event.
|
||||||
|
pub fn on_dnd_enter(
|
||||||
|
mut self,
|
||||||
|
dnd_enter_handler: impl Fn(Entity, Vec<String>) -> Message + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.on_dnd_enter = Some(Box::new(dnd_enter_handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the dnd leave event.
|
||||||
|
pub fn on_dnd_leave(mut self, dnd_leave_handler: impl Fn(Entity) -> Message + 'static) -> Self {
|
||||||
|
self.on_dnd_leave = Some(Box::new(dnd_leave_handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget implementation
|
||||||
|
impl<'a, SelectionMode, Item, Category, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
|
for TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
SelectionMode: Default,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
Category: ItemCategory + Hash + 'static,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
let mut children = Vec::new();
|
||||||
|
|
||||||
|
// Item context tree
|
||||||
|
if let Some(ref item_context) = self.item_context_tree {
|
||||||
|
let mut tree = Tree::empty();
|
||||||
|
tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children = menu_roots_children(&item_context);
|
||||||
|
children.push(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category context tree
|
||||||
|
if let Some(ref category_context) = self.category_context_tree {
|
||||||
|
let mut tree = Tree::empty();
|
||||||
|
tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children = menu_roots_children(&category_context);
|
||||||
|
children.push(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
children
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State<Category>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
#[allow(clippy::default_trait_access)]
|
||||||
|
tree::State::new(State::<Category> {
|
||||||
|
num_items: self.model.order.len(),
|
||||||
|
selected: self.model.active.clone(),
|
||||||
|
paragraphs: SecondaryMap::new(),
|
||||||
|
item_layout: Vec::new(),
|
||||||
|
text_hashes: SecondaryMap::new(),
|
||||||
|
|
||||||
|
sort_hash: HashMap::new(),
|
||||||
|
header_paragraphs: HashMap::new(),
|
||||||
|
category_layout: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
let state = tree.state.downcast_mut::<State<Category>>();
|
||||||
|
|
||||||
|
let sort_state = self.model.sort;
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
sort_state.hash(&mut hasher);
|
||||||
|
let cat_hash = hasher.finish();
|
||||||
|
for category in self.model.categories.iter().copied() {
|
||||||
|
if let Some(prev_hash) = state.sort_hash.insert(category, cat_hash) {
|
||||||
|
if prev_hash == cat_hash {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let category_name = category.to_string();
|
||||||
|
let text = Text {
|
||||||
|
content: category_name.as_str(),
|
||||||
|
size: iced::Pixels(self.font_size),
|
||||||
|
bounds: Size::INFINITY,
|
||||||
|
font: crate::font::bold(),
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
shaping: Shaping::Advanced,
|
||||||
|
wrapping: Wrapping::default(),
|
||||||
|
line_height: LineHeight::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(header) = state.header_paragraphs.get_mut(&category) {
|
||||||
|
header.update(text);
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
.header_paragraphs
|
||||||
|
.insert(category, crate::Plain::new(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in self.model.iter() {
|
||||||
|
if let Some(item) = self.model.item(key) {
|
||||||
|
// TODO: add hover support if design approves it
|
||||||
|
let button_state = self.model.is_active(key);
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
button_state.hash(&mut hasher);
|
||||||
|
// hash each text
|
||||||
|
for category in self.model.categories.iter().copied() {
|
||||||
|
let text = item.get_text(category);
|
||||||
|
|
||||||
|
text.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
let text_hash = hasher.finish();
|
||||||
|
|
||||||
|
if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
|
||||||
|
// If the text didn't change, don't update the paragraph
|
||||||
|
if prev_hash == text_hash {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.selected.insert(key, button_state);
|
||||||
|
|
||||||
|
// Update the paragraph if the text changed
|
||||||
|
for category in self.model.categories.iter().copied() {
|
||||||
|
if let Some(text) = item.get_text(category) {
|
||||||
|
let text = Text {
|
||||||
|
content: text.as_ref(),
|
||||||
|
size: iced::Pixels(self.font_size),
|
||||||
|
bounds: Size::INFINITY,
|
||||||
|
font: crate::font::default(),
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
|
shaping: Shaping::Advanced,
|
||||||
|
wrapping: Wrapping::default(),
|
||||||
|
line_height: LineHeight::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(item) = state.paragraphs.get_mut(key) {
|
||||||
|
if let Some(paragraph) = item.get_mut(&category) {
|
||||||
|
paragraph.update(text);
|
||||||
|
} else {
|
||||||
|
item.insert(category, crate::Plain::new(text));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut hm = HashMap::new();
|
||||||
|
hm.insert(category, crate::Plain::new(text));
|
||||||
|
state.paragraphs.insert(key, hm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG: IF THE CATEGORY_CONTEXT AND ITEM_CONTEXT ARE CLEARED AND A DIFFERENT ONE IS SET, IT BREAKS
|
||||||
|
// Diff the item context menu
|
||||||
|
if let Some(ref mut item_context) = self.item_context_tree {
|
||||||
|
if let Some(ref mut category_context) = self.category_context_tree {
|
||||||
|
if tree.children.is_empty() {
|
||||||
|
let mut child_tree = Tree::empty();
|
||||||
|
child_tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children.push(child_tree);
|
||||||
|
|
||||||
|
let mut child_tree = Tree::empty();
|
||||||
|
child_tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children.push(child_tree);
|
||||||
|
} else {
|
||||||
|
tree.children.truncate(2);
|
||||||
|
}
|
||||||
|
menu_roots_diff(item_context, &mut tree.children[0]);
|
||||||
|
menu_roots_diff(category_context, &mut tree.children[1]);
|
||||||
|
} else {
|
||||||
|
if tree.children.is_empty() {
|
||||||
|
let mut child_tree = Tree::empty();
|
||||||
|
child_tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children.push(child_tree);
|
||||||
|
} else {
|
||||||
|
tree.children.truncate(1);
|
||||||
|
}
|
||||||
|
menu_roots_diff(item_context, &mut tree.children[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(ref mut category_context) = self.category_context_tree {
|
||||||
|
if tree.children.is_empty() {
|
||||||
|
let mut child_tree = Tree::empty();
|
||||||
|
child_tree.state = tree::State::new(MenuBarState::default());
|
||||||
|
tree.children.push(child_tree);
|
||||||
|
} else {
|
||||||
|
tree.children.truncate(1);
|
||||||
|
}
|
||||||
|
menu_roots_diff(category_context, &mut tree.children[0]);
|
||||||
|
} else {
|
||||||
|
tree.children.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> iced::Size<Length> {
|
||||||
|
Size::new(self.width, self.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
renderer: &crate::Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let state = tree.state.downcast_mut::<State<Category>>();
|
||||||
|
let size = {
|
||||||
|
state.item_layout.clear();
|
||||||
|
state.category_layout.clear();
|
||||||
|
let limits = limits.width(self.width);
|
||||||
|
|
||||||
|
for category in self.model.categories.iter().copied() {
|
||||||
|
state
|
||||||
|
.category_layout
|
||||||
|
.push(Size::new(category.width() as f32, 20.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (width, item_height) = self.max_item_dimensions(state, renderer);
|
||||||
|
|
||||||
|
for size in &mut state.item_layout {
|
||||||
|
size.width = width.max(limits.max().width);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spacing = f32::from(self.item_spacing);
|
||||||
|
let mut height = state.category_layout[0].height;
|
||||||
|
for _ in self.model.iter() {
|
||||||
|
height += spacing + 1.0 + spacing;
|
||||||
|
height += item_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
limits.height(Length::Fixed(height)).resolve(
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
Size::new(width, height),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
layout::Node::new(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut crate::Renderer,
|
||||||
|
theme: &crate::Theme,
|
||||||
|
style: &iced_core::renderer::Style,
|
||||||
|
layout: iced_core::Layout<'_>,
|
||||||
|
cursor: iced_core::mouse::Cursor,
|
||||||
|
viewport: &iced::Rectangle,
|
||||||
|
) {
|
||||||
|
let state = tree.state.downcast_ref::<State<Category>>();
|
||||||
|
let cosmic = theme.cosmic();
|
||||||
|
let accent = theme::active().cosmic().accent_color();
|
||||||
|
|
||||||
|
let bounds = layout.bounds().shrink(self.element_padding);
|
||||||
|
|
||||||
|
let header_quad = Rectangle::new(bounds.position(), Size::new(bounds.width, 20.0)).shrink(
|
||||||
|
Padding::new(0.0)
|
||||||
|
.left(self.item_padding.left)
|
||||||
|
.right(self.item_padding.right),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: HEADER TEXT
|
||||||
|
let mut category_origin = header_quad.position();
|
||||||
|
for (nth, category) in self.model.categories.iter().copied().enumerate() {
|
||||||
|
let category_quad = Rectangle::new(category_origin, state.category_layout[nth]);
|
||||||
|
if let Some(category_quad) = category_quad.intersection(&bounds) {
|
||||||
|
renderer.fill_paragraph(
|
||||||
|
state.header_paragraphs.get(&category).unwrap().raw(),
|
||||||
|
Point::new(category_quad.position().x, category_quad.center_y()),
|
||||||
|
cosmic.on_bg_color().into(),
|
||||||
|
category_quad,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
category_origin.x += category_quad.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_quad = Rectangle::new(
|
||||||
|
layout.position() + Vector::new(0.0, 20.0),
|
||||||
|
Size::new(bounds.width, bounds.height - 20.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.model.order.is_empty() {
|
||||||
|
let divider_quad = layout
|
||||||
|
.bounds()
|
||||||
|
.shrink(self.divider_padding)
|
||||||
|
.apply(|mut dq| {
|
||||||
|
dq.height = 1.0;
|
||||||
|
dq
|
||||||
|
});
|
||||||
|
|
||||||
|
// If empty, draw a single divider and quit
|
||||||
|
renderer.fill_quad(
|
||||||
|
Quad {
|
||||||
|
bounds: divider_quad,
|
||||||
|
border: Default::default(),
|
||||||
|
shadow: Default::default(),
|
||||||
|
},
|
||||||
|
Background::Color(cosmic.bg_divider().into()),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut divider_quad = body_quad.shrink(self.divider_padding).apply(|mut dq| {
|
||||||
|
dq.height = 1.0;
|
||||||
|
dq
|
||||||
|
});
|
||||||
|
let mut item_quad = body_quad.shrink(self.element_padding);
|
||||||
|
for (nth, entity) in self.model.iter().enumerate() {
|
||||||
|
// draw divider above
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
Quad {
|
||||||
|
bounds: divider_quad,
|
||||||
|
border: Default::default(),
|
||||||
|
shadow: Default::default(),
|
||||||
|
},
|
||||||
|
Background::Color(cosmic.bg_divider().into()),
|
||||||
|
);
|
||||||
|
|
||||||
|
divider_quad.y += 1.0;
|
||||||
|
item_quad.y += 1.0;
|
||||||
|
item_quad.width = state.item_layout[nth].width;
|
||||||
|
item_quad.height = state.item_layout[nth].height;
|
||||||
|
|
||||||
|
if state.selected.get(entity).copied().unwrap_or(false) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
Quad {
|
||||||
|
bounds: item_quad,
|
||||||
|
border: Border {
|
||||||
|
color: iced::Color::TRANSPARENT,
|
||||||
|
width: 0.0,
|
||||||
|
radius: cosmic.radius_xs().into(),
|
||||||
|
},
|
||||||
|
shadow: Default::default(),
|
||||||
|
},
|
||||||
|
Background::Color(accent.into()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_quad = item_quad.clone().shrink(self.item_padding);
|
||||||
|
let mut item_content_quad = content_quad.clone();
|
||||||
|
if let Some(item) = self.model.item(entity) {
|
||||||
|
for (nth, category) in self.model.categories.iter().copied().enumerate() {
|
||||||
|
// TODO: Icons and text
|
||||||
|
let mut icon_spacing = 0;
|
||||||
|
if let Some((_, _)) = item.get_icon(category).zip(item.get_text(category)) {
|
||||||
|
icon_spacing = self.icon_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
item_content_quad.width = state.category_layout[nth].width;
|
||||||
|
|
||||||
|
if let Some(mut item_content_quad) =
|
||||||
|
item_content_quad.intersection(&content_quad)
|
||||||
|
{
|
||||||
|
let mut offset = Point::<f32>::default();
|
||||||
|
if let Some(icon) = item.get_icon(category) {
|
||||||
|
let layout_node = layout::Node::new(Size {
|
||||||
|
width: self.icon_size as f32,
|
||||||
|
height: self.icon_size as f32,
|
||||||
|
})
|
||||||
|
.move_to(Point {
|
||||||
|
x: item_content_quad.x,
|
||||||
|
y: item_content_quad.y,
|
||||||
|
});
|
||||||
|
Widget::<Message, crate::Theme, crate::Renderer>::draw(
|
||||||
|
Element::<Message>::from(icon.clone()).as_widget(),
|
||||||
|
&Tree::empty(),
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
Layout::new(&layout_node),
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
offset.x += self.icon_size as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text) = item.get_text(category) {
|
||||||
|
offset.x += icon_spacing as f32;
|
||||||
|
|
||||||
|
item_content_quad.x = item_content_quad.x + offset.x;
|
||||||
|
item_content_quad.width -= offset.x;
|
||||||
|
|
||||||
|
renderer.fill_paragraph(
|
||||||
|
state
|
||||||
|
.paragraphs
|
||||||
|
.get(entity)
|
||||||
|
.unwrap()
|
||||||
|
.get(&category)
|
||||||
|
.unwrap()
|
||||||
|
.raw(),
|
||||||
|
Point::new(
|
||||||
|
item_content_quad.x,
|
||||||
|
item_content_quad.y + item_content_quad.height / 2.0,
|
||||||
|
),
|
||||||
|
cosmic.on_bg_color().into(),
|
||||||
|
item_content_quad,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
item_content_quad.x += state.category_layout[nth].width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item_quad.y += state.item_layout[nth].height;
|
||||||
|
divider_quad.y += item_quad.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, SelectionMode, Item, Category, Message>
|
||||||
|
TableView<'a, SelectionMode, Item, Category, Message>
|
||||||
|
where
|
||||||
|
SelectionMode: Default,
|
||||||
|
Model<SelectionMode, Item, Category>: Selectable,
|
||||||
|
Category: ItemCategory + Hash + 'static,
|
||||||
|
Item: ItemInterface<Category>,
|
||||||
|
Message: Clone + 'static,
|
||||||
|
{
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn element_standard(&self) -> Element<'a, Message> {
|
pub fn element_standard(&self) -> Element<'a, Message> {
|
||||||
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
@ -145,12 +791,13 @@ where
|
||||||
.model
|
.model
|
||||||
.categories
|
.categories
|
||||||
.iter()
|
.iter()
|
||||||
|
.copied()
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
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) = self.model.sort {
|
.push_maybe(if let Some(sort) = self.model.sort {
|
||||||
if sort.0 == *category {
|
if sort.0 == category {
|
||||||
match sort.1 {
|
match sort.1 {
|
||||||
true => Some(widget::icon::from_name("pan-up-symbolic").icon()),
|
true => Some(widget::icon::from_name("pan-up-symbolic").icon()),
|
||||||
false => Some(widget::icon::from_name("pan-down-symbolic").icon()),
|
false => Some(widget::icon::from_name("pan-down-symbolic").icon()),
|
||||||
|
|
@ -162,27 +809,11 @@ where
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.padding(
|
|
||||||
Padding::default()
|
|
||||||
.left(self.item_padding.left)
|
|
||||||
.right(self.item_padding.right),
|
|
||||||
)
|
|
||||||
.width(category.width())
|
.width(category.width())
|
||||||
.apply(widget::mouse_area)
|
.apply(widget::mouse_area)
|
||||||
.apply(|mouse_area| {
|
.apply(|mouse_area| {
|
||||||
if let Some(ref on_category_select) = self.on_category_select {
|
if let Some(ref on_category_select) = self.on_category_select {
|
||||||
mouse_area.on_press((on_category_select)(
|
mouse_area.on_press((on_category_select)(category))
|
||||||
*category,
|
|
||||||
if let Some(sort) = self.model.sort {
|
|
||||||
if sort.0 == *category {
|
|
||||||
!sort.1
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
mouse_area
|
mouse_area
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +822,11 @@ where
|
||||||
})
|
})
|
||||||
.collect::<Vec<Element<'a, Message>>>()
|
.collect::<Vec<Element<'a, Message>>>()
|
||||||
.apply(widget::row::with_children)
|
.apply(widget::row::with_children)
|
||||||
|
.padding(
|
||||||
|
Padding::default()
|
||||||
|
.left(self.item_padding.left)
|
||||||
|
.right(self.item_padding.right),
|
||||||
|
)
|
||||||
.apply(Element::from);
|
.apply(Element::from);
|
||||||
let items_full = if self.model.items.is_empty() {
|
let items_full = if self.model.items.is_empty() {
|
||||||
vec![divider::horizontal::default()
|
vec![divider::horizontal::default()
|
||||||
|
|
@ -214,9 +850,15 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
widget::row()
|
widget::row()
|
||||||
.spacing(space_xxxs)
|
.spacing(self.icon_spacing)
|
||||||
.push_maybe(item.get_icon(*category).map(|icon| icon.size(24)))
|
.push_maybe(
|
||||||
.push(widget::text::body(item.get_text(*category)))
|
item.get_icon(*category)
|
||||||
|
.map(|icon| icon.size(self.icon_size)),
|
||||||
|
)
|
||||||
|
.push_maybe(
|
||||||
|
item.get_text(*category)
|
||||||
|
.map(|text| widget::text::body(text)),
|
||||||
|
)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
.apply(container)
|
.apply(container)
|
||||||
.width(category.width())
|
.width(category.width())
|
||||||
|
|
@ -255,7 +897,7 @@ where
|
||||||
}))
|
}))
|
||||||
.apply(widget::mouse_area)
|
.apply(widget::mouse_area)
|
||||||
.apply(|mouse_area| {
|
.apply(|mouse_area| {
|
||||||
if let Some(ref on_item_select) = self.on_item_select {
|
if let Some(ref on_item_select) = self.on_item_left {
|
||||||
mouse_area.on_press((on_item_select)(entity))
|
mouse_area.on_press((on_item_select)(entity))
|
||||||
} else {
|
} else {
|
||||||
mouse_area
|
mouse_area
|
||||||
|
|
@ -301,7 +943,10 @@ where
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
widget::column()
|
widget::column()
|
||||||
.push(widget::text::body(item.get_text(Category::default())))
|
.push_maybe(
|
||||||
|
item.get_text(Category::default())
|
||||||
|
.map(|text| widget::text::body(text)),
|
||||||
|
)
|
||||||
.push({
|
.push({
|
||||||
let mut elements = self
|
let mut elements = self
|
||||||
.model
|
.model
|
||||||
|
|
@ -309,12 +954,17 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.skip_while(|cat| **cat != Category::default())
|
.skip_while(|cat| **cat != Category::default())
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
|
item.get_text(*category)
|
||||||
|
.map(|text| {
|
||||||
vec![
|
vec![
|
||||||
widget::text::caption(item.get_text(*category))
|
widget::text::caption(text)
|
||||||
|
.apply(Element::from),
|
||||||
|
widget::text::caption("-")
|
||||||
.apply(Element::from),
|
.apply(Element::from),
|
||||||
widget::text::caption("-").apply(Element::from),
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<Element<'a, Message>>>();
|
.collect::<Vec<Element<'a, Message>>>();
|
||||||
elements.pop();
|
elements.pop();
|
||||||
|
|
@ -355,7 +1005,7 @@ where
|
||||||
}))
|
}))
|
||||||
.apply(widget::mouse_area)
|
.apply(widget::mouse_area)
|
||||||
.apply(|ma| {
|
.apply(|ma| {
|
||||||
if let Some(on_item_select) = &self.on_item_select {
|
if let Some(on_item_select) = &self.on_item_left {
|
||||||
ma.on_press((on_item_select)(entity))
|
ma.on_press((on_item_select)(entity))
|
||||||
} else {
|
} else {
|
||||||
ma
|
ma
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,21 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use iced::Size;
|
||||||
use slotmap::SecondaryMap;
|
use slotmap::SecondaryMap;
|
||||||
|
|
||||||
use crate::widget::table::Entity;
|
use crate::widget::table::{Entity, ItemCategory};
|
||||||
|
|
||||||
pub struct State {
|
pub struct State<Category>
|
||||||
|
where
|
||||||
|
Category: ItemCategory + 'static,
|
||||||
|
{
|
||||||
pub(super) num_items: usize,
|
pub(super) num_items: usize,
|
||||||
pub(super) selected: SecondaryMap<Entity, bool>,
|
pub(super) selected: SecondaryMap<Entity, bool>,
|
||||||
|
pub(super) paragraphs: SecondaryMap<Entity, HashMap<Category, crate::Plain>>,
|
||||||
|
pub(super) text_hashes: SecondaryMap<Entity, u64>,
|
||||||
|
pub(super) item_layout: Vec<Size>,
|
||||||
|
|
||||||
|
pub(super) sort_hash: HashMap<Category, u64>,
|
||||||
|
pub(super) header_paragraphs: HashMap<Category, crate::Plain>,
|
||||||
|
pub(super) category_layout: Vec<Size>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue