commit 399b0b7144949a982df83e163c8b53c034a856ea Author: Adrian Wozniak Date: Tue Jan 1 11:43:10 2019 +0100 x diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bb8d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +.idea diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..434b976 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,288 @@ +[[package]] +name = "bit-set" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "c_vec" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "editor" +version = "0.1.0" +dependencies = [ + "plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lalr" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "plex" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lalr 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "redfa 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redfa" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "c_vec 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2-sys" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" +"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum c_vec 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6c32b15e95ce816aaf991a41420854e6ba772a2679a9296d906eab1114f1b4e9" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum lalr 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "106d7548f95adbe3019b4fc4954554d7b72535867aa9ce326d2f766b68958de7" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "59d79bd74b3d0be2619e58217e8b2b96372e3feca8426e5c560623205d70c146" +"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" +"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" +"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" +"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" +"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" +"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum redfa 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "29cc2771cc9f5fb0061cdedc05a37170254694dffec6b89920a6e767f08c4220" +"checksum sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74c2a98a354b20713b90cce70aef9e927e46110d1bc4ef728fd74e0d53eba60" +"checksum sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c543ce8a6e33a30cb909612eeeb22e693848211a84558d5a00bb11e791b7ab7" +"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..737d566 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "editor" +version = "0.1.0" +authors = ["Adrian Wozniak "] +edition = "2018" + +[dependencies] +rand = "0.5" +plex = "*" + +[dependencies.sdl2] +version = "0.31.0" +features = ["gfx", "image", "mixer", "ttf"] diff --git a/assets/fonts/Beyond Wonderland.ttf b/assets/fonts/Beyond Wonderland.ttf new file mode 100644 index 0000000..8cf093b Binary files /dev/null and b/assets/fonts/Beyond Wonderland.ttf differ diff --git a/assets/fonts/hinted-ElaineSans-Medium.ttf b/assets/fonts/hinted-ElaineSans-Medium.ttf new file mode 100644 index 0000000..dfc92ce Binary files /dev/null and b/assets/fonts/hinted-ElaineSans-Medium.ttf differ diff --git a/src/app/app_state.rs b/src/app/app_state.rs new file mode 100644 index 0000000..fe2d5fe --- /dev/null +++ b/src/app/app_state.rs @@ -0,0 +1,44 @@ +use std::rc::Rc; +use std::sync::Arc; +use std::boxed::Box; +use crate::app::{UpdateResult, WindowCanvas}; +use crate::file::*; +use crate::renderer::Renderer; +use crate::file::editor_file::EditorFile; + +pub struct AppState<'a> { + pub files: Vec>, + pub current_file: i16, +} + +impl<'a> AppState<'a> { + pub fn new() -> Self { + Self { + files: vec![], + current_file: -1, + } + } + + pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) { + use std::fs::read_to_string; + if let Ok(buffer) = read_to_string(&file_path) { + println!("read: {}\n{}", file_path, buffer); + let file = EditorFile::new(file_path.clone(), buffer, renderer); + self.current_file = self.files.len() as i16; + self.files.push(file); + }; + } + + pub fn update(&mut self, ticks: i32) -> UpdateResult { + if let Some(ref mut file) = self.files.get(self.current_file as usize) { + file.update(ticks); + } + UpdateResult::NoOp + } + + pub fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) { + if let Some(ref mut file) = self.files.get(self.current_file as usize) { + file.render(canvas, renderer); + } + } +} diff --git a/src/app/config.rs b/src/app/config.rs new file mode 100644 index 0000000..113e6fa --- /dev/null +++ b/src/app/config.rs @@ -0,0 +1,25 @@ +#[derive(Debug, Clone)] +pub struct EditorConfig { + pub character_size: u16, + pub font_path: String, +} + +#[derive(Debug, Clone)] +pub struct Config { + pub width: u32, + pub height: u32, + pub editor_config: EditorConfig, +} + +impl Config { + pub fn new() -> Self { + Self { + width: 1024, + height: 860, + editor_config: EditorConfig { + character_size: 24, + font_path: "./assets/fonts/hinted-ElaineSans-Medium.ttf".to_string(), + }, + } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..6ed15c1 --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,137 @@ +pub mod app_state; +pub mod config; + +use sdl2::event::Event; +use sdl2::hint; +use sdl2::pixels::Color; +use sdl2::render::Canvas; +use sdl2::video::Window; +use sdl2::EventPump; +use sdl2::{Sdl, TimerSubsystem}; + +use std::thread::sleep; +use std::time::Duration; + +pub type WindowCanvas = Canvas; + +use crate::app::app_state::AppState; +use crate::app::config::Config; +use crate::renderer::Renderer; + +#[derive(PartialEq, Clone, Debug)] +pub enum UpdateResult { + NoOp, + Stop, + RefreshPositions, +} + +pub enum Task { + OpenFile { file_path: String }, +} + +pub struct Application { + config: Config, + sdl_context: Sdl, + canvas: WindowCanvas, + tasks: Vec, +} + +impl Application { + pub fn new() -> Self { + let config = Config::new(); + let sdl_context = sdl2::init().unwrap(); + hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1"); + hint::set("SDL_GL_MULTISAMPLESAMPLES", "8"); + hint::set("SDL_GL_ACCELERATED_VISUAL", "1"); + hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2"); + hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1"); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem + .window("Editor", config.width, config.height) + .position_centered() + .opengl() + .build() + .unwrap(); + + let canvas = window.into_canvas().accelerated().build().unwrap(); + + Self { + config, + sdl_context, + canvas, + tasks: vec![], + } + } + + pub fn init(&mut self) { + self.clear(); + } + + pub fn run(&mut self) { + let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap(); + let mut event_pump = self.sdl_context.event_pump().unwrap(); + let font_context = sdl2::ttf::init().unwrap(); + let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); + let mut app_state = AppState::new(); + let mut renderer = Renderer::new( + self.config.clone(), + &font_context, + self.canvas.texture_creator() + ); + + 'running: loop { + match self.handle_events(&mut event_pump) { + UpdateResult::Stop => break 'running, + UpdateResult::RefreshPositions => (), + UpdateResult::NoOp => (), + } + for task in self.tasks.iter() { + match task { + Task::OpenFile { file_path } => { + use crate::file::editor_file::*; + app_state.open_file(file_path.clone(), &mut renderer); +// use std::fs::read_to_string; +// if let Ok(buffer) = read_to_string(&file_path) { +// println!("read: {}\n{}", file_path, buffer); +// let file = EditorFile::new(file_path.clone(), buffer, &mut renderer); +// app_state.current_file = app_state.files.len() as i16; +// app_state.files.push(file); +// } + }, + } + } + self.tasks.clear(); + + self.clear(); + + app_state.update(timer.ticks() as i32); + app_state.render(&mut self.canvas, &mut renderer); + + self.present(); + sleep(sleep_time); + } + } + + pub fn open_file(&mut self, file_path: String) { + self.tasks.push(Task::OpenFile { file_path }); + } + + fn present(&mut self) { + self.canvas.present(); + } + + fn clear(&mut self) { + self.canvas.set_draw_color(Color::RGB(255, 255, 255)); + self.canvas.clear(); + } + + fn handle_events(&mut self, event_pump: &mut EventPump) -> UpdateResult { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } => return UpdateResult::Stop, + _ => (), + } + } + UpdateResult::NoOp + } +} diff --git a/src/file/caret.rs b/src/file/caret.rs new file mode 100644 index 0000000..e7b567b --- /dev/null +++ b/src/file/caret.rs @@ -0,0 +1,19 @@ +pub struct Caret<'a> { + character: char, + source: Rect, + dest: Rect, + visible: bool, + texture: Option>>, +} + +impl<'a> Caret<'a> { + pub fn new() -> Self { + Self { + character: '│', + source: Rect::new(0, 0, 0, 0), + dest: Rect::new(0, 0, 0, 0), + visible: true, + texture: None, + } + } +} diff --git a/src/file/editor_file.rs b/src/file/editor_file.rs new file mode 100644 index 0000000..c441d0f --- /dev/null +++ b/src/file/editor_file.rs @@ -0,0 +1,45 @@ +use sdl2::rect::Rect; +use crate::file::editor_file_section::EditorFileSection; +use crate::renderer::Renderer; +use crate::app::UpdateResult; +use crate::app::WindowCanvas; + +#[derive(Clone)] +pub struct EditorFile<'l> { + pub path: String, + pub sections: Vec>, +} + +impl<'l> EditorFile<'l> { + pub fn new(path: String, buffer: String, renderer: &'l mut Renderer) -> Self { + let section = EditorFileSection::new(buffer, renderer); + let sections = vec![section]; + Self { path, sections } + } + + pub fn update(&mut self, ticks: i32) -> UpdateResult { + let mut result = UpdateResult::NoOp; + for section in self.sections.iter_mut() { + result = section.update(ticks); + } + + if result == UpdateResult::RefreshPositions { + self.refresh_characters_position(); + result = UpdateResult::NoOp; + } + result + } + + pub fn render(&self, canvas: &mut WindowCanvas, renderer: &mut Renderer) { + for ref section in self.sections.iter() { + section.render(canvas, renderer); + } + } + + fn refresh_characters_position(&mut self) { + let mut current: Rect = Rect::new(0, 0, 0, 0); + for section in self.sections.iter_mut() { + section.update_positions(&mut current); + } + } +} diff --git a/src/file/editor_file_section.rs b/src/file/editor_file_section.rs new file mode 100644 index 0000000..6e0766b --- /dev/null +++ b/src/file/editor_file_section.rs @@ -0,0 +1,51 @@ +use sdl2::rect::Rect; +use crate::lexer::Language; +use crate::app::UpdateResult; +use crate::app::WindowCanvas; +use crate::renderer::Renderer; +use crate::file::editor_file_token::EditorFileToken; + +#[derive(Clone)] +pub struct EditorFileSection<'l> { + pub tokens: Vec>, + pub language: Language, +} + +impl<'l> EditorFileSection<'l> { + pub fn new(buffer: String, renderer: &'l mut Renderer) -> Self { + use crate::lexer; + let lexer_tokens = lexer::parse(buffer.clone(), Language::PlainText); + + let mut tokens: Vec = vec![]; + for token_type in lexer_tokens { + let token = EditorFileToken::new( + renderer, + token_type.get_start(), + token_type.clone(), + ); + tokens.push(token.clone()); + } + let language = Language::PlainText; + Self { tokens, language } + } + + pub fn update(&mut self, ticks: i32) -> UpdateResult { + let mut result = UpdateResult::NoOp; + for file_char in self.tokens.iter_mut() { + result = file_char.update(ticks) + } + result + } + + pub fn render(&self, canvas: &mut WindowCanvas, renderer: &mut Renderer) { + for ref character in self.tokens.iter() { + character.render(canvas, renderer); + } + } + + pub fn update_positions(&mut self, current: &mut Rect) { + for c in self.tokens.iter_mut() { + c.update_position(current); + } + } +} diff --git a/src/file/editor_file_token.rs b/src/file/editor_file_token.rs new file mode 100644 index 0000000..23abe38 --- /dev/null +++ b/src/file/editor_file_token.rs @@ -0,0 +1,117 @@ +use std::rc::Rc; +use sdl2::rect::Rect; +use sdl2::render::Texture; +use sdl2::ttf::Font; +use crate::lexer::TokenType; +use crate::renderer::Renderer; +use crate::renderer::managers::TextDetails; +use crate::renderer::resolve_color::resolve_color; +use crate::app::UpdateResult; +use crate::app::WindowCanvas; +use crate::renderer::managers::FontDetails; + +#[derive(Clone)] +pub struct EditorFileToken<'l> { + pos: usize, + text: String, + font_size: u16, + source: Rect, + dest: Rect, + token_type: TokenType, + texture: Option>>, +} + +impl<'l> EditorFileToken<'l> { + pub fn new(renderer: &'l mut Renderer, pos: usize, token_type: TokenType) -> Self { + let c = match token_type { + _ if token_type.is_space() => "°".to_string(), + _ if token_type.is_new_line() => "\n".to_string(), + TokenType::Whitespace { .. } => "°".to_string(), + _ => token_type.get_text(), + }; + let details = TextDetails { + text: c.clone(), + font_details: FontDetails::new( + renderer.config.editor_config.font_path.as_str(), + renderer.config.editor_config.character_size.clone(), + ), + color: resolve_color(&token_type), + }; + Self { + pos, + text: c, + font_size: 0, + source: Rect::new(0, 0, 0, 0), + dest: Rect::new(0, 0, 0, 0), + token_type, + texture: renderer.render_text(details).clone(), + } + } + + pub fn update(&mut self, _ticks: i32) -> UpdateResult { +// if self.font_size != config.editor_config.character_size { +// self.update_view(renderer); +// return UpdateResult::RefreshPositions; +// } + UpdateResult::NoOp + } + + pub fn render(&self, canvas: &mut WindowCanvas, renderer: &mut Renderer) { + if self.token_type.is_new_line() { + return; + } + match &self.texture { + Some(texture) => { + renderer.render_texture(canvas, &texture, &self.source, &self.dest) + } + _ => {} + } + } + + pub fn update_position(&mut self, current: &mut Rect) { + match self.token_type { + _ if self.token_type.is_new_line() => { + current.set_x(0); + current.set_y( + (self.token_type.line() as usize * self.source.height() as usize) as i32, + ); + } + _ => { + self.dest.set_x(current.x()); + self.dest.set_y(current.y()); + self.dest.set_width(self.source.width()); + self.dest.set_height(self.source.height()); + current.set_x(self.dest.x() + self.source.width() as i32); + } + }; + } + + fn update_view(&mut self, renderer: &mut Renderer) { + self.font_size = renderer.config.editor_config.character_size.clone(); + let font_details = FontDetails::new( + renderer.config.editor_config.font_path.as_str(), + self.font_size.clone(), + ); + + if let Ok(font) = renderer.font_manager.load(&font_details) { + if let Some((width, height)) = self.measure_text(&font) { + self.source.set_width(width as u32); + self.source.set_height(height as u32); + } + }; + } + + fn measure_text(&self, font: &Rc) -> Option<(usize, usize)> { + let mut w: usize = 0; + let mut h: usize = 0; + for c in self.text.chars() { + if let Ok((width, height)) = font.size_of_char(c) { + w += width as usize; + h = height as usize; + } else { + return None; + } + } + Some((w, h)) + } +} diff --git a/src/file/mod.rs b/src/file/mod.rs new file mode 100644 index 0000000..7ddf5d7 --- /dev/null +++ b/src/file/mod.rs @@ -0,0 +1,3 @@ +pub mod editor_file; +pub mod editor_file_section; +pub mod editor_file_token; diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs new file mode 100644 index 0000000..2c29e3a --- /dev/null +++ b/src/lexer/mod.rs @@ -0,0 +1,153 @@ +use std::ops::Deref; + +pub mod plain; + +#[derive(Debug, Clone)] +pub enum Language { + PlainText, +} + +#[derive(Debug, Clone)] +pub enum TokenType { + Whitespace { token: Token }, + Keyword { token: Token }, + String { token: Token }, + Number { token: Token }, + Identifier { token: Token }, +} + +impl TokenType { + pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { + match self { + TokenType::Whitespace { token } => TokenType::Whitespace { + token: token.move_to(line, character, start, end), + }, + TokenType::Keyword { token } => TokenType::Keyword { + token: token.move_to(line, character, start, end), + }, + TokenType::String { token } => TokenType::String { + token: token.move_to(line, character, start, end), + }, + TokenType::Number { token } => TokenType::Number { + token: token.move_to(line, character, start, end), + }, + TokenType::Identifier { token } => TokenType::Identifier { + token: token.move_to(line, character, start, end), + }, + } + } + + pub fn is_new_line(&self) -> bool { + match self { + TokenType::Whitespace { token } => token.text() == "\n".to_string(), + _ => false, + } + } + + pub fn is_space(&self) -> bool { + match self { + TokenType::Whitespace { token } => token.text() == " ".to_string(), + _ => false, + } + } + + pub fn get_start(&self) -> usize { + match self { + TokenType::Whitespace { token } => token.start(), + TokenType::Keyword { token } => token.start(), + TokenType::String { token } => token.start(), + TokenType::Number { token } => token.start(), + TokenType::Identifier { token } => token.start(), + } + } + + pub fn get_text(&self) -> String { + match self { + TokenType::Whitespace { token } => token.text(), + TokenType::Keyword { token } => token.text(), + TokenType::String { token } => token.text(), + TokenType::Number { token } => token.text(), + TokenType::Identifier { token } => token.text(), + } + } +} + +impl Deref for TokenType { + type Target = Token; + + fn deref(&self) -> &::Target { + match self { + TokenType::Whitespace { token } => token, + TokenType::Keyword { token } => token, + TokenType::String { token } => token, + TokenType::Number { token } => token, + TokenType::Identifier { token } => token, + } + } +} + +#[derive(Debug, Clone)] +pub struct Token { + line: usize, + character: usize, + start: usize, + end: usize, + pub text: String, +} + +#[derive(Debug, Clone, Copy)] +pub struct Span { + pub lo: usize, + pub hi: usize, +} + +impl Token { + pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self { + Self { + text, + line, + character, + start, + end, + } + } + + pub fn text(&self) -> String { + self.text.clone() + } + + pub fn line(&self) -> usize { + self.line.clone() + } + + pub fn character(&self) -> usize { + self.character.clone() + } + + pub fn start(&self) -> usize { + self.start.clone() + } + + pub fn end(&self) -> usize { + self.end.clone() + } + + pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self { + Self { + text: self.text.clone(), + line, + character, + start, + end, + } + } +} + +pub fn parse(text: String, language: Language) -> Vec { + match language { + Language::PlainText => plain::lexer::Lexer::new(text.as_str()) + .inspect(|tok| eprintln!("tok: {:?}", tok)) + .map(|t| t.0) + .collect(), + } +} diff --git a/src/lexer/plain.rs b/src/lexer/plain.rs new file mode 100644 index 0000000..1283614 --- /dev/null +++ b/src/lexer/plain.rs @@ -0,0 +1,79 @@ +use crate::lexer::{Token, TokenType}; + +pub mod lexer { + use crate::lexer::{Span, Token, TokenType}; + use plex::lexer; + + lexer! { + fn next_token(text: 'a) -> (TokenType, &'a str); + + r"[ \t\r\n]" => (TokenType::Whitespace { + token: Token::new(text.to_string(), 0, 0, 0, 0) + }, text), + r"[^ \t\r\n]+" => (TokenType::Identifier { + token: Token::new(text.to_string(), 0, 0, 0, 0) + }, text), + } + + pub struct Lexer<'a> { + original: &'a str, + remaining: &'a str, + line: usize, + character: usize, + } + + impl<'a> Lexer<'a> { + pub fn new(s: &'a str) -> Self { + Self { + original: s, + remaining: s, + line: 0, + character: 0, + } + } + } + + impl<'a> Iterator for Lexer<'a> { + type Item = (TokenType, Span); + + fn next(&mut self) -> Option<(TokenType, Span)> { + loop { + let tok: (TokenType, &str) = + if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) { + self.remaining = new_remaining; + if token_type.is_new_line() { + self.line += 1; + self.character = text.len(); + } else { + self.character += text.len(); + } + (token_type, text) + } else { + return None; + }; + match tok { + (tok, text) => { + let span = self.span_in(text); + let token = tok.move_to( + self.line.clone(), + self.character - text.len(), + span.lo.clone(), + span.hi.clone(), + ); + return Some((token, span)); + } + } + } + } + } + + impl<'a> Lexer<'a> { + fn span_in(&self, s: &str) -> Span { + let lo = s.as_ptr() as usize - self.original.as_ptr() as usize; + Span { + lo, + hi: lo + s.len(), + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..29fa1ae --- /dev/null +++ b/src/main.rs @@ -0,0 +1,20 @@ +#![allow(unused_imports)] +#![feature(use_extern_macros)] + +extern crate plex; +extern crate rand; +extern crate sdl2; + +pub mod app; +pub mod file; +pub mod lexer; +pub mod renderer; + +use crate::app::Application; + +fn main() { + let mut app = Application::new(); + app.init(); + app.open_file("./tests/example.txt".to_string()); + app.run(); +} diff --git a/src/renderer/managers.rs b/src/renderer/managers.rs new file mode 100644 index 0000000..402a359 --- /dev/null +++ b/src/renderer/managers.rs @@ -0,0 +1,148 @@ +use std::borrow::Borrow; +use std::collections::HashMap; +#[allow(unused_imports)] +use std::env; +use std::hash::Hash; +use std::rc::Rc; + +use sdl2::image::LoadTexture; +use sdl2::pixels::Color; +use sdl2::render::{Texture, TextureCreator}; +use sdl2::ttf::{Font, Sdl2TtfContext}; + +pub trait ResourceLoader<'l, R> { + type Args: ?Sized; + + fn load(&'l self, data: &Self::Args) -> Result; +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct FontDetails { + pub path: String, + pub size: u16, +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct TextDetails { + pub text: String, + pub color: Color, + pub font_details: FontDetails, +} + +impl TextDetails { + pub fn get_cache_key(&self) -> String { + format!( + "text({}) size({}) {:?}", + self.text, self.font_details.size, self.color + ) + .to_string() + } +} + +impl<'a> From<&'a TextDetails> for TextDetails { + fn from(details: &'a Self) -> Self { + Self { + text: details.text.clone(), + color: details.color.clone(), + font_details: details.font_details.clone(), + } + } +} + +impl FontDetails { + pub fn new(path: &str, size: u16) -> FontDetails { + Self { + path: path.to_string(), + size, + } + } +} + +impl<'a> From<&'a FontDetails> for FontDetails { + fn from(details: &'a FontDetails) -> Self { + Self { + path: details.path.clone(), + size: details.size, + } + } +} + +pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator>; +pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; + +#[derive(Clone)] +pub struct ResourceManager<'l, K, R, L> +where + K: Hash + Eq, + L: 'l + ResourceLoader<'l, R>, +{ + loader: &'l L, + cache: HashMap>, +} + +impl<'l, K, R, L> ResourceManager<'l, K, R, L> +where + K: Hash + Eq, + L: ResourceLoader<'l, R>, +{ + pub fn new(loader: &'l L) -> Self { + Self { cache: HashMap::new(), loader } + } + + pub fn load(&mut self, details: &D) -> Result, String> + where + L: ResourceLoader<'l, R, Args = D>, + D: Eq + Hash + ?Sized, + K: Borrow + for<'a> From<&'a D>, + { + self.cache.get(details).cloned().map_or_else( + || { + let resource = Rc::new(self.loader.load(details)?); + self.cache.insert(details.into(), resource.clone()); + Ok(resource) + }, + Ok, + ) + } +} + +impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { + type Args = str; + + fn load(&'l self, path: &str) -> Result { + println!("Loading {}...", path); + self.load_texture(path) + } +} + +impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { + type Args = FontDetails; + + fn load(&'l self, data: &FontDetails) -> Result, String> { + println!("Loading font {}...", data.path); + self.load_font(&data.path, data.size) + } +} + +//impl<'l, T> TextureManager<'l, T> { +// pub fn load_text( +// &mut self, +// details: &mut TextDetails, +// font: &Font, +// ) -> Result>, String> { +// let key = details.get_cache_key(); +// self.cache.get(key.as_str()).cloned().map_or_else( +// || { +// let surface = font +// .render(details.text.as_str()) +// .blended(details.color) +// .unwrap(); +// let texture = self.loader.create_texture_from_surface(&surface).unwrap(); +// let resource = Rc::new(texture); +// self.cache.insert(key, resource.clone()); +// Ok(resource) +// }, +// Ok, +// ) +// } +//} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 0000000..fb8b8f1 --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,74 @@ +pub mod managers; +pub mod resolve_color; + +use crate::renderer::managers::{FontManager, TextureManager}; + +use sdl2::rect::{Point, Rect}; +use sdl2::render::{Texture, TextureCreator}; +use sdl2::ttf::Sdl2TtfContext; +use sdl2::video::WindowContext; + +use std::rc::Rc; + +use crate::app::config::Config; +use crate::app::WindowCanvas; +use crate::renderer::managers::TextDetails; + +pub struct Renderer<'a> { + pub config: Config, + pub font_manager: FontManager<'a>, + pub texture_manager: TextureManager<'a, WindowContext>, + pub texture_creator: TextureCreator, + pub scroll: Point, +} + +impl<'a> Renderer<'a> { + pub fn new( + config: Config, + font_context: &'a Sdl2TtfContext, + texture_creator: TextureCreator, + ) -> Self { + Self { + config, + font_manager: FontManager::new(&font_context), + texture_manager: TextureManager::new(&texture_creator), + texture_creator, + scroll: (0, 0).into(), + } + } + + pub fn render_texture(&mut self, canvas: &mut WindowCanvas, texture: &Rc, src: &Rect, dest: &Rect) { + canvas + .copy_ex( + texture, + Some(src.clone()), + Some(dest.clone()), + 0.0, + None, + false, + false, + ) + .unwrap(); + } + + pub fn render_text(&mut self, details: TextDetails) -> Option> { + let font = self.font_manager.load(&details.font_details).unwrap(); + let surface = font + .render(details.text.as_str()) + .blended(details.color); + let surface = if let Ok(s) = surface { + s + } else { + return None; + }; + let texture = self + .texture_creator + .create_texture_from_surface(&surface); + let texture = if let Ok(t) = texture { + Rc::new(t) + } else { + return None; + }; + Some(texture) + } +} diff --git a/src/renderer/resolve_color.rs b/src/renderer/resolve_color.rs new file mode 100644 index 0000000..7b3fc65 --- /dev/null +++ b/src/renderer/resolve_color.rs @@ -0,0 +1,10 @@ +use sdl2::pixels::Color; + +use crate::lexer::TokenType; + +pub fn resolve_color(token_type: &TokenType) -> Color { + match token_type { + &TokenType::Whitespace { .. } => Color::RGBA(220, 220, 220, 90), + _ => Color::RGBA(0, 0, 0, 0), + } +} diff --git a/tests/example.txt b/tests/example.txt new file mode 100644 index 0000000..4c0bf59 --- /dev/null +++ b/tests/example.txt @@ -0,0 +1,3 @@ +Hello world +foo bar +example com