mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 04:39:48 -04:00
wip: support column and row styling
This commit is contained in:
parent
f6c9e95fda
commit
3219e01176
153
src/book/mod.rs
153
src/book/mod.rs
@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
|
|||||||
use ironcalc::{
|
use ironcalc::{
|
||||||
base::{
|
base::{
|
||||||
expressions::types::Area,
|
expressions::types::Area,
|
||||||
types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet},
|
types::{SheetData, Style, Worksheet},
|
||||||
worksheet::WorksheetDimension,
|
worksheet::WorksheetDimension,
|
||||||
Model, UserModel,
|
Model, UserModel,
|
||||||
},
|
},
|
||||||
@ -17,7 +17,12 @@ use crate::ui::Address;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
const COL_PIXELS: f64 = 5.0;
|
pub(crate) const COL_PIXELS: f64 = 5.0;
|
||||||
|
// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it
|
||||||
|
// publically.
|
||||||
|
pub(crate) const LAST_COLUMN: i32 = 16_384;
|
||||||
|
pub(crate) const LAST_ROW: i32 = 1_048_576;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AddressRange<'book> {
|
pub struct AddressRange<'book> {
|
||||||
@ -281,82 +286,6 @@ impl Book {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_column(&self, sheet: u32, col: usize) -> Result<Option<&Col>> {
|
|
||||||
Ok(self
|
|
||||||
.model
|
|
||||||
.get_model()
|
|
||||||
.workbook
|
|
||||||
.worksheet(sheet)
|
|
||||||
.map_err(|e| anyhow!("{}", e))?
|
|
||||||
.cols
|
|
||||||
.get(col))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_row(&self, sheet: u32, col: usize) -> Result<Option<&Row>> {
|
|
||||||
Ok(self
|
|
||||||
.model
|
|
||||||
.get_model()
|
|
||||||
.workbook
|
|
||||||
.worksheet(sheet)
|
|
||||||
.map_err(|e| anyhow!("{}", e))?
|
|
||||||
.rows
|
|
||||||
.get(col))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_column_style(&self, sheet: u32, col: usize) -> Result<Option<Style>> {
|
|
||||||
// TODO(jwall): This is modeled a little weird. We should probably record
|
|
||||||
// the error *somewhere* but for the user there is nothing to be done except
|
|
||||||
// not use a style.
|
|
||||||
if let Some(col) = self.get_column(sheet, col)? {
|
|
||||||
if let Some(style_idx) = col.style.map(|idx| idx as usize) {
|
|
||||||
let styles = &self.model.get_model().workbook.styles;
|
|
||||||
if styles.cell_style_xfs.len() <= style_idx {
|
|
||||||
return Ok(Some(Style {
|
|
||||||
alignment: None,
|
|
||||||
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
|
|
||||||
fill: styles.fills[style_idx].clone(),
|
|
||||||
font: styles.fonts[style_idx].clone(),
|
|
||||||
border: styles.borders[style_idx].clone(),
|
|
||||||
quote_prefix: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_row_style(&self, sheet: u32, row: usize) -> Result<Option<Style>> {
|
|
||||||
// TODO(jwall): This is modeled a little weird. We should probably record
|
|
||||||
// the error *somewhere* but for the user there is nothing to be done except
|
|
||||||
// not use a style.
|
|
||||||
if let Some(row) = self.get_row(sheet, row)? {
|
|
||||||
let style_idx = row.s as usize;
|
|
||||||
let styles = &self.model.get_model().workbook.styles;
|
|
||||||
if styles.cell_style_xfs.len() <= style_idx {
|
|
||||||
return Ok(Some(Style {
|
|
||||||
alignment: None,
|
|
||||||
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
|
|
||||||
fill: styles.fills[style_idx].clone(),
|
|
||||||
font: styles.fonts[style_idx].clone(),
|
|
||||||
border: styles.borders[style_idx].clone(),
|
|
||||||
quote_prefix: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_style(&mut self) -> Style {
|
|
||||||
Style {
|
|
||||||
alignment: None,
|
|
||||||
num_fmt: String::new(),
|
|
||||||
fill: Fill::default(),
|
|
||||||
font: Font::default(),
|
|
||||||
border: Border::default(),
|
|
||||||
quote_prefix: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> {
|
pub fn set_cell_style(&mut self, style: &[(&str, &str)], area: &Area) -> Result<()> {
|
||||||
for (path, val) in style {
|
for (path, val) in style {
|
||||||
self.model
|
self.model
|
||||||
@ -366,28 +295,56 @@ impl Book {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_col_style(&mut self, style: &[(&str, &str)], sheet: u32, col: usize) -> Result<()> {
|
fn get_col_range(&self, sheet: u32, col_idx: usize) -> Area {
|
||||||
todo!()
|
Area {
|
||||||
//let idx = self.create_or_get_style_idx(style);
|
sheet,
|
||||||
//let sheet = self.model.workbook.worksheet_mut(sheet)
|
row: 1,
|
||||||
// .map_err(|e| anyhow!("{}", e))?;
|
column: col_idx as i32,
|
||||||
//let width = sheet.get_column_width(col as i32)
|
width: 1,
|
||||||
// .map_err(|e| anyhow!("{}", e))?;
|
height: LAST_ROW,
|
||||||
//sheet.set_column_style(col as i32, idx)
|
}
|
||||||
// .map_err(|e| anyhow!("{}", e))?;
|
|
||||||
//sheet.set_column_width(col as i32, width)
|
|
||||||
// .map_err(|e| anyhow!("{}", e))?;
|
|
||||||
//Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_row_style(&mut self, style: &[(&str, &str)], sheet: u32, row: usize) -> Result<()> {
|
fn get_row_range(&self, sheet: u32, row_idx: usize) -> Area {
|
||||||
todo!()
|
Area {
|
||||||
//let idx = self.create_or_get_style_idx(style);
|
sheet,
|
||||||
//self.model.workbook.worksheet_mut(sheet)
|
row: row_idx as i32,
|
||||||
// .map_err(|e| anyhow!("{}", e))?
|
column: 1,
|
||||||
// .set_row_style(row as i32, idx)
|
width: LAST_COLUMN,
|
||||||
// .map_err(|e| anyhow!("{}", e))?;
|
height: 1,
|
||||||
//Ok(())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_col_style(
|
||||||
|
&mut self,
|
||||||
|
style: &[(&str, &str)],
|
||||||
|
sheet: u32,
|
||||||
|
col_idx: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
// TODO(jeremy): This is a little hacky and the underlying model
|
||||||
|
// supports a better mechanism but UserModel doesn't support it yet.
|
||||||
|
// https://github.com/ironcalc/IronCalc/issues/273
|
||||||
|
// https://github.com/ironcalc/IronCalc/pull/276 is the coming fix.
|
||||||
|
// NOTE(jwall): Because of the number of cells required to modify
|
||||||
|
// this is crazy slow
|
||||||
|
let area = self.get_col_range(sheet, col_idx);
|
||||||
|
self.set_cell_style(style, &area)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_row_style(
|
||||||
|
&mut self,
|
||||||
|
style: &[(&str, &str)],
|
||||||
|
sheet: u32,
|
||||||
|
row_idx: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
// TODO(jeremy): This is a little hacky and the underlying model
|
||||||
|
// supports a better mechanism but UserModel doesn't support it yet.
|
||||||
|
// https://github.com/ironcalc/IronCalc/issues/273
|
||||||
|
// https://github.com/ironcalc/IronCalc/pull/276 is the coming fix.
|
||||||
|
let area = self.get_row_range(sheet, row_idx);
|
||||||
|
self.set_cell_style(style, &area)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a cells rendered content for display.
|
/// Get a cells rendered content for display.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Ui rendering logic
|
//! Ui rendering logic
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
use crate::book::{AddressRange, Book};
|
use crate::book::{self, AddressRange, Book};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
@ -235,7 +235,7 @@ impl<'ws> Workspace<'ws> {
|
|||||||
/// Move a row down in the current sheet.
|
/// Move a row down in the current sheet.
|
||||||
pub fn move_down(&mut self) -> Result<()> {
|
pub fn move_down(&mut self) -> Result<()> {
|
||||||
let mut loc = self.book.location.clone();
|
let mut loc = self.book.location.clone();
|
||||||
if loc.row < render::viewport::LAST_ROW {
|
if loc.row < (book::LAST_ROW as usize) {
|
||||||
loc.row += 1;
|
loc.row += 1;
|
||||||
self.book.move_to(&loc)?;
|
self.book.move_to(&loc)?;
|
||||||
}
|
}
|
||||||
@ -274,7 +274,7 @@ impl<'ws> Workspace<'ws> {
|
|||||||
/// Move a column to the left in the current sheet.
|
/// Move a column to the left in the current sheet.
|
||||||
pub fn move_right(&mut self) -> Result<()> {
|
pub fn move_right(&mut self) -> Result<()> {
|
||||||
let mut loc = self.book.location.clone();
|
let mut loc = self.book.location.clone();
|
||||||
if loc.col < render::viewport::LAST_COLUMN {
|
if loc.col < (book::LAST_COLUMN as usize) {
|
||||||
loc.col += 1;
|
loc.col += 1;
|
||||||
self.book.move_to(&loc)?;
|
self.book.move_to(&loc)?;
|
||||||
}
|
}
|
||||||
@ -466,8 +466,8 @@ impl<'ws> Workspace<'ws> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Ok(Some(Cmd::Quit)) => Ok(Some(ExitCode::SUCCESS)),
|
Ok(Some(Cmd::Quit)) => Ok(Some(ExitCode::SUCCESS)),
|
||||||
Ok(Some(Cmd::ColorRows(_count, color))) => {
|
Ok(Some(Cmd::ColorRows(count, color))) => {
|
||||||
let row_count = _count.unwrap_or(1);
|
let row_count = count.unwrap_or(1);
|
||||||
let row = self.book.location.row;
|
let row = self.book.location.row;
|
||||||
for r in row..(row + row_count) {
|
for r in row..(row + row_count) {
|
||||||
self.book.set_row_style(
|
self.book.set_row_style(
|
||||||
@ -478,8 +478,8 @@ impl<'ws> Workspace<'ws> {
|
|||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Ok(Some(Cmd::ColorColumns(_count, color))) => {
|
Ok(Some(Cmd::ColorColumns(count, color))) => {
|
||||||
let col_count = _count.unwrap_or(1);
|
let col_count = count.unwrap_or(1);
|
||||||
let col = self.book.location.col;
|
let col = self.book.location.col;
|
||||||
for c in col..(col + col_count) {
|
for c in col..(col + col_count) {
|
||||||
self.book.set_col_style(
|
self.book.set_col_style(
|
||||||
|
@ -7,14 +7,9 @@ use ratatui::{
|
|||||||
widgets::{Block, Cell, Row, StatefulWidget, Table, Widget},
|
widgets::{Block, Cell, Row, StatefulWidget, Table, Widget},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::book;
|
||||||
use super::{Address, Book, RangeSelection};
|
use super::{Address, Book, RangeSelection};
|
||||||
|
|
||||||
// TODO(zaphar): Move this to the book module.
|
|
||||||
// NOTE(zaphar): This is stolen from ironcalc but ironcalc doesn't expose it
|
|
||||||
// publically.
|
|
||||||
pub(crate) const LAST_COLUMN: usize = 16_384;
|
|
||||||
pub(crate) const LAST_ROW: usize = 1_048_576;
|
|
||||||
|
|
||||||
/// A visible column to show in our Viewport.
|
/// A visible column to show in our Viewport.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct VisibleColumn {
|
pub struct VisibleColumn {
|
||||||
@ -68,7 +63,7 @@ impl<'ws> Viewport<'ws> {
|
|||||||
let start_row = std::cmp::min(self.selected.row, state.prev_corner.row);
|
let start_row = std::cmp::min(self.selected.row, state.prev_corner.row);
|
||||||
let mut start = start_row;
|
let mut start = start_row;
|
||||||
let mut end = start_row;
|
let mut end = start_row;
|
||||||
for row_idx in start_row..=LAST_ROW {
|
for row_idx in start_row..=(book::LAST_ROW as usize) {
|
||||||
let updated_length = length + 1;
|
let updated_length = length + 1;
|
||||||
if updated_length <= height {
|
if updated_length <= height {
|
||||||
length = updated_length;
|
length = updated_length;
|
||||||
@ -95,7 +90,7 @@ impl<'ws> Viewport<'ws> {
|
|||||||
// We start out with a length of 5 already reserved
|
// We start out with a length of 5 already reserved
|
||||||
let mut length = 5;
|
let mut length = 5;
|
||||||
let start_idx = std::cmp::min(self.selected.col, state.prev_corner.col);
|
let start_idx = std::cmp::min(self.selected.col, state.prev_corner.col);
|
||||||
for idx in start_idx..=LAST_COLUMN {
|
for idx in start_idx..=(book::LAST_COLUMN as usize) {
|
||||||
let size = self.book.get_col_size(idx)? as u16;
|
let size = self.book.get_col_size(idx)? as u16;
|
||||||
let updated_length = length + size;
|
let updated_length = length + size;
|
||||||
let col = VisibleColumn { idx, length: size };
|
let col = VisibleColumn { idx, length: size };
|
||||||
|
@ -2,6 +2,7 @@ use std::process::ExitCode;
|
|||||||
|
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::book;
|
||||||
use crate::ui::cmd::parse_color;
|
use crate::ui::cmd::parse_color;
|
||||||
use crate::ui::{Address, Modality};
|
use crate::ui::{Address, Modality};
|
||||||
|
|
||||||
@ -1221,6 +1222,57 @@ fn test_color_cells() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_row() {
|
||||||
|
let mut ws = new_workspace();
|
||||||
|
script()
|
||||||
|
.char(':')
|
||||||
|
.chars("color-rows red")
|
||||||
|
.enter()
|
||||||
|
.run(&mut ws)
|
||||||
|
.expect("Unable to run script");
|
||||||
|
for ci in [1, book::LAST_COLUMN] {
|
||||||
|
let style = ws
|
||||||
|
.book
|
||||||
|
.get_cell_style(ws.book.current_sheet, &Address { row: 1, col: ci as usize })
|
||||||
|
.expect("failed to get style");
|
||||||
|
assert_eq!(
|
||||||
|
"#800000",
|
||||||
|
style
|
||||||
|
.fill
|
||||||
|
.bg_color
|
||||||
|
.expect(&format!("No background color set for {}:{}", 1, ci))
|
||||||
|
.as_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_col() {
|
||||||
|
let mut ws = new_workspace();
|
||||||
|
script()
|
||||||
|
.char(':')
|
||||||
|
.chars("color-columns red")
|
||||||
|
.enter()
|
||||||
|
.run(&mut ws)
|
||||||
|
.expect("Unable to run script");
|
||||||
|
for ri in [1, book::LAST_ROW] {
|
||||||
|
let style = ws
|
||||||
|
.book
|
||||||
|
.get_cell_style(ws.book.current_sheet, &Address { row: ri as usize, col: 1 })
|
||||||
|
.expect("failed to get style");
|
||||||
|
assert_eq!(
|
||||||
|
"#800000",
|
||||||
|
style
|
||||||
|
.fill
|
||||||
|
.bg_color
|
||||||
|
.expect(&format!("No background color set for {}:{}", ri, 1))
|
||||||
|
.as_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn new_workspace<'a>() -> Workspace<'a> {
|
fn new_workspace<'a>() -> Workspace<'a> {
|
||||||
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook")
|
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user