use crossterm::event::KeyCode;
use unicode_width::UnicodeWidthStr;
use crate::canvas::*;
use std::rc::Rc;
use super::*;
pub type DataGridSelectionChangedCallback = dyn Fn(&mut DataGrid);
pub type DataGridKeyHandler = fn(&mut DataGrid, event: &mut WindowEvent);
#[derive(Debug)]
pub enum DataValue<'a> {
String(&'a str),
Integer(i32),
Boolean(bool),
Float(f32),
}
impl<'a> DataValue<'a> {
pub fn width(&self, radix: Radix) -> TSize {
match self {
Self::String(s) => s.width() as TSize,
Self::Integer(i) => {
i.abs().checked_ilog(radix as i32).unwrap_or(0) as TSize
+ 1 + i.is_negative() as TSize
+ radix.prefix_glyphs().len() as TSize
}
Self::Float(f) => {
if f.is_nan() {
return 3;
}
else if *f == f32::INFINITY {
return 1;
}
else if *f == f32::NEG_INFINITY {
return 2;
}
let sign = (f.is_sign_negative() && *f != 0.0) as TSize;
let fract = {
let mut f2 = f.fract();
let mut cnt = 0;
while f2 != 0.0 {
f2 = (f2 * radix as usize as f32).fract();
cnt += 1;
}
cnt + 1
};
let int = (f.trunc() as i32)
.abs()
.checked_ilog(radix as i32)
.unwrap_or(0) as TSize
+ 1;
sign + fract + int + radix.prefix_glyphs().len() as TSize
}
Self::Boolean(_) => 3,
}
}
}
pub trait DataRow {
fn get_data<'a>(&'a self, column: usize) -> Option<DataValue<'a>>;
}
pub trait DataSource {
fn column_definitions(&self) -> &[ColumnDefiniton];
fn rows(&self) -> usize;
fn get_row(&self, row: usize) -> &dyn DataRow;
}
#[derive(Default)]
struct EmptyRow {}
impl DataRow for EmptyRow {
fn get_data<'a>(&'a self, _column: usize) -> Option<DataValue<'a>> {
None
}
}
#[derive(Default)]
struct EmptyDataSource(EmptyRow);
impl DataSource for EmptyDataSource {
fn column_definitions(&self) -> &[ColumnDefiniton] {
&[]
}
fn rows(&self) -> usize {
usize::MIN
}
fn get_row(&self, _row: usize) -> &dyn DataRow {
&self.0
}
}
#[derive(Clone, PartialEq)]
pub struct ColumnDefiniton {
name: String,
header_width: TSize,
}
impl ColumnDefiniton {
pub fn new<S: ToString>(name: S) -> Self {
let n = name.to_string();
let width = n.width() as TSize;
Self {
name: n,
header_width: width,
}
}
}
pub struct DataGrid {
base: WidgetBase,
data: Box<dyn DataSource>,
offset: Vector2D<usize>,
selection: Vector2D<usize>,
render_size: Vector2D<TSize>,
callback: Rc<DataGridSelectionChangedCallback>,
avail_size: TPoint,
col_widths: Vec<TSize>,
show_cols: usize,
key_handler: DataGridKeyHandler,
radix: Radix,
sel_color: Option<Color>,
}
impl Default for DataGrid {
fn default() -> Self {
Self::new()
}
}
impl DataGrid {
pub fn new() -> Self {
let mut s = Self {
base: WidgetBase::default(),
data: Box::new(EmptyDataSource::default()),
offset: Vector2D::default(),
selection: Vector2D::default(),
render_size: Vector2D::default(),
avail_size: Default::default(),
col_widths: Vec::default(),
show_cols: 0,
radix: Radix::default(),
sel_color: None,
callback: Rc::new(Self::default_callback),
key_handler: Self::default_key_handler,
};
s.base.constraints.y.min = NonZero::new(3).unwrap();
s.base.constraints.y.max = Size::Relative(Percent::from_int(100));
s
}
pub fn default_callback(_: &mut Self) {}
pub fn default_key_handler(&mut self, event: &mut WindowEvent) {
if let Event::Key(k) = event.raw() {
match k.code {
KeyCode::Up => self.move_selection_up(),
KeyCode::Down => self.move_selection_down(),
KeyCode::Left => self.move_selection_left(),
KeyCode::Right => self.move_selection_right(),
KeyCode::PageUp => self.move_page_up(),
KeyCode::PageDown => self.move_page_down(),
_ => {}
}
}
}
pub fn set_key_handler(&mut self, f: DataGridKeyHandler) {
self.key_handler = f;
}
pub fn set_callback<F: Fn(&mut Self) + 'static>(&mut self, callback: F) {
self.callback = Rc::new(callback);
}
pub fn set_radix(&mut self, radix: Radix) {
self.radix = radix;
self.reevaluate_columns();
}
pub fn set_selection_color(&mut self, color: Option<Color>) {
self.sel_color = color;
}
pub fn move_selection_up(&mut self) {
if self.selection.y > usize::MIN {
self.selection.y -= 1;
if self.offset.y > self.selection.y {
self.offset.y -= 1;
}
self.selection_changed();
}
}
pub fn move_selection_down(&mut self) {
if self.selection.y < self.data.rows() - 1 {
self.selection.y += 1;
if self.selection.y >= self.rendered_rows() + self.offset.y {
self.offset.y += 1;
}
self.selection_changed();
}
}
pub fn move_selection_left(&mut self) {
if self.selection.x > usize::MIN {
self.selection.x -= 1;
if self.offset.x > self.selection.x {
self.offset.x -= 1;
self.reevaluate_columns_range(true);
}
self.selection_changed();
}
}
pub fn move_selection_right(&mut self) {
if self.selection.x < self.data.column_definitions().len() - 1 {
self.selection.x += 1;
if self.selection.x > self.show_cols - 1 {
self.offset.x += 1;
self.reevaluate_columns_range(true);
}
self.selection_changed();
}
}
fn selection_changed(&mut self) {
let cb = self.callback.clone();
cb(self);
}
pub fn move_page_up(&mut self) {
self.selection.y = self.selection.y.saturating_sub(self.rendered_rows());
self.offset.y = self.offset.y.saturating_sub(self.rendered_rows());
self.selection_changed();
}
fn rendered_rows(&self) -> usize {
(self.avail_size.y as usize).saturating_sub(2)
}
pub fn move_page_down(&mut self) {
let y = self.selection.y + self.rendered_rows();
if y >= self.data.rows() {
self.selection.y = self.data.rows() - 1;
self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
self.selection_changed();
}
else {
self.selection.y = y;
self.offset.y += self.rendered_rows();
if self.offset.y + self.rendered_rows() > self.data.rows() {
self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
}
self.selection_changed();
}
}
fn reevaluate_columns_range(&mut self, provoke: bool) {
self.offset.x = 0;
self.show_cols = self.col_widths.len();
loop {
let left = self.selection.x != self.offset.x;
let width = self.col_widths[self.offset.x..self.show_cols]
.iter()
.sum::<TSize>()
+ self.show_cols as TSize
- 1;
if width > self.avail_size.x {
if left {
self.offset.x += 1;
}
else {
self.show_cols -= 1;
}
}
else {
break;
}
}
if !(self.offset.y..self.offset.y + self.rendered_rows()).contains(&self.selection.y) {
self.offset.y = self.selection.y.saturating_sub(self.rendered_rows() - 1);
}
let old = self.base.constraints.x;
if !self.col_widths.is_empty() {
self.base.constraints.x.min = NonZero::new(self.col_widths[self.offset.x]).unwrap();
self.base.constraints.x.max = Size::Fixed(
NonZero::new(
self.col_widths[self.offset.x..self.show_cols]
.iter()
.sum::<TSize>() + self.col_widths.len() as TSize
- 1,
)
.unwrap(),
);
}
else {
self.base.constraints.x.max = Size::Relative(Percent::from_int(100));
}
if provoke && self.base.constraints.x != old {
self.provoke_changed_property(WindowProperty::Size);
}
}
pub fn reevaluate_columns(&mut self) {
self.col_widths.fill(0);
self.col_widths
.resize(self.data.column_definitions().len(), 0);
for (x, max) in self.col_widths.iter_mut().enumerate() {
for y in 0..self.data.rows() {
let row = self.data.get_row(y);
if let Some(data) = row.get_data(x) {
let w = data.width(self.radix);
if w > *max {
*max = w;
}
}
}
let c_name_w = self.data.column_definitions()[x].header_width;
if c_name_w > *max {
*max = c_name_w;
}
}
if self.render_size != Vector2D::new(0, 0) {
self.reevaluate_columns_range(true);
}
}
pub fn set_data_source(&mut self, data: Box<dyn DataSource>) {
self.data = data;
self.reevaluate_columns();
}
}
impl Window for DataGrid {
fn render(&self, canvas: &mut crate::canvas::Canvas) {
let mut current_width = 0;
for idx in self.offset.x..self.show_cols {
let c = &self.data.column_definitions()[idx];
let mut row0 = canvas.get_row_variable_width(0).unwrap();
let mut row1 = canvas.get_row(1, GlyphWidth::Half).unwrap();
let next_w = self.col_widths[idx];
row0.skip_until(current_width);
row0.add_string(&c.name, None, None, Style::None, None).ok();
row0.skip_until(current_width + next_w);
if idx != self.show_cols - 1 {
row0.add_grapheme(BorderKind::Solid.line_style()[1], None, None, Style::None)
.ok();
}
for x in current_width..current_width + next_w {
let mut g1 = row1.get(x).unwrap();
g1.set_grapheme(BorderKind::Solid.line_style()[0]).ok();
}
if idx != self.show_cols - 1 {
let mut g1 = row1.get(current_width + next_w).unwrap();
g1.set_grapheme(BorderKind::Solid.cross_style()).ok();
}
current_width += next_w;
current_width += 1;
}
let mut max = self.data.rows();
if max > self.rendered_rows() {
max = self.rendered_rows();
}
for y in 0..max {
let drow = self.data.get_row(y + self.offset.y);
let mut row = canvas.get_row_variable_width(2 + y as TSize).unwrap();
if let Some(bg) = self.base.colors.base_bg {
row.fill_background(bg);
}
let base_fg = self.base.colors.base_fg;
let mut current_width = 0;
for x in 0..self.show_cols - self.offset.x {
let index = self.offset.x + x;
let col = self.col_widths[index];
if let Some(value) = drow.get_data(index) {
row.skip_until(current_width);
if x > 0 {
row.add_grapheme(
BorderKind::Solid.line_style()[1],
base_fg,
None,
Style::None,
)
.ok();
current_width += 1;
}
let fg = match self.selection == Vector2D::new(x, y) + self.offset {
true => self.sel_color.or(base_fg),
false => base_fg,
};
match value {
DataValue::Float(f) => {
for g in self.radix.iter_float(f) {
row.add_grapheme(g, fg, None, Style::None).ok();
}
}
DataValue::Boolean(b) => {
row.add_grapheme(Checkbox::OPEN_BOX, fg, None, Style::None)
.ok();
if b {
row.add_grapheme(Checkbox::CROSS_MARK, fg, None, Style::None)
.ok();
}
else {
row.skip(1);
}
row.add_grapheme(Checkbox::CLOSE_BOX, fg, None, Style::ResetAfter)
.ok();
}
DataValue::Integer(i) => {
for g in self.radix.iter_integer(i) {
row.add_grapheme(g, fg, None, Style::None).ok();
}
}
DataValue::String(s) => {
row.add_string(s, fg, None, Style::None, None).ok();
}
}
}
current_width += col;
}
}
}
fn handle_event(&mut self, event: &mut WindowEvent) {
match event.raw() {
Event::Key(_) => (self.key_handler)(self, event),
Event::Resize(w, h) => {
self.render_size = Vector2D::new(*w, *h);
if self.offset.y + self.rendered_rows() > self.data.rows() {
self.offset.y = self.data.rows().saturating_sub(self.rendered_rows());
}
}
_ => {}
}
}
fn is_enabled(&self) -> bool {
self.base.enabled
}
}
impl WindowLayout for DataGrid {
fn desired_size_pre_hook(&mut self, available_size: TPoint) {
if self.avail_size != available_size {
self.avail_size = available_size;
self.reevaluate_columns_range(false);
}
}
fn desired_size(&self, available_size: TPoint) -> TPoint {
self.base.desired_size(available_size)
}
fn alignment(&self) -> (HorizontalAlignment, VerticalAlignment) {
self.base.alignment
}
fn margin(&self) -> Thickness {
self.base.margin
}
fn border(&self) -> BorderStyle {
self.base.border
}
fn is_visible(&self) -> bool {
self.base.visibility
}
}
impl HasWindowUID for DataGrid {
fn uid(&self) -> WindowUID {
self.base.uid
}
}
impl Widget for DataGrid {
fn set_alignment(
&mut self,
horizontal: super::HorizontalAlignment,
vertical: super::VerticalAlignment,
) {
self.base.alignment = (horizontal, vertical);
self.provoke_changed_property(WindowProperty::Alignment);
}
fn set_visibility(&mut self, visibility: bool) {
self.base.visibility = visibility;
self.provoke_changed_property(WindowProperty::IsVisible);
}
fn set_width(&mut self, width: super::Size) {
self.base.size.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height(&mut self, height: super::Size) {
self.base.size.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_margin(&mut self, margin: Thickness) {
self.base.margin = margin;
self.provoke_changed_property(WindowProperty::Margin);
}
fn set_border(&mut self, border: BorderStyle) {
self.base.border = border;
self.provoke_changed_property(WindowProperty::Border);
}
fn set_enabled_state(&mut self, is_enabled: bool) {
self.base.enabled = is_enabled;
}
fn set_width_constraint(&mut self, width: SizeConstraint) {
self.base.constraints.x = width;
self.provoke_changed_property(WindowProperty::Size);
}
fn set_height_constraint(&mut self, height: SizeConstraint) {
self.base.constraints.y = height;
self.provoke_changed_property(WindowProperty::Size);
}
}
impl WidgetColors for DataGrid {
fn set_disabled_color(&mut self, color: Option<Color>) {
self.base.colors.disabled = color;
}
fn set_base_fg_color(&mut self, color: Option<Color>) {
self.base.colors.base_fg = color;
}
fn set_base_bg_color(&mut self, color: Option<Color>) {
self.base.colors.base_bg = color;
}
}