Compare commits

...

6 Commits

  1. 48
      src/input.rs
  2. 166
      src/main.rs
  3. 88
      src/render.rs

@ -0,0 +1,48 @@
use sdl2::keyboard::Keycode;
mod keybinds {
pub(super) enum Clipboard {
CutSelection,
CutLines,
CopySelection,
CopyLines,
Paste,
PasteOverwrite,
}
pub(super) enum Editing {
Undo,
Redo,
NewLines,
DeleteLines,
}
pub(super) enum Movement {
DocBegin,
DocEnd,
LineBack,
LineBegin,
LineForward,
LineEnd,
CharBack,
CharForward,
WordBack,
WordForward,
ParaBack,
ParaForward,
}
pub(super) enum Selection {
CharBack,
Forward,
WordBack,
WordForward,
ParaBack,
ParaForward,
WholeLines,
}
}
fn input(buffer: &str, modifiers: crate::ModifierKeys, key: Keycode) {
}

@ -1,13 +1,13 @@
extern crate sdl2; extern crate sdl2;
use clipboard::{ClipboardProvider, ClipboardContext}; use clipboard::{ClipboardProvider, ClipboardContext};
use num_format::{Locale, ToFormattedString}; use sdl2::{
use sdl2::event::{Event, WindowEvent}; event::{Event, WindowEvent},
use sdl2::keyboard::Keycode; keyboard::Keycode,
use sdl2::pixels::Color; };
use sdl2::rect::{Point, Rect};
mod editor_render; mod render;
mod input;
static SCREEN_WIDTH: u32 = 1280; static SCREEN_WIDTH: u32 = 1280;
static SCREEN_HEIGHT: u32 = 720; static SCREEN_HEIGHT: u32 = 720;
@ -16,7 +16,7 @@ static SCREEN_HEIGHT: u32 = 720;
static REFRESH_RATE: u32 = 50; static REFRESH_RATE: u32 = 50;
// TODO: Make this configurable // TODO: Make this configurable
static UNDO_TIME: u32 = 500; static UNDO_TIME: u32 = 250;
static UNDO_TIME_COUNT: u32 = (REFRESH_RATE as f32 * (UNDO_TIME as f32 / 1000.0)) as u32; static UNDO_TIME_COUNT: u32 = (REFRESH_RATE as f32 * (UNDO_TIME as f32 / 1000.0)) as u32;
struct ModifierKeys { struct ModifierKeys {
@ -25,11 +25,16 @@ struct ModifierKeys {
shift: bool, shift: bool,
} }
pub fn main() -> Result<(), String> { // struct EditorGraphics {
let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false}; // canvas: Canvas<Window>,
// glyph_atlas: GlyphAtlas,
// }
pub fn main() -> Result<(), String> {
// Initialize clipboard
let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap(); let mut clipboard_context: ClipboardContext = ClipboardProvider::new().unwrap();
// Initialize SDL2, window, and canvas
let sdl_context = sdl2::init()?; let sdl_context = sdl2::init()?;
let video_subsys = sdl_context.video()?; let video_subsys = sdl_context.video()?;
@ -43,83 +48,38 @@ pub fn main() -> Result<(), String> {
let mut canvas = window.into_canvas().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()?; // Initalize buffer
let mut buffer = String::new(); 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_history: Vec<(String, usize)> = vec![];
let mut undo_position: usize = 0; let mut undo_position: usize = 0;
let mut undo_timer: u32 = UNDO_TIME_COUNT; let mut undo_timer: u32 = UNDO_TIME_COUNT;
let pad_offset = Point::new(10, 10); // Initialize input values
let mut modifier_keys = ModifierKeys {alt: false, ctrl: false, shift: false};
let (glyph_atlas, glyph_metrics) = editor_render::generate_glyph_data(); let mut cursor_position = 0;
let mut selection_anchor: Option<usize> = None;
let mut draw = |window_size: (u32, u32), text: &str, pos: usize| -> Result<(), String> {
// Draw background // Initialize graphics data and values
canvas.set_draw_color(Color::RGB(32, 32, 32)); let glyph_atlas = render::generate_glyph_data();
canvas.clear();
// Easier way to please the borrow checker
// Draw text macro_rules! draw {
canvas.set_draw_color(Color::RGB(240, 240, 240)); () => {
let fb_text = editor_render::draw_text( render::draw_everything(&mut canvas, &glyph_atlas, &buffer, cursor_position)?
&glyph_atlas, };
&glyph_metrics, }
text,
pad_offset draw!();
);
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 { 'mainloop: loop {
// TODO: Make this completely user-configurable instead of hardcoded // TODO: Make this completely user-configurable instead of hardcoded
for event in sdl_context.event_pump()?.poll_iter() { for event in sdl_context.event_pump()?.poll_iter() {
match event { match event {
Event::Window { win_event, .. } => { Event::Window { win_event, .. } => {
if let WindowEvent::Resized(w, h) = win_event { if let WindowEvent::Resized(_w, _h) = win_event {
window_size = (w as u32, h as u32); draw!();
draw(window_size, &buffer, cursor_position)?
} }
} }
// ESC or SIGKILL // ESC or SIGKILL
@ -172,7 +132,8 @@ pub fn main() -> Result<(), String> {
selection_anchor = None; selection_anchor = None;
buffer.remove(cursor_position); buffer.remove(cursor_position);
draw(window_size, &buffer, cursor_position)?
draw!();
} }
}, },
@ -184,7 +145,8 @@ pub fn main() -> Result<(), String> {
let key = '\n'; let key = '\n';
buffer.insert(cursor_position, key); buffer.insert(cursor_position, key);
cursor_position += 1; cursor_position += 1;
draw(window_size, &buffer, cursor_position)?
draw!();
}, },
// HOME key // HOME key
@ -192,7 +154,8 @@ pub fn main() -> Result<(), String> {
selection_anchor = None; selection_anchor = None;
cursor_position = 0; cursor_position = 0;
draw(window_size, &buffer, cursor_position)?
draw!();
}, },
// END key // END key
@ -200,7 +163,8 @@ pub fn main() -> Result<(), String> {
selection_anchor = None; selection_anchor = None;
cursor_position = buffer.len(); cursor_position = buffer.len();
draw(window_size, &buffer, cursor_position)?
draw!();
}, },
// Left/Back arrow // Left/Back arrow
@ -209,7 +173,8 @@ pub fn main() -> Result<(), String> {
cursor_position = usize::checked_sub(cursor_position, 1) cursor_position = usize::checked_sub(cursor_position, 1)
.unwrap_or(0); .unwrap_or(0);
draw(window_size, &buffer, cursor_position)?
draw!();
}, },
// Right/Forward arrow // Right/Forward arrow
@ -217,7 +182,8 @@ pub fn main() -> Result<(), String> {
selection_anchor = None; selection_anchor = None;
cursor_position = (cursor_position + 1).min(buffer.len()); cursor_position = (cursor_position + 1).min(buffer.len());
draw(window_size, &buffer, cursor_position)?
draw!();
}, },
// BACKSPACE key // BACKSPACE key
@ -229,7 +195,8 @@ pub fn main() -> Result<(), String> {
buffer.remove(cursor_position - 1); buffer.remove(cursor_position - 1);
cursor_position -= 1; cursor_position -= 1;
draw(window_size, &buffer, cursor_position)?
draw!();
} }
}, },
@ -240,6 +207,15 @@ pub fn main() -> Result<(), String> {
// CTRL down // CTRL down
(false, true, false) => { (false, true, false) => {
match keycode { match keycode {
// Select all
Some(Keycode::A) => {
selection_anchor = Some(buffer.len());
cursor_position = 0;
draw!()
},
// Undo
Some(Keycode::Z) => { Some(Keycode::Z) => {
if undo_position > 1 { if undo_position > 1 {
undo_position -= 1; undo_position -= 1;
@ -247,15 +223,20 @@ pub fn main() -> Result<(), String> {
buffer = last_undo.0; buffer = last_undo.0;
cursor_position = last_undo.1; cursor_position = last_undo.1;
draw(window_size, &buffer, cursor_position)? draw!()
} }
}, },
// TODO: Cut
Some(Keycode::X) => println!("Cut"), Some(Keycode::X) => println!("Cut"),
// Copy
// TODO: Use selection
Some(Keycode::C) => { Some(Keycode::C) => {
clipboard_context.set_contents(buffer.clone()).unwrap() clipboard_context.set_contents(buffer.clone()).unwrap()
}, },
// Paste
Some(Keycode::V) => { Some(Keycode::V) => {
let paste = clipboard_context.get_contents().unwrap(); let paste = clipboard_context.get_contents().unwrap();
for character in paste.chars() { for character in paste.chars() {
@ -263,9 +244,8 @@ pub fn main() -> Result<(), String> {
cursor_position += 1; cursor_position += 1;
undo_timer = UNDO_TIME_COUNT; undo_timer = UNDO_TIME_COUNT;
} }
draw(window_size, &buffer, cursor_position)? draw!()
}, },
// BACKSPACE key // BACKSPACE key
@ -287,13 +267,14 @@ pub fn main() -> Result<(), String> {
buffer.remove(cursor_position - 1); buffer.remove(cursor_position - 1);
cursor_position -= 1; cursor_position -= 1;
draw(window_size, &buffer, cursor_position)? draw!()
} }
}, },
_ => (), _ => (),
} }
}, },
// SHIFT + CTRL down
(true, true, false) => { (true, true, false) => {
match keycode { match keycode {
Some(Keycode::Z) => { Some(Keycode::Z) => {
@ -303,7 +284,7 @@ pub fn main() -> Result<(), String> {
buffer = last_redo.0; buffer = last_redo.0;
cursor_position = last_redo.1; cursor_position = last_redo.1;
draw(window_size, &buffer, cursor_position)? draw!();
} }
}, },
@ -318,15 +299,16 @@ pub fn main() -> Result<(), String> {
} }
}, },
// Process user input
Event::TextInput { text, .. } => { Event::TextInput { text, .. } => {
undo_timer = 0; undo_timer = 0;
selection_anchor = None; selection_anchor = None;
let input_char = text.chars().nth(0).expect("Empty"); let input_char = text.chars().nth(0).expect("Empty");
buffer.insert(cursor_position, input_char); buffer.insert(cursor_position, input_char);
cursor_position += 1; cursor_position += 1;
draw(window_size, &buffer, cursor_position)?;
draw!();
}, },
_ => {} _ => {}
@ -337,20 +319,18 @@ pub fn main() -> Result<(), String> {
if undo_timer < UNDO_TIME_COUNT { if undo_timer < UNDO_TIME_COUNT {
undo_timer += 1; undo_timer += 1;
} else if undo_timer == UNDO_TIME_COUNT { } else if undo_timer == UNDO_TIME_COUNT {
// println!("Saving undo step."); // Upon editing after an undo, this clears every action after such undo
if undo_position < undo_history.len() { if undo_position < undo_history.len() {
undo_history.truncate(undo_position); undo_history.truncate(undo_position);
} }
undo_history.push((buffer.clone(), cursor_position)); undo_history.push((buffer.clone(), cursor_position));
undo_timer += 1; undo_timer += 1;
undo_position += 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}"); println!("{buffer}");
Ok(()) Ok(())
} }

