use std::rc::Rc;
use teksilo_core::widget::Widget;
use teksilo_data::SortDirection;
use teksilo_i18n::LocalizedString;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ColumnWidth {
Fixed(f32),
Flex(f32),
Auto,
}
impl Default for ColumnWidth {
fn default() -> Self {
ColumnWidth::Flex(1.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PinnedSide {
Leading,
#[default]
None,
Trailing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Alignment {
#[default]
Leading,
Center,
Trailing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TruncationPolicy {
#[default]
Ellipsis,
None,
Fade,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GridLines {
#[default]
None,
Horizontal,
Vertical,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColumnResizePolicy {
#[default]
Live,
OnRelease,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EditTriggers(u8);
impl EditTriggers {
pub const NONE: Self = Self(0);
pub const F2: Self = Self(1 << 0);
pub const ANY_KEY: Self = Self(1 << 1);
pub const SINGLE_CLICK: Self = Self(1 << 2);
pub const DOUBLE_CLICK: Self = Self(1 << 3);
pub const ALL: Self = Self(0b0000_1111);
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn intersection(self, other: Self) -> Self {
Self(self.0 & other.0)
}
}
impl Default for EditTriggers {
fn default() -> Self {
Self::F2.union(Self::ANY_KEY).union(Self::DOUBLE_CLICK)
}
}
impl std::ops::BitOr for EditTriggers {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl std::ops::BitOrAssign for EditTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = self.union(rhs);
}
}
impl std::ops::BitAnd for EditTriggers {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
self.intersection(rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TabTraversal {
#[default]
CellsThenRows,
OutOfTable,
}
#[derive(Debug, Clone)]
pub struct CellContext {
pub row_index: usize,
pub col_id: String,
pub col_index: usize,
pub is_selected: bool,
pub is_focused: bool,
pub is_hovered: bool,
pub is_editing: bool,
pub depth: Option<usize>,
pub is_tree_column: bool,
}
#[derive(Debug, Clone)]
pub struct ColumnContext {
pub col_id: String,
pub col_index: usize,
pub sort: Option<SortDirection>,
pub filter_text: String,
pub is_hovered: bool,
}
pub struct Column<T: 'static> {
pub(crate) id: String,
pub(crate) header_label: LocalizedString,
pub(crate) width: ColumnWidth,
pub(crate) min_width: Option<f32>,
pub(crate) max_width: Option<f32>,
pub(crate) alignment: Alignment,
pub(crate) resizable: bool,
pub(crate) reorderable: bool,
pub(crate) sortable: bool,
pub(crate) filterable: bool,
pub(crate) editable: bool,
pub(crate) edit_triggers: Option<EditTriggers>,
pub(crate) pinned: PinnedSide,
pub(crate) truncation: TruncationPolicy,
pub(crate) cell: Rc<dyn Fn(&T, &CellContext) -> Box<dyn Widget>>,
pub(crate) header_override: Option<Rc<dyn Fn(&ColumnContext) -> Box<dyn Widget>>>,
}
impl<T: 'static> Column<T> {
pub fn new(
id: impl Into<String>,
header: impl Into<LocalizedString>,
cell: impl Fn(&T, &CellContext) -> Box<dyn Widget> + 'static,
) -> Self {
Self {
id: id.into(),
header_label: header.into(),
width: ColumnWidth::default(),
min_width: None,
max_width: None,
alignment: Alignment::default(),
resizable: true,
reorderable: true,
sortable: false,
filterable: false,
editable: false,
edit_triggers: None,
pinned: PinnedSide::None,
truncation: TruncationPolicy::default(),
cell: Rc::new(cell),
header_override: None,
}
}
pub fn width(mut self, w: ColumnWidth) -> Self {
self.width = w;
self
}
pub fn min_width(mut self, px: f32) -> Self {
self.min_width = Some(px);
self
}
pub fn max_width(mut self, px: f32) -> Self {
self.max_width = Some(px);
self
}
pub fn alignment(mut self, a: Alignment) -> Self {
self.alignment = a;
self
}
pub fn resizable(mut self, b: bool) -> Self {
self.resizable = b;
self
}
pub fn reorderable(mut self, b: bool) -> Self {
self.reorderable = b;
self
}
pub fn sortable(mut self, b: bool) -> Self {
self.sortable = b;
self
}
pub fn filterable(mut self, b: bool) -> Self {
self.filterable = b;
self
}
pub fn editable(mut self, b: bool) -> Self {
self.editable = b;
self
}
pub fn edit_triggers(mut self, triggers: EditTriggers) -> Self {
self.edit_triggers = Some(triggers);
self
}
pub fn effective_edit_triggers(&self, view: EditTriggers) -> EditTriggers {
if !self.editable {
return EditTriggers::NONE;
}
self.edit_triggers.unwrap_or(view)
}
pub fn pinned(mut self, side: PinnedSide) -> Self {
self.pinned = side;
self
}
pub fn truncation(mut self, p: TruncationPolicy) -> Self {
self.truncation = p;
self
}
pub fn header_override(
mut self,
f: impl Fn(&ColumnContext) -> Box<dyn Widget> + 'static,
) -> Self {
self.header_override = Some(Rc::new(f));
self
}
pub fn id(&self) -> &str {
&self.id
}
}
impl<T: 'static> Clone for Column<T> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
header_label: self.header_label.clone(),
width: self.width,
min_width: self.min_width,
max_width: self.max_width,
alignment: self.alignment,
resizable: self.resizable,
reorderable: self.reorderable,
sortable: self.sortable,
filterable: self.filterable,
editable: self.editable,
edit_triggers: self.edit_triggers,
pinned: self.pinned,
truncation: self.truncation,
cell: self.cell.clone(),
header_override: self.header_override.clone(),
}
}
}
impl<T: 'static> std::fmt::Debug for Column<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Column")
.field("id", &self.id)
.field("width", &self.width)
.field("alignment", &self.alignment)
.field("pinned", &self.pinned)
.finish()
}
}