Draw caret and handle click.

This commit is contained in:
Adrian Woźniak 2019-01-03 15:19:56 +01:00
parent f01ad336eb
commit fdf5d0332a
22 changed files with 769 additions and 359 deletions

Binary file not shown.

View File

@ -37,9 +37,6 @@ italic
``keyword
#CB7832
``keyword
#CB7832
``regex
#A4C260

View File

@ -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<EditorFile>,
pub current_file: i16,
menu_bar: MenuBar,
files: Vec<EditorFile>,
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
}
}

View File

@ -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()),
}
}
}

View File

@ -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<Window>;
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<Window>;
#[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<Task>,
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)),
_ => (),
},
_ => (),
}
}

35
src/config/creator.rs Normal file
View File

@ -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!"));
}
}

22
src/config/directories.rs Normal file
View File

@ -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
}

99
src/config/mod.rs Normal file
View File

@ -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
}
}

View File

@ -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<EditorFileSection>,
path: String,
sections: Vec<EditorFileSection>,
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
}
}

View File

@ -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<EditorFileToken>,
pub language: Language,
tokens: Vec<EditorFileToken>,
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<EditorFileToken> = 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
}
}

View File

@ -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<Color> 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
}
}

View File

@ -92,7 +92,7 @@ pub struct Token {
character: usize,
start: usize,
end: usize,
pub text: String,
text: String,
}
#[derive(Debug, Clone, Copy)]

View File

@ -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();

View File

@ -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<T>>;
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<D>(&mut self, details: &D) -> Result<Rc<R>, 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<T> {
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,

View File

@ -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<Texture>, 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<Texture>,
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<Rc<Texture>> {
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 {

View File

@ -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<Theme> {
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,
},
},
}
}

View File

@ -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<Color> for SerdeColor {
impl Into<Color> 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()

View File

@ -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)
}
}

55
src/ui/menu_bar.rs Normal file
View File

@ -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())
}
}

View File

@ -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;
}

View File

@ -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())
}
}