From bd5c6cc10624348decf3dec40a2f3464a33f5e1e Mon Sep 17 00:00:00 2001 From: Rushmore75 Date: Mon, 10 Nov 2025 10:55:00 -0700 Subject: [PATCH] add ui --- .gitignore | 2 + Cargo.lock | 591 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/calc.rs | 343 ++++++++++++++++++++++++++++++ src/ctx.rs | 113 ++-------- src/main.rs | 357 ++++++++++++++++--------------- 6 files changed, 1127 insertions(+), 282 deletions(-) create mode 100644 .gitignore create mode 100644 src/calc.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f84cc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9a30716..02efb31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,13 +2,604 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "evalexpr" version = "13.0.0" +source = "git+https://github.com/Rushmore75/evalexpr.git#cb0f504884ee4e70e682a90d6b231c9917b84f4c" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "sc_rs" version = "0.1.0" dependencies = [ "evalexpr", + "ratatui", ] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 7963f0d..666a85a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2024" [dependencies] # evalexpr = "13.0.0" -evalexpr = { path='../evalexpr' } +evalexpr = { git="https://github.com/Rushmore75/evalexpr.git" } +ratatui = "0.29.0" diff --git a/src/calc.rs b/src/calc.rs new file mode 100644 index 0000000..f2d3e3c --- /dev/null +++ b/src/calc.rs @@ -0,0 +1,343 @@ +use std::{fmt::Display, thread::yield_now}; + +use evalexpr::*; +use ratatui::{ + layout::{Constraint, Layout, Rect}, style::{Style, palette::material::WHITE, *}, widgets::{Paragraph, Widget} +}; + +use crate::ctx; + +// if this is very large at all it will overflow the stack +const LEN: usize = 100; + +pub struct Grid { + // a b c ... + // 0 + // 1 + // 2 + // ... + cells: [[Option>; LEN]; LEN], + /// (X, Y) + pub selected_cell: (usize, usize), +} + +impl std::fmt::Debug for Grid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Grid") + .field("cells", &"Too many to print") + .finish() + } +} + +impl Grid { + pub fn new() -> Self { + // TODO this needs to be moved to the heap + let b: [[Option>; LEN]; LEN] = + core::array::from_fn(|_| core::array::from_fn(|_| None)); + + Self { + cells: b, + selected_cell: (0, 0), + } + } + + pub fn evaluate(&self, mut eq: &str) -> Option { + if eq.starts_with('=') { + eq = &eq[1..]; + } else { + // Should be evaluating an equation + return None + } + + let ctx = ctx::CallbackContext::new(&self); + // let mut ctx = HashMapContext::::new(); + + match eval_with_context(eq, &ctx) { + Ok(e) => { + let val = e.as_float().expect("Should be float"); + return Some(val); + } + Err(e) => match e { + EvalexprError::VariableIdentifierNotFound(e) => { + // panic!("Will not be able to parse this equation, cell {e} not found") + return None + } + _ => panic!("{}", e), + }, + } + None + } + + fn parse_to_idx(i: &str) -> (usize, usize) { + let chars = i + .chars() + .take_while(|c| c.is_alphabetic()) + .collect::>(); + let nums = i + .chars() + .skip(chars.len()) + .take_while(|c| c.is_numeric()) + .collect::(); + + // get the x index from the chars + let x_idx = chars + .iter() + .enumerate() + .map(Self::char_to_idx) + .fold(0, |a, b| a + b); + + // get the y index from the numbers + let y_idx = nums + .parse::() + .expect("Got non-number character after sorting for just numeric characters"); + + (x_idx, y_idx) + } + + pub fn set_cell>>(&mut self, cell_id: &str, val: T) { + let loc = Self::parse_to_idx(cell_id); + self.set_cell_raw(loc, val); + } + + pub fn set_cell_raw>>(&mut self, (x,y): (usize, usize), val: T) { + // TODO check oob + self.cells[x][y] = Some(val.into()); + } + + /// Get cells via text like: + /// A6, + /// F0, + /// etc + pub fn get_cell(&self, cell_id: &str) -> &Option> { + let (x, y) = Self::parse_to_idx(cell_id); + self.get_cell_raw(x, y) + } + + pub fn get_cell_raw(&self, x: usize, y: usize) -> &Option> { + // TODO check oob + &self.cells[x][y] + } + + // this function has unit tests + fn char_to_idx((idx, c): (usize, &char)) -> usize { + (c.to_ascii_lowercase() as usize - 97) + 26 * idx + } + + fn num_to_char(idx: usize) -> String { + /* + A = 0 + AA = 26 + AAA = Not going to worry about it yet + */ + + let mut word: [char; 2] = [' '; 2]; + + if idx >= 26 { + word[0]= ((idx/26) + 65 -1) as u8 as char; + } + word[1]= ((idx%26) + 65) as u8 as char; + + word.iter().collect() + } + +} + +impl Default for Grid { + fn default() -> Self { + Self::new() + } +} + +pub trait Cell { + /// Important! This is IS NOT the return value of an equation. + /// This is the raw equation it's self. + fn as_raw_string(&self) -> String; + fn can_be_number(&self) -> bool; + fn as_num(&self) -> f64; + fn is_equation(&self) -> bool { + self.as_raw_string().starts_with('=') + } +} + +impl Display for dyn Cell { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + + let disp = if self.can_be_number() { + self.as_num().to_string() + } else { + self.as_raw_string() + }; + + write!(f, "{disp}") + } +} + +impl Cell for f64 { + fn as_raw_string(&self) -> String { + ToString::to_string(self) + } + + fn can_be_number(&self) -> bool { + true + } + + fn as_num(&self) -> f64 { + *self + } +} + +impl Into> for f64 { + fn into(self) -> Box { + Box::new(self) + } +} + +impl Into> for String { + fn into(self) -> Box { + Box::new(self) + } +} + +impl Cell for String { + fn as_raw_string(&self) -> String { + ToString::to_string(self) + } + + fn can_be_number(&self) -> bool { + // checking if the string is an equation + self.starts_with('=') + } + + fn as_num(&self) -> f64 { + unimplemented!("&str cannot be used in a numeric context") + } +} + +#[test] +fn test_cells() { + let mut grid = Grid::new(); + + assert!(&grid.cells[0][0].is_none()); + grid.set_cell("A0", "Hello".to_string()); + assert!(grid.get_cell("A0").is_some()); + + assert_eq!( + grid.get_cell("A0").as_ref().unwrap().as_raw_string(), + String::from("Hello") + ); +} + +#[test] +fn c_to_i() { + assert_eq!(Grid::char_to_idx((0, &'a')), 0); + assert_eq!(Grid::char_to_idx((0, &'A')), 0); + assert_eq!(Grid::char_to_idx((0, &'z')), 25); + assert_eq!(Grid::char_to_idx((0, &'Z')), 25); + assert_eq!(Grid::char_to_idx((1, &'a')), 26); + + assert_eq!(Grid::parse_to_idx("A0"), (0, 0)); + assert_eq!(Grid::parse_to_idx("AA0"), (26, 0)); + assert_eq!(Grid::parse_to_idx("A1"), (0, 1)); + assert_eq!(Grid::parse_to_idx("A10"), (0, 10)); + assert_eq!(Grid::parse_to_idx("Aa10"), (26, 10)); +} + +#[test] +fn i_to_c() { + assert_eq!(Grid::num_to_char(0).trim(), "A"); + assert_eq!(Grid::num_to_char(25).trim(), "Z"); + assert_eq!(Grid::num_to_char(26), "AA"); + assert_eq!(Grid::num_to_char(51), "AZ"); + assert_eq!(Grid::num_to_char(701), "ZZ"); +} + +impl Widget for &Grid { + fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) { + let len = LEN as u16; + + let cell_height = 1; + let cell_length = 5; + + + let x_max = if area.width / cell_length > len { + len - 1 + } else { + area.width / cell_length + }; + let y_max = if area.height / cell_height > len { + len - 1 + } else { + area.height / cell_height + }; + + for x in 0..x_max { + for y in 0..y_max { + let mut display = String::new(); + let mut style = Style::new().white(); + + const ORANGE1: Color = Color::Rgb(200, 160, 0); + const ORANGE2: Color = Color::Rgb(180, 130, 0); + + match (x == 0, y == 0) { + (true, true) => {}, + (true, false) => { + // row names + display = y.to_string(); + + let bg = if y%2==0 { + ORANGE1 + } else { + ORANGE2 + }; + style = Style::new().fg(Color::White).bg(bg); + + }, + (false, true) => { + // column names + display = Grid::num_to_char(x as usize -1); + + let bg = if x%2==0 { + ORANGE1 + } else { + ORANGE2 + }; + + style = Style::new().fg(Color::White).bg(bg) + }, + (false, false) => { + // minus 1 because of header cells + let x_idx = x as usize -1; + let y_idx = y as usize -1; + + if let Some(cell) = self.get_cell_raw(x_idx, y_idx) { + display = cell.as_raw_string(); + + if cell.can_be_number() { + if let Some(val) = self.evaluate(&cell.as_raw_string()) { + display = val.to_string(); + } else { + // broken formulas + if cell.is_equation() { + style = Style::new().underline_color(Color::Red).add_modifier(Modifier::UNDERLINED) + } + } + } + } + if (x_idx, y_idx) == self.selected_cell { + style = Style::new().fg(Color::Black).bg(Color::White); + } + } + } + + let area = Rect::new( + area.x + (x * cell_length), + area.y + (y * cell_height), + cell_length, + cell_height, + ); + + Paragraph::new(display).style(style).render(area, buf); + } + } + } +} diff --git a/src/ctx.rs b/src/ctx.rs index b630343..3d3023f 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -4,17 +4,16 @@ use evalexpr::{error::EvalexprResultValue, *}; use crate::Grid; -pub struct CallbackContext<'a, NumericTypes: EvalexprNumericTypes = DefaultNumericTypes> { - variables: Rc<&'a Grid>, - functions: HashMap>, +pub struct CallbackContext<'a, T: EvalexprNumericTypes = DefaultNumericTypes> { + variables: &'a Grid, + functions: HashMap>, /// True if builtin functions are disabled. without_builtin_functions: bool, } impl<'a, NumericTypes: EvalexprNumericTypes> CallbackContext<'a, NumericTypes> { - /// Constructs a `HashMapContext` with no mappings. - pub fn new(grid: Rc<&'a Grid>) -> Self { + pub fn new(grid: &'a Grid) -> Self { Self { variables: grid, functions: Default::default(), @@ -22,55 +21,30 @@ impl<'a, NumericTypes: EvalexprNumericTypes> CallbackContext<'a, NumericTypes> { } } - /// Removes all variables from the context. - /// This allows to reuse the context without allocating a new HashMap. - /// - /// # Example - /// - /// ```rust - /// # use evalexpr::*; - /// - /// let mut context = HashMapContext::::new(); - /// context.set_value("abc".into(), "def".into()).unwrap(); - /// assert_eq!(context.get_value("abc"), Some(&("def".into()))); - /// context.clear_variables(); - /// assert_eq!(context.get_value("abc"), None); - /// ``` pub fn clear_variables(&mut self) { () } - /// Removes all functions from the context. - /// This allows to reuse the context without allocating a new HashMap. pub fn clear_functions(&mut self) { self.functions.clear() } - /// Removes all variables and functions from the context. - /// This allows to reuse the context without allocating a new HashMap. - /// - /// # Example - /// - /// ```rust - /// # use evalexpr::*; - /// - /// let mut context = HashMapContext::::new(); - /// context.set_value("abc".into(), "def".into()).unwrap(); - /// assert_eq!(context.get_value("abc"), Some(&("def".into()))); - /// context.clear(); - /// assert_eq!(context.get_value("abc"), None); - /// ``` pub fn clear(&mut self) { self.clear_variables(); self.clear_functions(); } } -impl<'a, NumericTypes: EvalexprNumericTypes> Context for CallbackContext<'a, NumericTypes> { - type NumericTypes = NumericTypes; +impl<'a> Context for CallbackContext<'a, DefaultNumericTypes> { + type NumericTypes = DefaultNumericTypes; - fn get_value(&self, identifier: &str) -> Option<&Value> { - return Some(4.); + fn get_value(&self, identifier: &str) -> Option> { + if let Some(v) = self.variables.get_cell(identifier) { + if v.can_be_number() { + return Some(Value::Float(v.as_num())); + } + } + return None; } fn call_function( @@ -88,67 +62,8 @@ impl<'a, NumericTypes: EvalexprNumericTypes> Context for CallbackContext<'a, Num fn set_builtin_functions_disabled( &mut self, disabled: bool, - ) -> EvalexprResult<(), NumericTypes> { + ) -> EvalexprResult<(), Self::NumericTypes> { self.without_builtin_functions = disabled; Ok(()) } } - -impl<'a, NumericTypes: EvalexprNumericTypes> ContextWithMutableVariables - for CallbackContext<'a, NumericTypes> -{ - fn set_value( - &mut self, - identifier: String, - value: Value, - ) -> EvalexprResult<(), NumericTypes> { - Ok(()) - } - - fn remove_value( - &mut self, - identifier: &str, - ) -> EvalexprResult>, Self::NumericTypes> { - // Removes a value from the `self.variables`, returning the value at the key if the key was previously in the map. - // Ok(self.variables.remove(identifier)) - todo!(); - } -} - -impl<'a, NumericTypes: EvalexprNumericTypes> ContextWithMutableFunctions - for CallbackContext<'a, NumericTypes> -{ - fn set_function( - &mut self, - identifier: String, - function: Function, - ) -> EvalexprResult<(), Self::NumericTypes> { - self.functions.insert(identifier, function); - Ok(()) - } -} - -impl<'b, NumericTypes: EvalexprNumericTypes> IterateVariablesContext for CallbackContext<'b, NumericTypes> { - type VariableIterator<'a> - = std::iter::Map< - std::collections::hash_map::Iter<'a, String, Value>, - fn((&String, &Value)) -> (String, Value), - > - where - Self: 'a; - type VariableNameIterator<'a> - = std::iter::Cloned>> - where - Self: 'a; - - fn iter_variables(&self) -> Self::VariableIterator<'_> { - todo!() - // self.variables.iter().map(|(string, value)| (string.clone(), value.clone())) - } - - fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> { - todo!() - // self.variables.keys().cloned() - } -} - diff --git a/src/main.rs b/src/main.rs index b83f640..c721ddc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,205 +1,198 @@ +// #![feature(impl_trait_in_bindings)] + +mod calc; mod ctx; -use std::rc::Rc; +use std::io; -use evalexpr::*; +use ratatui::{ + crossterm::event, + layout::{Constraint, Layout}, + text::*, + widgets::{Paragraph, Widget}, + *, +}; -// if this is very large at all it will overflow the stack -const LEN: usize = 100; - -struct Grid { - // a b c ... - // 0 - // 1 - // 2 - // ... - cells: [[Option>; LEN]; LEN], -} - -impl std::fmt::Debug for Grid { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Grid").field("cells", &"Too many to print").finish() - } -} - -impl Grid { - fn new() -> Self { - let b: [[Option>; LEN]; LEN] = - core::array::from_fn(|_| core::array::from_fn(|_| None)); - - Self { cells: b } - } - - fn eval(&self, mut eq: &str) -> f64 { - if eq.starts_with('=') { - eq = &eq[1..]; - } - - let mut ctx = ctx::CallbackContext::::new(Rc::new(self)); - // let mut ctx = HashMapContext::::new(); - - let val; - loop { - match eval_with_context(eq, &ctx) { - Ok(e) => { - val = e.as_float().expect("Should be float"); - break; - } - Err(e) => match e { - // TODO this is kinda a slow way to do this, the equation will get parsed - // multiple times. Might be good to modify the lib so that you can provide - // a callback for variables that are not found. - EvalexprError::VariableIdentifierNotFound(e) => { - panic!("Will not be able to parse this equation, cell {e} not found") - } - _ => panic!("{}", e), - }, - } - } - val - } - - fn parse_to_idx(i: &str) -> (usize, usize) { - let chars = i - .chars() - .take_while(|c| c.is_alphabetic()) - .collect::>(); - let nums = i - .chars() - .skip(chars.len()) - .take_while(|c| c.is_numeric()) - .collect::(); - - // get the x index from the chars - let x_idx = chars - .iter() - .enumerate() - .map(Self::char_to_idx) - .fold(0, |a, b| a + b); - - // get the y index from the numbers - let y_idx = nums - .parse::() - .expect("Got non-number character after sorting for just numeric characters"); - - (x_idx, y_idx) - } - - fn set_cell(&mut self, cell_id: &str, val: Box) { - let (x, y) = Self::parse_to_idx(cell_id); - // TODO check oob - self.cells[x][y] = Some(val); - } - - /// Get cells via text like: - /// A6 - /// F0 - fn get_cell(&self, cell_id: &str) -> &Option> { - let (x, y) = Self::parse_to_idx(cell_id); - // TODO check oob - &self.cells[x][y] - } - - // this function has unit tests - fn char_to_idx((idx, c): (usize, &char)) -> usize { - (c.to_ascii_lowercase() as usize - 97) + 26 * idx - } -} - -impl Default for Grid { - fn default() -> Self { - Self::new() - } -} - -trait Cell { - fn to_string(&self) -> String; - fn can_be_number(&self) -> bool; - fn as_num(&self) -> f32; - fn is_eq(&self) -> bool { - self.to_string().starts_with('=') - } -} - -impl Cell for f32 { - fn to_string(&self) -> String { - ToString::to_string(self) - } - - fn can_be_number(&self) -> bool { - true - } - - fn as_num(&self) -> f32 { - *self - } -} - -impl Cell for &str { - fn to_string(&self) -> String { - ToString::to_string(self) - } - - fn can_be_number(&self) -> bool { - // checking if the string is an equation - self.starts_with('=') - } - - fn as_num(&self) -> f32 { - unimplemented!("&str cannot be used in a numeric context") - } -} +use crate::calc::Grid; #[test] fn test_math() { + use evalexpr::*; + let mut grid = Grid::new(); - grid.set_cell("A0", Box::new(2.)); - grid.set_cell("B0", Box::new(1.)); - grid.set_cell("C0", Box::new("=A0+B0")); + grid.set_cell("A0", 2.); + grid.set_cell("B0", 1.); + grid.set_cell("C0", "=A0+B0".to_string()); assert_eq!(eval("1+2").unwrap(), Value::Int(3)); - let disp = &grid.get_cell("C0"); - if let Some(inner) = disp { - if inner.is_eq() { - println!("{}", inner.to_string()); - let display = grid.eval(&inner.to_string()); - assert_eq!(display, 3.); + let cell_text = &grid.get_cell("C0"); + if let Some(text) = cell_text { + if text.is_equation() { + println!("{}", text.as_raw_string()); + let display = grid.evaluate(&text.as_raw_string()); + assert_eq!(display, Some(3.)); return; } } panic!("Should've found the value and returned"); } -#[test] -fn test_cells() { - let mut grid = Grid::new(); +fn main() -> Result<(), std::io::Error> { + let term = ratatui::init(); + let mut app = App::new(); + app.grid.set_cell("A0", 10.); + app.grid.set_cell("B1", 10.); + app.grid.set_cell("C2", "=A0+B1".to_string()); - assert!(&grid.cells[0][0].is_none()); - grid.set_cell("A0", Box::new("Hello")); - assert!(grid.get_cell("A0").is_some()); - - assert_eq!( - grid.get_cell("A0").as_ref().unwrap().to_string(), - String::from("Hello") - ); + let res = app.run(term); + ratatui::restore(); + return res; } -#[test] -fn c_to_i() { - assert_eq!(Grid::char_to_idx((0, &'a')), 0); - assert_eq!(Grid::char_to_idx((0, &'A')), 0); - assert_eq!(Grid::char_to_idx((0, &'z')), 25); - assert_eq!(Grid::char_to_idx((0, &'Z')), 25); - assert_eq!(Grid::char_to_idx((1, &'a')), 26); - - assert_eq!(Grid::parse_to_idx("A0"), (0, 0)); - assert_eq!(Grid::parse_to_idx("AA0"), (26, 0)); - assert_eq!(Grid::parse_to_idx("A1"), (0, 1)); - assert_eq!(Grid::parse_to_idx("A10"), (0, 10)); - assert_eq!(Grid::parse_to_idx("Aa10"), (26, 10)); +struct App { + exit: bool, + grid: Grid, + /// Buffer for key-chords + chord_buf: String, + editor: Option, } -fn main() { - println!("Only tests exist atm"); +impl Widget for &App { + fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { + Paragraph::new("Status").render(area, buf); + } } + +impl App { + fn new() -> Self { + Self { + exit: false, + grid: Grid::new(), + chord_buf: String::new(), + editor: None, + } + } + + fn run(&mut self, mut term: DefaultTerminal) -> Result<(), std::io::Error> { + while !self.exit { + term.draw(|frame| self.draw(frame))?; + self.handle_events()?; + } + Ok(()) + } + fn draw(&self, frame: &mut Frame) { + + let layout = Layout::default() + .direction(layout::Direction::Vertical) + .constraints([ + Constraint::Length(1), + Constraint::Min(1), + Constraint::Length(1), + ]) + .split(frame.area()); + + if let Some(editor) = &self.editor { + frame.render_widget(editor, layout[0]); + } else { + frame.render_widget(Paragraph::new("sc_rs"), layout[0]); + } + + frame.render_widget(&self.grid, layout[1]); + frame.render_widget(self, layout[2]); + } + + fn handle_events(&mut self) -> io::Result<()> { + match event::read()? { + event::Event::Key(key_event) => match key_event.code { + event::KeyCode::Enter => { + if let Some(editor) = &self.editor { + let loc= self.grid.selected_cell; + + let val = editor.buf.trim().to_string(); + + // insert as number if at all possible + if let Ok(val) = val.parse::() { + self.grid.set_cell_raw(loc, val); + } else { + self.grid.set_cell_raw(loc, val); + }; + + self.editor = None; + } + } + event::KeyCode::Backspace => { + if let Some(editor) = &mut self.editor { + editor.buf.pop(); + } + } + event::KeyCode::F(_) => todo!(), + event::KeyCode::Char(c) => { + + if let Some(editor) = &mut self.editor { + editor.buf += &c.to_string(); + return Ok(()); + } + + if !self.chord_buf.is_empty() {} + + match c { + 'q' => self.exit = true, + // < + 'h' => self.grid.selected_cell.0 = self.grid.selected_cell.0.saturating_sub(1), + // v + 'j' => self.grid.selected_cell.1 = self.grid.selected_cell.1.saturating_add(1), + // ^ + 'k' => self.grid.selected_cell.1 = self.grid.selected_cell.1.saturating_sub(1), + // > + 'l' => self.grid.selected_cell.0 = self.grid.selected_cell.0.saturating_add(1), + // edit cell + 'i' | 'a' => { + let (x,y) = self.grid.selected_cell; + let starting_val = if let Some(val) = self.grid.get_cell_raw(x, y) { + val.as_raw_string() + } else { + String::new() + }; + self.editor = Some(Editor::from(starting_val)) + }, + 'I' => {/* insert col before */} + 'A' => {/* insert col after */} + 'o' => {/* insert row below */} + 'O' => {/* insert row above */} + ':' => {/* enter command mode */} + c => { + // start entering c for words + self.chord_buf += &c.to_string(); + } + } + }, + _ => {} + }, + _ => {} + event::Event::Paste(_) => todo!(), + event::Event::Resize(_, _) => todo!(), + } + Ok(()) + } +} + +struct Editor { + buf: String, + cursor: usize, +} + +impl From for Editor { + fn from(value: String) -> Self { + Self { + buf: value.to_string(), + cursor: value.len(), + } + } +} + +impl Widget for &Editor { + fn render(self, area: prelude::Rect, buf: &mut prelude::Buffer) { + Paragraph::new(self.buf.clone()).render(area, buf); + } +} \ No newline at end of file