use std::sync::{Arc, RwLock}; use mlua::{IntoLua, UserData, Value}; use self::{bar::SheetViewBarState, mode::Mode}; use super::{GlobalState, DUMMY_STATE}; use crate::{ cursor::{Cursor, CursorMove}, sheet::{ luaref::SheetLuaRef, register::{Register, SheetId}, Sheet, }, }; pub mod bar; pub mod mode; #[derive(Default, Debug)] pub struct SheetViewState { pub cursor: Cursor, active_sheet: Option, pub mode: Mode, pub selection_anchor: Option<(usize, usize)>, pub bar: SheetViewBarState, } impl SheetViewState { pub const fn new() -> Self { Self { cursor: Cursor::new(), active_sheet: None, mode: Mode::Normal, selection_anchor: None, bar: SheetViewBarState::new(), } } pub fn set_active_sheet(&mut self, sheet: Option) { if let Some(id) = sheet { if let Some(lock) = Register::get(id) { let sheet = lock.read().unwrap(); self.cursor.set_x_max(sheet.width() - 1); self.cursor.set_y_max(sheet.height() - 1); } } self.active_sheet = sheet } pub fn active_sheet(&self) -> Option>> { if let Some(id) = self.active_sheet { Register::get(id) } else { None } } pub fn cancel_mode(&mut self) { if self.selection_anchor.is_some() { self.mode = Mode::Visual; } else { self.mode = Mode::Normal; } } fn selection_range(&self) -> Option<((usize, usize), (usize, usize))> { if let Some(selection) = self.selection_anchor { let row = if selection.0 > self.cursor.y() { (self.cursor.y(), selection.0) } else { (selection.0, self.cursor.y()) }; let column = if selection.1 > self.cursor.x() { (self.cursor.x(), selection.1) } else { (selection.1, self.cursor.x()) }; Some((row, column)) } else { None } } pub fn selection(&self) -> Vec<(usize, usize)> { let mut selection = Vec::new(); if let Some((row, column)) = self.selection_range() { for i in row.0..=row.1 { for j in column.0..=column.1 { selection.push((i, j)) } } } selection } pub fn selection_or_cursor(&self) -> Vec<(usize, usize)> { if self.selection_anchor.is_some() { self.selection() } else { vec![(self.cursor.y(), self.cursor.x())] } } pub fn selection_contains(&self, row: usize, column: usize) -> bool { if let Some((row_range, column_range)) = self.selection_range() { row >= row_range.0 && row <= row_range.1 && column >= column_range.0 && column <= column_range.1 } else { false } } } macro_rules! cfg { () => { GlobalState::instance().view }; } macro_rules! cfg_mut { () => { GlobalState::instance_mut().view }; } impl UserData for SheetViewState { fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_function_get("active", |lua, _| { if let Some(id) = cfg!().active_sheet { SheetLuaRef::new(id).into_lua(lua) } else { Ok(Value::Nil) } }); fields.add_field_function_set("active", |_, _, sheet: Option| { if let Some(r) = sheet { cfg_mut!().active_sheet = Some(r.id()) } else { cfg_mut!().active_sheet = None } Ok(()) }); fields.add_field_function_get("cursor", |lua, _| { let table = lua.create_table()?; table.set("row", cfg!().cursor.y())?; table.set("column", cfg!().cursor.x())?; Ok(table) }); fields.add_field_function_get("mode", |_, _| Ok(cfg!().mode)); fields.add_field_function_set("mode", |_, _, mode: Mode| { let this = &mut cfg_mut!(); match mode { Mode::Visual => match this.mode { Mode::Visual => {} _ => this.selection_anchor = Some((this.cursor.y(), this.cursor.x())), }, _ => this.selection_anchor = None, } this.mode = mode; Ok(()) }); fields.add_field_function_get("bar", |_, _| Ok(DUMMY_STATE.view.bar)) } fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_function("move_cursor", |_, m: CursorMove| { cfg_mut!().cursor.move_checked(m); Ok(()) }); methods.add_function("cancel_mode", |_, _: ()| { cfg_mut!().cancel_mode(); Ok(()) }) } }