A simple CPU rendered GUI IDE experience.
selections
bendn 5 months ago
parent ec99956 · commit f367520
-rw-r--r--Cargo.toml5
-rw-r--r--src/bar.rs15
-rw-r--r--src/main.rs105
-rw-r--r--src/text.rs102
4 files changed, 182 insertions, 45 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9645cf2..296025b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,7 +22,10 @@ lower = "0.2.0"
amap = "0.1.2"
run_times = "0.1.0"
array_chunks = "1.0.0"
-rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = ["diagram"] }
+rust-fsm = { version = "0.8.0", path = "../rust-fsm/rust-fsm", features = [
+ "diagram",
+] }
+clipp = "0.1.0"
[build-dependencies]
cc = "*"
diff --git a/src/bar.rs b/src/bar.rs
index d2aacf9..2475ba1 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -4,6 +4,8 @@ use dsb::Cell;
use dsb::cell::Style;
use winit::keyboard::{Key, ModifiersState, NamedKey};
+use crate::text::TextArea;
+
pub struct Bar {
pub text: crate::text::TextArea,
pub last_action: String,
@@ -18,6 +20,7 @@ impl Bar {
oy: usize,
fname: &str,
state: &super::State,
+ t: &TextArea,
) {
let row = &mut into[oy * w..oy * w + w];
row.fill(Cell {
@@ -35,8 +38,8 @@ impl Bar {
.chain(s("ave, "))
.chain(once(('Q', Style::BOLD)))
.chain(s("uit, "))
- .chain(once(('C', Style::BOLD)))
- .chain(s("opy }"));
+ .chain(once(('V', Style::BOLD)))
+ .chain(s("aste }"));
x.zip(row).for_each(|((x, z), y)| {
*y = Cell {
@@ -75,6 +78,14 @@ impl Bar {
}
});
}
+ State::Selection(x) => {
+ let [(x1, y1), (x2, y2)] = t.position(x.clone());
+ format!("selection from ({x1}, {y1}) to ({x2}, {y2})")
+ .chars()
+ .rev()
+ .zip(row.iter_mut().rev())
+ .for_each(|(x, y)| y.letter = Some(x));
+ }
State::Save => unreachable!(),
_ => {}
}
diff --git a/src/main.rs b/src/main.rs
index 6899a92..ea3f974 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,7 +30,7 @@ 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, ModifiersState, NamedKey};
+use winit::keyboard::{Key, ModifiersState, NamedKey, SmolStr};
use crate::bar::Bar;
use crate::text::TextArea;
@@ -155,6 +155,10 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
style: Style { color: BG, bg: BG, flags: 0 },
letter: None,
});
+ let x = match &state {
+ State::Selection(x) => Some(x.clone()),
+ _ => None,
+ };
let t_ox = text.line_numbers(
(c, r - 1),
[67, 76, 87],
@@ -168,6 +172,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
BG,
(&mut cells, (c, r)),
(t_ox, 0),
+ x,
);
text.c = c - t_ox;
text.r = r - 1;
@@ -178,6 +183,7 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
r - 1,
&origin.as_deref().unwrap_or("new buffer"),
&state,
+ &text,
);
println!("cell=");
@@ -279,6 +285,16 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
event: WindowEvent::KeyboardInput { event, .. },
..
} if event.state == ElementState::Pressed => {
+ if matches!(
+ event.logical_key,
+ Key::Named(
+ NamedKey::Shift
+ | NamedKey::Alt
+ | NamedKey::Control
+ )
+ ) {
+ return;
+ }
let o = state
.consume(Action::K(event.logical_key.clone()))
.unwrap();
@@ -306,7 +322,13 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
let State::Selection(x) = &mut state else {
panic!()
};
- *x = text.cursor..text.cursor
+ let Key::Named(y) = event.logical_key else {
+ panic!()
+ };
+ *x = text.extend_selection(
+ y,
+ text.cursor..text.cursor,
+ );
}
Some(Do::UpdateSelection) => {
let State::Selection(x) = &mut state else {
@@ -317,6 +339,25 @@ pub(crate) fn entry(event_loop: EventLoop<()>) {
};
*x = text.extend_selection(y, x.clone());
}
+ Some(Do::Insert((x, c))) => {
+ text.rope.remove(x);
+ text.insert_(c);
+ }
+ Some(Do::Delete(x)) => {
+ text.rope.remove(x);
+ }
+ Some(Do::Copy(x)) => {
+ clipp::copy(text.rope.slice(x).to_string());
+ }
+ Some(Do::Cut(x)) => {
+ clipp::copy(
+ text.rope.slice(x.clone()).to_string(),
+ );
+ text.rope.remove(x);
+ }
+ Some(Do::Paste) => {
+ text.insert(&clipp::paste());
+ }
None => {}
}
window.request_redraw();
@@ -337,8 +378,17 @@ fn handle2(key: Key, text: &mut TextArea) {
Named(Space) => text.insert(" "),
Named(Backspace) => text.backspace(),
Named(ArrowLeft) => text.left(),
+ Named(Home) if ctrl() => {
+ text.cursor = 0;
+ text.vo = 0;
+ }
+ Named(End) if ctrl() => {
+ text.cursor = text.rope.len_chars();
+ text.vo = text.l() - text.r;
+ }
Named(Home) => text.home(),
Named(End) => text.end(),
+ // Named(Tab)
Named(ArrowRight) => text.right(),
Named(ArrowUp) => text.up(),
Named(ArrowDown) => text.down(),
@@ -380,36 +430,33 @@ fn shift() -> bool {
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
+#[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)),
+Dead => K(Key => _) => Dead,
+Default => {
+ K(Key::Character(x) if x == "s" && ctrl()) => Save [Save],
+ K(Key::Character(x) if x == "q" && ctrl()) => Dead [Quit],
+ K(Key::Character(x) if x == "v" && ctrl()) => Default [Paste],
+ K(Key::Named(NamedKey::ArrowUp | NamedKey::ArrowLeft | NamedKey::ArrowDown | NamedKey::ArrowRight | NamedKey::Home | NamedKey::End) if shift()) => Selection(Range<usize> => 0..0) [StartSelection],
+ K(_) => Default [Edit],
+},
+Selection(x) => {
+ K(Key::Named(NamedKey::ArrowUp | NamedKey::ArrowLeft | NamedKey::ArrowDown | NamedKey::ArrowRight | NamedKey::Home | NamedKey::End) if shift()) => Selection(x) [UpdateSelection],
+ K(Key::Named(NamedKey::Backspace)) => Default [Delete(Range<usize> => x)],
+ K(Key::Character(y) if y == "x" && ctrl()) => Default [Cut(Range<usize> => x)],
+ K(Key::Character(y) if y == "c" && ctrl()) => Default [Copy(Range<usize> => x)],
+ K(Key::Character(y)) => Default [Insert((Range<usize>, SmolStr) => (x, y))],
+ K(_) => Default [Edit],
+},
+Save => {
+ RequireFilename => InputFname(TextArea => default()),
+ Saved => Default,
+},
+InputFname(t) => K(Key::Named(NamedKey::Enter)) => Default [SaveTo(String => t.rope.to_string())],
+InputFname(t) => K(k) => InputFname(TextArea => handle(k, t)),
}
diff --git a/src/text.rs b/src/text.rs
index bfb6d1c..373b04f 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -1,4 +1,5 @@
use std::fmt::Debug;
+use std::ops::Range;
use std::sync::LazyLock;
use atools::Chunked;
@@ -73,6 +74,17 @@ impl Clone for TextArea {
}
impl TextArea {
+ pub fn position(
+ &self,
+ Range { start, end }: Range<usize>,
+ ) -> [(usize, usize); 2] {
+ let y1 = self.rope.char_to_line(start);
+ let y2 = self.rope.char_to_line(end);
+ let x1 = start - self.rope.line_to_char(y1);
+ let x2 = end - self.rope.line_to_char(y2);
+ [(x1, y1), (x2, y2)]
+ }
+
pub fn l(&self) -> usize {
self.rope.len_lines()
}
@@ -213,6 +225,7 @@ impl TextArea {
bg: [u8; 3],
(into, (w, _)): (&mut [Cell], (usize, usize)),
(ox, oy): (usize, usize),
+ selection: Option<Range<usize>>,
) {
static HL: LazyLock<HighlightConfiguration> =
LazyLock::new(|| {
@@ -261,9 +274,11 @@ impl TextArea {
// }
let y1 = self.rope.byte_to_line(start);
let y2 = self.rope.byte_to_line(end);
- let x1 = start - self.rope.line_to_char(y1);
- let x2 = end - self.rope.line_to_char(y2);
- // dbg!((x1, y1), (x2, y2));
+ let x1 = self.rope.byte_to_char(start)
+ - self.rope.line_to_char(y1);
+ let x2 = self.rope.byte_to_char(end)
+ - self.rope.line_to_char(y2);
+
cells.get_mut(y1 * c + x1..y2 * c + x2).map(|x| {
x.iter_mut().for_each(|x| {
x.style.flags = STYLES[s].unwrap_or_default();
@@ -309,6 +324,32 @@ impl TextArea {
}
}
}
+ selection.map(|x| self.position(x)).map(|[(x1, y1), (x2, y2)]| {
+ (y1 * c + x1..y2 * c + x2).for_each(|x| {
+ cells
+ .get_mut(x)
+ .filter(
+ _.letter.is_some()
+ || (x % c == 0)
+ || (self
+ .rope
+ .get_line(x / c)
+ .map(_.len_chars())
+ .unwrap_or_default()
+ .saturating_sub(1)
+ == x % c),
+ )
+ .map(|x| {
+ if x.letter == Some(' ') {
+ x.letter = Some('ยท'); // tabs? what are those
+ x.style.color = [0x4e, 0x62, 0x79];
+ }
+ x.style.bg = [0x27, 0x43, 0x64];
+ // 0x23, 0x34, 0x4B
+ });
+ });
+ });
+
let cells = &cells[self.vo * c..self.vo * c + r * c];
assert_eq!(cells.len(), c * r);
@@ -346,23 +387,58 @@ impl TextArea {
}
pub fn extend_selection(
- &self,
+ &mut self,
key: NamedKey,
r: std::ops::Range<usize>,
) -> std::ops::Range<usize> {
+ macro_rules! left {
+ () => {
+ if self.cursor != 0 && self.cursor >= r.start {
+ // left to right going left (shrink right end)
+ r.start..self.cursor
+ } else {
+ // right to left going left (extend left end)
+ self.cursor..r.end
+ }
+ };
+ }
+ macro_rules! right {
+ () => {
+ if self.cursor == self.rope.len_chars() {
+ r
+ } else if self.cursor > r.end {
+ // left to right (extend right end)
+ r.start..self.cursor
+ } else {
+ // right to left (shrink left end)
+ self.cursor..r.end
+ }
+ };
+ }
match key {
- NamedKey::ArrowLeft => r.start.saturating_sub(1)..r.end,
- NamedKey::ArrowRight => r.start..r.end + 1,
+ NamedKey::Home => {
+ self.home();
+ left!()
+ }
+ NamedKey::End => {
+ self.end();
+ right!()
+ }
+ NamedKey::ArrowLeft => {
+ self.left();
+ left!()
+ }
+ NamedKey::ArrowRight => {
+ self.right();
+ right!()
+ }
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
+ self.up();
+ left!()
}
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)
+ self.down();
+ right!()
}
_ => unreachable!(),
}