use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::Texture; use sdl2::render::TextureCreator; use sdl2::render::TextureQuery; use sdl2::ttf; use sdl2::ttf::Font; use sdl2::video::WindowContext; fn string_err(e: impl ToString) -> String { e.to_string() } const PADDING: i32 = 32; struct State { pub width: u32, pub height: u32, pub line: usize, pub content: String, } impl Default for State { fn default() -> Self { Self { width: 800, height: 600, line: 0, content: String::new(), } } } enum Action { Input(String), Backspace, Newline, NoOp, } fn main() -> Result<(), String> { let mut state = State::default(); let context = sdl2::init()?; let video = context.video()?; let ttf_context = ttf::init().map_err(string_err)?; let window = video .window("medit", state.width, state.height) .position_centered() .allow_highdpi() .build() .map_err(string_err)?; let mut canvas = window.into_canvas().build().map_err(string_err)?; let texture_creator = canvas.texture_creator(); let font = ttf_context.load_font("resources/fonts/ttf/FiraCode-Retina.ttf", 64)?; canvas.set_draw_color(Color::WHITE); canvas.clear(); canvas.present(); let mut event_pump = context.event_pump()?; 'running: loop { for event in event_pump.poll_iter() { let action = match event { Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, Event::TextEditing { .. } => { todo!("handle TextEdit event") } Event::TextInput { text, .. } => Action::Input(text), Event::KeyDown { keycode: Some(k), .. } if is_control_key(&k) => control_key_action(&k).unwrap(), _ => Action::NoOp, }; match action { Action::Input(text) => state.content += &text, Action::Backspace => { if !state.content.is_empty() { state.content.truncate(state.content.len() - 1) } } Action::Newline => { state.line += 1; state.content += "\n"; } Action::NoOp => {} } } canvas.clear(); if let Some(texture) = string_texture(&font, &state, &texture_creator, &state.content)? { let target = target_rect(PADDING, PADDING, &texture); canvas.copy(&texture, None, Some(target))?; } canvas.present(); std::thread::sleep(std::time::Duration::new(0, 1_000_000_000u32 / 60)); } Ok(()) } fn control_key_action(keycode: &Keycode) -> Option { let action = match keycode { Keycode::Backspace => Action::Backspace, Keycode::Return | Keycode::Return2 => Action::Newline, _ => return None, }; Some(action) } fn is_control_key(keycode: &Keycode) -> bool { control_key_action(keycode).is_some() } fn string_texture<'f, 't>( font: &Font<'f, 'f>, state: &State, texture_creator: &'t TextureCreator, string: impl AsRef, ) -> Result>, String> { if string.as_ref().is_empty() { return Ok(None); } let surface = font .render(string.as_ref()) .blended_wrapped(Color::BLACK, (state.width - PADDING as u32) * 2) .map_err(string_err)?; texture_creator .create_texture_from_surface(surface) .map_err(string_err) .map(Some) } fn target_rect(x: i32, y: i32, texture: &Texture) -> Rect { let TextureQuery { width, height, .. } = texture.query(); Rect::new(x, y, width, height) }