use std::marker::PhantomData;
use iced::alignment::{Horizontal, Vertical};
use iced::widget::text::Wrapping;
use iced::widget::{container, progress_bar, row, text};
use iced::{Element, Font, Length, Padding};
use oriel_core::columns::{Column, ColumnId, ColumnSizing};
const TEXT_BOX_HEIGHT: f32 = 19.0;
const TEXT_BOX_TOP: f32 = 0.55;
pub const HEADER_FONT: Font = Font {
weight: iced::font::Weight::Semibold,
..Font::DEFAULT
};
pub struct TextColumn<Row, Message> {
id: ColumnId,
title: String,
value: Box<dyn Fn(&Row) -> String>,
footer: Option<Box<dyn Fn() -> String>>,
sizing: ColumnSizing,
align: Horizontal,
sortable: bool,
_message: PhantomData<fn() -> Message>,
}
impl<Row, Message> TextColumn<Row, Message> {
pub fn new(
id: ColumnId,
title: impl Into<String>,
sizing: impl Into<ColumnSizing>,
value: impl Fn(&Row) -> String + 'static,
) -> Self {
Self {
id,
title: title.into(),
value: Box::new(value),
footer: None,
sizing: sizing.into(),
align: Horizontal::Left,
sortable: true,
_message: PhantomData,
}
}
#[must_use]
pub fn footer(mut self, footer: impl Fn() -> String + 'static) -> Self {
self.footer = Some(Box::new(footer));
self
}
#[must_use]
pub fn numeric(mut self) -> Self {
self.align = Horizontal::Right;
self
}
#[must_use]
pub fn sortable(mut self, sortable: bool) -> Self {
self.sortable = sortable;
self
}
}
impl<Row, Message: 'static> Column for TextColumn<Row, Message> {
type Element = Element<'static, Message>;
type Row = Row;
fn id(&self) -> ColumnId {
self.id
}
fn sizing(&self) -> ColumnSizing {
self.sizing
}
fn sortable(&self) -> bool {
self.sortable
}
fn header(&self) -> Self::Element {
text(self.title.clone()).size(13.0).font(HEADER_FONT).wrapping(Wrapping::None).into()
}
fn cell(&self, row: &Row) -> Self::Element {
let content = text((self.value)(row)).size(13.0).wrapping(Wrapping::None);
let boxed = if self.align == Horizontal::Left {
container(content)
} else {
container(content.width(Length::Fill).align_x(self.align)).width(Length::Fill)
};
boxed
.height(Length::Fixed(TEXT_BOX_HEIGHT))
.padding(Padding { top: TEXT_BOX_TOP, right: 0.0, bottom: 0.0, left: 0.0 })
.into()
}
fn footer(&self) -> Option<Self::Element> {
let footer = self.footer.as_ref()?;
let content = text(footer()).size(13.0).font(HEADER_FONT).wrapping(Wrapping::None);
Some(if self.align == Horizontal::Left {
content.into()
} else {
content.width(Length::Fill).align_x(self.align).into()
})
}
fn measure(&self, row: &Row) -> Option<Self::Element> {
Some(text((self.value)(row)).size(13.0).wrapping(Wrapping::None).into())
}
fn measure_footer(&self) -> Option<Self::Element> {
let footer = self.footer.as_ref()?;
Some(text(footer()).size(13.0).font(HEADER_FONT).wrapping(Wrapping::None).into())
}
}
pub struct BarColumn<Row, Message> {
id: ColumnId,
title: String,
value: Box<dyn Fn(&Row) -> f32>,
footer: Option<Box<dyn Fn() -> f32>>,
max: f32,
sizing: ColumnSizing,
sortable: bool,
_message: PhantomData<fn() -> Message>,
}
impl<Row, Message> BarColumn<Row, Message> {
pub fn new(
id: ColumnId,
title: impl Into<String>,
sizing: impl Into<ColumnSizing>,
max: f32,
value: impl Fn(&Row) -> f32 + 'static,
) -> Self {
Self {
id,
title: title.into(),
value: Box::new(value),
footer: None,
max: max.max(f32::MIN_POSITIVE),
sizing: sizing.into(),
sortable: true,
_message: PhantomData,
}
}
#[must_use]
pub fn footer(mut self, footer: impl Fn() -> f32 + 'static) -> Self {
self.footer = Some(Box::new(footer));
self
}
#[must_use]
pub fn sortable(mut self, sortable: bool) -> Self {
self.sortable = sortable;
self
}
}
impl<Row, Message: 'static> Column for BarColumn<Row, Message> {
type Element = Element<'static, Message>;
type Row = Row;
fn id(&self) -> ColumnId {
self.id
}
fn sizing(&self) -> ColumnSizing {
self.sizing
}
fn sortable(&self) -> bool {
self.sortable
}
fn header(&self) -> Self::Element {
text(self.title.clone()).size(13.0).font(HEADER_FONT).wrapping(Wrapping::None).into()
}
fn cell(&self, row: &Row) -> Self::Element {
let value = (self.value)(row);
Self::bar(value, self.max)
}
fn footer(&self) -> Option<Self::Element> {
let footer = self.footer.as_ref()?;
Some(Self::bar(footer(), self.max))
}
fn measure(&self, _row: &Row) -> Option<Self::Element> {
None
}
fn measure_footer(&self) -> Option<Self::Element> {
None
}
}
impl<Row, Message: 'static> BarColumn<Row, Message> {
fn bar(value: f32, max: f32) -> Element<'static, Message> {
row![
progress_bar(0.0..=max, value)
.girth(Length::Fixed(10.0))
.length(Length::Fill),
text(format!("{value:.0}")).size(12.0).wrapping(Wrapping::None),
]
.spacing(6)
.align_y(Vertical::Center)
.into()
}
}