medit/src/main.rs

145 lines
3.9 KiB
Rust

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<Action> {
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<WindowContext>,
string: impl AsRef<str>,
) -> Result<Option<Texture<'t>>, 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)
}