Compare commits

..

No commits in common. '749eb316160ecc41aa5101376a074592d1c1a870' and '7a47787747e83a66d1ac85e8ffccdd8202342afd' have entirely different histories.

  1. 1
      .gitignore
  2. 11
      Cargo.toml
  3. 49
      TODO.org
  4. BIN
      fonts/Monoid-Bold.ttf
  5. BIN
      fonts/Monoid-Regular.ttf
  6. BIN
      fonts/Terminus12x6.xcf
  7. BIN
      fonts/Terminus14x8.data
  8. BIN
      fonts/Terminus14x8.data.pal
  9. BIN
      fonts/Terminus14x8.xcf
  10. 120
      src/editor_render.rs
  11. 69
      src/font_build.rs
  12. 326
      src/main.rs

1
.gitignore vendored

@ -1 +0,0 @@
/target

@ -1,11 +0,0 @@
[package]
name = "rude"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
opt-level = 3
[dependencies]
sdl2 = { version = "0.35.2" }

@ -1,49 +0,0 @@
#+title: Todo List
* Movement [0/3]
- [-] Arrow keys [1/4]
* [X] L/R (char/char)
* [ ] L/R (word/word)
* [ ] L/R (sentence/sentence)
* [ ] Up and down (line by line)
- [-] HOME and END keys [1/2]
* [X] Start to end of document
* [ ] Confine to line unless CTRL is pressed
- [ ] Mouse input
* Editing [0/5]
- [ ] Type text
- [-] Remove text [3/6]
* [X] Character backspace
* [X] Character delete
* [X] Word backspace
* [ ] Word delete
* [ ] Sentence backspace
* [ ] Sentence delete
- [ ] Select text
- [ ] Cut, Copy, & Paste
- [ ] Undo / Redo
* Rendering [1/6]
- [-] Text [1/3]
* [X] Redneck bitmap
* [ ] Refined bitmap (.otb format)
* [ ] Vector (.ttf, .otf, etc)
- [X] Cursor
- [ ] Selection
- [ ] Scaling/Zoom
- [ ] Scrolling
- [ ] Syntax Highlighting [/]
- [ ] Built-in basic Rust highlighting
* Misc [0/3]
- [ ] Search [0/1]
- [ ] Regex support
- [ ] Replace [0/2]
- [ ] Regex support
- [ ] Sed style
- [ ] File handling [0/3]
- [ ] Open files
- [ ] Save files
- [ ] Save files as
- [ ] Folder handling (for projects)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,120 +0,0 @@
//! A redneck bitmap font renderer
//!
//! This takes a janky ass raw image file with width info inserted
//! and turns it into a bunch of points for SDL2 to read.
use std::{fs::File, path::Path, io::Read};
use sdl2::rect::Point;
const GLYPH_WIDTH: usize = 8;
const GLYPH_HEIGHT: usize = 14;
const GLYPH_AREA: usize = GLYPH_WIDTH * GLYPH_HEIGHT;
type Glyph = Vec<Point>;
/// Reads the file and turns it into a Vec of u8s
fn read_file(file_name: String) -> Vec<u8> {
let path = Path::new(&file_name);
if !path.exists() {
return String::from("Not Found!").into();
}
let mut file_content = Vec::new();
let mut file = File::open(&file_name).expect("Unable to open file");
file.read_to_end(&mut file_content).expect("Unable to read");
file_content
}
pub fn generate_glyph_atlas() -> Vec<Glyph> {
// Retrieve font data from file
let file_path = String::from("./fonts/Terminus14x8.data");
let contents = read_file(file_path);
// Get width of image for proper positioning of pixels
let width_left_byte = contents[0];
let width_right_byte = contents[1];
let number = [width_left_byte, width_right_byte];
let width = u16::from_be_bytes(number);
println!("Left Byte: {width_left_byte}, Right Byte: {width_right_byte}, Byte Pair: {width}");
let gtable_prune = &contents[width as usize + 2 ..];
// Generate the glyph atlas
let mut glyph_atlas: Vec<Glyph> = vec![];
for glyph in 0..96 {
let mut new_glyph: Glyph = vec![];
for p in 0..GLYPH_AREA as u16 {
let x = p % GLYPH_WIDTH as u16;
let y = p / GLYPH_WIDTH as u16;
let multiplier = y * width;
let offset = glyph * GLYPH_WIDTH as u16;
let position = (x as u16 + multiplier + offset) as usize;
if gtable_prune[position] == 1 {
new_glyph.push(Point::new(x as i32, y as i32));
}
}
glyph_atlas.push(new_glyph);
}
glyph_atlas
}
/// Method for generating points to render, using given string
pub fn draw_text(content: &str, glyph_atlas: Vec<Glyph>) -> Vec<Point> {
let mut points: Vec<Point> = vec![];
let lines = content.split('\n');
for (y, chars) in lines.enumerate() {
for (x, chara) in chars.chars().enumerate() {
let index;
if chara.is_lowercase() {
index = chara.to_ascii_lowercase() as usize;
} else {
index = chara.to_ascii_uppercase() as usize;
}
for pixel in &glyph_atlas[index - 32] {
let x_offset = x * GLYPH_WIDTH;
let y_offset = y * GLYPH_HEIGHT;
let positioned_pixel = Point::new(
pixel.x + x_offset as i32,
pixel.y + y_offset as i32,
);
points.push(positioned_pixel);
}
}
}
points
}
pub fn draw_cursor(content: &str, mut cursor_position: usize) -> (Point, Point) {
let mut x = 0;
let mut y = 0;
if cursor_position > 0 {
cursor_position = cursor_position.checked_sub(1).unwrap_or(0);
for (idx, chara) in content.chars().enumerate() {
x += 1;
if chara == '\n' {
x = 0;
y += 1;
}
if idx == cursor_position {
let point_a = Point::new((x * GLYPH_WIDTH) as i32,
(y * GLYPH_HEIGHT) as i32
);
let point_b = Point::new(point_a.x,
point_a.y + GLYPH_HEIGHT as i32
);
return (point_a, point_b)
}
}
}
(Point::new(0, 0), Point::new(0, GLYPH_HEIGHT as i32))
}

