diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 39f9b99..1f0def9 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -1,34 +1,10 @@ name: Release -on: [push, pull_request] +on: [push] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_TERM_COLOR: always jobs: - windows: - runs-on: windows-latest - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Build - run: BQN_WASM="D:/a/beacon/beacon/BQN.wasm" cargo build --release --no-default-features --features=bqnwasm - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: target/release/beacon.exe - linux: runs-on: ubuntu-latest steps: @@ -42,22 +18,39 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Build bqn shared lib + build/obj + build/obj2 + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + - name: Install Rust Windows target + run: | + rustup update && rustup target add x86_64-pc-windows-gnu + sudo apt-get update + sudo apt-get install binutils-mingw-w64 mingw-w64 + - name: Build bqn shared lib and wasm file run: | git clone https://github.com/dzaima/CBQN cd CBQN - FFI=0 make shared-o3 - mv libcbqn.so ../ - - name: Build + FFI=0 make shared-o3 notui=1 + wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-linux.tar.gz + tar xvf wasi-sdk-20.0-linux.tar.gz + FFI=0 make wasi-reactor-o3 notui=1 CC=./wasi-sdk-20.0/bin/clang + mv libcbqn.so BQN.wasm ../ + - name: Build Linux run: | - RUSTFLAGS="-L ${{ github.workspace }}" LD_LIBRARY_PATH="${{ github.workspace }}/libcbqn.so" cargo build --release - mv target/release/beacon ./beacon_linux + RUSTFLAGS="-L ${{ github.workspace }}" LD_LIBRARY_PATH="${{ github.workspace }}/libcbqn.so -C strip=symbols" cargo build --release + mv target/release/beacon ./beacon_x86_64-unknown-linux-gnu + - name: Build Windows + run: | + RUSTFLAGS='-C strip=symbols' BQN_WASM=${{ github.workspace }}/BQN.wasm cargo build --release --target x86_64-pc-windows-gnu --no-default-features --features=bqnwasm + mv target/x86_64-pc-windows-gnu/release/beacon.exe ./beacon_x86_64-pc-windows-gnu.exe + ./beacon_x86_64-pc-windows-gnu.exe - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: - files: ./beacon_linux + files: | + beacon_x86_64-unknown-linux-gnu + beacon_x86_64-pc-windows-gnu.exe macos: runs-on: macos-11 @@ -71,20 +64,39 @@ jobs: ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ + build/obj + build/obj2 key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install ARM target + - name: Install Rust ARM target run: | rustup update && rustup target add aarch64-apple-darwin cargo install cargo-bundle || echo "already installed" + - name: Build x86 + run: | + git clone https://github.com/dzaima/CBQN + cd CBQN + FFI=0 make shared-o3 notui=1 + mv libcbqn.dylib .. + cd .. + RUSTFLAGS="-L ${{ github.workspace }}" LD_LIBRARY_PATH="${{ github.workspace }}/libcbqn_86.dylib" cargo bundle --release - name: Build ARM - run: RUSTFLAGS="-L ${{ github.workspace }}" LD_LIBRARY_PATH="${{ github.workspace }}/libcbqn.dylib" cargo bundle --release --target=aarch64-apple-darwin + run: | + rm libcbqn.dylib + cd CBQN + FFI=0 CCFLAGS=--target=aarch64-apple-darwin make shared-o3 target_arch=aarch64 notui=1 + mv libcbqn.dylib .. + cd .. + RUSTFLAGS="-L ${{ github.workspace }}" LD_LIBRARY_PATH="${{ github.workspace }}/libcbqn_arm.dylib" cargo bundle --release --target=aarch64-apple-darwin - name: Create DMG run: | git clone https://github.com/create-dmg/create-dmg chmod u+x create-dmg/create-dmg - ./create-dmg/create-dmg Beacon.dmg target/aarch64-apple-darwin/release/bundle/osx/Beacon.app + ./create-dmg/create-dmg Beacon_aarch64-apple-darwin.dmg target/aarch64-apple-darwin/release/bundle/osx/Beacon.app + ./create-dmg/create-dmg Beacon_x86_64-apple-darwin.dmg target/release/bundle/osx/Beacon.app - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: - files: Beacon.dmg + files: | + Beacon_aarch64-apple-darwin.dmg + Beacon_x86_64-apple-darwin.dmg diff --git a/.gitignore b/.gitignore index 54088d5..5ac26f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target .DS_Store +lib*.* diff --git a/BQN.wasm b/BQN.wasm deleted file mode 100644 index 8f6c476..0000000 Binary files a/BQN.wasm and /dev/null differ diff --git a/Cargo.lock b/Cargo.lock index 26dd61e..022af4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,14 +335,11 @@ dependencies = [ "iced_core", "iced_runtime", "iced_style", - "itertools", "ngnk", "once_cell", "phf", "serde", "serde_json", - "tracing", - "tracing-subscriber", "unicode-segmentation", ] @@ -1673,6 +1670,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1916,6 +1919,7 @@ dependencies = [ "iced_runtime", "iced_style", "num-traits", + "ouroboros", "thiserror", "unicode-segmentation", ] @@ -1999,15 +2003,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.6" @@ -2193,15 +2188,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "memchr" version = "2.5.0" @@ -2463,16 +2449,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -2647,10 +2623,28 @@ dependencies = [ ] [[package]] -name = "overload" -version = "0.1.1" +name = "ouroboros" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "d813b7b31a82efae94bd30ffaac09aec85efc18db2d5ec3aead1a220ee954351" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a56f651b4dd45ae3ac3d260ced32eaf0620cddaae5f26c69b554a9016594726" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.18", +] [[package]] name = "owned_ttf_parser" @@ -3110,24 +3104,9 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.7.2" @@ -3550,15 +3529,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - [[package]] name = "shared-buffer" version = "0.1.3" @@ -3947,16 +3917,6 @@ dependencies = [ "syn 2.0.18", ] -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "time" version = "0.2.27" @@ -4178,36 +4138,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -4351,12 +4281,6 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "value-bag" version = "1.4.0" @@ -4435,7 +4359,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19bc05e8380515c4337c40ef03b2ff233e391315b178a320de8640703d522efe" dependencies = [ - "heck", + "heck 0.3.3", "wai-bindgen-gen-core", ] @@ -4445,7 +4369,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f35ce5e74086fac87f3a7bd50f643f00fe3559adb75c88521ecaa01c8a6199" dependencies = [ - "heck", + "heck 0.3.3", "wai-bindgen-gen-core", "wai-bindgen-gen-rust", ] @@ -4456,7 +4380,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f61484185d8c520a86d5a7f7f8265f446617c2f9774b2e20a52de19b6e53432" dependencies = [ - "heck", + "heck 0.3.3", "wai-bindgen-gen-core", "wai-bindgen-gen-rust", ] diff --git a/Cargo.toml b/Cargo.toml index bd45400..fbe3d1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ directories-next = "2.0" cbqn = { version = "0.1.0", default-features=false, optional = true } phf = "0.11.1" unicode-segmentation = "1.10.1" -iced = { git = "https://github.com/iced-rs/iced", features = ["async-std", "debug"], rev = "b38f7d28372712e345672515d8c1643bc040c1f1" } +iced = { git = "https://github.com/iced-rs/iced", features = ["async-std", "debug", "lazy"], rev = "b38f7d28372712e345672515d8c1643bc040c1f1" } iced_core = { git = "https://github.com/iced-rs/iced", rev = "b38f7d28372712e345672515d8c1643bc040c1f1" } iced_runtime = { git = "https://github.com/iced-rs/iced", rev = "b38f7d28372712e345672515d8c1643bc040c1f1" } iced_style = { git = "https://github.com/iced-rs/iced", rev = "b38f7d28372712e345672515d8c1643bc040c1f1" } -itertools = "0.11.0" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +# itertools = "0.11.0" +# tracing = "0.1.37" +# tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } ngnk = { version = "0.2.3", optional = true} diff --git a/libcbqn.dylib b/libcbqn.dylib deleted file mode 100755 index c70bda1..0000000 Binary files a/libcbqn.dylib and /dev/null differ diff --git a/libk.so b/libk.so deleted file mode 100755 index 67d372c..0000000 Binary files a/libk.so and /dev/null differ diff --git a/src/k.rs b/src/k.rs deleted file mode 100644 index ab8ae18..0000000 --- a/src/k.rs +++ /dev/null @@ -1,5 +0,0 @@ -use ngnk::{K, K0}; - -pub fn keval(s: &'static str, args: Vec) -> K { - K0(s, args) -} diff --git a/src/main.rs b/src/main.rs index 4cfaa3b..f254677 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,57 +1,42 @@ -use cbqn::{eval, BQNValue}; +#![allow(dead_code, unused_variables)] + +use iced::widget::pane_grid::{self, PaneGrid}; +use iced::widget::responsive; use iced::{ event::{self, Event}, keyboard::{self, Modifiers}, subscription, - widget::{button, column, container, row, scrollable, text, tooltip, Column, Container}, + widget::{column, container, scrollable, text}, window, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_core::{text::LineHeight, Font}; use iced_runtime::font::load; -use itertools::Itertools; use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::time::Instant; -use std::{collections::HashMap, time::Duration}; +use styles::CanvasStyle; +use views::tabs::tab_view; // use tracing::{event as e, info, instrument, Level}; mod docs; -#[cfg(feature = "k")] -mod k; mod save; mod styles; +mod utils; +mod views; mod widgets; use crate::save::*; -use crate::styles::*; +use crate::utils::{EvalCell, Ty}; use crate::widgets::text_input; -use crate::widgets::wrap::Wrap; -use docs::content::glyph_to_documentation; +#[cfg(feature = "k")] +use utils::keval; +use utils::{truncate, HistoryMap, REPL}; +use views::pane::{view_pane, Pane}; +use views::toolbar::toolbar_view; -static REPL: Lazy = Lazy::new(|| { - eval("(•ReBQN{repl⇐\"loose\"})⎊{𝕊: \"Error: \"∾•CurrentError@}") - .expect("Err on repl construction") -}); -static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); +pub static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); static SCROLL_ID: Lazy = Lazy::new(scrollable::Id::unique); -static GLYPHS: Lazy<[char; 64]> = Lazy::new(|| { - [ - '+', '¨', '⊸', '⊑', '´', '∾', '×', '-', '≠', '∘', '˜', '=', '/', '<', '↕', '⥊', '⊢', '⟜', - '⊏', '≡', '∧', '˘', '!', '>', '⌽', '↓', '¬', '↑', '∨', '`', '◶', '⍟', '⌜', '⊣', '⌾', '⌈', - '⋈', '⊔', '⌊', '»', '⊐', '∊', '○', '≤', '|', '≢', '⍉', '÷', '≍', '˝', '⁼', '«', '≥', '˙', - '⍋', '⍷', '⋆', '⊘', '⎉', '⚇', '⊒', '√', '⍒', '⎊', - ] -}); -macro_rules! bqn386 { - ($q:expr) => { - text($q) - .font(Font::with_name("BQN386 Unicode")) - .size(14) - .line_height(LineHeight::Absolute(12.into())) - }; -} pub fn main() -> iced::Result { - tracing_subscriber::fmt::init(); + // tracing_subscriber::fmt::init(); Beacon::run(Settings { window: window::Settings { size: (430, 800), @@ -61,63 +46,54 @@ pub fn main() -> iced::Result { }) } -#[derive(Debug)] enum Beacon { Loading, Loaded(State), } -#[derive(Debug, Clone, Serialize, Deserialize)] -enum Ty { - Array, - Number, - Character, - Function, - Mod1, - Mod2, - Namespace, - Err, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct EvalCell { - src: String, - res: String, - ty: Ty, - time: Duration, -} - -#[derive(Debug, Default)] struct State { - input_value: String, - eval_cells: History, + input_value: HashMap, + eval_cells: HistoryMap, tab_at: usize, dirty: bool, saving: bool, + panes: pane_grid::State, + panes_created: usize, + focus: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct History(HashMap>); -impl History { - fn min_tab(&self) -> &usize { - self.0.keys().min().unwrap_or(&0) - } - fn max_tab(&self) -> &usize { - self.0.keys().max().unwrap_or(&0) +impl Default for State { + fn default() -> Self { + let (panes, _) = pane_grid::State::new(Pane::new(0)); + Self { + input_value: HashMap::new(), + eval_cells: Default::default(), + tab_at: Default::default(), + dirty: Default::default(), + saving: Default::default(), + panes, + panes_created: 1, + focus: None, + } } - fn new() -> History { - let mut h = HashMap::new(); - h.insert(0, vec![]); - History(h) +} + +impl State { + fn focused_pane(&self) -> usize { + if let Some(f) = self.focus { + unsafe { std::mem::transmute::<_, usize>(f) } + } else { + 0 + } } } #[derive(Debug, Clone)] -enum Message { +pub enum Message { Loaded(Result), FontLoaded(Result<(), iced_runtime::font::Error>), Saved(Result<(), SaveError>), - InputChanged(String), + InputChanged(String, usize), RunInput, InputFocus, FillInput(String), @@ -128,6 +104,17 @@ enum Message { TabNext, TabPrev, BufferClear, + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), + Dragged(pane_grid::DragEvent), + Resized(pane_grid::ResizeEvent), + TogglePin(pane_grid::Pane), + Maximize(pane_grid::Pane), + Restore, + Close(pane_grid::Pane), + CloseFocused, } impl Application for Beacon { @@ -171,13 +158,17 @@ impl Application for Beacon { Message::Loaded(Ok(state)) => { *self = Beacon::Loaded(State { input_value: state.input_val, - eval_cells: state.history, + eval_cells: { + // let mut h = HashMap::new(); + // h.insert(state.at, state.history); + state.history + }, ..State::default() }); } Message::Loaded(Err(_)) => { *self = Beacon::Loaded(State { - eval_cells: { History::new() }, + eval_cells: { HistoryMap::new() }, ..State::default() }); } @@ -191,13 +182,102 @@ impl Application for Beacon { let max_idx = *state.eval_cells.max_tab(); let min_idx = *state.eval_cells.min_tab(); + let focused_pane = state.focused_pane(); let command = match message { - Message::InputChanged(value) => { - state.input_value = value; + Message::Split(axis, pane) => { + let result = state + .panes + .split(axis, &pane, Pane::new(state.panes_created)); + + if let Some((pane, _)) = result { + state.focus = Some(pane); + } + + state.panes_created += 1; + Command::none() + } + Message::SplitFocused(axis) => { + if let Some(pane) = state.focus { + let result = + state + .panes + .split(axis, &pane, Pane::new(state.panes_created)); + + if let Some((pane, _)) = result { + state.focus = Some(pane); + } + + state.panes_created += 1; + } + Command::none() + } + Message::FocusAdjacent(direction) => { + if let Some(pane) = state.focus { + if let Some(adjacent) = state.panes.adjacent(&pane, direction) { + state.focus = Some(adjacent); + } + } + Command::none() + } + Message::Clicked(pane) => { + state.focus = Some(pane); + Command::none() + } + Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { + state.panes.resize(&split, ratio); + Command::none() + } + Message::Dragged(pane_grid::DragEvent::Dropped { + pane, + target, + region, + }) => { + state.panes.split_with(&target, &pane, region); + Command::none() + } + Message::Dragged(_) => Command::none(), + Message::TogglePin(pane) => { + if let Some(Pane { is_pinned, .. }) = state.panes.get_mut(&pane) { + *is_pinned = !*is_pinned; + } + Command::none() + } + Message::Maximize(pane) => { + state.panes.maximize(&pane); + + Command::none() + } + Message::Restore => { + state.panes.restore(); + Command::none() + } + Message::Close(pane) => { + if let Some((_, sibling)) = state.panes.close(&pane) { + state.focus = Some(sibling); + } + Command::none() + } + Message::CloseFocused => { + if let Some(pane) = state.focus { + if let Some(Pane { is_pinned, .. }) = state.panes.get(&pane) { + if !is_pinned { + if let Some((_, sibling)) = state.panes.close(&pane) { + state.focus = Some(sibling); + } + } + } + } + Command::none() + } + Message::InputChanged(value, pane_idx) => { + let i = state.input_value.entry(pane_idx).or_insert(String::new()); + let ic = state.input_value.get_mut(&focused_pane).unwrap(); + *ic = value; Command::none() } Message::TabCreate => { - state.eval_cells.0.insert(max_idx + 1, vec![]); + let h = HashMap::new(); + state.eval_cells.0.insert(max_idx + 1, h); state.tab_at = max_idx + 1; Command::none() } @@ -234,11 +314,13 @@ impl Application for Beacon { Command::none() } Message::FillInput(o) => { - state.input_value = o; + let i = state.input_value.get_mut(&focused_pane).unwrap(); + *i = o; Command::none() } Message::ToolbarClick(c) => { - state.input_value += c.as_str(); + let i = state.input_value.get_mut(&focused_pane).unwrap(); + *i += c.as_str(); Command::none() } Message::Saved(_) => { @@ -247,7 +329,9 @@ impl Application for Beacon { Command::none() } Message::BufferClear => { - *state.eval_cells.0.get_mut(&state.tab_at).unwrap() = vec![]; + if let Some(h) = state.eval_cells.0.get_mut(&state.tab_at) { + h.insert(focused_pane, vec![]); + } Command::none() } Message::InputFocus => @@ -258,39 +342,31 @@ impl Application for Beacon { Command::none() } Message::RunInput => { - if state.input_value == "clear" { - state.eval_cells = History::new(); + let inp: String = state.input_value[&focused_pane].clone(); + if inp == "clear" { + state.eval_cells = HistoryMap::new(); state.tab_at = 0; return Command::none(); } - if state.input_value == "clean" { - if let Some(a) = state.eval_cells.0.get_mut(&state.tab_at) { - a.retain(|v| !matches!(v.ty, Ty::Err)); - } + if inp == "clean" { + // if let Some(a) = state.eval_cells.0.get_mut(&state.tab_at) { + // a.retain(|v| !matches!(v.ty, Ty::Err)); + // } return Command::none(); } - if state.input_value == "close" { + if inp == "close" { return Command::perform(async { Message::TabClose }, |_| { Message::TabClose }); } let now = Instant::now(); - let bqnc = REPL.call1(&state.input_value.clone().into()); + let bqnc = REPL.call1(&inp.clone().into()); #[cfg(feature = "k")] println!("{}", k::keval("`0:3+3", vec![])); let elapsed = now.elapsed(); - fn truncate(s: &str, max_chars: usize) -> &str { - match s.char_indices().nth(max_chars) { - None => s, - Some((idx, _)) => &s[..idx], - } - } - state - .eval_cells - .0 - .get_mut(&state.tab_at) - .unwrap() - .push(EvalCell { + if let Some(h) = state.eval_cells.0.get_mut(&state.tab_at) { + let vec = h.entry(focused_pane).or_insert(vec![]); + vec.push(EvalCell { res: truncate( match bqnc { Ok(b) => format!("{b:?}"), @@ -300,10 +376,11 @@ impl Application for Beacon { 500, ) .to_string(), - src: { state.input_value.clone() }, + src: inp, ty: { Ty::Number }, time: elapsed, }); + } scrollable::snap_to( SCROLL_ID.clone(), scrollable::RelativeOffset { x: 0.0, y: 1.0 }, @@ -340,116 +417,50 @@ impl Application for Beacon { fn view(&self) -> Element { match self { - Beacon::Loading => bqn386!("loading").into(), - Beacon::Loaded(State { - input_value, - eval_cells: outs, - tab_at: at, - .. - }) => { - let inp = text_input::text_input("", input_value) - .padding(15) - .style(InputStyle::theme()) - .size(18) - .font(Font::with_name("BQN386 Unicode")) - .on_submit(Message::RunInput) - .on_input(Message::InputChanged) - .id(INPUT_ID.clone()); - let glyphbar: Container<_> = Container::new( - GLYPHS - .iter() - .fold(Wrap::new(), |wrap, glyph| { - wrap.push( - tooltip( - button(bqn386!(glyph)) - .style(BtnStyle::theme()) - .on_press(Message::ToolbarClick(glyph.to_string())), - glyph_to_documentation(*glyph), - tooltip::Position::FollowCursor, - ) - .style(TooltipStyle::theme()), - ) - }) - .spacing(1), - ); - let out_cells: Column<_> = column( - outs.0[at] - .iter() - .map(|txt| { - let mut res = txt.res.to_string(); - let mut did_error = false; - if txt.res.starts_with("CBQN error:") { - res = res.replace("CBQN error: ", ""); - did_error = true; - } - let mut v = vec![ - button( - bqn386!(" ".to_string() + &txt.src) - .style(SrcCellStyle::theme()), - ) - .on_press(Message::FillInput(txt.src.to_string())) - .style(BtnStyle::theme()) - .into(), - button(bqn386!(res.clone()).style(if did_error { - ErroredCellStyle::theme() - } else { - Default::default() - })) - .on_press(Message::FillInput(res)) - .style(BtnStyle::theme()) - .into(), - ]; - v.push( - bqn386!(format!("{}ms", txt.time.as_millis())) - .size(12) - .style(ElapsedTimeStyle::theme()) - .into(), - ); - Container::new(column(v)) - .width(Length::Fill) - .style(CanvasStyle::theme()) - .into() - }) - .collect::>>(), - ) - .spacing(8); - let tabs: iced::widget::Row = row({ - outs.0 - .keys() - .sorted() - .map(|i| { - Container::new( - button(if i == at { - bqn386!(format!("{i}")).style(ActiveTabStyle::theme()) - } else { - bqn386!(format!("{i}")) - }) - .on_press(Message::TabChanged(*i)) - .style(TabStyle::theme()), - ) - .into() - }) - .collect() - }); - let new_tab_btn = button(bqn386!("+")) - .on_press(Message::TabCreate) - .style(TabStyle::theme()); - let content = row![column![ - column![ - container(glyphbar).style(ToolbarStyle::theme()), - row![tabs, new_tab_btn], - ] - .spacing(0), - scrollable(out_cells) - .height(Length::Fill) - .id(SCROLL_ID.clone()), - inp - ] - .spacing(20) - .max_width(800),]; - container(content) + Beacon::Loading => text("loading").into(), + Beacon::Loaded( + s @ State { + input_value, + eval_cells: outs, + tab_at: at, + focus, + panes, + .. + }, + ) => { + let glyphbar = toolbar_view(); + let tabs = tab_view(outs, *at); + + let focus = focus; + let total_panes = panes.len(); + let pane_grid = PaneGrid::new(&panes, |id, pane, is_maximized| { + let is_focused = *focus == Some(id); + let pane_outs = outs + .0 + .get(at) + .and_then(|h| h.get(unsafe { &std::mem::transmute::<_, usize>(id) })); + pane_grid::Content::new(responsive(move |size| { + view_pane( + id, + total_panes, + pane.is_pinned, + &input_value, + pane_outs, + *at, + ) + .into() + })) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_click(Message::Clicked) + .on_drag(Message::Dragged) + .on_resize(10, Message::Resized); + container(column![glyphbar, tabs, pane_grid]) .width(Length::Fill) - .center_x() + .height(Length::Fill) + .padding(10) .style(CanvasStyle::theme()) .into() } diff --git a/src/save.rs b/src/save.rs index 2e14d79..defd0d2 100644 --- a/src/save.rs +++ b/src/save.rs @@ -1,4 +1,6 @@ -use crate::History; +use std::collections::HashMap; + +use crate::HistoryMap; use async_std::prelude::*; use serde::{Deserialize, Serialize}; @@ -17,8 +19,8 @@ pub enum SaveError { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SavedState { - pub input_val: String, - pub history: History, + pub input_val: HashMap, + pub history: HistoryMap, pub at: usize, } diff --git a/src/styles.rs b/src/styles.rs index 459c19f..b28d2ff 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -13,6 +13,7 @@ pub struct ErroredCellStyle; pub struct ActiveTabStyle; pub struct InputStyle; pub struct TooltipStyle; +pub struct ScrollbarStyle; impl iced::widget::button::StyleSheet for BtnStyle { type Style = iced::Theme; @@ -194,3 +195,66 @@ impl TooltipStyle { iced::theme::Container::Custom(Box::from(TooltipStyle)) } } + +impl iced::widget::scrollable::StyleSheet for ScrollbarStyle { + type Style = iced::Theme; + + fn active(&self, style: &Self::Style) -> iced_style::scrollable::Scrollbar { + iced_style::scrollable::Scrollbar { + background: Some(iced::Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + })), + border_radius: [0.0, 0.0, 0.0, 0.0].into(), + border_width: 4.0, + border_color: Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }, + scroller: iced_style::scrollable::Scroller { + color: Color::WHITE, + border_radius: [0.0, 0.0, 0.0, 0.0].into(), + border_width: 4.0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered( + &self, + style: &Self::Style, + is_mouse_over_scrollbar: bool, + ) -> iced_style::scrollable::Scrollbar { + iced_style::scrollable::Scrollbar { + background: Some(iced::Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + })), + border_radius: [0.0, 0.0, 0.0, 0.0].into(), + border_width: 4.0, + border_color: Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }, + scroller: iced_style::scrollable::Scroller { + color: Color::WHITE, + border_radius: [0.0, 0.0, 0.0, 0.0].into(), + border_width: 4.0, + border_color: Color::TRANSPARENT, + }, + } + } +} +impl ScrollbarStyle { + pub fn theme() -> iced::theme::Scrollable { + iced::theme::Scrollable::Custom(Box::from(ScrollbarStyle)) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..85eec85 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,74 @@ +use std::{collections::HashMap, time::Duration}; + +use once_cell::sync::Lazy; + +pub fn truncate(s: &str, max_chars: usize) -> &str { + match s.char_indices().nth(max_chars) { + None => s, + Some((idx, _)) => &s[..idx], + } +} + +pub mod macros { + macro_rules! bqn386 { + ($q:expr) => { + text($q) + .font(Font::with_name("BQN386 Unicode")) + .size(14) + .line_height(LineHeight::Absolute(12.into())) + }; + } + pub(crate) use bqn386; +} + +#[cfg(feature = "k")] +use ngnk::{K, K0}; +#[cfg(feature = "k")] +pub fn keval(s: &'static str, args: Vec) -> K { + K0(s.to_string(), args) +} + +use cbqn::{eval, BQNValue}; +use serde::{Deserialize, Serialize}; +pub static REPL: Lazy = Lazy::new(|| { + eval("(•ReBQN{repl⇐\"loose\"})⎊{𝕊: \"Error: \"∾•CurrentError@}") + .expect("Err on repl construction") +}); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Ty { + Array, + Number, + Character, + Function, + Mod1, + Mod2, + Namespace, + Err, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EvalCell { + pub src: String, + pub res: String, + pub ty: Ty, + pub time: Duration, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct HistoryMap(pub HashMap>>); +impl HistoryMap { + pub fn min_tab(&self) -> &usize { + self.0.keys().min().unwrap_or(&0) + } + pub fn max_tab(&self) -> &usize { + self.0.keys().max().unwrap_or(&0) + } + pub fn new() -> HistoryMap { + let mut h = HashMap::new(); + let mut hi = HashMap::new(); + hi.insert(0, vec![]); + h.insert(0, hi); + HistoryMap(h) + } +} diff --git a/src/views/mod.rs b/src/views/mod.rs new file mode 100644 index 0000000..dea98e9 --- /dev/null +++ b/src/views/mod.rs @@ -0,0 +1,3 @@ +pub mod pane; +pub mod tabs; +pub mod toolbar; diff --git a/src/views/pane.rs b/src/views/pane.rs new file mode 100644 index 0000000..59ad711 --- /dev/null +++ b/src/views/pane.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; + +use crate::utils::macros::bqn386; +use crate::utils::EvalCell; +use crate::widgets::text_input; +use crate::{styles::*, INPUT_ID}; +use crate::{Message, SCROLL_ID}; +use iced::alignment::{self, Alignment}; +use iced::widget::pane_grid; +use iced::{ + widget::{button, column, container, row, scrollable, text, Column, Container}, + Element, Length, +}; + +use iced_core::{text::LineHeight, Font}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Serialize, Deserialize, Debug, Hash, PartialEq, Eq)] +pub struct Pane { + id: usize, + pub is_pinned: bool, +} + +impl Pane { + pub fn new(id: usize) -> Self { + Self { + id, + is_pinned: false, + } + } +} + +pub fn view_pane<'a>( + pane: pane_grid::Pane, + total_panes: usize, + is_pinned: bool, + input_value: &'a HashMap, + outs: Option<&'a Vec>, + idx: usize, +) -> Element<'a, Message> { + let inp = text_input::text_input( + "", + &input_value + .get(&unsafe { std::mem::transmute::<_, usize>(pane) }) + .unwrap_or(&String::new()), + ) + .padding(15) + .style(InputStyle::theme()) + .size(18) + .font(Font::with_name("BQN386 Unicode")) + .on_submit(Message::RunInput) + .on_input(move |s| { + Message::InputChanged(s.clone(), unsafe { std::mem::transmute::<_, usize>(pane) }) + }) + .id(INPUT_ID.clone()); + let out_cells: Column<_> = column( + outs.unwrap_or(&vec![]) + .iter() + .map(|txt| { + let mut res = txt.res.to_string(); + let mut did_error = false; + if txt.res.starts_with("CBQN error:") { + res = res.replace("CBQN error: ", ""); + did_error = true; + } + let mut v = vec![ + button(bqn386!(" ".to_string() + &txt.src).style(SrcCellStyle::theme())) + .on_press(Message::FillInput(txt.src.to_string())) + .style(BtnStyle::theme()) + .into(), + button(bqn386!(res.clone()).style(if did_error { + ErroredCellStyle::theme() + } else { + Default::default() + })) + .on_press(Message::FillInput(res)) + .style(BtnStyle::theme()) + .into(), + ]; + v.push( + bqn386!(format!("{}ms", txt.time.as_millis())) + .size(12) + .style(ElapsedTimeStyle::theme()) + .into(), + ); + Container::new(column(v)) + .width(Length::Fill) + .style(CanvasStyle::theme()) + .into() + }) + .collect::>>(), + ) + .spacing(8); + let button = |label, message| { + button( + text(label) + .horizontal_alignment(alignment::Horizontal::Center) + .size(16), + ) + .padding(2) + .style(TabStyle::theme()) + .on_press(message) + }; + + let mut controls = row![ + button("-", Message::Split(pane_grid::Axis::Horizontal, pane),), + button("|", Message::Split(pane_grid::Axis::Vertical, pane),) + ] + .spacing(5); + if total_panes > 1 && !is_pinned { + controls = controls.push(button("X", Message::Close(pane))); + } + + let content = column![ + controls, + scrollable(out_cells) + .height(Length::Fill) + .style(ScrollbarStyle::theme()) + .id(SCROLL_ID.clone()), + inp + ] + .width(Length::Fill) + .spacing(10) + .align_items(Alignment::Center); + + container(content) + .width(Length::Fill) + .height(Length::Fill) + .padding(5) + .center_y() + .into() +} diff --git a/src/views/tabs.rs b/src/views/tabs.rs new file mode 100644 index 0000000..991dce1 --- /dev/null +++ b/src/views/tabs.rs @@ -0,0 +1,36 @@ +use iced::{ + widget::{button, row, text, Container}, + Element, +}; +use iced_core::{text::LineHeight, Font}; + +use crate::{ + styles::{ActiveTabStyle, TabStyle}, + utils::{macros::bqn386, HistoryMap}, + Message, +}; + +pub fn tab_view<'a>(outs: &HistoryMap, at: usize) -> Element<'a, Message> { + let mut keys: Vec<_> = outs.0.keys().cloned().collect(); + keys.sort(); + let tabs: iced::widget::Row = row({ + keys.iter() + .map(|i| { + Container::new( + button(if *i == at { + bqn386!(format!("{i}")).style(ActiveTabStyle::theme()) + } else { + bqn386!(format!("{i}")) + }) + .on_press(Message::TabChanged(*i)) + .style(TabStyle::theme()), + ) + .into() + }) + .collect() + }); + let new_tab_btn = button(bqn386!("+")) + .on_press(Message::TabCreate) + .style(TabStyle::theme()); + row![tabs, new_tab_btn].spacing(0).spacing(20).into() +} diff --git a/src/views/toolbar.rs b/src/views/toolbar.rs new file mode 100644 index 0000000..20ca1d7 --- /dev/null +++ b/src/views/toolbar.rs @@ -0,0 +1,44 @@ +use iced::{ + widget::{button, container, text, tooltip}, + Element, +}; +use iced_core::{text::LineHeight, Font}; +use once_cell::sync::Lazy; + +use crate::{ + docs::content::glyph_to_documentation, + styles::{BtnStyle, TooltipStyle}, + utils::macros::bqn386, + widgets::wrap::Wrap, + Message, +}; + +static GLYPHS: Lazy<[char; 64]> = Lazy::new(|| { + [ + '+', '¨', '⊸', '⊑', '´', '∾', '×', '-', '≠', '∘', '˜', '=', '/', '<', '↕', '⥊', '⊢', '⟜', + '⊏', '≡', '∧', '˘', '!', '>', '⌽', '↓', '¬', '↑', '∨', '`', '◶', '⍟', '⌜', '⊣', '⌾', '⌈', + '⋈', '⊔', '⌊', '»', '⊐', '∊', '○', '≤', '|', '≢', '⍉', '÷', '≍', '˝', '⁼', '«', '≥', '˙', + '⍋', '⍷', '⋆', '⊘', '⎉', '⚇', '⊒', '√', '⍒', '⎊', + ] +}); + +pub fn toolbar_view<'a>() -> Element<'a, Message> { + container( + GLYPHS + .iter() + .fold(Wrap::new(), |wrap, glyph| { + wrap.push( + tooltip( + button(bqn386!(glyph)) + .style(BtnStyle::theme()) + .on_press(Message::ToolbarClick(glyph.to_string())), + glyph_to_documentation(*glyph), + tooltip::Position::FollowCursor, + ) + .style(TooltipStyle::theme()), + ) + }) + .spacing(1), + ) + .into() +}