A simple CPU rendered GUI IDE experience.
| -rw-r--r-- | src/main.rs | 139 | ||||
| -rw-r--r-- | src/text.rs | 45 |
2 files changed, 144 insertions, 40 deletions
diff --git a/src/main.rs b/src/main.rs index e9ade1c..08ffb50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ mod winit_app; fn main() { entry(EventLoop::new().unwrap()) } - +#[derive(Debug)] struct Hist { pub history: Vec<Diff>, pub redo_history: Vec<Diff>, @@ -52,7 +52,7 @@ struct Hist { pub changed: bool, } impl Hist { - pub fn push(&mut self, x: &TextArea) { + fn push(&mut self, x: &TextArea) { let d = diff_match_patch_rs::DiffMatchPatch::new(); self.history.push(Diff { changes: ( @@ -72,35 +72,56 @@ impl Hist { (x.cursor, x.column, x.vo), ], }); + println!("push {}", self.history.last().unwrap()); self.redo_history.clear(); self.last = x.clone(); + self.last_edit = Instant::now(); self.changed = false; } - pub fn undo(&mut self) -> Option<Diff> { + fn undo_(&mut self) -> Option<Diff> { self.history.pop().map(|x| { self.redo_history.push(x.clone()); x }) } - pub fn redo(&mut self) -> Option<Diff> { + fn redo_(&mut self) -> Option<Diff> { self.redo_history.pop().map(|x| { self.history.push(x.clone()); x }) } - pub fn test_push(&mut self, x: &TextArea) { - if self.last_edit.elapsed().as_millis() > 500 && self.changed { + pub fn undo(&mut self, t: &mut TextArea) { + self.push_if_changed(&t); + self.undo_().map(|x| { + x.apply(t, false); + self.last = t.clone(); + }); + } + pub fn redo(&mut self, t: &mut TextArea) { + self.redo_().map(|x| { + x.apply(t, true); + self.last = t.clone(); + }); + } + pub fn push_if_changed(&mut self, x: &TextArea) { + if self.changed || x.rope != self.last.rope { self.push(x); } } - pub fn record(&mut self, x: &TextArea) { - self.test_push(x); + pub fn test_push(&mut self, x: &TextArea) { + if self.last_edit.elapsed().as_millis() > 500 { + self.push_if_changed(x); + } + } + pub fn record(&mut self, _: &TextArea) { + // self.test_push(x); self.last_edit = Instant::now(); self.changed = true; } } static mut MODIFIERS: ModifiersState = ModifiersState::empty(); +static mut CLICKING: bool = false; const BG: [u8; 3] = [31, 36, 48]; const FG: [u8; 3] = [204, 202, 194]; @@ -257,8 +278,8 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { &text, ); - println!("cell="); - dbg!(now.elapsed()); + // println!("cell="); + // dbg!(now.elapsed()); let now = Instant::now(); unsafe { dsb::render( @@ -272,8 +293,8 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { i.as_mut(), ) }; - eprint!("rend="); - dbg!(now.elapsed()); + // eprint!("rend="); + // dbg!(now.elapsed()); let met = FONT.metrics(&[]); let fac = ppem / met.units_per_em as f32; let now = Instant::now(); @@ -301,7 +322,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { ); } }; - eprint!("conv = "); + // eprint!("conv = "); // } let buffer = surface.buffer_mut().unwrap(); @@ -316,7 +337,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { .as_chunks_unchecked_mut::<4>() }, ); - dbg!(now.elapsed()); + // dbg!(now.elapsed()); buffer.present().unwrap(); } } @@ -337,13 +358,37 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { (position.x / (fw) as f64).round() as usize, (position.y / (fh + ls * fac) as f64).floor() as usize, - ) + ); + match state + .consume(Action::C(cursor_position)) + .unwrap() + { + Some(Do::ExtendSelectionToMouse) => { + *state.sel() = text.extend_selection_to( + text.index_at(cursor_position), + state.sel().clone(), + ); + window.request_redraw(); + } + Some(Do::StartSelection) => { + let x = text.index_at(cursor_position); + hist.last.cursor = x; + text.cursor = x; + *state.sel() = x..x; + } + None => {} + x => unreachable!("{x:?}"), + } } + Event::WindowEvent { event: WindowEvent::MouseInput { state: bt, button, .. }, .. } if bt.is_pressed() => { + if button == MouseButton::Left { + unsafe { CLICKING = true }; + } match state.consume(Action::M(button)).unwrap() { Some(Do::MoveCursor) => { text.cursor = text.index_at(cursor_position); @@ -357,6 +402,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } Some(Do::StartSelection) => { let x = text.index_at(cursor_position); + hist.last.cursor = x; *state.sel() = text.extend_selection_to( x, text.cursor..text.cursor, @@ -367,6 +413,14 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { } } Event::WindowEvent { + event: + WindowEvent::MouseInput { + button: MouseButton::Left, + .. + }, + .. + } => unsafe { CLICKING = false }, + Event::WindowEvent { window_id: _, event: WindowEvent::MouseWheel { @@ -425,17 +479,20 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { save!(); } Some(Do::Edit) => { + hist.test_push(&text); handle2(event.logical_key, &mut text); text.scroll_to_cursor(); hist.record(&text); } Some(Do::Undo) => { hist.test_push(&text); - hist.undo().map(|x| x.apply(&mut text, false)); + hist.undo(&mut text); + bar.last_action = "undid".to_string(); } Some(Do::Redo) => { hist.test_push(&text); - hist.redo().map(|x| x.apply(&mut text, true)); + hist.redo(&mut text); + bar.last_action = "redid".to_string(); } Some(Do::Quit) => elwt.exit(), Some(Do::StartSelection) => { @@ -456,31 +513,35 @@ pub(crate) fn entry(event_loop: EventLoop<()>) { text.scroll_to_cursor(); } Some(Do::Insert((x, c))) => { - hist.push(&text); + hist.push_if_changed(&text); text.rope.remove(x.clone()); text.cursor = x.start; text.setc(); text.insert_(c); + hist.push_if_changed(&text); } Some(Do::Delete(x)) => { - hist.push(&text); + hist.push_if_changed(&text); text.cursor = x.start; text.rope.remove(x); + hist.push_if_changed(&text); } Some(Do::Copy(x)) => { clipp::copy(text.rope.slice(x).to_string()); } Some(Do::Cut(x)) => { - hist.push(&text); + hist.push_if_changed(&text); clipp::copy( text.rope.slice(x.clone()).to_string(), ); text.rope.remove(x.clone()); text.cursor = x.start; + hist.push_if_changed(&text); } Some(Do::Paste) => { - hist.push(&text); + hist.push_if_changed(&text); text.insert(&clipp::paste()); + hist.push_if_changed(&text); } Some( Do::MoveCursor | Do::ExtendSelectionToMouse, @@ -583,6 +644,8 @@ Default => { K(Key::Named(NamedKey::ArrowUp | NamedKey::ArrowLeft | NamedKey::ArrowDown | NamedKey::ArrowRight | NamedKey::Home | NamedKey::End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection], M(MouseButton => MouseButton::Left if shift()) => Selection(Range<usize> => 0..0) [StartSelection], M(MouseButton => MouseButton::Left) => Default [MoveCursor], + C((usize, usize) => _ if unsafe { CLICKING }) => Selection(0..0) [StartSelection], + C(_) => Default, K(_) => Default [Edit], M(_) => Default, }, @@ -591,6 +654,8 @@ Selection(x if shift()) => { M(MouseButton => MouseButton::Left) => Selection(x) [ExtendSelectionToMouse], }, Selection(x) => { + C(y if unsafe { CLICKING }) => Selection(x) [ExtendSelectionToMouse], + C(_) => Selection(x), M(MouseButton => MouseButton::Left) => Default [MoveCursor], K(Key::Named(NamedKey::Backspace)) => Default [Delete(Range<usize> => x)], K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)], @@ -606,16 +671,24 @@ InputFname(t) => K(Key::Named(NamedKey::Enter)) => Default [SaveTo(String => t.r InputFname(t) => K(k) => InputFname(TextArea => handle(k, t)), } #[test] -fn x() { - let d = diff_match_patch_rs::DiffMatchPatch::new(); - let diffs = d - .diff_main::<Efficient>("previous state th", "previous state") - .unwrap(); - dbg!(&diffs); - - let patch = d.patch_make(PatchInput::new_diffs(&diffs)).unwrap(); - let x = d.patch_apply(&patch, "previous state th").unwrap().0; - assert_eq!(x, "previous state"); - // diff = -th - // undo = previous state +fn history_test() { + let mut t = TextArea::default(); + let mut h = Hist { + history: vec![], + redo_history: vec![], + last: t.clone(), + last_edit: Instant::now(), + changed: false, + }; + t.insert("echo"); + h.push(&t); + t.insert(" test"); + h.push(&t); + h.undo(&mut t); + h.redo(&mut t); + h.undo(&mut t); + t.insert(" good"); + h.push(&t); + h.undo(&mut t); + assert_eq!(t.rope.to_string(), "echo"); } diff --git a/src/text.rs b/src/text.rs index 64ab23b..4a13518 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::ops::Range; use std::sync::LazyLock; @@ -35,15 +35,23 @@ const fn color(x: &[u8; 6]) -> [u8; 3] { |[a, b]| a * 16 + b ) } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Diff { pub changes: (Patches<u8>, Patches<u8>), pub data: [(usize, usize, usize); 2], } +impl Display for Diff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let d = DiffMatchPatch::new(); + writeln!(f, "{}", d.patch_to_text(&self.changes.1)) + } +} + impl Diff { pub fn apply(self, t: &mut TextArea, redo: bool) { let d = DiffMatchPatch::new(); + // println!("{}", d.patch_to_text(&self.changes.0)); t.rope = Rope::from_str( &d.patch_apply( &if redo { self.changes.1 } else { self.changes.0 }, @@ -122,14 +130,18 @@ impl TextArea { pub fn l(&self) -> usize { self.rope.len_lines() } - + #[implicit_fn::implicit_fn] pub fn index_at(&self, (x, y): (usize, usize)) -> usize { let l_i = self.vo + y; self.rope .try_line_to_char(l_i) .map(|l| { - l + (self.rope.line(l_i).len_chars() - 1) - .min(x.saturating_sub(self.line_number_offset() + 1)) + l + (self + .rope + .get_line(l_i) + .map(_.len_chars() - 1) + .unwrap_or_default()) + .min(x.saturating_sub(self.line_number_offset() + 1)) }) .unwrap_or(self.rope.len_chars()) } @@ -509,13 +521,32 @@ impl TextArea { to: usize, r: std::ops::Range<usize>, ) -> std::ops::Range<usize> { + if [r.start, r.end].contains(&to) { + return r; + } + dbg!(&r, to); let r = if self.cursor == r.start { - if to < r.start { to..r.end } else { r.end..to } // to > r.end + if to < r.start { + to..r.end + } else if to > r.end { + r.end..to + } else { + to..r.end + } } else if self.cursor == r.end { - if to > r.end { r.start..to } else { to..r.start } // to < r.start + println!("@ en"); + if to > r.end { + r.start..to + } else if to < r.start { + to..r.start + } else { + r.start..to + } } else { panic!() }; + dbg!(&r); + assert!(r.start < r.end); self.cursor = to; self.setc(); r |