Draw caret and handle click.
This commit is contained in:
parent
f01ad336eb
commit
fdf5d0332a
BIN
assets/fonts/DejaVuSansMono.ttf
Normal file
BIN
assets/fonts/DejaVuSansMono.ttf
Normal file
Binary file not shown.
@ -37,9 +37,6 @@ italic
|
||||
``keyword
|
||||
#CB7832
|
||||
|
||||
``keyword
|
||||
#CB7832
|
||||
|
||||
``regex
|
||||
#A4C260
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
@ -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
35
src/config/creator.rs
Normal 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
22
src/config/directories.rs
Normal 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
99
src/config/mod.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ pub struct Token {
|
||||
character: usize,
|
||||
start: usize,
|
||||
end: usize,
|
||||
pub text: String,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
13
src/main.rs
13
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();
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
155
src/themes/config_creator.rs
Normal file
155
src/themes/config_creator.rs
Normal 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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
55
src/ui/menu_bar.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user