A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/bar.rs | 63 | ||||
| -rw-r--r-- | src/main.rs | 186 | ||||
| -rw-r--r-- | src/text.rs | 65 |
4 files changed, 198 insertions, 118 deletions
@@ -22,7 +22,7 @@ lower = "0.2.0" amap = "0.1.2" run_times = "0.1.0" array_chunks = "1.0.0" -rust-fsm = { version = "0.8.0", features = ["diagram"] } +rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = ["diagram"] } [build-dependencies] cc = "*" @@ -2,30 +2,13 @@ use std::iter::{once, repeat}; use dsb::Cell; use dsb::cell::Style; +use winit::keyboard::{Key, ModifiersState, NamedKey}; pub struct Bar { - pub state: state::StateMachine, pub text: crate::text::TextArea, pub last_action: String, } -rust_fsm::state_machine! { - #[derive(Debug, PartialEq, Eq, Copy, Clone)] - pub(crate) state(Inactive) - - Inactive => { - Control => Control, - }, - Control => { - Saved => Inactive, - WaitingForFname => InputFname, - Released => Inactive, - }, - InputFname => { - Enter => Inactive, - } -} - impl Bar { pub fn write_to( &self, @@ -34,6 +17,7 @@ impl Bar { (into, (w, _)): (&mut [Cell], (usize, usize)), oy: usize, fname: &str, + state: &super::State, ) { let row = &mut into[oy * w..oy * w + w]; row.fill(Cell { @@ -43,26 +27,14 @@ impl Bar { fn s(s: &str) -> impl Iterator<Item = (char, u8)> { s.chars().zip(repeat(0)) } - match self.state.state() { - state::State::Inactive => { - row[1.."gracilaria".len() + 1] - .iter_mut() - .zip("gracilaria".chars()) - .for_each(|(x, y)| x.letter = Some(y)); - row[w / 2 - fname.len() / 2 - ..w / 2 - fname.len() / 2 + fname.len()] - .iter_mut() - .zip(fname.chars()) - .for_each(|(x, y)| x.letter = Some(y)); - row.iter_mut() - .rev() - .zip(self.last_action.chars().rev()) - .for_each(|(x, y)| x.letter = Some(y)); - } - state::State::Control => { + use super::State; + match state { + State::Default if super::ctrl() => { let x = s("C + { ") .chain(once(('S', Style::BOLD))) .chain(s("ave, ")) + .chain(once(('Q', Style::BOLD))) + .chain(s("uit, ")) .chain(once(('C', Style::BOLD))) .chain(s("opy }")); @@ -74,11 +46,26 @@ impl Bar { } }); } - state::State::InputFname => { + State::Default => { + row[1.."gracilaria".len() + 1] + .iter_mut() + .zip("gracilaria".chars()) + .for_each(|(x, y)| x.letter = Some(y)); + row[w / 2 - fname.len() / 2 + ..w / 2 - fname.len() / 2 + fname.len()] + .iter_mut() + .zip(fname.chars()) + .for_each(|(x, y)| x.letter = Some(y)); + row.iter_mut() + .rev() + .zip(self.last_action.chars().rev()) + .for_each(|(x, y)| x.letter = Some(y)); + } + State::InputFname(x) => { "write to file: " .chars() .zip(repeat(Style::BOLD | Style::ITALIC)) - .chain(s(&self.text.rope.to_string())) + .chain(s(&x.rope.to_string())) .zip(row) .for_each(|((x, z), y)| { *y = Cell { @@ -88,6 +75,8 @@ impl Bar { } }); } + State::Save => unreachable!(), + _ => {} } } } diff --git a/src/main.rs b/src/main.rs index 5935a9f..6899a92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ +// this looks pretty good though #![feature(tuple_trait, unboxed_closures, fn_traits)] #![feature( + import_trait_associated_functions, + guard_patterns, if_let_guard, deref_patterns, generic_const_exprs, @@ -17,6 +20,7 @@ use std::simd::prelude::*; use std::sync::LazyLock; use std::time::Instant; +use Default::default; use array_chunks::*; use atools::prelude::*; use dsb::cell::Style; @@ -26,10 +30,9 @@ use rust_fsm::StateMachineImpl; use swash::{FontRef, Instance}; use winit::event::{ElementState, Event, MouseScrollDelta, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; -use winit::keyboard::{Key, NamedKey}; +use winit::keyboard::{Key, ModifiersState, NamedKey}; use crate::bar::Bar; -use crate::bar::state::{Input, State}; use crate::text::TextArea; mod bar; mod text; @@ -38,6 +41,8 @@ fn main() { entry(EventLoop::new().unwrap()) } +static mut MODIFIERS: ModifiersState = ModifiersState::empty(); + const BG: [u8; 3] = [31, 36, 48]; const FG: [u8; 3] = [204, 202, 194]; #[implicit_fn::implicit_fn] @@ -48,16 +53,14 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { let mut text = TextArea::default(); let mut origin = std::env::args().nth(1); let mut fonts = dsb::Fonts::new( - *FONT, F::instance(*FONT, *BFONT), + *FONT, *IFONT, F::instance(*IFONT, *BIFONT), ); - let mut bar = Bar { - text: TextArea::default(), - state: bar::state::StateMachine::new(), - last_action: String::default(), - }; + let mut state = State::Default; + let mut bar = + Bar { text: TextArea::default(), last_action: String::default() }; let mut i = Image::build(1, 1).fill(BG); let mut cells = vec![]; std::env::args().nth(1).map(|x| { @@ -101,7 +104,6 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { window.inner_size().height as _, ), ); - dbg!(&bar.state); match event { Event::WindowEvent { window_id, @@ -167,12 +169,15 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { (&mut cells, (c, r)), (t_ox, 0), ); + text.c = c - t_ox; + text.r = r - 1; bar.write_to( BG, FG, (&mut cells, (c, r)), r - 1, &origin.as_deref().unwrap_or("new buffer"), + &state, ); println!("cell="); @@ -264,89 +269,56 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } } Event::WindowEvent { - event: WindowEvent::KeyboardInput { event, .. }, + event: WindowEvent::ModifiersChanged(modifiers), .. - } if event.state == ElementState::Released - && *bar.state.state() == State::Control - && event.logical_key == NamedKey::Control => - { - bar.state.consume(&Input::Released).unwrap(); + } => { + unsafe { MODIFIERS = modifiers.state() }; window.request_redraw(); } Event::WindowEvent { event: WindowEvent::KeyboardInput { event, .. }, .. - } if *bar.state.state() == State::Control - && event.state == ElementState::Pressed => - { - use Key::*; - match event.logical_key { - Character(x) if x == "s" => match &origin { + } if event.state == ElementState::Pressed => { + let o = state + .consume(Action::K(event.logical_key.clone())) + .unwrap(); + match o { + Some(Do::Save) => match &origin { Some(_) => { - bar.state.consume(&Input::Saved).unwrap(); + state.consume(Action::Saved).unwrap(); save!(); } None => { - bar.state - .consume(&Input::WaitingForFname) + state + .consume(Action::RequireFilename) .unwrap(); } }, - // Character(x) - // if x == "s" - // && => - // { - // bar.state.consume(State::Save); - // text.rope - // .write_to(File::open(origin).unwrap()) - // .unwrap(); - // } - // Character(x) if x == "s" => {} - _ => panic!(), - } - window.request_redraw(); - } - - Event::WindowEvent { - event: WindowEvent::KeyboardInput { event, .. }, - .. - } if event.state == ElementState::Pressed => { - use Key::*; - use NamedKey::*; - let text = if bar.state.state() == &State::InputFname { - &mut bar.text - } else { - &mut text - }; - match event.logical_key { - Named(Space) => text.insert(" "), - Named(Backspace) => text.backspace(), - Named(ArrowLeft) => text.left(), - Named(Home) => text.home(), - Named(End) => text.end(), - Named(ArrowRight) => text.right(), - Named(ArrowUp) => text.up(), - Named(ArrowDown) => text.down(r), - Named(Enter) - if bar.state.state() == &State::InputFname => - { - bar.state.consume(&Input::Enter).unwrap(); - origin = Some( - std::mem::take(&mut bar.text) - .rope - .to_string(), - ); + Some(Do::SaveTo(x)) => { + origin = Some(x); save!(); } - Named(Enter) => text.enter(), - Named(Control) => { - bar.state.consume(&Input::Control).unwrap(); + Some(Do::Edit) => { + handle2(event.logical_key, &mut text); } - Character(x) => { - text.insert(&*x); + Some(Do::Quit) => elwt.exit(), + Some(Do::StartSelection) => { + let State::Selection(x) = &mut state else { + panic!() + }; + *x = text.cursor..text.cursor } - _ => {} - }; + Some(Do::UpdateSelection) => { + let State::Selection(x) = &mut state else { + panic!() + }; + let Key::Named(y) = event.logical_key else { + panic!() + }; + *x = text.extend_selection(y, x.clone()); + } + None => {} + } window.request_redraw(); } _ => {} @@ -356,6 +328,31 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { winit_app::run_app(event_loop, app); } + +fn handle2(key: Key, text: &mut TextArea) { + use Key::*; + use NamedKey::*; + + match key { + Named(Space) => text.insert(" "), + Named(Backspace) => text.backspace(), + Named(ArrowLeft) => text.left(), + Named(Home) => text.home(), + Named(End) => text.end(), + Named(ArrowRight) => text.right(), + Named(ArrowUp) => text.up(), + Named(ArrowDown) => text.down(), + Named(Enter) => text.enter(), + Character(x) => { + text.insert(&*x); + } + _ => {} + }; +} +fn handle(key: Key, mut text: TextArea) -> TextArea { + handle2(key, &mut text); + text +} pub static FONT: LazyLock<FontRef<'static>> = LazyLock::new(|| { FontRef::from_index( &include_bytes!("/home/os/CascadiaCodeNF.ttf")[..], @@ -377,3 +374,42 @@ pub static BIFONT: LazyLock<Instance<'static>> = LazyLock::new(|| { pub static BFONT: LazyLock<Instance<'static>> = LazyLock::new(|| FONT.instances().find_by_name("Bold").unwrap()); +fn shift() -> bool { + unsafe { MODIFIERS }.shift_key() +} +fn ctrl() -> bool { + unsafe { MODIFIERS }.control_key() +} +fn arrow(k: &Key) -> bool { + matches!( + k, + Key::Named( + NamedKey::ArrowLeft + | NamedKey::ArrowRight + | NamedKey::ArrowDown + | NamedKey::ArrowUp + ) + ) +} + +// use NamedKey::Arrow +use std::ops::Range; +rust_fsm::state_machine! { + #[derive(Clone, Debug)] + pub(crate) State => Action => Do + + Dead => K(Key => _) => Dead, + Default => { + K(Key => Key::Character(x) if x == "s" && ctrl()) => Save [Save], + K(Key => Key::Character(x) if x == "q" && ctrl()) => Dead [Quit], + K(Key => x if shift() && arrow(&x)) => Selection(Range<usize> => 0..0) [StartSelection], + K(Key => _) => Default [Edit], + }, + Selection(Range<usize> => x) => K(Key => y if arrow(&y) && shift()) => Selection(Range<usize> => x) [UpdateSelection], + Save => { + RequireFilename => InputFname(TextArea => default()), + Saved => Default, + }, + InputFname(TextArea => t) => K(Key => Key::Named(NamedKey::Enter)) => Default [SaveTo(String => t.rope.to_string())], + InputFname(TextArea => t) => K(Key => k) => InputFname(TextArea => handle(k, t)), +} diff --git a/src/text.rs b/src/text.rs index 1f92481..bfb6d1c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::sync::LazyLock; use atools::Chunked; @@ -7,7 +8,7 @@ use ropey::Rope; use tree_sitter_highlight::{ HighlightConfiguration, HighlightEvent, Highlighter, }; -use winit::keyboard::SmolStr; +use winit::keyboard::{NamedKey, SmolStr}; #[rustfmt::skip] const NAMES: [&str; 13] = ["attribute", "comment", "constant", "function", "keyword", "number", "operator", "punctuation", "string", "tag", "type", "variable", "variable.parameter"]; @@ -40,6 +41,35 @@ pub struct TextArea { highlighter: Highlighter, column: usize, pub vo: usize, + + pub r: usize, + pub c: usize, +} +impl Debug for TextArea { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TextArea") + .field("rope", &self.rope) + .field("cursor", &self.cursor) + .field("column", &self.column) + .field("vo", &self.vo) + .field("r", &self.r) + .field("c", &self.c) + .finish() + } +} + +impl Clone for TextArea { + fn clone(&self) -> Self { + Self { + rope: self.rope.clone(), + cursor: self.cursor, + highlighter: Highlighter::default(), + column: self.column, + vo: self.vo, + r: self.r, + c: self.c, + } + } } impl TextArea { @@ -120,7 +150,7 @@ impl TextArea { (|| self.insert(" ")).run(n); } - pub fn down(&mut self, r: usize) { + pub fn down(&mut self) { let l = self.rope.try_char_to_line(self.cursor).unwrap_or(0); // next line size @@ -145,10 +175,10 @@ impl TextArea { .unwrap_or(0) }; if self.rope.char_to_line(self.cursor) - >= (self.vo + r).saturating_sub(5) + >= (self.vo + self.r).saturating_sub(5) { self.vo += 1; - self.vo = self.vo.min(self.l() - r); + self.vo = self.vo.min(self.l() - self.r); } } @@ -170,7 +200,9 @@ impl TextArea { } pub fn backspace(&mut self) { - _ = self.rope.try_remove(self.cursor - 1..self.cursor); + _ = self + .rope + .try_remove(self.cursor.saturating_sub(1)..self.cursor); self.cursor = self.cursor.saturating_sub(1); } #[implicit_fn::implicit_fn] @@ -312,4 +344,27 @@ impl TextArea { need } + + pub fn extend_selection( + &self, + key: NamedKey, + r: std::ops::Range<usize>, + ) -> std::ops::Range<usize> { + match key { + NamedKey::ArrowLeft => r.start.saturating_sub(1)..r.end, + NamedKey::ArrowRight => r.start..r.end + 1, + NamedKey::ArrowUp => { + let l = self.rope.char_to_line(r.start); + self.rope.line_to_char(l - 1) + r.start + - self.rope.line_to_char(l)..r.end + } + NamedKey::ArrowDown => { + let l = self.rope.char_to_line(r.end); + r.start + ..self.rope.line_to_char(l + 1) + r.end + - self.rope.line_to_char(l) + } + _ => unreachable!(), + } + } } |