diff --git a/assets/fonts/DejaVuSansMono.ttf b/assets/fonts/DejaVuSansMono.ttf new file mode 100644 index 0000000..f4b1bf8 Binary files /dev/null and b/assets/fonts/DejaVuSansMono.ttf differ diff --git a/assets/fonts/hinted-ElaineSans-Medium.ttf b/assets/fonts/ElaineSans-Medium.ttf similarity index 100% rename from assets/fonts/hinted-ElaineSans-Medium.ttf rename to assets/fonts/ElaineSans-Medium.ttf diff --git a/assets/theme.txt b/assets/theme.txt index 4045dc1..86e0fdb 100644 --- a/assets/theme.txt +++ b/assets/theme.txt @@ -37,9 +37,6 @@ italic ``keyword #CB7832 -``keyword -#CB7832 - ``regex #A4C260 diff --git a/src/app/app_state.rs b/src/app/app_state.rs index 55a94d2..f9e7144 100644 --- a/src/app/app_state.rs +++ b/src/app/app_state.rs @@ -1,42 +1,51 @@ -use std::rc::Rc; -use std::sync::Arc; -use std::boxed::Box; use crate::app::{UpdateResult, WindowCanvas}; -use crate::ui::*; -use crate::ui::caret::Caret; +use crate::config::Config; use crate::file::*; use crate::file::editor_file::EditorFile; use crate::renderer::Renderer; +use crate::ui::*; +use crate::ui::caret::Caret; +use crate::ui::menu_bar::MenuBar; +use sdl2::rect::Point; +use std::boxed::Box; +use std::rc::Rc; +use std::sync::Arc; pub struct AppState { - pub files: Vec, - pub current_file: i16, + menu_bar: MenuBar, + files: Vec, + current_file: usize, caret: Caret, } impl AppState { - pub fn new() -> Self { + pub fn new(config: &Config) -> Self { Self { + menu_bar: MenuBar::new(), files: vec![], - current_file: -1, - caret: Caret::new(), + current_file: 0, + caret: Caret::new(config), } } - pub fn open_file(&mut self, file_path: String) { + pub fn open_file(&mut self, file_path: String, config: &Config) { 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); - self.current_file = self.files.len() as i16; + let file = EditorFile::new(file_path.clone(), buffer, config); + self.current_file = self.files.len(); self.files.push(file); }; } + + pub fn caret(&mut self) -> &mut Caret { + &mut self.caret + } } impl Render for AppState { fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - if let Some(file) = self.files.get_mut(self.current_file as usize) { + self.menu_bar.render(canvas, renderer); + if let Some(file) = self.files.get_mut(self.current_file) { file.render(canvas, renderer); } self.caret.render(canvas, renderer); @@ -46,10 +55,34 @@ impl Render for AppState { impl Update for AppState { fn update(&mut self, ticks: i32) -> UpdateResult { - if let Some(file) = self.files.get_mut(self.current_file as usize) { + self.menu_bar.update(ticks); + if let Some(file) = self.files.get_mut(self.current_file) { file.update(ticks); } self.caret.update(ticks); UpdateResult::NoOp } } + +impl ClickHandler for AppState { + fn on_left_click(&mut self, point: &Point, config: &Config) -> UpdateResult { + if self.menu_bar.is_left_click_target(point) { + return self.menu_bar.on_left_click(point, config); + } + if let Some(current_file) = self.files.get_mut(self.current_file) { + if current_file.is_left_click_target(point) { + match current_file.on_left_click(point, config) { + UpdateResult::MoveCaret(rect) => { + self.caret.move_caret(Point::new(rect.x(), rect.y())); + } + _ => (), + }; + } + } + UpdateResult::NoOp + } + + fn is_left_click_target(&self, _point: &Point) -> bool { + true + } +} diff --git a/src/app/config.rs b/src/app/config.rs deleted file mode 100644 index e46cc8e..0000000 --- a/src/app/config.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::themes::Theme; - -#[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, - pub theme: Theme, -} - -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(), - }, - theme: Theme::load("default".to_string()), - } - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs index f5eff84..5a26d0b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,30 +1,31 @@ -pub mod app_state; -pub mod config; - +use crate::app::app_state::AppState; +use crate::config::Config; +use crate::renderer::Renderer; +use crate::themes::*; +use crate::ui::*; +use sdl2::{Sdl, TimerSubsystem}; use sdl2::event::Event; +use sdl2::EventPump; use sdl2::hint; +use sdl2::mouse::MouseButton; use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; 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; +pub mod app_state; -use crate::app::app_state::AppState; -use crate::app::config::Config; -use crate::themes::Theme; -use crate::ui::*; -use crate::renderer::Renderer; +pub type WindowCanvas = Canvas; #[derive(PartialEq, Clone, Debug)] pub enum UpdateResult { NoOp, Stop, RefreshPositions, + MouseLeftClicked(Point), + MoveCaret(Rect), } pub enum Task { @@ -36,6 +37,7 @@ pub struct Application { sdl_context: Sdl, canvas: WindowCanvas, tasks: Vec, + clear_color: Color, } impl Application { @@ -49,7 +51,7 @@ impl Application { 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) + .window("Editor", config.width(), config.height()) .position_centered() .opengl() .build() @@ -58,10 +60,11 @@ impl Application { let canvas = window.into_canvas().accelerated().build().unwrap(); Self { - config, sdl_context, canvas, tasks: vec![], + clear_color: config.theme().background().into(), + config, } } @@ -75,25 +78,25 @@ impl Application { let font_context = sdl2::ttf::init().unwrap(); let texture_creator = self.canvas.texture_creator(); 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, - &texture_creator - ); + let mut app_state = AppState::new(&self.config); + let mut renderer = Renderer::new(self.config.clone(), &font_context, &texture_creator); 'running: loop { match self.handle_events(&mut event_pump) { UpdateResult::Stop => break 'running, UpdateResult::RefreshPositions => (), UpdateResult::NoOp => (), + UpdateResult::MoveCaret(_) => (), + UpdateResult::MouseLeftClicked(point) => { + app_state.on_left_click(&point, renderer.config()); + } } for task in self.tasks.iter() { match task { Task::OpenFile { file_path } => { use crate::file::editor_file::*; - app_state.open_file(file_path.clone()); - }, + app_state.open_file(file_path.clone(), renderer.config()); + } } } self.tasks.clear(); @@ -117,7 +120,7 @@ impl Application { } fn clear(&mut self) { - self.canvas.set_draw_color(Color::RGB(255, 255, 255)); + self.canvas.set_draw_color(self.clear_color.clone()); self.canvas.clear(); } @@ -125,6 +128,12 @@ impl Application { for event in event_pump.poll_iter() { match event { Event::Quit { .. } => return UpdateResult::Stop, + Event::MouseButtonUp { + mouse_btn, x, y, .. + } => match mouse_btn { + MouseButton::Left => return UpdateResult::MouseLeftClicked(Point::new(x, y)), + _ => (), + }, _ => (), } } diff --git a/src/config/creator.rs b/src/config/creator.rs new file mode 100644 index 0000000..d4d5345 --- /dev/null +++ b/src/config/creator.rs @@ -0,0 +1,35 @@ +use crate::config::directories::*; +use crate::themes::config_creator; +use dirs; +use std::fs; +use std::path; + +pub fn create() { + if !themes_dir().exists() { + fs::create_dir_all(&themes_dir()) + .unwrap_or_else(|_| panic!("Cannot create themes config directory")); + } + + if !fonts_dir().exists() { + fs::create_dir_all(&fonts_dir()) + .unwrap_or_else(|_| panic!("Cannot create fonts config directory")); + write_default_fonts(); + } +} + +fn write_default_fonts() { + { + let mut default_font_path = fonts_dir(); + default_font_path.push("DejaVuSansMono.ttf"); + let contents = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf"); + fs::write(default_font_path, contents.to_vec()) + .unwrap_or_else(|_| panic!("Cannot write default font file!")); + } + { + let mut default_font_path = fonts_dir(); + default_font_path.push("ElaineSans-Medium.ttf"); + let contents = include_bytes!("../../assets/fonts/ElaineSans-Medium.ttf"); + fs::write(default_font_path, contents.to_vec()) + .unwrap_or_else(|_| panic!("Cannot write default font file!")); + } +} diff --git a/src/config/directories.rs b/src/config/directories.rs new file mode 100644 index 0000000..dd1f692 --- /dev/null +++ b/src/config/directories.rs @@ -0,0 +1,22 @@ +use dirs; +use std::path::PathBuf; + +pub fn themes_dir() -> PathBuf { + let mut themes_dir = config_dir(); + themes_dir.push("themes"); + themes_dir +} + +pub fn fonts_dir() -> PathBuf { + let mut fonts_dir = config_dir(); + fonts_dir.push("fonts"); + fonts_dir +} + +pub fn config_dir() -> PathBuf { + let home_dir = dirs::config_dir().unwrap(); + + let mut config_dir = home_dir.clone(); + config_dir.push("rider"); + config_dir +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..b1c424e --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,99 @@ +use crate::themes::Theme; +use dirs; +use std::fs; + +mod creator; +pub mod directories; + +#[derive(Debug, Clone)] +pub struct EditorConfig { + character_size: u16, + font_path: String, + current_theme: String, + margin_left: u16, + margin_top: u16, +} + +impl EditorConfig { + pub fn new() -> Self { + let mut default_font_path = directories::fonts_dir(); + default_font_path.push("DejaVuSansMono.ttf"); + Self { + character_size: 24, + font_path: default_font_path.to_str().unwrap().to_string(), + current_theme: "railscasts".to_string(), + margin_left: 10, + margin_top: 10, + } + } + + pub fn character_size(&self) -> u16 { + self.character_size + } + + pub fn font_path(&self) -> &String { + &self.font_path + } + + pub fn current_theme(&self) -> &String { + &self.current_theme + } + + pub fn margin_left(&self) -> u16 { + self.margin_left + } + + pub fn margin_top(&self) -> u16 { + self.margin_top + } +} + +#[derive(Debug, Clone)] +pub struct Config { + width: u32, + height: u32, + menu_height: u16, + editor_config: EditorConfig, + theme: Theme, +} + +impl Config { + pub fn new() -> Self { + creator::create(); + let editor_config = EditorConfig::new(); + Self { + width: 1024, + height: 860, + menu_height: 60, + theme: Theme::load(editor_config.current_theme().clone()), + editor_config, + } + } + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn editor_config(&self) -> &EditorConfig { + &self.editor_config + } + + pub fn theme(&self) -> &Theme { + &self.theme + } + + pub fn menu_height(&self) -> u16 { + self.menu_height + } + + pub fn editor_top_margin(&self) -> i32 { + (self.menu_height() as i32) + (self.editor_config().margin_top() as i32) + } + + pub fn editor_left_margin(&self) -> i32 { + self.editor_config().margin_left() as i32 + } +} diff --git a/src/file/editor_file.rs b/src/file/editor_file.rs index d7790cb..04ebb8e 100644 --- a/src/file/editor_file.rs +++ b/src/file/editor_file.rs @@ -1,26 +1,34 @@ -use sdl2::rect::Rect; +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; use crate::file::editor_file_section::EditorFileSection; use crate::renderer::Renderer; -use crate::app::{UpdateResult, WindowCanvas}; use crate::ui::*; +use sdl2::rect::{Point, Rect}; #[derive(Clone)] pub struct EditorFile { - pub path: String, - pub sections: Vec, + path: String, + sections: Vec, + render_position: Rect, } impl EditorFile { - pub fn new(path: String, buffer: String) -> Self { - let section = EditorFileSection::new(buffer); + pub fn new(path: String, buffer: String, config: &Config) -> Self { + let section = EditorFileSection::new(buffer, config); let sections = vec![section]; - Self { path, sections } + let x = config.editor_left_margin(); + let y = config.editor_top_margin(); + Self { + path, + sections, + render_position: Rect::new(x, y, 0, 0), + } } - fn refresh_characters_position(&mut self) { - let mut current: Rect = Rect::new(0, 0, 0, 0); + fn refresh_characters_position(&mut self, config: &Config) { + let mut current: Rect = self.render_position.clone(); for section in self.sections.iter_mut() { - section.update_positions(&mut current); + section.update_positions(&mut current, config); } } } @@ -32,7 +40,7 @@ impl Render for EditorFile { res = section.render(canvas, renderer); } if res == UpdateResult::RefreshPositions { - self.refresh_characters_position(); + self.refresh_characters_position(renderer.config()); for section in self.sections.iter_mut() { section.render(canvas, renderer); } @@ -50,3 +58,23 @@ impl Update for EditorFile { result } } + +impl ClickHandler for EditorFile { + fn on_left_click(&mut self, point: &Point, config: &Config) -> UpdateResult { + for section in self.sections.iter_mut() { + if section.is_left_click_target(point) { + return section.on_left_click(point, config); + } + } + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point) -> bool { + for section in self.sections.iter() { + if section.is_left_click_target(point) { + return true; + } + } + false + } +} diff --git a/src/file/editor_file_section.rs b/src/file/editor_file_section.rs index b4d9159..44ec921 100644 --- a/src/file/editor_file_section.rs +++ b/src/file/editor_file_section.rs @@ -1,34 +1,34 @@ -use sdl2::rect::Rect; -use crate::lexer::Language; -use crate::app::UpdateResult; -use crate::app::WindowCanvas; -use crate::renderer::Renderer; +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; use crate::file::editor_file_token::EditorFileToken; +use crate::lexer::Language; +use crate::renderer::Renderer; use crate::ui::*; +use sdl2::rect::{Point, Rect}; #[derive(Clone)] pub struct EditorFileSection { - pub tokens: Vec, - pub language: Language, + tokens: Vec, + language: Language, } impl EditorFileSection { - pub fn new(buffer: String) -> Self { + pub fn new(buffer: String, config: &Config) -> 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(token_type); + let token = EditorFileToken::new(token_type, config); tokens.push(token.clone()); } let language = Language::PlainText; Self { tokens, language } } - pub fn update_positions(&mut self, current: &mut Rect) { + pub fn update_positions(&mut self, current: &mut Rect, config: &Config) { for c in self.tokens.iter_mut() { - c.update_position(current); + c.update_position(current, config); } } } @@ -55,3 +55,23 @@ impl Update for EditorFileSection { result } } + +impl ClickHandler for EditorFileSection { + fn on_left_click(&mut self, point: &Point, config: &Config) -> UpdateResult { + for token in self.tokens.iter_mut() { + if token.is_left_click_target(point) { + return token.on_left_click(point, config); + } + } + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point) -> bool { + for token in self.tokens.iter() { + if token.is_left_click_target(point) { + return true; + } + } + false + } +} diff --git a/src/file/editor_file_token.rs b/src/file/editor_file_token.rs index 05c3894..cd50be5 100644 --- a/src/file/editor_file_token.rs +++ b/src/file/editor_file_token.rs @@ -1,22 +1,15 @@ -use std::rc::Rc; -use sdl2::rect::Rect; -use sdl2::render::Texture; -use sdl2::ttf::Font; -use sdl2::pixels::Color; - -use crate::lexer::TokenType; -use crate::renderer::Renderer; -use crate::renderer::managers::TextDetails; use crate::app::{UpdateResult, WindowCanvas}; -use crate::renderer::managers::FontDetails; +use crate::config::Config; +use crate::lexer::TokenType; +use crate::renderer::managers::{FontDetails, TextDetails}; +use crate::renderer::Renderer; use crate::ui::*; use crate::ui::text_character::*; - -#[derive(Clone)] -pub struct TextCharacterMeasure { - source: Rect, - dest: Rect, -} +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use sdl2::render::Texture; +use sdl2::ttf::Font; +use std::rc::Rc; #[derive(Clone)] pub struct EditorFileToken { @@ -24,36 +17,36 @@ pub struct EditorFileToken { token_type: TokenType, } -impl Into for TokenType { - fn into(self) -> Color { - match &self { - &TokenType::Whitespace { .. } => Color::RGBA(220, 220, 220, 90), - _ => Color::RGBA(0, 0, 0, 0), - } - } -} - impl EditorFileToken { - pub fn new(token_type: TokenType) -> Self { + pub fn new(token_type: TokenType, _config: &Config) -> Self { Self { characters: vec![], token_type, } } - pub fn update_position(&mut self, current: &mut Rect) { + pub fn update_position(&mut self, current: &mut Rect, config: &Config) { for text_character in self.characters.iter_mut() { - text_character.update_position(current); + text_character.update_position(current, config); } } fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult { + let config = renderer.config().theme().code_highlighting(); + let color: Color = match self.token_type { + TokenType::Whitespace { .. } => config.whitespace().color().into(), + TokenType::Keyword { .. } => config.keyword().color().into(), + TokenType::String { .. } => config.string().color().into(), + TokenType::Number { .. } => config.number().color().into(), + TokenType::Identifier { .. } => config.identifier().color().into(), + TokenType::Literal { .. } => config.literal().color().into(), + TokenType::Comment { .. } => config.comment().color().into(), + TokenType::Operator { .. } => config.operator().color().into(), + TokenType::Separator { .. } => config.separator().color().into(), + }; for c in self.token_type.text().chars() { - let mut text_character = TextCharacter::new( - c.clone(), - self.token_type.line(), - self.token_type.clone().into(), - ); + let mut text_character = + TextCharacter::new(c.clone(), self.token_type.line(), color.clone()); text_character.update_view(renderer); self.characters.push(text_character); } @@ -64,9 +57,9 @@ impl EditorFileToken { impl Render for EditorFileToken { /** - * Must first create targets so even if new line appear renderer will know - * where move render starting point - */ + * Must first create targets so even if new line appear renderer will know + * where move render starting point + */ fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { if self.characters.is_empty() { return self.update_view(renderer); @@ -89,3 +82,23 @@ impl Update for EditorFileToken { UpdateResult::NoOp } } + +impl ClickHandler for EditorFileToken { + fn on_left_click(&mut self, point: &Point, config: &Config) -> UpdateResult { + for text_character in self.characters.iter_mut() { + if text_character.is_left_click_target(point) { + return text_character.on_left_click(point, config); + } + } + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point) -> bool { + for text_character in self.characters.iter() { + if text_character.is_left_click_target(point) { + return true; + } + } + false + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 18b4fb3..e4774fa 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -92,7 +92,7 @@ pub struct Token { character: usize, start: usize, end: usize, - pub text: String, + text: String, } #[derive(Debug, Clone, Copy)] diff --git a/src/main.rs b/src/main.rs index e21f994..4ef6445 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,25 @@ #![allow(unused_imports)] +extern crate dirs; extern crate plex; extern crate rand; extern crate sdl2; -extern crate dirs; #[macro_use] extern crate serde; #[macro_use] -extern crate serde_json; -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate serde_json; + +use crate::app::Application; pub mod app; -pub mod ui; +pub mod config; pub mod file; pub mod lexer; pub mod renderer; pub mod themes; - -use crate::app::Application; +pub mod ui; fn main() { let mut app = Application::new(); diff --git a/src/renderer/managers.rs b/src/renderer/managers.rs index af75dc0..caeb1a3 100644 --- a/src/renderer/managers.rs +++ b/src/renderer/managers.rs @@ -1,3 +1,7 @@ +use sdl2::image::LoadTexture; +use sdl2::pixels::Color; +use sdl2::render::{Texture, TextureCreator}; +use sdl2::ttf::{Font, Sdl2TtfContext}; use std::borrow::Borrow; use std::collections::HashMap; #[allow(unused_imports)] @@ -5,11 +9,6 @@ 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; @@ -34,7 +33,8 @@ impl TextDetails { format!( "text({}) size({}) {:?}", self.text, self.font.size, self.color - ).to_string() + ) + .to_string() } } @@ -66,6 +66,7 @@ impl<'a> From<&'a FontDetails> for FontDetails { } } +//noinspection RsWrongLifetimeParametersNumber pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator>; pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; @@ -85,7 +86,10 @@ impl<'l, K, R, L> ResourceManager<'l, K, R, L> L: ResourceLoader<'l, R>, { pub fn new(loader: &'l L) -> Self { - Self { cache: HashMap::new(), loader } + Self { + cache: HashMap::new(), + loader, + } } pub fn load(&mut self, details: &D) -> Result, String> @@ -109,6 +113,7 @@ impl<'l, K, R, L> ResourceManager<'l, K, R, L> } } +//noinspection RsWrongLifetimeParametersNumber impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { type Args = str; @@ -128,6 +133,7 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { } impl<'l, T> TextureManager<'l, T> { + //noinspection RsWrongLifetimeParametersNumber pub fn load_text( &mut self, details: &mut TextDetails, @@ -143,6 +149,7 @@ impl<'l, T> TextureManager<'l, T> { let texture = self.loader.create_texture_from_surface(&surface).unwrap(); let resource = Rc::new(texture); self.cache.insert(key, resource.clone()); + println!("texture for '{}' created", details.text); Ok(resource) }, Ok, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 111c9a2..185edec 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,23 +1,20 @@ -pub mod managers; - +use crate::app::WindowCanvas; +use crate::config::Config; use crate::renderer::managers::{FontManager, TextureManager}; - +use crate::renderer::managers::TextDetails; 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 mod managers; pub struct Renderer<'a> { - pub config: Config, - pub font_manager: FontManager<'a>, - pub texture_manager: TextureManager<'a, WindowContext>, - pub scroll: Point, + config: Config, + font_manager: FontManager<'a>, + texture_manager: TextureManager<'a, WindowContext>, + scroll: Point, } impl<'a> Renderer<'a> { @@ -34,7 +31,29 @@ impl<'a> Renderer<'a> { } } - pub fn render_texture(&mut self, canvas: &mut WindowCanvas, texture: &Rc, src: &Rect, dest: &Rect) { + pub fn config(&self) -> &Config { + &self.config + } + + pub fn font_manager(&mut self) -> &mut FontManager<'a> { + &mut self.font_manager + } + + pub fn texture_manager(&mut self) -> &mut TextureManager<'a, WindowContext> { + &mut self.texture_manager + } + + pub fn scroll(&self) -> &Point { + &self.scroll + } + + pub fn render_texture( + &mut self, + canvas: &mut WindowCanvas, + texture: &Rc, + src: &Rect, + dest: &Rect, + ) { canvas .copy_ex( texture, @@ -50,9 +69,7 @@ impl<'a> Renderer<'a> { pub fn render_text(&mut self, details: TextDetails) -> Option> { let font = self.font_manager.load(&details.font).unwrap(); - let surface = font - .render(details.text.as_str()) - .blended(details.color); + let surface = font.render(details.text.as_str()).blended(details.color); let surface = if let Ok(s) = surface { s } else { diff --git a/src/themes/config_creator.rs b/src/themes/config_creator.rs new file mode 100644 index 0000000..03b38af --- /dev/null +++ b/src/themes/config_creator.rs @@ -0,0 +1,155 @@ +use crate::config::directories::*; +use crate::themes::*; +use dirs; +use std::fs; +use std::path::PathBuf; + +pub fn create() { + fs::create_dir_all(themes_dir()) + .unwrap_or_else(|_| panic!("Cannot create theme config directory")); + for theme in default_styles() { + write_theme(&theme); + } +} + +fn write_theme(theme: &Theme) { + let mut theme_path = themes_dir(); + theme_path.push(format!("{}.json", theme.name)); + let contents = serde_json::to_string_pretty(&theme).unwrap(); + fs::write(&theme_path, contents.clone()) + .unwrap_or_else(|_| panic!("Failed to crate theme config file")); +} + +fn default_styles() -> Vec { + vec![default_theme(), railscasts_theme()] +} + +fn default_theme() -> Theme { + Theme::default() +} + +fn railscasts_theme() -> Theme { + Theme { + name: "railscasts".to_string(), + background: SerdeColor { + r: 60, + g: 60, + b: 60, + a: 0, + }, + caret: CaretColor { + bright: ThemeConfig { + color: SerdeColor { + r: 121, + g: 121, + b: 121, + a: 0, + }, + italic: false, + bold: false, + }, + blur: ThemeConfig { + color: SerdeColor { + r: 21, + g: 21, + b: 21, + a: 0, + }, + italic: false, + bold: false, + }, + }, + code_highlighting: CodeHighlightingColor { + whitespace: ThemeConfig { + color: SerdeColor { + r: 220, + g: 220, + b: 220, + a: 90, + }, + italic: false, + bold: false, + }, + keyword: ThemeConfig { + color: SerdeColor { + r: 203, + g: 120, + b: 50, + a: 0, + }, + italic: false, + bold: true, + }, + string: ThemeConfig { + color: SerdeColor { + r: 164, + g: 194, + b: 96, + a: 0, + }, + italic: false, + bold: false, + }, + number: ThemeConfig { + color: SerdeColor { + r: 164, + g: 194, + b: 96, + a: 0, + }, + italic: false, + bold: false, + }, + identifier: ThemeConfig { + color: SerdeColor { + r: 21, + g: 21, + b: 21, + a: 0, + }, + italic: false, + bold: false, + }, + literal: ThemeConfig { + color: SerdeColor { + r: 21, + g: 21, + b: 21, + a: 0, + }, + italic: false, + bold: false, + }, + comment: ThemeConfig { + color: SerdeColor { + r: 188, + g: 147, + b: 88, + a: 0, + }, + italic: true, + bold: false, + }, + operator: ThemeConfig { + color: SerdeColor { + r: 0, + g: 0, + b: 0, + a: 0, + }, + italic: false, + bold: false, + }, + separator: ThemeConfig { + color: SerdeColor { + r: 21, + g: 21, + b: 21, + a: 0, + }, + italic: false, + bold: false, + }, + }, + } +} diff --git a/src/themes/mod.rs b/src/themes/mod.rs index 4592987..7681635 100644 --- a/src/themes/mod.rs +++ b/src/themes/mod.rs @@ -1,31 +1,34 @@ +use crate::config::directories::*; +use sdl2::pixels::Color; +use serde::ser::{Serialize, SerializeMap, Serializer, SerializeSeq}; +use serde_json; +use std::env; use std::fs; use std::path::PathBuf; -use std::env; -use sdl2::pixels::Color; -use serde_json; -use serde::ser::{Serialize, Serializer, SerializeSeq, SerializeMap}; + +pub mod config_creator; #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct SerdeColor { pub r: u8, pub g: u8, pub b: u8, - pub a: u8 + pub a: u8, } impl SerdeColor { - pub fn new(r: u8,g: u8,b: u8,a: u8) -> Self { - Self { r,g,b,a } + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } } } -impl Into for SerdeColor { +impl Into for &SerdeColor { fn into(self) -> Color { Color { r: self.r, g: self.g, b: self.b, - a: self.a + a: self.a, } } } @@ -197,8 +200,9 @@ pub struct Theme { impl Default for Theme { fn default() -> Self { + use crate::themes::config_creator; Self { - name: "Default".to_string(), + name: "default".to_string(), background: SerdeColor::new(255, 255, 255, 0), caret: CaretColor::default(), code_highlighting: CodeHighlightingColor::default(), @@ -223,152 +227,26 @@ impl Theme { &self.code_highlighting } - fn railscasts() -> Self { - Self { - name: "railscasts".to_string(), - background: SerdeColor { - r: 60, - g: 60, - b: 60, - a: 0 - }, - caret: CaretColor { bright: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, blur: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - } }, - code_highlighting: CodeHighlightingColor { - whitespace: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - keyword: ThemeConfig { - color: SerdeColor { - r: 203, - g: 120, - b: 50, - a: 0 - }, - italic: false, - bold: true - }, - string: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - number: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - identifier: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - literal: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - comment: ThemeConfig { - color: SerdeColor { - r: 188, - g: 147, - b: 88, - a: 0 - }, - italic: true, - bold: false - }, - operator: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - }, - separator: ThemeConfig { - color: SerdeColor { - r: 0, - g: 0, - b: 0, - a: 0 - }, - italic: false, - bold: false - } - } - } - } - - pub fn load(_theme_name: String) -> Self { + pub fn load(theme_name: String) -> Self { use dirs; let home_dir = dirs::config_dir().unwrap(); let mut config_dir = home_dir.clone(); - config_dir.push("rider/themes"); + config_dir.push("rider"); fs::create_dir_all(&config_dir) .unwrap_or_else(|_| panic!("Cannot create config directory")); - let theme = Self::load_content(&config_dir, "default.json"); - println!("theme config:\n{:?}", theme); - theme + Self::load_content(format!("{}.json", theme_name).as_str()) } - fn load_content(config_dir: &PathBuf, file_name: &str) -> Theme { - let mut config_file = config_dir.clone(); + fn load_content(file_name: &str) -> Theme { + let mut config_file = themes_dir(); config_file.push(file_name); let contents = match fs::read_to_string(&config_file) { Ok(s) => s, Err(_) => { - let contents = serde_json::to_string_pretty(&Theme::default()) - .unwrap(); - fs::write(&config_file, contents.clone()) - .unwrap_or_else(|_| panic!("Failed to crate theme config file")); - contents.to_string() + use crate::themes::config_creator; + config_creator::create(); + fs::read_to_string(&config_file) + .unwrap_or_else(|_| panic!("Failed to load theme config file")) } }; serde_json::from_str(&contents).unwrap_or_default() diff --git a/src/ui/caret.rs b/src/ui/caret.rs index 365baf1..a2431a1 100644 --- a/src/ui/caret.rs +++ b/src/ui/caret.rs @@ -1,12 +1,11 @@ -use sdl2::rect::Rect; -use sdl2::render::Texture; -use sdl2::pixels::Color; -use crate::app::{WindowCanvas, UpdateResult}; +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; +use crate::renderer::Renderer; use crate::ui::*; use crate::ui::text_character::TextCharacter; -use crate::renderer::Renderer; - -const CARET_CHARACTER: char = '│'; +use sdl2::pixels::Color; +use sdl2::rect::{Point, Rect}; +use sdl2::render::Texture; #[derive(Clone, Debug, PartialEq)] enum CaretState { @@ -14,21 +13,31 @@ enum CaretState { Blur, } -#[derive(Clone)] pub struct Caret { state: CaretState, - bright_character: TextCharacter, - blur_character: TextCharacter, blink_delay: u8, + position: Rect, + bright_character_color: Color, + blur_character_color: Color, + pending: bool, } impl Caret { - pub fn new() -> Self { + pub fn new(config: &Config) -> Self { + let bright_character_color = config.theme().caret().bright().color().into(); + let blur_character_color = config.theme().caret().blur().color().into(); Self { - bright_character: TextCharacter::new(CARET_CHARACTER, 0, Color::RGBA(0, 0, 0, 0)), - blur_character: TextCharacter::new(CARET_CHARACTER, 0, Color::RGBA(100, 100, 100, 0)), state: CaretState::Bright, blink_delay: 0, + position: Rect::new( + config.editor_left_margin(), + config.editor_top_margin(), + 4, + 0, + ), + bright_character_color, + blur_character_color, + pending: true, } } @@ -39,20 +48,44 @@ impl Caret { CaretState::Bright }; } + + pub fn move_caret(&mut self, pos: Point) { + self.position.set_x(pos.x()); + self.position.set_y(pos.y()); + } } impl Render for Caret { fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - match self.state { - CaretState::Bright => { - self.bright_character.update_position(&mut Rect::new(100, 220, 0, 0)); - self.bright_character.render(canvas, renderer) - }, - CaretState::Blur => { - self.blur_character.update_position(&mut Rect::new(100, 220, 0, 0)); - self.blur_character.render(canvas, renderer) - }, + if self.pending { + use crate::renderer::managers::FontDetails; + let config = renderer.config().clone(); + let font = renderer + .font_manager() + .load(&FontDetails { + path: config.editor_config().font_path().clone(), + size: config.editor_config().character_size(), + }) + .unwrap_or_else(|_| panic!("Unable to load font")); + if let Ok((_, h)) = font.size_of_char('W') { + self.position.set_height(h); + } + self.pending = false; } + let start = Point::new(self.position.x(), self.position.y()); + let end = Point::new( + self.position.x(), + self.position.y() + self.position.height() as i32, + ); + let color = match self.state { + CaretState::Bright => &self.bright_character_color, + CaretState::Blur => &self.blur_character_color, + }; + canvas.set_draw_color(color.clone()); + canvas + .draw_line(start, end) + .unwrap_or_else(|_| panic!("Failed to draw a caret")); + UpdateResult::NoOp } } @@ -66,3 +99,14 @@ impl Update for Caret { UpdateResult::NoOp } } + +impl ClickHandler for Caret { + fn on_left_click(&mut self, _point: &Point, _config: &Config) -> UpdateResult { + // self.move_caret(Point::new(self.position.x(), self.position.y())); + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point) -> bool { + is_in_rect(point, &self.position) + } +} diff --git a/src/ui/menu_bar.rs b/src/ui/menu_bar.rs new file mode 100644 index 0000000..6361085 --- /dev/null +++ b/src/ui/menu_bar.rs @@ -0,0 +1,55 @@ +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; +use crate::renderer::Renderer; +use crate::ui::*; +use sdl2::pixels::Color; +use sdl2::rect::Rect; + +pub struct MenuBar { + background_color: Color, + dest: Rect, +} + +impl MenuBar { + pub fn new() -> Self { + Self { + background_color: Color::RGB(10, 10, 10), + dest: Rect::new(0, 0, 0, 0), + } + } + + pub fn background_color(&self) -> &Color { + &self.background_color + } + + pub fn dest(&self) -> &Rect { + &self.dest + } +} + +impl Render for MenuBar { + fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { + let width = renderer.config().width(); + let height = renderer.config().menu_height() as u32; + self.dest = Rect::new(0, 0, width, height); + canvas.set_draw_color(self.background_color.clone()); + canvas.draw_rect(self.dest.clone()).unwrap(); + UpdateResult::NoOp + } +} + +impl Update for MenuBar { + fn update(&mut self, _ticks: i32) -> UpdateResult { + UpdateResult::NoOp + } +} + +impl ClickHandler for MenuBar { + fn on_left_click(&mut self, _point: &Point, _config: &Config) -> UpdateResult { + unimplemented!() + } + + fn is_left_click_target(&self, point: &Point) -> bool { + is_in_rect(point, self.dest()) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 05cbd0f..576465a 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,8 +1,20 @@ +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; +use crate::renderer::Renderer; +use sdl2::rect::{Point, Rect}; + pub mod caret; +pub mod menu_bar; pub mod text_character; -use crate::renderer::Renderer; -use crate::app::{WindowCanvas,UpdateResult}; +pub fn is_in_rect(point: &Point, rect: &Rect) -> bool { + let start = Point::new(rect.x(), rect.y()); + let end = Point::new( + rect.x() + (rect.width() as i32), + rect.y() + (rect.height() as i32), + ); + start.x() <= point.x() && start.y() <= point.y() && end.x() >= point.x() && end.y() >= point.y() +} pub trait Render { fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult; @@ -11,3 +23,9 @@ pub trait Render { pub trait Update { fn update(&mut self, ticks: i32) -> UpdateResult; } + +pub trait ClickHandler { + fn on_left_click(&mut self, point: &Point, config: &Config) -> UpdateResult; + + fn is_left_click_target(&self, point: &Point) -> bool; +} diff --git a/src/ui/text_character.rs b/src/ui/text_character.rs index 2b890fe..69ae107 100644 --- a/src/ui/text_character.rs +++ b/src/ui/text_character.rs @@ -1,15 +1,15 @@ -use std::rc::Rc; +use crate::app::{UpdateResult, WindowCanvas}; +use crate::config::Config; +use crate::lexer::TokenType; +use crate::renderer::managers::FontDetails; +use crate::renderer::managers::TextDetails; +use crate::renderer::Renderer; +use crate::ui::*; +use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::Texture; use sdl2::ttf::Font; -use sdl2::pixels::Color; - -use crate::lexer::TokenType; -use crate::renderer::Renderer; -use crate::renderer::managers::TextDetails; -use crate::app::{UpdateResult, WindowCanvas}; -use crate::renderer::managers::FontDetails; -use crate::ui::*; +use std::rc::Rc; #[derive(Clone)] pub struct TextCharacter { @@ -45,11 +45,11 @@ impl TextCharacter { &self.color } - pub fn update_position(&mut self, current: &mut Rect) { + pub fn update_position(&mut self, current: &mut Rect, config: &Config) { if self.is_new_line() { - let y = (self.line * self.source.height() as usize) as i32; - current.set_x(0); - current.set_y(y); + let y = self.source.height() as i32; + current.set_x(config.editor_left_margin()); + current.set_y(current.y() + y); } else { self.dest.set_x(current.x()); self.dest.set_y(current.y()); @@ -60,12 +60,11 @@ impl TextCharacter { } pub fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult { - let config = &renderer.config.editor_config; - let font_details = FontDetails::new( - config.font_path.as_str(), - config.character_size.clone(), - ); - let font = renderer.font_manager + let config = renderer.config().editor_config(); + let font_details = + FontDetails::new(config.font_path().as_str(), config.character_size().clone()); + let font = renderer + .font_manager() .load(&font_details) .unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); @@ -79,10 +78,10 @@ impl TextCharacter { color: self.color.clone(), font: font_details.clone(), }; - renderer.texture_manager + renderer + .texture_manager() .load_text(&mut details, &font) .unwrap_or_else(|_| panic!("Could not create texture for {:?}", c)); - println!("texture for '{}' created", self.text_character); self.pending = false; UpdateResult::RefreshPositions @@ -94,16 +93,16 @@ impl TextCharacter { } #[inline] - fn is_pending(&self) -> bool { + pub fn is_pending(&self) -> bool { self.pending } } impl Render for TextCharacter { /** - * Must first create targets so even if new line appear renderer will know - * where move render starting point - */ + * Must first create targets so even if new line appear renderer will know + * where move render starting point + */ fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { if self.is_pending() { return self.update_view(renderer); @@ -112,12 +111,11 @@ impl Render for TextCharacter { return UpdateResult::NoOp; } - let config = &renderer.config.editor_config; - let font_details = FontDetails::new( - config.font_path.as_str(), - config.character_size.clone(), - ); - let font = renderer.font_manager + let config = renderer.config().editor_config(); + let font_details = + FontDetails::new(config.font_path().as_str(), config.character_size().clone()); + let font = renderer + .font_manager() .load(&font_details) .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); @@ -127,7 +125,7 @@ impl Render for TextCharacter { color: self.color.clone(), font: font_details.clone(), }; - if let Ok(texture) = renderer.texture_manager.load_text(&mut details, &font) { + if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) { renderer.render_texture(canvas, &texture, &self.source, &self.dest); } UpdateResult::NoOp @@ -139,3 +137,13 @@ impl Update for TextCharacter { UpdateResult::NoOp } } + +impl ClickHandler for TextCharacter { + fn on_left_click(&mut self, _point: &Point, _config: &Config) -> UpdateResult { + UpdateResult::MoveCaret(self.dest().clone()) + } + + fn is_left_click_target(&self, point: &Point) -> bool { + is_in_rect(point, self.dest()) + } +}