@ -3,13 +3,21 @@
//! This takes a janky ass raw image file with width info inserted //! This takes a janky ass raw image file with width info inserted
//! and turns it into a bunch of points for SDL2 to read. //! and turns it into a bunch of points for SDL2 to read.
use num_format::{Locale, ToFormattedString};
use sdl2::{
pixels::Color,
rect::{Point, Rect},
render::Canvas,
video::Window,
};
use std::{fs::File, path::Path, io::Read}; use std::{fs::File, path::Path, io::Read};
use sdl2::rect::Point; type Glyph = Vec<Point>;
pub struct GlyphMetrics { struct GlyphMetrics {
pub width: usize, width: usize,
pub height: usize, height: usize,
} }
impl GlyphMetrics { impl GlyphMetrics {
@ -18,7 +26,10 @@ impl GlyphMetrics {
} }
} }
type Glyph = Vec<Point>; pub struct GlyphAtlas {
glyphs: Vec<Glyph>,
metrics: GlyphMetrics,
}
/// Reads the file and turns it into a Vec of u8s /// Reads the file and turns it into a Vec of u8s
fn read_file(file_name: String) -> Vec<u8> { fn read_file(file_name: String) -> Vec<u8> {
@ -35,7 +46,7 @@ fn read_file(file_name: String) -> Vec<u8> {
file_content file_content
} }
pub fn generate_glyph_data() -> (Vec<Glyph>, GlyphMetrics) { pub fn generate_glyph_data() -> GlyphAtlas {
// Retrieve font data from file // Retrieve font data from file
let file_path = String::from("./fonts/Terminus14x8.data"); let file_path = String::from("./fonts/Terminus14x8.data");
let contents = read_file(file_path); let contents = read_file(file_path);
@ -49,7 +60,7 @@ pub fn generate_glyph_data() -> (Vec<Glyph>, GlyphMetrics) {
let width_right_byte = contents[1]; let width_right_byte = contents[1];
let width_bytes = [width_left_byte, width_right_byte]; let width_bytes = [width_left_byte, width_right_byte];
let width = u16::from_be_bytes(width_bytes); let width = u16::from_be_bytes(width_bytes);
println!("Left Byte: {width_left_byte}, Right Byte: {width_right_byte}, Byte Pair: {width}"); // println!("Left Byte: {width_left_byte}, Right Byte: {width_right_byte}, Byte Pair: {width}");
let gtable_prune = &contents[width as usize + 2 ..]; let gtable_prune = &contents[width as usize + 2 ..];
@ -73,15 +84,15 @@ pub fn generate_glyph_data() -> (Vec<Glyph>, GlyphMetrics) {
} }
glyph_atlas.push(new_glyph); glyph_atlas.push(new_glyph);
} }
(glyph_atlas, glyph_metrics) GlyphAtlas { glyphs: glyph_atlas, metrics: glyph_metrics }
} }
/// Method for generating points to render, using given string /// Method for generating points to render, using given string
pub fn draw_text(glyph_atlas: &Vec<Glyph>, glyph_metrics: &GlyphMetrics, content: &str, offset: Point) -> Vec<Point> { fn draw_text(glyph_atlas: &GlyphAtlas, content: &str, offset: Point) -> Vec<Point> {
let mut points: Vec<Point> = vec![]; let mut points: Vec<Point> = vec![];
let glyph_width = glyph_metrics.width; let glyph_width = glyph_atlas.metrics.width;
let glyph_height = glyph_metrics.height; let glyph_height = glyph_atlas.metrics.height;
let lines = content.split('\n'); let lines = content.split('\n');
for (y, chars) in lines.enumerate() { for (y, chars) in lines.enumerate() {
@ -93,7 +104,7 @@ pub fn draw_text(glyph_atlas: &Vec<Glyph>, glyph_metrics: &GlyphMetrics, content
index = chara.to_ascii_uppercase() as usize; index = chara.to_ascii_uppercase() as usize;
} }
for pixel in &glyph_atlas[index - 32] { for pixel in &glyph_atlas.glyphs[index - 32] {
let x_glyph = x * glyph_width; let x_glyph = x * glyph_width;
let y_glyph = y * glyph_height; let y_glyph = y * glyph_height;
@ -108,9 +119,9 @@ pub fn draw_text(glyph_atlas: &Vec<Glyph>, glyph_metrics: &GlyphMetrics, content
points points
} }
pub fn draw_cursor(glyph_metrics: &GlyphMetrics, mut cursor_position: usize, content: &str, offset: Point) -> (Point, Point) { pub fn draw_cursor(glyph_atlas: &GlyphAtlas, mut cursor_position: usize, content: &str, offset: Point) -> (Point, Point) {
let glyph_width = glyph_metrics.width; let glyph_width = glyph_atlas.metrics.width;
let glyph_height = glyph_metrics.height; let glyph_height = glyph_atlas.metrics.height;
let mut x = 0; let mut x = 0;
let mut y = 0; let mut y = 0;
@ -118,7 +129,6 @@ pub fn draw_cursor(glyph_metrics: &GlyphMetrics, mut cursor_position: usize, con
cursor_position = cursor_position.checked_sub(1).unwrap_or(0); cursor_position = cursor_position.checked_sub(1).unwrap_or(0);
for (idx, chara) in content.chars().enumerate() { for (idx, chara) in content.chars().enumerate() {
x += 1; x += 1;
if chara == '\n' { if chara == '\n' {
x = 0; x = 0;
y += 1; y += 1;
@ -136,3 +146,49 @@ pub fn draw_cursor(glyph_metrics: &GlyphMetrics, mut cursor_position: usize, con
} }
(Point::new(offset.x, offset.y), Point::new(offset.x, offset.y + glyph_height as i32)) (Point::new(offset.x, offset.y), Point::new(offset.x, offset.y + glyph_height as i32))
} }
/// Draw all contents to the window
pub fn draw_everything(canvas: &mut Canvas<Window>,
glyph_atlas: &GlyphAtlas,
buffer: &str,
cursor_position: usize) -> Result<(), String> {
// Quick initialization
let window_size = canvas.output_size()?;
let text_offset = Point::new(10, 10);
// 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 = draw_text(&glyph_atlas, buffer, text_offset);
canvas.draw_points(&fb_text[..])?;
// Draw info
let status = buffer.len().to_formatted_string(&Locale::en);
let status_position = Point::new(
text_offset.x,
window_size.1 as i32 - glyph_atlas.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_atlas.metrics.height as u32 + 10
))?;
canvas.set_draw_color(Color::RGB(127, 240, 240));
let status_bar = draw_text(&glyph_atlas, &status, status_position);
canvas.draw_points(&status_bar[..])?;
// Draw cursor
canvas.set_draw_color(Color::RGB(64, 240, 240));
let fb_cursor = draw_cursor(&glyph_atlas, cursor_position, buffer, text_offset);
canvas.draw_line(fb_cursor.0, fb_cursor.1)?;
canvas.present();
Ok(())
}
Loading…
Cancel
Save