|
|
|
|
@ -1,13 +1,13 @@ |
|
|
|
|
extern crate sdl2; |
|
|
|
|
|
|
|
|
|
use clipboard::{ClipboardProvider, ClipboardContext}; |
|
|
|
|
use sdl2::{ |
|
|
|
|
event::{Event, WindowEvent}, |
|
|
|
|
keyboard::Keycode, |
|
|
|
|
}; |
|
|
|
|
use num_format::{Locale, ToFormattedString}; |
|
|
|
|
use sdl2::event::{Event, WindowEvent}; |
|
|
|
|
use sdl2::keyboard::Keycode; |
|
|
|
|
use sdl2::pixels::Color; |
|
|
|
|
use sdl2::rect::{Point, Rect}; |
|
|
|
|
|
|
|
|
|
mod render; |
|
|
|
|
mod input; |
|
|
|
|
mod editor_render; |
|
|
|
|
|
|
|
|
|
static SCREEN_WIDTH: u32 = 1280; |
|
|
|
|
static SCREEN_HEIGHT: u32 = 720; |
|
|
|
|
@ -16,7 +16,7 @@ static SCREEN_HEIGHT: u32 = 720; |
|
|
|
|
static REFRESH_RATE: u32 = 50; |
|
|
|
|
|
|
|
|
|
// TODO: Make this configurable
|
|
|
|
|
static UNDO_TIME: u32 = 250; |
|
|
|
|
static UNDO_TIME: u32 = 500; |
|
|
|
|
static UNDO_TIME_COUNT: u32 = (REFRESH_RATE as f32 * (UNDO_TIME as f32 / 1000.0)) as u32; |
|
|
|
|
|
|
|
|
|
struct ModifierKeys { |
|
|
|
|
@ -25,16 +25,11 @@ struct ModifierKeys { |
|
|
|
|
shift: bool, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// struct EditorGraphics {
|
|
|
|
|
// canvas: Canvas<Window>,
|
|
|
|
|
// glyph_atlas: GlyphAtlas,
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
pub fn main() -> Result<(), String> { |
|
|
|
|
// Initialize clipboard
|
|
|
|
|
let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false}; |
|
|
|
|
|
|
|
|
|
let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap(); |
|
|
|
|
|
|
|
|
|
// Initialize SDL2, window, and canvas
|
|
|
|
|
let sdl_context = sdl2::init()?; |
|
|
|
|
let video_subsys = sdl_context.video()?; |
|
|
|
|
|
|
|
|
|
@ -48,38 +43,83 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
|
|
|
|
|
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; |
|
|
|
|
|
|
|
|
|
// Initalize buffer
|
|
|
|
|
let mut window_size = canvas.output_size()?; |
|
|
|
|
|
|
|
|
|
let mut buffer = String::new(); |
|
|
|
|
let mut cursor_position = 0; |
|
|
|
|
let mut selection_anchor: Option<usize> = None; |
|
|
|
|
|
|
|
|
|
// 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 = ModifierKeys {alt: false, ctrl: false, shift: false}; |
|
|
|
|
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!(); |
|
|
|
|
let pad_offset = Point::new(10, 10); |
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
|
// Draw text
|
|
|
|
|
canvas.set_draw_color(Color::RGB(240, 240, 240)); |
|
|
|
|
let fb_text = editor_render::draw_text( |
|
|
|
|
&glyph_atlas, |
|
|
|
|
&glyph_metrics, |
|
|
|
|
text, |
|
|
|
|
pad_offset |
|
|
|
|
); |
|
|
|
|
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( |
|
|
|
|
&glyph_metrics, |
|
|
|
|
pos, |
|
|
|
|
text, |
|
|
|
|
pad_offset |
|
|
|
|
); |
|
|
|
|
canvas.draw_line(fb_cursor.0, fb_cursor.1)?; |
|
|
|
|
|
|
|
|
|
canvas.present(); |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
draw!(); |
|
|
|
|
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
|
|
|
|
|
@ -132,8 +172,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
buffer.remove(cursor_position); |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
@ -145,8 +184,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
let key = '\n'; |
|
|
|
|
buffer.insert(cursor_position, key); |
|
|
|
|
cursor_position += 1; |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// HOME key
|
|
|
|
|
@ -154,8 +192,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = 0; |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// END key
|
|
|
|
|
@ -163,8 +200,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = buffer.len(); |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// Left/Back arrow
|
|
|
|
|
@ -173,8 +209,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
|
|
|
|
|
cursor_position = usize::checked_sub(cursor_position, 1) |
|
|
|
|
.unwrap_or(0); |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// Right/Forward arrow
|
|
|
|
|
@ -182,8 +217,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
selection_anchor = None; |
|
|
|
|
|
|
|
|
|
cursor_position = (cursor_position + 1).min(buffer.len()); |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// BACKSPACE key
|
|
|
|
|
@ -195,8 +229,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
|
|
|
|
|
buffer.remove(cursor_position - 1); |
|
|
|
|
cursor_position -= 1; |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
@ -207,15 +240,6 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
// 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; |
|
|
|
|
@ -223,20 +247,15 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
buffer = last_undo.0; |
|
|
|
|
cursor_position = last_undo.1; |
|
|
|
|
|
|
|
|
|
draw!() |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 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() { |
|
|
|
|
@ -244,8 +263,9 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
cursor_position += 1; |
|
|
|
|
|
|
|
|
|
undo_timer = UNDO_TIME_COUNT; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
draw!() |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// BACKSPACE key
|
|
|
|
|
@ -267,14 +287,13 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
buffer.remove(cursor_position - 1); |
|
|
|
|
cursor_position -= 1; |
|
|
|
|
|
|
|
|
|
draw!() |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
_ => (), |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// SHIFT + CTRL down
|
|
|
|
|
(true, true, false) => { |
|
|
|
|
match keycode { |
|
|
|
|
Some(Keycode::Z) => { |
|
|
|
|
@ -284,7 +303,7 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
buffer = last_redo.0; |
|
|
|
|
cursor_position = last_redo.1; |
|
|
|
|
|
|
|
|
|
draw!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)? |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
@ -299,16 +318,15 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 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!(); |
|
|
|
|
draw(window_size, &buffer, cursor_position)?; |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
_ => {} |
|
|
|
|
@ -319,18 +337,20 @@ pub fn main() -> Result<(), String> { |
|
|
|
|
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
|
|
|
|
|
// 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)); |
|
|
|
|
::std::thread::sleep(std::time::Duration::new(0, 1_000_000_000 / REFRESH_RATE)); |
|
|
|
|
} |
|
|
|
|
format!("{selection_anchor:?}"); |
|
|
|
|
println!("{buffer}"); |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|