|
|
|
|
@ -1,15 +1,23 @@ |
|
|
|
|
extern crate sdl2; |
|
|
|
|
|
|
|
|
|
use clipboard::{ClipboardProvider, ClipboardContext}; |
|
|
|
|
use sdl2::event::Event; |
|
|
|
|
use num_format::{Locale, ToFormattedString}; |
|
|
|
|
use sdl2::event::{Event, WindowEvent}; |
|
|
|
|
use sdl2::keyboard::Keycode; |
|
|
|
|
use sdl2::pixels::Color; |
|
|
|
|
use sdl2::rect::Point; |
|
|
|
|
use sdl2::rect::{Point, Rect}; |
|
|
|
|
|
|
|
|
|
mod editor_render; |
|
|
|
|
|
|
|
|
|
static SCREEN_WIDTH: u32 = 1680; |
|
|
|
|
static SCREEN_HEIGHT: u32 = 945; |
|
|
|
|
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 = 500; |
|
|
|
|
static UNDO_TIME_COUNT: u32 = (REFRESH_RATE as f32 * (UNDO_TIME as f32 / 1000.0)) as u32; |
|
|
|
|
|
|
|
|
|
struct ModifierKeys { |
|
|
|
|
alt: bool, |
|
|
|
|
@ -18,8 +26,9 @@ struct ModifierKeys { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn main() -> Result<(), String> { |
|
|
|
|
let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false}; |
|
|
|
|
|
|
|
|
|
let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap(); |
|
|
|
|
let (glyph_atlas, glyph_metrics) = editor_render::generate_glyph_data(); |
|
|
|
|
|
|
|
|
|
let sdl_context = sdl2::init()?; |
|
|
|
|
let video_subsys = sdl_context.video()?; |
|
|
|
|
@ -27,19 +36,28 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
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())?; |
|
|
|
|
|
|
|
|
|
let mut window_size = canvas.output_size()?; |
|
|
|
|
|
|
|
|
|
let mut buffer = String::new(); |
|
|
|
|
let mut cursor_position = 0; |
|
|
|
|
let mut selection_anchor: Option<usize> = None; |
|
|
|
|
|
|
|
|
|
let mut undo_history: Vec<(String, usize)> = vec![]; |
|
|
|
|
let mut undo_position: usize = 0; |
|
|
|
|
let mut undo_timer: u32 = UNDO_TIME_COUNT; |
|
|
|
|
|
|
|
|
|
let pad_offset = Point::new(10, 10); |
|
|
|
|
|
|
|
|
|
let mut draw_text = |text: &str, pos: usize| -> Result<(), String> { |
|
|
|
|
let (glyph_atlas, glyph_metrics) = editor_render::generate_glyph_data(); |
|
|
|
|
|
|
|
|
|
let mut draw = |window_size: (u32, u32), text: &str, pos: usize| -> Result<(), String> { |
|
|
|
|
// Draw background
|
|
|
|
|
canvas.set_draw_color(Color::RGB(32, 32, 32)); |
|
|
|
|
canvas.clear(); |
|
|
|
|
@ -54,6 +72,29 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
); |
|
|
|
|
canvas.draw_points(&fb_text[..])?; |
|
|
|
|
|
|
|
|
|
// Draw info
|
|
|
|
|
let status = text.len().to_formatted_string(&Locale::en); |
|
|
|
|
let status_position = Point::new( |
|
|
|
|
pad_offset.x, |
|
|
|
|
window_size.1 as i32 - glyph_metrics.height as i32 * 2 |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
canvas.set_draw_color(Color::RGB(16, 64, 64)); |
|
|
|
|
canvas.fill_rect(Rect::new(0, |
|
|
|
|
status_position.y - 5, |
|
|
|
|
window_size.0, |
|
|
|
|
glyph_metrics.height as u32 + 10 |
|
|
|
|
))?; |
|
|
|
|
|
|
|
|
|
canvas.set_draw_color(Color::RGB(127, 240, 240)); |
|
|
|
|
let status_bar = editor_render::draw_text( |
|
|
|
|
&glyph_atlas, |
|
|
|
|
&glyph_metrics, |
|
|
|
|
&status, |
|
|
|
|
status_position |
|
|
|
|
); |
|
|
|
|
canvas.draw_points(&status_bar[..])?; |
|
|
|
|
|
|
|
|
|
// Draw cursor
|
|
|
|
|
canvas.set_draw_color(Color::RGB(64, 240, 240)); |
|
|
|
|
let fb_cursor = editor_render::draw_cursor( |
|
|
|
|
@ -69,16 +110,20 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
Ok(()) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
draw_text("", cursor_position)?; |
|
|
|
|
|
|
|
|
|
let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false}; |
|
|
|
|
draw(window_size, "", cursor_position)?; |
|
|
|
|
|
|
|
|
|
'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 { |
|
|
|
|
window_size = (w as u32, h as u32); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// ESC or SIGKILL
|
|
|
|
|
Event::KeyDown { keycode: Some(Keycode::Escape), .. } | |
|
|
|
|
// Event::KeyDown { keycode: Some(Keycode::Escape), .. } |
|
|
|
|
|
Event::Quit { .. } => break 'mainloop, |
|
|
|
|
|
|
|
|
|
Event::KeyUp { keycode, .. } => { |
|
|
|
|
@ -123,53 +168,68 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
// DELETE key
|
|
|
|
|
Some(Keycode::Delete) => { |
|
|
|
|
if buffer.len() > 0 && cursor_position < buffer.len() { |
|
|
|
|
undo_timer = 0; |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
buffer.remove(cursor_position); |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// ENTER key
|
|
|
|
|
Some(Keycode::Return) => { |
|
|
|
|
undo_timer = 0; |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
let key = '\n'; |
|
|
|
|
buffer.insert(cursor_position, key); |
|
|
|
|
cursor_position += 1; |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// HOME key
|
|
|
|
|
Some(Keycode::Home) => { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = 0; |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// END key
|
|
|
|
|
Some(Keycode::End) => { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = buffer.len(); |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// Left/Back arrow
|
|
|
|
|
Some(Keycode::Left) => { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = usize::checked_sub(cursor_position, 1) |
|
|
|
|
.unwrap_or(0); |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// Right/Forward arrow
|
|
|
|
|
Some(Keycode::Right) => { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = (cursor_position + 1).min(buffer.len()); |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// BACKSPACE key
|
|
|
|
|
// Character backspace
|
|
|
|
|
Some(Keycode::Backspace) => { |
|
|
|
|
if buffer.len() > 0 { |
|
|
|
|
// Character backspace; regular
|
|
|
|
|
undo_timer = 0; |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
buffer.remove(cursor_position - 1); |
|
|
|
|
cursor_position -= 1; |
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
@ -180,18 +240,42 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
// CTRL down
|
|
|
|
|
(false, true, false) => { |
|
|
|
|
match keycode { |
|
|
|
|
Some(Keycode::Z) => println!("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(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
Some(Keycode::X) => println!("Cut"), |
|
|
|
|
|
|
|
|
|
Some(Keycode::C) => { |
|
|
|
|
clipboard_context.set_contents(buffer.clone()).unwrap() |
|
|
|
|
}, |
|
|
|
|
Some(Keycode::V) => println!("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(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// BACKSPACE key
|
|
|
|
|
// Word backspace
|
|
|
|
|
// TODO: Clean up this cursed expression
|
|
|
|
|
Some(Keycode::Backspace) => { |
|
|
|
|
if buffer.len() > 0 { |
|
|
|
|
undo_timer = 0; |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
let buffer_chars: Vec<char> = buffer.chars() |
|
|
|
|
.collect(); |
|
|
|
|
while !(buffer_chars[cursor_position - 1] == ' ' || |
|
|
|
|
@ -203,7 +287,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
buffer.remove(cursor_position - 1); |
|
|
|
|
cursor_position -= 1; |
|
|
|
|
|
|
|
|
|
draw_text(&buffer, cursor_position)? |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
_ => (), |
|
|
|
|
@ -212,8 +296,19 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
|
|
|
|
|
(true, true, false) => { |
|
|
|
|
match keycode { |
|
|
|
|
Some(Keycode::Z) => println!("Redo"), |
|
|
|
|
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(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
Some(Keycode::X) => println!("Cut line(s)"), |
|
|
|
|
|
|
|
|
|
Some(Keycode::C) => println!("Copy line(s)"), |
|
|
|
|
_ => (), |
|
|
|
|
} |
|
|
|
|
@ -224,16 +319,36 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
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_text(&buffer, cursor_position)?; |
|
|
|
|
draw(window_size, &buffer, cursor_position)?; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
_ => {} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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 { |
|
|
|
|
// println!("Saving undo step.");
|
|
|
|
|
if undo_position < undo_history.len() { |
|
|
|
|
undo_history.truncate(undo_position); |
|
|
|
|
} |
|
|
|
|
undo_history.push((buffer.clone(), cursor_position)); |
|
|
|
|
undo_timer += 1; |
|
|
|
|
undo_position += 1; |
|
|
|
|
|
|
|
|
|
//println!("Undo data: {undo_history:?}");
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
::std::thread::sleep(std::time::Duration::new(0, 1_000_000_000 / REFRESH_RATE)); |
|
|
|
|
} |
|
|
|
|
format!("{selection_anchor:?}"); |
|
|
|
|
println!("{buffer}"); |
|
|
|
|
|