@ -0,0 +1,69 @@
use std::{fs::File, path::Path, io::Read};
const GLYPH_WIDTH: usize = 8;
const GLYPH_HEIGHT: usize = 14;
const GLYPH_AREA: usize = GLYPH_WIDTH * GLYPH_HEIGHT;
#[allow(dead_code)]
type Glyph = [[bool; GLYPH_WIDTH]; GLYPH_HEIGHT];
/// Reads the file and turns it into a Vec of u8s
fn read_file(file_name: String) -> Vec<u8> {
let path = Path::new(&file_name);
if !path.exists() {
return String::from("Not Found!").into();
}
let mut file_content = Vec::new();
let mut file = File::open(&file_name).expect("Unable to open file");
file.read_to_end(&mut file_content).expect("Unable to read");
file_content
}
pub fn get_font() {
// Retrieve font data from file
let file_path = String::from("./fonts/Terminus14x8.data");
println!("In file {file_path}");
let contents = read_file(file_path);
let width_left_byte = contents[0];
let width_right_byte = contents[1];
let number = [width_left_byte, width_right_byte];
let width = u16::from_be_bytes(number);
println!("Left Byte: {width_left_byte}, Right Byte: {width_right_byte}, Byte Pair {width}");
/*
let gtable_prune = contents[2..].iter()
.filter(|x| **x % 3 == 0)
.collect::<Vec<_>>();
*/
// Remove useless Green and Blue data
let gtable_prune: Vec<u8> = contents[2..].iter()
.enumerate()
.filter(|x| x.0 % 3 == 0)
.map(|(_, x)| *x)
.collect();
println!("Pruned Glyph Table is {} long; it should be {}.",
gtable_prune.len(),
(contents.len() - 2) / 3
);
let glyph_atlas: Vec<Glyph>;
for glyph in 0..95 {
let new_glyph: Glyph = [[false; GLYPH_WIDTH]; GLYPH_HEIGHT];
for p in 0..GLYPH_AREA as u16 {
let multiplier = p / GLYPH_WIDTH as u16 * width;
let offset = glyph * GLYPH_WIDTH as u16;
let position = (p % GLYPH_WIDTH as u16 + multiplier + offset) as usize;
println!("Glyph num: {glyph:2.} | Addr: {p:3.} | Line: {multiplier:5.} | Pos: {position:5.}");
if gtable_prune[position] == 255 {
}
}
}
}

