summaryrefslogtreecommitdiff
path: root/src/state/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/view')
-rw-r--r--src/state/view/mod.rs186
-rw-r--r--src/state/view/mode.rs48
2 files changed, 234 insertions, 0 deletions
diff --git a/src/state/view/mod.rs b/src/state/view/mod.rs
new file mode 100644
index 0000000..73f02ab
--- /dev/null
+++ b/src/state/view/mod.rs
@@ -0,0 +1,186 @@
+use std::sync::{Arc, RwLock};
+
+use mlua::{IntoLua, UserData, Value};
+
+use self::mode::Mode;
+
+use super::GlobalState;
+use crate::{
+ cursor::{Cursor, CursorMove},
+ sheet::{
+ luaref::SheetLuaRef,
+ register::{Register, SheetId},
+ Sheet,
+ },
+};
+
+pub mod mode;
+
+#[derive(Default, Debug)]
+pub struct SheetViewState {
+ pub cursor: Cursor,
+ active_sheet: Option<SheetId>,
+ pub mode: Mode,
+ pub selection_anchor: Option<(usize, usize)>,
+}
+
+impl SheetViewState {
+ pub const fn new() -> Self {
+ Self {
+ cursor: Cursor::new(),
+ active_sheet: None,
+ mode: Mode::Normal,
+ selection_anchor: None,
+ }
+ }
+
+ pub fn set_active_sheet(&mut self, sheet: Option<SheetId>) {
+ if let Some(id) = sheet {
+ if let Some(lock) = Register::get(id) {
+ let sheet = lock.read().unwrap();
+ self.cursor.set_x_max(sheet.width());
+ self.cursor.set_y_max(sheet.height());
+ }
+ }
+
+ self.active_sheet = sheet
+ }
+
+ pub fn active_sheet(&self) -> Option<Arc<RwLock<Sheet>>> {
+ 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 as usize > self.cursor.y() {
+ (self.cursor.y(), selection.0)
+ } else {
+ (selection.0, self.cursor.y())
+ };
+
+ let column = if selection.1 as usize > 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().sheetview
+ };
+}
+
+macro_rules! cfg_mut {
+ () => {
+ GlobalState::instance_mut().sheetview
+ };
+}
+
+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<SheetLuaRef>| {
+ 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(())
+ })
+ }
+
+ fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+ methods.add_function("move_cursor", |_, (row, column): (usize, usize)| {
+ cfg_mut!()
+ .cursor
+ .move_checked(CursorMove::Jump((row, column)));
+ Ok(())
+ });
+
+ methods.add_function("cancel_mode", |_, _: ()| {
+ cfg_mut!().cancel_mode();
+ Ok(())
+ })
+ }
+}
diff --git a/src/state/view/mode.rs b/src/state/view/mode.rs
new file mode 100644
index 0000000..3fa9c63
--- /dev/null
+++ b/src/state/view/mode.rs
@@ -0,0 +1,48 @@
+use mlua::{FromLua, IntoLua};
+
+#[derive(Default, Debug, Clone, Copy)]
+pub enum Mode {
+ #[default]
+ Normal,
+ Visual,
+ Insert,
+ Command,
+}
+
+impl<'lua> FromLua<'lua> for Mode {
+ fn from_lua(
+ value: mlua::prelude::LuaValue<'lua>,
+ _lua: &'lua mlua::prelude::Lua,
+ ) -> mlua::prelude::LuaResult<Self> {
+ if value.is_string() {
+ match value.as_str().unwrap().to_lowercase().as_ref() {
+ "normal" => Ok(Mode::Normal),
+ "visual" => Ok(Mode::Visual),
+ "insert" => Ok(Mode::Visual),
+ "command" => Ok(Mode::Visual),
+ _ => Err(mlua::Error::runtime(
+ "mode needs to be 'normal', 'visual' or 'insert'",
+ )),
+ }
+ } else {
+ Err(mlua::Error::runtime(
+ "mode needs to be 'normal', 'visual' or 'insert'",
+ ))
+ }
+ }
+}
+
+impl<'lua> IntoLua<'lua> for Mode {
+ fn into_lua(
+ self,
+ lua: &'lua mlua::prelude::Lua,
+ ) -> mlua::prelude::LuaResult<mlua::prelude::LuaValue<'lua>> {
+ match self {
+ Mode::Normal => "normal",
+ Mode::Visual => "insert",
+ Mode::Insert => "visual",
+ Mode::Command => "command",
+ }
+ .into_lua(lua)
+ }
+}