extern crate sdl2; use clipboard::{ClipboardProvider, ClipboardContext}; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::rect::Point; mod editor_render; static SCREEN_WIDTH: u32 = 1680; static SCREEN_HEIGHT: u32 = 945; struct ModifierKeys { alt: bool, ctrl: bool, shift: bool, } pub fn main() -> Result<(), String> { 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()?; 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 buffer = String::new(); let mut cursor_position = 0; let mut selection_anchor: Option = None; let pad_offset = Point::new(10, 10); let mut draw_text = |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 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_text("", cursor_position)?; let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false}; '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_text(&buffer, cursor_position)? } } // 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 }, _ => (), }; match (modifier_keys.shift, modifier_keys.ctrl, modifier_keys.alt) { // All modifiers up (false, false, false) => { match keycode { // DELETE key Some(Keycode::Delete) => { if buffer.len() > 0 && cursor_position < buffer.len() { buffer.remove(cursor_position); draw_text(&buffer, cursor_position)? } }, // ENTER key Some(Keycode::Return) => { let key = '\n'; buffer.insert(cursor_position, key); cursor_position += 1; draw_text(&buffer, cursor_position)? }, // HOME key Some(Keycode::Home) => { cursor_position = 0; draw_text(&buffer, cursor_position)? }, // END key Some(Keycode::End) => { cursor_position = buffer.len(); draw_text(&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)? }, // Right/Forward arrow Some(Keycode::Right) => { selection_anchor = None; cursor_position = (cursor_position + 1).min(buffer.len()); draw_text(&buffer, cursor_position)? }, // BACKSPACE key Some(Keycode::Backspace) => { if buffer.len() > 0 { // Character backspace; regular buffer.remove(cursor_position - 1); cursor_position -= 1; draw_text(&buffer, cursor_position)? } }, _ => (), } }, // CTRL down (false, true, false) => { match keycode { Some(Keycode::Z) => println!("Undo"), Some(Keycode::X) => println!("Cut"), Some(Keycode::C) => { clipboard_context.set_contents(buffer.clone()).unwrap() }, Some(Keycode::V) => println!("Paste"), // BACKSPACE key // Word backspace // TODO: Clean up this cursed expression Some(Keycode::Backspace) => { if buffer.len() > 0 { let buffer_chars: Vec = buffer.chars() .collect(); while !(buffer_chars[cursor_position - 1] == ' ' || buffer_chars[cursor_position - 1] == '\n') && cursor_position > 1 { buffer.remove(cursor_position - 1); cursor_position -= 1; } buffer.remove(cursor_position - 1); cursor_position -= 1; draw_text(&buffer, cursor_position)? } }, _ => (), } }, (true, true, false) => { match keycode { Some(Keycode::Z) => println!("Redo"), Some(Keycode::X) => println!("Cut line(s)"), Some(Keycode::C) => println!("Copy line(s)"), _ => (), } }, _ => (), } }, Event::TextInput { text, .. } => { let input_char = text.chars().nth(0).expect("Empty"); buffer.insert(cursor_position, input_char); cursor_position += 1; draw_text(&buffer, cursor_position)?; }, _ => {} } } ::std::thread::sleep(std::time::Duration::new(0, 50_000_000)); } format!("{selection_anchor:?}"); println!("{buffer}"); Ok(()) }