@ -1,10 +1,19 @@
extern crate sdl2;
use std::path::Path;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::TextureQuery;
//mod _catlas;
//use _catlas::*;
mod editor_render;
mod font_build;
#[allow(unused_imports)]
use font_build::*;
static SCREEN_WIDTH: u32 = 1680;
static SCREEN_HEIGHT: u32 = 945;
@ -15,205 +24,214 @@ struct ModifierKeys {
shift: bool,
}
// handle the annoying Rect i32
macro_rules! rect(
($x:expr, $y:expr, $w:expr, $h:expr) => (
Rect::new($x as i32, $y as i32, $w as u32, $h as u32)
)
);
// Scale fonts to a reasonable size when they're too big (though they might look less smooth)
/*
fn get_centered_rect(rect_width: u32, rect_height: u32, cons_width: u32, cons_height: u32) -> Rect {
let wr = rect_width as f32 / cons_width as f32;
let hr = rect_height as f32 / cons_height as f32;
let (w, h) = if wr > 1f32 || hr > 1f32 {
if wr > hr {
println!("Scaling down! The text will look worse!");
let h = (rect_height as f32 / wr) as i32;
(cons_width as i32, h)
} else {
println!("Scaling down! The text will look worse!");
let w = (rect_width as f32 / hr) as i32;
(w, cons_height as i32)
}
} else {
(rect_width as i32, rect_height as i32)
};
let cx = (SCREEN_WIDTH as i32 - w) / 2;
let cy = (SCREEN_HEIGHT as i32 - h) / 2;
rect!(cx, cy, w, h)
}
*/
fn get_corner_rect(rect_width: u32, rect_height: u32) -> Rect {
let (w,h) = (rect_width as i32, rect_height as i32);
let cx = 15;
let cy = 15;
rect!(cx, cy, w, h)
}
pub fn main() -> Result<(), String> {
let glyph_atlas = editor_render::generate_glyph_atlas();
font_build::get_font();
let font_path: &Path = Path::new("./fonts/Monoid-Regular.ttf");
let sdl_context = sdl2::init()?;
let video_subsys = sdl_context.video()?;
let ttf_context = sdl2::ttf::init().map_err(|e| e.to_string())?;
let window = video_subsys
.window("Rude", SCREEN_WIDTH, SCREEN_HEIGHT)
.window("SDL2_TTF Example", SCREEN_WIDTH, SCREEN_HEIGHT)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
let texture_creator = canvas.texture_creator();
// Load a font
let mut font = ttf_context.load_font(font_path, 12)?;
font.set_style(sdl2::ttf::FontStyle::BOLD);
let mut buffer = String::new();
let mut cursor_position = 0;
let mut selection_anchor: Option<usize> = None;
let mut draw_text = |text: &str, pos: usize| -> Result<(), String> {
// Draw background
let mut draw_text = |text: &str| -> Result<(), String> {
// render a surface, and convert it to a texture bound to the canvas
let surface = font
.render(text)
.blended(Color::RGBA(255, 255, 255, 255))
.map_err(|e| e.to_string())?;
let texture = texture_creator
.create_texture_from_surface(&surface)
.map_err(|e| e.to_string())?;
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(text, glyph_atlas.clone());
canvas.draw_points(&fb_text[..])?;
let TextureQuery { width, height, .. } = texture.query();
// Draw cursor
canvas.set_draw_color(Color::RGB(64, 240, 240));
let fb_cursor = editor_render::draw_cursor(text, pos);
canvas.draw_line(fb_cursor.0, fb_cursor.1)?;
// If the example text is too big for the screen, downscale it (and center regardless)
// let padding = 64;
let target = get_corner_rect(
width,
height,
);
canvas.copy(&texture, None, Some(target))?;
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 {
// 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
},
_ => (),
}
// ALT down
Event::KeyDown { keycode: Some(Keycode::LAlt), .. } |
Event::KeyDown { keycode: Some(Keycode::RAlt), .. } => {
modifier_keys.alt = true
},
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) {
(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) => {
cursor_position = usize::checked_sub(cursor_position, 1)
.unwrap_or(0);
draw_text(&buffer, cursor_position)?
},
// Right/Forward arrow
Some(Keycode::Right) => {
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)?
}
},
_ => (),
}
},
(false, true, false) => {
match keycode {
Some(Keycode::Z) => println!("Undo"),
Some(Keycode::X) => println!("Cut"),
Some(Keycode::C) => println!("Copy"),
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<char> = 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)"),
_ => (),
}
},
_ => (),
}
// ALT up
Event::KeyUp { keycode: Some(Keycode::LAlt), .. } |
Event::KeyUp { keycode: Some(Keycode::RAlt), .. } => {
modifier_keys.alt = false
},
// CTRL down
Event::KeyDown { keycode: Some(Keycode::LCtrl), .. } |
Event::KeyDown { keycode: Some(Keycode::RCtrl), .. } => {
modifier_keys.ctrl = true
},
// CTRL up
Event::KeyUp { keycode: Some(Keycode::LCtrl), .. } |
Event::KeyUp { keycode: Some(Keycode::RCtrl), .. } => {
modifier_keys.ctrl = false
},
// SHIFT down
Event::KeyDown { keycode: Some(Keycode::LShift), .. } |
Event::KeyDown { keycode: Some(Keycode::RShift), .. } => {
modifier_keys.shift = true
},
// SHIFT up
Event::KeyUp { keycode: Some(Keycode::LShift), .. } |
Event::KeyUp { keycode: Some(Keycode::RShift), .. } => {
modifier_keys.shift = false
},
// Ignore SUPER
Event::KeyDown { keycode: Some(Keycode::LGui), .. } |
Event::KeyDown { keycode: Some(Keycode::RGui), .. } => (),
// Type spacebar
Event::KeyDown { keycode: Some(Keycode::Space), .. } => {
buffer.insert(cursor_position, ' ');
cursor_position += 1;
draw_text(&buffer)?
},
// Backspace
Event::KeyDown { keycode: Some(Keycode::Backspace), .. } => {
buffer.remove(cursor_position - 1);
cursor_position -= 1;
draw_text(&buffer)?
},
// Enter
Event::KeyDown { keycode: Some(Keycode::Return), .. } => {
let key = '\n';
buffer.insert(cursor_position, key);
cursor_position += 1;
draw_text(&buffer)?
},
// Left/Back arrow
Event::KeyDown { keycode: Some(Keycode::Left), .. } => {
cursor_position = usize::checked_sub(cursor_position, 1)
.unwrap_or(0)
},
// Home key
Event::KeyDown { keycode: Some(Keycode::Home), .. } => {
cursor_position = 0
},
Event::TextInput { text, .. } => {
let input_char = text.chars().nth(0).expect("Empty");
// End key
Event::KeyDown { keycode: Some(Keycode::End), .. } => {
cursor_position = buffer.len()
},
// Right/Forward arrow
Event::KeyDown { keycode: Some(Keycode::Right), .. } => {
cursor_position = (cursor_position + 1).min(buffer.len())
},
buffer.insert(cursor_position, input_char);
Event::KeyDown { keycode, .. } => {
let key = keycode.unwrap().to_string();
// Ignore multi-char keycodes
if key.len() > 1 { break }
let key_case = match modifier_keys.shift {
true => key.to_uppercase(),
false => key.to_lowercase()
}.chars().nth(0).expect("Empty");
buffer.insert(cursor_position, key_case);
cursor_position += 1;
draw_text(&buffer, cursor_position)?;
draw_text(&buffer)?;
println!("{key}");
},
_ => {}
}
}
}
println!("{buffer}");
Ok(())
}

Loading…
Cancel
Save