diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-07-25 00:40:37 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2024-07-25 00:40:37 +0200 |
| commit | 58b305b9f6d13007d2ea62775054c95177f81092 (patch) | |
| tree | 2123cb5379aa69476b1e549e0bcacc08d67fba71 /src/sheet | |
first sketch
Diffstat (limited to 'src/sheet')
| -rw-r--r-- | src/sheet/cell.rs | 198 | ||||
| -rw-r--r-- | src/sheet/mod.rs | 98 | ||||
| -rw-r--r-- | src/sheet/register.rs | 60 |
3 files changed, 356 insertions, 0 deletions
diff --git a/src/sheet/cell.rs b/src/sheet/cell.rs new file mode 100644 index 0000000..413e299 --- /dev/null +++ b/src/sheet/cell.rs @@ -0,0 +1,198 @@ +use std::fmt::Display; + +use mlua::{FromLua, IntoLua, UserData, Value}; + +use super::register::{Register, SheetId}; + +#[derive(Debug, Clone)] +pub enum Cell { + String(String), + Number(f64), +} + +impl Cell { + pub fn new_empty() -> Self { + Cell::String("".to_string()) + } +} + +impl<'lua> IntoLua<'lua> for Cell { + fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::Value<'lua>> { + match self { + Cell::String(s) => s.into_lua(lua), + Cell::Number(n) => n.into_lua(lua), + } + } +} + +impl<'lua> FromLua<'lua> for Cell { + fn from_lua(value: mlua::Value<'lua>, _: &'lua mlua::Lua) -> mlua::Result<Self> { + match value { + Value::Nil => Ok(Cell::new_empty()), + Value::Boolean(b) => Ok(Cell::String(b.to_string())), + Value::Integer(n) => Ok(Cell::String(n.to_string())), + Value::Number(n) => Ok(Cell::String(n.to_string())), + Value::String(s) => Ok(Cell::String(s.to_str()?.to_string())), + _ => Err(mlua::Error::runtime( + "cell content must be number or string", + )), + } + } +} + +impl Display for Cell { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Cell::String(s) => s.fmt(f), + Cell::Number(n) => n.fmt(f), + } + } +} + +impl From<String> for Cell { + fn from(value: String) -> Self { + Cell::String(value.clone()) + } +} + +impl From<&str> for Cell { + fn from(value: &str) -> Self { + Cell::String(value.to_string()) + } +} + +macro_rules! cell_from_integer { + ($($number:ty),+ $(,)?) => { + $( + impl From<$number> for Cell { + fn from(value: $number) -> Self { + Cell::Number(value as f64) + } + } + )* + }; +} + +#[rustfmt::skip] +cell_from_integer!( + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize, + f32, f64 +); + +#[derive(Debug, Clone, Copy)] +pub struct CellRef { + row: usize, + column: usize, + sheet_id: SheetId, +} + +impl CellRef { + pub fn new(sheet_id: SheetId, row: usize, column: usize) -> Option<Self> { + Register::get(sheet_id) + .map(|sheet| { + sheet.read().unwrap().get_cell(row, column).map(|_| Self { + sheet_id, + row, + column, + }) + }) + .unwrap_or(None) + } + + pub fn value(&self) -> Cell { + return Register::get(self.sheet_id) + .unwrap() + .read() + .unwrap() + .get_cell(self.row, self.column) + .unwrap() + .clone(); + } + + pub fn set_value(&mut self, value: Cell) { + Register::get(self.sheet_id) + .unwrap() + .write() + .unwrap() + .set_cell(self.row, self.column, value); + } + + pub fn left(&self) -> Option<Self> { + if self.column > 0 { + Self::new(self.sheet_id, self.row, self.column - 1) + } else { + None + } + } + + pub fn right(&self) -> Option<Self> { + Self::new(self.sheet_id, self.row, self.column + 1) + } + + pub fn up(&self) -> Option<Self> { + if self.row > 0 { + Self::new(self.sheet_id, self.row - 1, self.column) + } else { + None + } + } + + pub fn down(&self) -> Option<Self> { + Self::new(self.sheet_id, self.row + 1, self.column) + } + + pub fn begin(&self) -> Option<Self> { + Self::new(self.sheet_id, self.row, 0) + } + + pub fn end(&self) -> Option<Self> { + Self::new( + self.sheet_id, + self.row, + Register::get(self.sheet_id) + .unwrap() + .read() + .unwrap() + .width() + - 1, + ) + } + + pub fn top(&self) -> Option<Self> { + Self::new(self.sheet_id, 0, self.column) + } + + pub fn bottom(&self) -> Option<Self> { + Self::new( + self.sheet_id, + Register::get(self.sheet_id) + .unwrap() + .read() + .unwrap() + .height() + - 1, + self.column, + ) + } +} + +impl UserData for CellRef +where + Self: Sized, +{ + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("row", |_, this| Ok(this.row)); + fields.add_field_method_get("column", |_, this| Ok(this.column)); + fields.add_field_method_get("value", |_, this| Ok(this.value())); + fields.add_field_method_set("value", |_, this, value| Ok(this.set_value(value))); + fields.add_field_method_get("left", |lua, this| this.left().into_lua(lua)); + fields.add_field_method_get("right", |lua, this| this.right().into_lua(lua)); + fields.add_field_method_get("up", |lua, this| this.up().into_lua(lua)); + fields.add_field_method_get("down", |lua, this| this.down().into_lua(lua)); + fields.add_field_method_get("begin", |lua, this| this.begin().into_lua(lua)); + fields.add_field_method_get("end", |lua, this| this.end().into_lua(lua)); + fields.add_field_method_get("top", |lua, this| this.top().into_lua(lua)); + fields.add_field_method_get("bottom", |lua, this| this.bottom().into_lua(lua)); + } +} diff --git a/src/sheet/mod.rs b/src/sheet/mod.rs new file mode 100644 index 0000000..015e3d8 --- /dev/null +++ b/src/sheet/mod.rs @@ -0,0 +1,98 @@ +use cell::{Cell, CellRef}; +use mlua::{IntoLua, UserData, Value}; +use register::{Register, SheetId}; + +pub mod cell; +pub mod register; + +#[derive(Debug, Default, Clone)] +pub struct Sheet { + id: register::SheetId, + rows: Vec<Vec<Cell>>, +} + +impl Sheet { + pub(self) fn new(width: usize, height: usize, id: register::SheetId) -> Self { + Self { + id, + rows: vec![vec![Cell::new_empty(); width]; height], + } + } + + pub fn set_cell(&mut self, row: usize, column: usize, cell: Cell) { + if row < self.height() && column < self.width() { + self.rows[row][column] = cell + } + } + + pub fn get_cell<'a>(&'a self, row: usize, column: usize) -> Option<&'a Cell> { + if let Some(r) = self.rows.get(row) { + if let Some(_) = r.get(column) { + return Some(&self.rows[row][column]); + } + } + + None + } + + pub fn get_ref(&self, row: usize, column: usize) -> Option<CellRef> { + CellRef::new(self.id, row, column) + } + + pub fn height(&self) -> usize { + self.rows.len() + } + + pub fn set_height(&mut self, mut height: usize) { + height = height.max(1); + self.rows + .resize(height, vec![Cell::new_empty(); self.width()]) + } + + pub fn width(&self) -> usize { + self.rows.get(0).map(|r| r.len()).unwrap_or(1) + } + + pub fn set_width(&mut self, mut width: usize) { + width = width.max(1); + for row in self.rows.iter_mut() { + row.resize(width, Cell::new_empty()); + } + } + + pub fn id(&self) -> register::SheetId { + self.id + } +} + +struct SheetLuaRef { + id: SheetId, +} + +impl SheetLuaRef { + pub fn new(id: SheetId) -> Self { + Self { id } + } +} + +impl UserData for SheetLuaRef { + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("width", |_, luaref| { + Ok(Register::get(luaref.id).unwrap().read().unwrap().width()) + }); + + fields.add_field_method_get("height", |_, luaref| { + Ok(Register::get(luaref.id).unwrap().read().unwrap().height()) + }); + } + + fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method_mut("cell", |lua, luaref, (row, column): (usize, usize)| { + if let Some(_) = Register::get(luaref.id) { + Ok(CellRef::new(luaref.id, row, column).into_lua(lua).unwrap()) + } else { + Ok(Value::Nil) + } + }) + } +} diff --git a/src/sheet/register.rs b/src/sheet/register.rs new file mode 100644 index 0000000..aa5245b --- /dev/null +++ b/src/sheet/register.rs @@ -0,0 +1,60 @@ +use std::sync::{Arc, RwLock}; + +use mlua::{UserData, Value}; + +use super::{Sheet, SheetLuaRef}; + +static REGISTER: RwLock<Vec<Arc<RwLock<Sheet>>>> = RwLock::new(Vec::new()); + +pub type SheetId = usize; + +#[derive(Debug)] +pub struct Register; + +impl Register { + pub fn create(width: usize, height: usize) -> SheetId { + let mut register = REGISTER.write().unwrap(); + + let id = register.len(); + let sheet = Sheet::new(width, height, id); + register.push(Arc::new(RwLock::new(sheet))); + id + } + + pub fn get<'a>(id: SheetId) -> Option<Arc<RwLock<Sheet>>> { + let register = REGISTER.read().unwrap(); + + if id < register.len() { + Some(Arc::clone(®ister[id])) + } else { + None + } + } +} + +impl UserData for Register { + fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function_mut("create", |lua, (width, height): (usize, usize)| { + let id = Register::create(width, height); + let luaref = SheetLuaRef::new(id); + if let Ok(ud) = lua.create_userdata(luaref) { + Ok(Value::UserData(ud)) + } else { + Ok(Value::Nil) + } + }); + + methods.add_function_mut("get", |lua, id: SheetId| { + if let Some(_) = Register::get(id) { + let luaref = SheetLuaRef::new(id); + if let Ok(ud) = lua.create_userdata(luaref) { + Ok(Value::UserData(ud)) + } else { + Ok(Value::Nil) + } + } else { + Ok(Value::Nil) + } + }) + } +} |