Draft table widget
This commit is contained in:
parent
283d0e74a8
commit
b3ce0f23a5
6 changed files with 738 additions and 0 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -5633,6 +5633,13 @@ dependencies = [
|
||||||
"iced",
|
"iced",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "table"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"iced",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.16"
|
version = "0.12.16"
|
||||||
|
|
|
||||||
10
examples/table/Cargo.toml
Normal file
10
examples/table/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "table"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
|
edition = "2024"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
iced.workspace = true
|
||||||
|
iced.features = ["debug"]
|
||||||
155
examples/table/src/main.rs
Normal file
155
examples/table/src/main.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
use iced::font;
|
||||||
|
use iced::time::{Duration, hours, minutes};
|
||||||
|
use iced::widget::{center, scrollable, table, text};
|
||||||
|
use iced::{Element, Fill, Font};
|
||||||
|
|
||||||
|
pub fn main() -> iced::Result {
|
||||||
|
iced::application(Table::new, Table::update, Table::view).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Table {
|
||||||
|
events: Vec<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Message {}
|
||||||
|
|
||||||
|
impl Table {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
events: Event::list(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
match message {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<'_, Message> {
|
||||||
|
let table = {
|
||||||
|
let bold = |header| {
|
||||||
|
text(header).font(Font {
|
||||||
|
weight: font::Weight::Bold,
|
||||||
|
..Font::DEFAULT
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let columns = table::definition()
|
||||||
|
.column(bold("Name"), |event: &Event| {
|
||||||
|
text(&event.name).width(Fill)
|
||||||
|
})
|
||||||
|
.column(bold("Time"), |event| text!("{:?}", event.duration))
|
||||||
|
.column(bold("Price"), |event| text!("{:.2}", event.price))
|
||||||
|
.column(bold("Rating"), |event| text!("{:.2}", event.rating));
|
||||||
|
|
||||||
|
table(columns, &self.events).width(640).spacing_y(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
center(scrollable(table).spacing(10)).padding(10).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
name: String,
|
||||||
|
duration: Duration,
|
||||||
|
price: f32,
|
||||||
|
rating: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
fn list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
Event {
|
||||||
|
name: "Get lost in a hacker bookstore".to_owned(),
|
||||||
|
duration: hours(2),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Buy vintage synth at Noisebridge flea market".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 150.0,
|
||||||
|
rating: 4.8,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Eat a questionable hot dog at 2AM".to_owned(),
|
||||||
|
duration: minutes(20),
|
||||||
|
price: 5.0,
|
||||||
|
rating: 1.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Ride the MUNI for the story".to_owned(),
|
||||||
|
duration: minutes(60),
|
||||||
|
price: 3.0,
|
||||||
|
rating: 4.1,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Scream into the void from Twin Peaks".to_owned(),
|
||||||
|
duration: minutes(40),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Buy overpriced coffee and feel things".to_owned(),
|
||||||
|
duration: minutes(25),
|
||||||
|
price: 6.5,
|
||||||
|
rating: 4.5,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Attend an underground robot poetry slam".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 12.0,
|
||||||
|
rating: 4.8,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Browse cursed tech at a retro computer fair".to_owned(),
|
||||||
|
duration: hours(2),
|
||||||
|
price: 10.0,
|
||||||
|
rating: 4.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Try to order at a secret ramen place with no sign"
|
||||||
|
.to_owned(),
|
||||||
|
duration: minutes(50),
|
||||||
|
price: 14.0,
|
||||||
|
rating: 4.6,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Join a spontaneous rooftop drone rave".to_owned(),
|
||||||
|
duration: hours(3),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.9,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Sketch a stranger at Dolores Park".to_owned(),
|
||||||
|
duration: minutes(45),
|
||||||
|
price: 0.0,
|
||||||
|
rating: 4.4,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Visit the Museum of Obsolete APIs".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 9.99,
|
||||||
|
rating: 4.2,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Chase the last working payphone".to_owned(),
|
||||||
|
duration: minutes(35),
|
||||||
|
price: 0.25,
|
||||||
|
rating: 4.0,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Trade zines with a punk on BART".to_owned(),
|
||||||
|
duration: minutes(30),
|
||||||
|
price: 3.5,
|
||||||
|
rating: 4.7,
|
||||||
|
},
|
||||||
|
Event {
|
||||||
|
name: "Get a tattoo of the Git logo".to_owned(),
|
||||||
|
duration: hours(1),
|
||||||
|
price: 200.0,
|
||||||
|
rating: 4.6,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,8 @@ use crate::{Column, Grid, MouseArea, Pin, Pop, Row, Space, Stack, Themer};
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
pub use crate::table::table;
|
||||||
|
|
||||||
/// Creates a [`Column`] with the given children.
|
/// Creates a [`Column`] with the given children.
|
||||||
///
|
///
|
||||||
/// Columns distribute their children vertically.
|
/// Columns distribute their children vertically.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ pub mod row;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
pub mod scrollable;
|
pub mod scrollable;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
|
pub mod table;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod text_editor;
|
pub mod text_editor;
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
|
|
|
||||||
563
widget/src/table.rs
Normal file
563
widget/src/table.rs
Normal file
|
|
@ -0,0 +1,563 @@
|
||||||
|
#![allow(missing_docs, missing_debug_implementations)]
|
||||||
|
use crate::core;
|
||||||
|
use crate::core::layout;
|
||||||
|
use crate::core::mouse;
|
||||||
|
use crate::core::renderer;
|
||||||
|
use crate::core::widget;
|
||||||
|
use crate::core::{
|
||||||
|
Background, Element, Layout, Length, Pixels, Rectangle, Size, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn table<'a, R, T, Message, Theme, Renderer>(
|
||||||
|
columns: impl IntoIterator<Item = Column<'a, T, Message, Theme, Renderer>>,
|
||||||
|
rows: R,
|
||||||
|
) -> Table<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
R: IntoIterator<Item = T>,
|
||||||
|
R::IntoIter: Clone,
|
||||||
|
Theme: Catalog,
|
||||||
|
Renderer: core::Renderer,
|
||||||
|
{
|
||||||
|
Table::new(columns, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn definition<'a, T, Message, Theme, Renderer>()
|
||||||
|
-> Definition<'a, T, Message, Theme, Renderer> {
|
||||||
|
Definition {
|
||||||
|
columns: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column<'a, T, E, Message, Theme, Renderer>(
|
||||||
|
header: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
view: impl Fn(T) -> E + 'a,
|
||||||
|
) -> Column<'a, T, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
T: 'a,
|
||||||
|
E: Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
{
|
||||||
|
Column {
|
||||||
|
header: header.into(),
|
||||||
|
view: Box::new(move |data| view(data).into()),
|
||||||
|
width: Length::Shrink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Table<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
|
||||||
|
where
|
||||||
|
Theme: Catalog,
|
||||||
|
{
|
||||||
|
columns: Vec<Length>,
|
||||||
|
cells: Vec<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
width: Length,
|
||||||
|
height: Length,
|
||||||
|
spacing_x: f32,
|
||||||
|
spacing_y: f32,
|
||||||
|
separator_x: f32,
|
||||||
|
separator_y: f32,
|
||||||
|
class: Theme::Class<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> Table<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Theme: Catalog,
|
||||||
|
Renderer: core::Renderer,
|
||||||
|
{
|
||||||
|
pub fn new<R, T>(
|
||||||
|
columns: impl IntoIterator<Item = Column<'a, T, Message, Theme, Renderer>>,
|
||||||
|
rows: R,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
R: IntoIterator<Item = T>,
|
||||||
|
R::IntoIter: Clone,
|
||||||
|
{
|
||||||
|
let columns = columns.into_iter();
|
||||||
|
let rows = rows.into_iter();
|
||||||
|
|
||||||
|
let mut width = Length::Shrink;
|
||||||
|
let mut height = Length::Shrink;
|
||||||
|
let mut cells = Vec::with_capacity(
|
||||||
|
columns.size_hint().0 * (1 + rows.size_hint().0),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
columns: columns
|
||||||
|
.into_iter()
|
||||||
|
.map(|column| {
|
||||||
|
let mut column_width = column.width;
|
||||||
|
|
||||||
|
cells.push(column.header);
|
||||||
|
cells.extend(rows.clone().map(|row| {
|
||||||
|
let cell = (column.view)(row);
|
||||||
|
let size_hint = cell.as_widget().size_hint();
|
||||||
|
|
||||||
|
column_width = column_width.enclose(size_hint.width);
|
||||||
|
height = height.enclose(size_hint.height);
|
||||||
|
|
||||||
|
cell
|
||||||
|
}));
|
||||||
|
|
||||||
|
width = width.enclose(column_width);
|
||||||
|
|
||||||
|
column_width
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
cells,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
spacing_x: 10.0,
|
||||||
|
spacing_y: 10.0,
|
||||||
|
separator_x: 1.0,
|
||||||
|
separator_y: 1.0,
|
||||||
|
class: Theme::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||||
|
self.width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spacing(self, spacing: impl Into<Pixels>) -> Self {
|
||||||
|
let spacing = spacing.into();
|
||||||
|
|
||||||
|
self.spacing_x(spacing).spacing_y(spacing)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spacing_x(mut self, spacing: impl Into<Pixels>) -> Self {
|
||||||
|
self.spacing_x = spacing.into().0;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spacing_y(mut self, spacing: impl Into<Pixels>) -> Self {
|
||||||
|
self.spacing_y = spacing.into().0;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Metrics {
|
||||||
|
column_widths: Vec<f32>,
|
||||||
|
row_heights: Vec<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for Table<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Theme: Catalog,
|
||||||
|
Renderer: core::Renderer,
|
||||||
|
{
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
Size {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> widget::tree::Tag {
|
||||||
|
widget::tree::Tag::of::<Metrics>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> widget::tree::State {
|
||||||
|
widget::tree::State::new(Metrics {
|
||||||
|
column_widths: Vec::new(),
|
||||||
|
row_heights: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<widget::Tree> {
|
||||||
|
self.cells
|
||||||
|
.iter()
|
||||||
|
.map(|cell| widget::Tree::new(cell.as_widget()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&self, state: &mut widget::Tree) {
|
||||||
|
state.diff_children(&self.cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut widget::Tree,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let metrics = tree.state.downcast_mut::<Metrics>();
|
||||||
|
let limits = limits.width(self.width).height(self.height);
|
||||||
|
let rows = self.cells.len() / self.columns.len();
|
||||||
|
let available = limits.max();
|
||||||
|
|
||||||
|
let mut cells = Vec::with_capacity(self.cells.len());
|
||||||
|
cells.resize(self.cells.len(), layout::Node::default());
|
||||||
|
|
||||||
|
metrics.column_widths = vec![0.0; self.columns.len()];
|
||||||
|
metrics.row_heights = vec![0.0; rows];
|
||||||
|
|
||||||
|
let mut column_factors = vec![0; self.columns.len()];
|
||||||
|
let mut row_factors = vec![0; rows];
|
||||||
|
|
||||||
|
let spacing_x = self.spacing_x * 2.0 + self.separator_x;
|
||||||
|
let spacing_y = self.spacing_y * 2.0 + self.separator_y;
|
||||||
|
|
||||||
|
// FIRST PASS
|
||||||
|
// Lay out non-fluid cells
|
||||||
|
let mut x = self.spacing_x;
|
||||||
|
let mut y = self.spacing_y;
|
||||||
|
|
||||||
|
for (i, (cell, state)) in
|
||||||
|
self.cells.iter().zip(&mut tree.children).enumerate()
|
||||||
|
{
|
||||||
|
let column = i / rows;
|
||||||
|
let row = i % rows;
|
||||||
|
let size = cell.as_widget().size();
|
||||||
|
|
||||||
|
if size.width.fill_factor() != 0 || size.height.fill_factor() != 0 {
|
||||||
|
column_factors[column] =
|
||||||
|
column_factors[column].max(size.width.fill_factor());
|
||||||
|
|
||||||
|
row_factors[row] =
|
||||||
|
row_factors[row].max(size.height.fill_factor());
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let limits = layout::Limits::new(
|
||||||
|
Size::ZERO,
|
||||||
|
Size::new(available.width - x, available.height - y),
|
||||||
|
)
|
||||||
|
.width(self.columns[i / rows]);
|
||||||
|
|
||||||
|
let layout = cell.as_widget().layout(state, renderer, &limits);
|
||||||
|
let size = layout.size();
|
||||||
|
|
||||||
|
metrics.column_widths[column] =
|
||||||
|
metrics.column_widths[column].max(size.width);
|
||||||
|
metrics.row_heights[row] =
|
||||||
|
metrics.row_heights[row].max(size.height);
|
||||||
|
cells[i] = layout;
|
||||||
|
|
||||||
|
if row == 0 {
|
||||||
|
y = self.spacing_y;
|
||||||
|
|
||||||
|
if column > 0 {
|
||||||
|
x += metrics.column_widths[column - 1] + spacing_x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
y += size.height + spacing_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SECOND PASS
|
||||||
|
// Lay out fluid cells, using metrics from the first pass as limits
|
||||||
|
let left = Size::new(
|
||||||
|
available.width
|
||||||
|
- metrics
|
||||||
|
.column_widths
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| column_factors[*i] == 0)
|
||||||
|
.map(|(_, width)| width)
|
||||||
|
.sum::<f32>(),
|
||||||
|
available.height
|
||||||
|
- metrics
|
||||||
|
.row_heights
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| row_factors[*i] == 0)
|
||||||
|
.map(|(_, height)| height)
|
||||||
|
.sum::<f32>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let width_unit = (left.width
|
||||||
|
- spacing_x * self.columns.len().saturating_sub(1) as f32
|
||||||
|
- self.spacing_x * 2.0)
|
||||||
|
/ column_factors.iter().sum::<u16>() as f32;
|
||||||
|
|
||||||
|
let height_unit = (left.height
|
||||||
|
- spacing_y * rows.saturating_sub(1) as f32
|
||||||
|
- self.spacing_y * 2.0)
|
||||||
|
/ row_factors.iter().sum::<u16>() as f32;
|
||||||
|
|
||||||
|
let mut x = self.spacing_x;
|
||||||
|
let mut y = self.spacing_y;
|
||||||
|
|
||||||
|
for (i, (cell, state)) in
|
||||||
|
self.cells.iter().zip(&mut tree.children).enumerate()
|
||||||
|
{
|
||||||
|
let column = i / rows;
|
||||||
|
let row = i % rows;
|
||||||
|
let size = cell.as_widget().size();
|
||||||
|
|
||||||
|
if size.width.fill_factor() != 0 || size.height.fill_factor() != 0 {
|
||||||
|
let column_factor = column_factors[column];
|
||||||
|
let row_factor = row_factors[row];
|
||||||
|
|
||||||
|
let max_width = if column_factor == 0 {
|
||||||
|
(available.width - x).max(0.0)
|
||||||
|
} else {
|
||||||
|
width_unit * column_factor as f32
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_height = if row_factor == 0 {
|
||||||
|
(available.height - y).max(0.0)
|
||||||
|
} else {
|
||||||
|
height_unit * row_factor as f32
|
||||||
|
};
|
||||||
|
|
||||||
|
let limits = layout::Limits::new(
|
||||||
|
Size::ZERO,
|
||||||
|
Size::new(max_width, max_height),
|
||||||
|
)
|
||||||
|
.width(self.columns[i / rows]);
|
||||||
|
|
||||||
|
let layout = cell.as_widget().layout(state, renderer, &limits);
|
||||||
|
let size = layout.size();
|
||||||
|
|
||||||
|
metrics.column_widths[column] =
|
||||||
|
metrics.column_widths[column].max(size.width);
|
||||||
|
metrics.row_heights[row] =
|
||||||
|
metrics.row_heights[row].max(size.height);
|
||||||
|
cells[i] = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if row == 0 {
|
||||||
|
y = self.spacing_y;
|
||||||
|
|
||||||
|
if column > 0 {
|
||||||
|
x += metrics.column_widths[column - 1] + spacing_x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
y += cells[i].size().height + spacing_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIRD PASS
|
||||||
|
// Position each cell
|
||||||
|
let mut x = self.spacing_x;
|
||||||
|
let mut y = self.spacing_y;
|
||||||
|
|
||||||
|
for (i, cell) in cells.iter_mut().enumerate() {
|
||||||
|
let column = i / rows;
|
||||||
|
let row = i % rows;
|
||||||
|
|
||||||
|
if row == 0 {
|
||||||
|
y = self.spacing_y;
|
||||||
|
|
||||||
|
if column > 0 {
|
||||||
|
x += metrics.column_widths[column - 1] + spacing_x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.move_to_mut((x, y));
|
||||||
|
|
||||||
|
y += metrics.row_heights[row] + spacing_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
let intrinsic = limits.resolve(
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
Size::new(
|
||||||
|
x + metrics
|
||||||
|
.column_widths
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.map(|width| width + self.spacing_x)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
y - spacing_y + self.spacing_y,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
layout::Node::with_children(intrinsic, cells)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &widget::Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
for ((cell, state), layout) in
|
||||||
|
self.cells.iter().zip(&tree.children).zip(layout.children())
|
||||||
|
{
|
||||||
|
cell.as_widget()
|
||||||
|
.draw(state, renderer, theme, style, layout, cursor, viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let metrics = tree.state.downcast_ref::<Metrics>();
|
||||||
|
let style = theme.style(&self.class);
|
||||||
|
|
||||||
|
if self.separator_x > 0.0 {
|
||||||
|
let mut x = self.spacing_x;
|
||||||
|
|
||||||
|
for width in &metrics.column_widths
|
||||||
|
[..metrics.column_widths.len().saturating_sub(1)]
|
||||||
|
{
|
||||||
|
x += width + self.spacing_x;
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + x,
|
||||||
|
y: bounds.y,
|
||||||
|
width: self.separator_x,
|
||||||
|
height: bounds.height,
|
||||||
|
},
|
||||||
|
snap: true,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
style.separator_x,
|
||||||
|
);
|
||||||
|
|
||||||
|
x += self.separator_x + self.spacing_x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.separator_y > 0.0 {
|
||||||
|
let mut y = self.spacing_y;
|
||||||
|
|
||||||
|
for height in &metrics.row_heights
|
||||||
|
[..metrics.row_heights.len().saturating_sub(1)]
|
||||||
|
{
|
||||||
|
y += height + self.spacing_y;
|
||||||
|
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y + y,
|
||||||
|
width: bounds.width,
|
||||||
|
height: self.separator_y,
|
||||||
|
},
|
||||||
|
snap: true,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
style.separator_y,
|
||||||
|
);
|
||||||
|
|
||||||
|
y += self.separator_y + self.spacing_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<Table<'a, Message, Theme, Renderer>>
|
||||||
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: 'a,
|
||||||
|
Theme: Catalog + 'a,
|
||||||
|
Renderer: core::Renderer + 'a,
|
||||||
|
{
|
||||||
|
fn from(table: Table<'a, Message, Theme, Renderer>) -> Self {
|
||||||
|
Element::new(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Definition<
|
||||||
|
'a,
|
||||||
|
T,
|
||||||
|
Message,
|
||||||
|
Theme = crate::Theme,
|
||||||
|
Renderer = crate::Renderer,
|
||||||
|
> {
|
||||||
|
columns: Vec<Column<'a, T, Message, Theme, Renderer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, Message, Theme, Renderer>
|
||||||
|
Definition<'a, T, Message, Theme, Renderer>
|
||||||
|
{
|
||||||
|
pub fn column<E>(
|
||||||
|
mut self,
|
||||||
|
header: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
view: impl Fn(T) -> E + 'a,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: 'a,
|
||||||
|
E: Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
{
|
||||||
|
self.columns.push(column(header, view));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, Message, Theme, Renderer> IntoIterator
|
||||||
|
for Definition<'a, T, Message, Theme, Renderer>
|
||||||
|
{
|
||||||
|
type Item = Column<'a, T, Message, Theme, Renderer>;
|
||||||
|
type IntoIter = ::std::vec::IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.columns.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Column<
|
||||||
|
'a,
|
||||||
|
T,
|
||||||
|
Message,
|
||||||
|
Theme = crate::Theme,
|
||||||
|
Renderer = crate::Renderer,
|
||||||
|
> {
|
||||||
|
header: Element<'a, Message, Theme, Renderer>,
|
||||||
|
view: Box<dyn Fn(T) -> Element<'a, Message, Theme, Renderer> + 'a>,
|
||||||
|
width: Length,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, Message, Theme, Renderer> Column<'a, T, Message, Theme, Renderer> {
|
||||||
|
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||||
|
self.width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Style {
|
||||||
|
pub separator_x: Background,
|
||||||
|
pub separator_y: Background,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The theme catalog of a [`Table`].
|
||||||
|
pub trait Catalog {
|
||||||
|
/// The item class of the [`Catalog`].
|
||||||
|
type Class<'a>;
|
||||||
|
|
||||||
|
/// The default class produced by the [`Catalog`].
|
||||||
|
fn default<'a>() -> Self::Class<'a>;
|
||||||
|
|
||||||
|
/// The [`Style`] of a class with the given status.
|
||||||
|
fn style(&self, class: &Self::Class<'_>) -> Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A styling function for a [`Table`].
|
||||||
|
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
|
||||||
|
|
||||||
|
impl<Theme> From<Style> for StyleFn<'_, Theme> {
|
||||||
|
fn from(style: Style) -> Self {
|
||||||
|
Box::new(move |_theme| style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Catalog for crate::Theme {
|
||||||
|
type Class<'a> = StyleFn<'a, Self>;
|
||||||
|
|
||||||
|
fn default<'a>() -> Self::Class<'a> {
|
||||||
|
Box::new(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(&self, class: &Self::Class<'_>) -> Style {
|
||||||
|
class(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default style of a [`Table`].
|
||||||
|
pub fn default(theme: &crate::Theme) -> Style {
|
||||||
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
|
Style {
|
||||||
|
separator_x: palette.background.strong.color.into(),
|
||||||
|
separator_y: palette.background.strong.color.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue