/* * meli * * Copyright 2017-2022 Manos Pitsidianakis * * This file is part of meli. * * meli is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * meli is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ //! UI components to display tabular lists. use std::mem::MaybeUninit; use super::*; use crate::segment_tree::SegmentTree; #[derive(Debug, Default, Copy, Clone)] pub enum ColumnElasticity { #[default] Rigid, Grow { min: usize, max: Option, }, } impl ColumnElasticity { pub fn set_rigid(&mut self) { *self = Self::Rigid; } pub fn set_grow(&mut self, min: usize, max: Option) { *self = Self::Grow { min, max }; } } /*#[derive(Debug, Default, Clone)] pub enum TableRowFormat { #[default] None, Fill(FormatTag), Range { start: usize, end: usize, val: FormatTag, }, } impl TableRowFormat { pub const SELECTED: u8 = 0; pub const UNREAD: u8 = 1; } */ #[derive(Debug, Default, Clone)] pub struct TableThemeConfig { pub theme: TableTheme, //pub row_formats: HashMap>, } impl TableThemeConfig { pub fn set_single_theme(&mut self, value: ThemeAttribute) -> &mut Self { self.theme = TableTheme::Single(value); self } pub fn set_even_odd_theme(&mut self, even: ThemeAttribute, odd: ThemeAttribute) -> &mut Self { self.theme = TableTheme::EvenOdd { even, odd }; self } } #[derive(Debug, Clone)] pub enum TableTheme { Single(ThemeAttribute), EvenOdd { even: ThemeAttribute, odd: ThemeAttribute, }, } impl Default for TableTheme { fn default() -> Self { Self::Single(ThemeAttribute::default()) } } #[derive(Debug, Default, Clone)] pub struct TableCursorConfig { pub handle: bool, pub theme: TableTheme, } impl TableCursorConfig { pub fn set_handle(&mut self, value: bool) -> &mut Self { self.handle = value; self } pub fn set_single_theme(&mut self, value: ThemeAttribute) -> &mut Self { self.theme = TableTheme::Single(value); self } pub fn set_even_odd_theme(&mut self, even: ThemeAttribute, odd: ThemeAttribute) -> &mut Self { self.theme = TableTheme::EvenOdd { even, odd }; self } } #[derive(Debug, Clone)] pub struct DataColumns { pub cursor_config: TableCursorConfig, pub theme_config: TableThemeConfig, pub columns: Box<[CellBuffer; N]>, /// widths of columns calculated in first draw and after size changes pub widths: [usize; N], pub elasticities: [ColumnElasticity; N], pub x_offset: usize, pub width_accum: usize, pub segment_tree: Box<[SegmentTree; N]>, } // Workaround because Default derive doesn't work for const generic array // lengths yet. impl Default for DataColumns { fn default() -> Self { fn init_array(cl: impl Fn() -> T) -> [T; N] { // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element let mut data: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; for elem in &mut data[..] { elem.write(cl()); } let ptr = &data as *const [MaybeUninit; N]; std::mem::forget(data); unsafe { (ptr as *const [T; N]).read() } } Self { cursor_config: TableCursorConfig::default(), theme_config: TableThemeConfig::default(), columns: Box::new(init_array(CellBuffer::default)), widths: [0_usize; N], elasticities: [ColumnElasticity::default(); N], x_offset: 0, width_accum: 0, segment_tree: Box::new(init_array(SegmentTree::default)), } } } impl DataColumns { pub fn recalc_widths( &mut self, (screen_width, screen_height): (usize, usize), top_idx: usize, ) -> usize { let mut width_accum = 0; let mut growees = 0; let mut growees_max = 0; let grow_minmax = None; for i in 0..N { if screen_height == 0 { self.widths[i] = 0; continue; } self.widths[i] = self.segment_tree[i].get_max(top_idx, top_idx + screen_height - 1) as usize; if self.widths[i] == 0 { self.widths[i] = self.columns[i].cols; } match self.elasticities[i] { ColumnElasticity::Rigid => {} ColumnElasticity::Grow { min, max: Some(max), } => { self.widths[i] = std::cmp::max(min, std::cmp::min(max, self.widths[i])); growees += 1; } ColumnElasticity::Grow { min, max: None } => { self.widths[i] = std::cmp::max(min, self.widths[i]); growees += 1; growees_max += 1; } } width_accum += self.widths[i]; } // add column gaps width_accum += 2 * N.saturating_sub(1); debug_assert!(growees >= growees_max); debug_assert!(grow_minmax.is_none() || growees_max > 0); if width_accum >= screen_width || screen_height == 0 || screen_width == 0 || growees == 0 { self.width_accum = width_accum; return width_accum; } let distribute = screen_width - width_accum; let maxmins = growees_max * grow_minmax.unwrap_or(0); let part = if maxmins != 0 && growees_max < growees { distribute.saturating_sub(maxmins) / (growees - growees_max) } else if maxmins != 0 { distribute.saturating_sub(maxmins) / growees_max + grow_minmax.unwrap_or(0) } else { distribute / growees }; for i in 0..N { match self.elasticities[i] { ColumnElasticity::Rigid => {} ColumnElasticity::Grow { min: _, max: Some(_), } => {} ColumnElasticity::Grow { min: _, max: None } => { self.widths[i] += part; width_accum += part; } } } self.width_accum = width_accum; width_accum } pub fn draw( &self, grid: &mut CellBuffer, top_idx: usize, cursor_pos: usize, mut bounds: BoundsIterator, ) { let mut _relative_x_offset = 0; let mut skip_cols = (0, 0); let mut start_col = 0; let total_area = bounds.area(); let (width, height) = (width!(total_area), height!(total_area)); while _relative_x_offset < self.x_offset && start_col < N { _relative_x_offset += self.widths[start_col] + 2; if self.x_offset <= _relative_x_offset { skip_cols.0 = start_col; skip_cols.1 = _relative_x_offset - self.x_offset; _relative_x_offset = self.x_offset; break; } start_col += 1; } for col in skip_cols.0..N { if bounds.is_empty() { break; } let mut column_width = self.widths[col]; if column_width > bounds.width { column_width = bounds.width; } else if column_width == 0 { skip_cols.1 = 0; continue; } let mut column_area = bounds.add_x(column_width + 2); copy_area( grid, &self.columns[col], column_area.area(), ( (skip_cols.1, top_idx), ( column_width.saturating_sub(1), self.columns[col].rows.saturating_sub(1), ), ), ); let gap_area = column_area.add_x(column_width); match self.theme_config.theme { TableTheme::Single(row_attr) => { change_colors(grid, gap_area.area(), row_attr.fg, row_attr.bg); } TableTheme::EvenOdd { even, odd } => { change_colors(grid, gap_area.area(), even.fg, even.bg); let mut top_idx = top_idx; for row in gap_area { if top_idx % 2 != 0 { change_colors(grid, row.area(), odd.fg, odd.bg); } top_idx += 1; } } }; skip_cols.1 = 0; } if self.cursor_config.handle && (top_idx..(top_idx + height)).contains(&cursor_pos) { let offset = cursor_pos - top_idx; let row_attr = match self.cursor_config.theme { TableTheme::Single(attr) => attr, TableTheme::EvenOdd { even, odd: _ } if cursor_pos % 2 == 0 => even, TableTheme::EvenOdd { even: _, odd } => odd, }; change_colors( grid, ( pos_inc(upper_left!(total_area), (0, offset)), pos_inc(upper_left!(total_area), (width, offset)), ), row_attr.fg, row_attr.bg, ); } } }