You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
10 KiB
282 lines
10 KiB
extern crate sdl2;
|
|
|
|
use clipboard::{ClipboardContext, ClipboardProvider};
|
|
use sdl2::{
|
|
event::{Event, WindowEvent},
|
|
keyboard::Keycode,
|
|
};
|
|
|
|
mod file;
|
|
mod input;
|
|
mod render;
|
|
|
|
static SCREEN_WIDTH: u32 = 1280;
|
|
static SCREEN_HEIGHT: u32 = 720;
|
|
|
|
// TODO: Make this configurable
|
|
static REFRESH_RATE: u32 = 50;
|
|
|
|
// TODO: Make this configurable
|
|
static UNDO_TIME: u32 = 250;
|
|
static UNDO_TIME_COUNT: u32 = (REFRESH_RATE as f32 * (UNDO_TIME as f32 / 1000.0)) as u32;
|
|
|
|
pub fn main() -> Result<(), String> {
|
|
// Initialize clipboard
|
|
let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap();
|
|
|
|
// Initialize SDL2, window, and canvas
|
|
let sdl_context = sdl2::init()?;
|
|
let video_subsys = sdl_context.video()?;
|
|
|
|
let window = video_subsys
|
|
.window("Rude", SCREEN_WIDTH, SCREEN_HEIGHT)
|
|
.position_centered()
|
|
.resizable()
|
|
.opengl()
|
|
.build()
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
|
|
|
|
// Initalize buffer
|
|
let mut buffer = String::new();
|
|
|
|
// Initialize undo data & values
|
|
let mut undo_history: Vec<(String, usize)> = vec![];
|
|
let mut undo_position: usize = 0;
|
|
let mut undo_timer: u32 = UNDO_TIME_COUNT;
|
|
|
|
// Initialize input values
|
|
let mut modifier_keys = input::ModifierKeys::new();
|
|
let mut cursor_position = 0;
|
|
let mut selection_anchor: Option<usize> = None;
|
|
|
|
// Initialize graphics data and values
|
|
let glyph_atlas = render::generate_glyph_data()?;
|
|
|
|
// Easier way to please the borrow checker
|
|
macro_rules! draw {
|
|
() => {
|
|
render::draw_everything(&mut canvas, &glyph_atlas, &buffer, cursor_position)?
|
|
};
|
|
}
|
|
|
|
draw!();
|
|
|
|
'mainloop: loop {
|
|
// TODO: Make this completely user-configurable instead of hardcoded
|
|
for event in sdl_context.event_pump()?.poll_iter() {
|
|
match event {
|
|
Event::Window { win_event, .. } => {
|
|
if let WindowEvent::Resized(_w, _h) = win_event {
|
|
draw!();
|
|
}
|
|
}
|
|
// ESC or SIGKILL
|
|
// Event::KeyDown { keycode: Some(Keycode::Escape), .. } |
|
|
Event::Quit { .. } => break 'mainloop,
|
|
|
|
Event::KeyUp { keycode, .. } => match keycode {
|
|
Some(Keycode::LAlt) | Some(Keycode::RAlt) => modifier_keys.alt = false,
|
|
Some(Keycode::LCtrl) | Some(Keycode::RCtrl) => modifier_keys.ctrl = false,
|
|
Some(Keycode::LShift) | Some(Keycode::RShift) => modifier_keys.shift = false,
|
|
Some(Keycode::LGui) | Some(Keycode::RGui) => break,
|
|
_ => (),
|
|
},
|
|
|
|
Event::KeyDown { keycode, .. } => {
|
|
match keycode {
|
|
Some(Keycode::LAlt) | Some(Keycode::RAlt) => modifier_keys.alt = true,
|
|
Some(Keycode::LCtrl) | Some(Keycode::RCtrl) => modifier_keys.ctrl = true,
|
|
Some(Keycode::LShift) | Some(Keycode::RShift) => modifier_keys.shift = true,
|
|
Some(Keycode::LGui) | Some(Keycode::RGui) => break,
|
|
|
|
// DELETE key
|
|
Some(Keycode::Delete) => {
|
|
if buffer.len() > 0 && cursor_position < buffer.len() {
|
|
undo_timer = 0;
|
|
selection_anchor = None;
|
|
input::delete(&mut buffer, cursor_position, &modifier_keys);
|
|
|
|
draw!();
|
|
}
|
|
}
|
|
|
|
// BACKSPACE key
|
|
Some(Keycode::Backspace) => {
|
|
if buffer.len() > 0 {
|
|
undo_timer = 0;
|
|
selection_anchor = None;
|
|
input::backspace(&mut buffer, &mut cursor_position, &modifier_keys);
|
|
|
|
draw!();
|
|
}
|
|
}
|
|
|
|
_ => (),
|
|
};
|
|
|
|
match (modifier_keys.shift, modifier_keys.ctrl, modifier_keys.alt) {
|
|
// All modifiers up
|
|
(false, false, false) => {
|
|
match keycode {
|
|
// ENTER key
|
|
Some(Keycode::Return) => {
|
|
undo_timer = 0;
|
|
selection_anchor = None;
|
|
|
|
let key = '\n';
|
|
buffer.insert(cursor_position, key);
|
|
cursor_position += 1;
|
|
|
|
draw!();
|
|
}
|
|
|
|
// HOME key
|
|
Some(Keycode::Home) => {
|
|
selection_anchor = None;
|
|
|
|
cursor_position = 0;
|
|
|
|
draw!();
|
|
}
|
|
|
|
// END key
|
|
Some(Keycode::End) => {
|
|
selection_anchor = None;
|
|
|
|
cursor_position = buffer.len();
|
|
|
|
draw!();
|
|
}
|
|
|
|
// Left/Back arrow
|
|
Some(Keycode::Left) => {
|
|
selection_anchor = None;
|
|
|
|
cursor_position =
|
|
usize::checked_sub(cursor_position, 1).unwrap_or(0);
|
|
|
|
draw!();
|
|
}
|
|
|
|
// Right/Forward arrow
|
|
Some(Keycode::Right) => {
|
|
selection_anchor = None;
|
|
|
|
cursor_position = (cursor_position + 1).min(buffer.len());
|
|
|
|
draw!();
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// CTRL down
|
|
(false, true, false) => {
|
|
match keycode {
|
|
// Select all
|
|
Some(Keycode::A) => {
|
|
selection_anchor = Some(buffer.len());
|
|
cursor_position = 0;
|
|
|
|
draw!()
|
|
}
|
|
|
|
// Undo
|
|
Some(Keycode::Z) => {
|
|
if undo_position > 1 {
|
|
undo_position -= 1;
|
|
let last_undo = undo_history[undo_position - 1].clone();
|
|
buffer = last_undo.0;
|
|
cursor_position = last_undo.1;
|
|
|
|
draw!()
|
|
}
|
|
}
|
|
|
|
// TODO: Cut
|
|
Some(Keycode::X) => println!("Cut"),
|
|
|
|
// Copy
|
|
// TODO: Use selection
|
|
Some(Keycode::C) => {
|
|
clipboard_context.set_contents(buffer.clone()).unwrap()
|
|
}
|
|
|
|
// Paste
|
|
Some(Keycode::V) => {
|
|
let paste = clipboard_context.get_contents().unwrap();
|
|
for character in paste.chars() {
|
|
buffer.insert(cursor_position, character);
|
|
cursor_position += 1;
|
|
|
|
undo_timer = UNDO_TIME_COUNT;
|
|
}
|
|
draw!()
|
|
}
|
|
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// SHIFT + CTRL down
|
|
(true, true, false) => match keycode {
|
|
Some(Keycode::Z) => {
|
|
if undo_position < undo_history.len() {
|
|
undo_position += 1;
|
|
let last_redo = undo_history[undo_position - 1].clone();
|
|
buffer = last_redo.0;
|
|
cursor_position = last_redo.1;
|
|
|
|
draw!();
|
|
}
|
|
}
|
|
|
|
Some(Keycode::X) => println!("Cut line(s)"),
|
|
|
|
Some(Keycode::C) => println!("Copy line(s)"),
|
|
_ => (),
|
|
},
|
|
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// Process user input
|
|
Event::TextInput { text, .. } => {
|
|
undo_timer = 0;
|
|
selection_anchor = None;
|
|
|
|
let input_char = text.chars().nth(0).expect("Empty");
|
|
buffer.insert(cursor_position, input_char);
|
|
cursor_position += 1;
|
|
|
|
draw!();
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Wait for pause in input to snapshot buffer for undoing/redoing
|
|
if undo_timer < UNDO_TIME_COUNT {
|
|
undo_timer += 1;
|
|
} else if undo_timer == UNDO_TIME_COUNT {
|
|
// Upon editing after an undo, this clears every action after such undo
|
|
if undo_position < undo_history.len() {
|
|
undo_history.truncate(undo_position);
|
|
}
|
|
undo_history.push((buffer.clone(), cursor_position));
|
|
|
|
undo_timer += 1;
|
|
undo_position += 1;
|
|
}
|
|
|
|
std::thread::sleep(std::time::Duration::new(0, 1_000_000_000 / REFRESH_RATE));
|
|
}
|
|
format!("{selection_anchor:?}");
|
|
println!("{buffer}");
|
|
Ok(())
|
|
}
|
|
|