Add circleci and codecov (#13)
* Add circleci and codecov * Update version * Fix format test * Fix CI * Add tarpaulin * Format code * Fix tarpaulin * lexers tests * Remove non-lexer token type, add more tests * More tests * More tests * Refactor, some test, split code, add more traits * Add codecov config * Fix code style * Test text character * Menu bar tests * Caret tests * Refactor caret, more caret tests
This commit is contained in:
parent
0cf8629868
commit
4e13b4d2d7
54
.circleci/config.yml
Normal file
54
.circleci/config.yml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
working_directory: ~/rider
|
||||||
|
docker:
|
||||||
|
- image: abronan/rust-circleci:latest
|
||||||
|
|
||||||
|
environment:
|
||||||
|
CODECOV_TOKEN: "e58da505-19f2-481c-8068-e845cb36fbe4"
|
||||||
|
TZ: "/usr/share/zoneinfo/Europe/Paris"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: project-cache
|
||||||
|
- run:
|
||||||
|
name: Install dependencies
|
||||||
|
command: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y -qq xvfb mesa-utils mesa-vdpau-drivers
|
||||||
|
apt-get install -y libssl-dev pkg-config cmake zlib1g-dev
|
||||||
|
apt-get install -q -y libsdl2-dev libsdl2-2.0-0 libsdl2-gfx-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev
|
||||||
|
- run:
|
||||||
|
name: Check formatting
|
||||||
|
command: |
|
||||||
|
rustfmt --version
|
||||||
|
cargo fmt -- --error-on-unformatted --unstable-features --check
|
||||||
|
- run:
|
||||||
|
name: Nightly Build
|
||||||
|
command: |
|
||||||
|
rustup run nightly rustc --version --verbose
|
||||||
|
rustup run nightly cargo --version --verbose
|
||||||
|
rustup run nightly cargo build
|
||||||
|
- run:
|
||||||
|
name: Test and code coverage
|
||||||
|
command: |
|
||||||
|
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
|
||||||
|
Xvfb :5 -screen 0 800x600x24 +extension GLX +render -noreset &
|
||||||
|
export DISPLAY=:5
|
||||||
|
export XDG_RUNTIME_DIR=$(pwd)
|
||||||
|
rustup run nightly cargo tarpaulin --ciserver circle-ci --out Xml
|
||||||
|
- run:
|
||||||
|
name: Upload Coverage
|
||||||
|
command: |
|
||||||
|
wget -O - -q "https://codecov.io/bash" > .codecov
|
||||||
|
chmod +x .codecov
|
||||||
|
./.codecov -t $CODECOV_TOKEN
|
||||||
|
echo "Uploaded code coverage"
|
||||||
|
- save_cache:
|
||||||
|
key: project-cache
|
||||||
|
paths:
|
||||||
|
- "~/.cargo"
|
||||||
|
- "./target"
|
18
.codecov.yml
Normal file
18
.codecov.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
codecov:
|
||||||
|
notify:
|
||||||
|
require_ci_to_pass: yes
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
precision: 2
|
||||||
|
round: down
|
||||||
|
range: "70...100"
|
||||||
|
|
||||||
|
status:
|
||||||
|
project: yes
|
||||||
|
patch: yes
|
||||||
|
changes: yes
|
||||||
|
|
||||||
|
comment:
|
||||||
|
layout: "header, diff"
|
||||||
|
behavior: default
|
||||||
|
require_changes: no
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@
|
|||||||
.idea
|
.idea
|
||||||
log
|
log
|
||||||
.rider
|
.rider
|
||||||
|
.codecov
|
||||||
|
cobertura.xml
|
||||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -416,6 +416,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"plex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -14,6 +14,7 @@ serde_derive = "*"
|
|||||||
log = "*"
|
log = "*"
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
simplelog = "*"
|
simplelog = "*"
|
||||||
|
lazy_static = "*"
|
||||||
|
|
||||||
[dependencies.sdl2]
|
[dependencies.sdl2]
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
|
@ -18,8 +18,9 @@ Text editor in rust
|
|||||||
* [ ] Lock scroll when no available content
|
* [ ] Lock scroll when no available content
|
||||||
* [ ] Config edit menu
|
* [ ] Config edit menu
|
||||||
* [ ] Project tree
|
* [ ] Project tree
|
||||||
* [ ] Cover `rider` with tests
|
* [ ] Cover `rider` with tests at least 50%
|
||||||
* [x] Handle resize window
|
* [x] Handle resize window
|
||||||
|
* [ ] Selection
|
||||||
|
|
||||||
### v1.1
|
### v1.1
|
||||||
|
|
||||||
@ -29,6 +30,8 @@ Text editor in rust
|
|||||||
* [ ] Git support
|
* [ ] Git support
|
||||||
* [ ] Context menu
|
* [ ] Context menu
|
||||||
* [ ] Keep indent
|
* [ ] Keep indent
|
||||||
|
* [ ] Multi-selection
|
||||||
|
* [ ] Cover `rider` with tests at least 75%
|
||||||
|
|
||||||
### v1.2
|
### v1.2
|
||||||
* [ ] Multi-caret
|
* [ ] Multi-caret
|
||||||
|
130
guitest/main.rs
Normal file
130
guitest/main.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod file_editor {
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_text() {
|
||||||
|
use crate::tests::support;
|
||||||
|
let config = support::build_config();
|
||||||
|
let canvas = support::build_canvas();
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(Arc::clone(&config), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut editor = FileEditor::new(Arc::clone(&config));
|
||||||
|
let mut file = EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
||||||
|
file.prepare_ui(&mut renderer);
|
||||||
|
assert_eq!(editor.open_file(file).is_none(), true);
|
||||||
|
assert_eq!(editor.caret().position().text_position(), 0);
|
||||||
|
assert_eq!(editor.file().is_some(), true);
|
||||||
|
assert_eq!(editor.file().unwrap().sections().len(), 1);
|
||||||
|
assert_eq!(editor.file().unwrap().get_character_at(0).is_some(), true);
|
||||||
|
|
||||||
|
editor.insert_text("z".to_string(), &mut renderer);
|
||||||
|
assert_eq!(editor.caret().position().text_position(), 1);
|
||||||
|
assert_eq!(editor.file().is_some(), true);
|
||||||
|
assert_eq!(editor.file().unwrap().buffer(), "zfoo".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod text_character {
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_source() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let canvas = support::build_canvas();
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(Arc::clone(&config), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(1, 12, 123), Arc::clone(&config));
|
||||||
|
widget.prepare_ui(&mut renderer);
|
||||||
|
assert_eq!(widget.source(), &Rect::new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_dest() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let canvas = support::build_canvas();
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(Arc::clone(&config), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(1, 12, 123), Arc::clone(&config));
|
||||||
|
widget.prepare_ui(&mut renderer);
|
||||||
|
assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_color() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let canvas = support::build_canvas();
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(Arc::clone(&config), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(1, 12, 123), Arc::clone(&config));
|
||||||
|
widget.prepare_ui(&mut renderer);
|
||||||
|
assert_eq!(widget.color(), &Color::RGB(1, 12, 123));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_update_position_of_new_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let video_subsystem = sdl_context.video().unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let window = video_subsystem
|
||||||
|
.window("Test", 1, 1)
|
||||||
|
.borderless()
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let canvas = window.into_canvas().accelerated().build().unwrap();
|
||||||
|
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(Arc::clone(&config), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.prepare_ui(&mut renderer);
|
||||||
|
let mut current = Rect::new(0, 0, 0, 0);
|
||||||
|
widget.update_position(&mut current);
|
||||||
|
assert_eq!(current, Rect::new(0, 0, 0, 0));
|
||||||
|
assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn main() {
|
||||||
|
let config = support::build_config();
|
||||||
|
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let video_subsystem = sdl_context.video().unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let window = video_subsystem
|
||||||
|
.window("Test", 1, 1)
|
||||||
|
.borderless()
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let canvas = window.into_canvas().accelerated().build().unwrap();
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
use crate::app::caret_manager;
|
use crate::app::caret_manager;
|
||||||
use crate::app::file_content_manager;
|
use crate::app::file_content_manager;
|
||||||
use crate::app::{UpdateResult, WindowCanvas as WC};
|
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::*;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::caret::Caret;
|
use crate::ui::caret::*;
|
||||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
|
||||||
use crate::ui::file::editor_file::EditorFile;
|
use crate::ui::file::editor_file::EditorFile;
|
||||||
use crate::ui::file::*;
|
use crate::ui::file::*;
|
||||||
use crate::ui::menu_bar::MenuBar;
|
use crate::ui::menu_bar::MenuBar;
|
||||||
@ -48,10 +47,6 @@ impl AppState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config(&self) -> &Arc<RwLock<Config>> {
|
|
||||||
&self.config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_editor(&self) -> &FileEditor {
|
pub fn file_editor(&self) -> &FileEditor {
|
||||||
&self.file_editor
|
&self.file_editor
|
||||||
}
|
}
|
||||||
@ -62,9 +57,9 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AppState {
|
impl Render for AppState {
|
||||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _parent: Parent) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _parent: Parent) {
|
||||||
self.file_editor.render(canvas, renderer, None);
|
self.file_editor.render(canvas, renderer, None);
|
||||||
self.menu_bar.render(canvas, renderer, None)
|
self.menu_bar.render(canvas, renderer, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
@ -108,3 +103,9 @@ impl AppState {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConfigHolder for AppState {
|
||||||
|
fn config(&self) -> &ConfigAccess {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub use crate::app::app_state::AppState;
|
pub use crate::app::app_state::AppState;
|
||||||
pub use crate::config::Config;
|
pub use crate::config::{Config, ConfigAccess, ConfigHolder};
|
||||||
pub use crate::renderer::Renderer;
|
pub use crate::renderer::Renderer;
|
||||||
use crate::themes::*;
|
use crate::themes::*;
|
||||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||||
@ -251,8 +251,10 @@ impl Application {
|
|||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn config(&self) -> &Arc<RwLock<Config>> {
|
impl ConfigHolder for Application {
|
||||||
|
fn config(&self) -> &ConfigAccess {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
114
src/config/config.rs
Normal file
114
src/config/config.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use crate::config::creator;
|
||||||
|
use crate::config::EditorConfig;
|
||||||
|
use crate::lexer::Language;
|
||||||
|
use crate::themes::Theme;
|
||||||
|
use dirs;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub type LanguageMapping = HashMap<String, Language>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
scroll_speed: i32,
|
||||||
|
menu_height: u16,
|
||||||
|
editor_config: EditorConfig,
|
||||||
|
theme: Theme,
|
||||||
|
extensions_mapping: LanguageMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
creator::create();
|
||||||
|
let editor_config = EditorConfig::new();
|
||||||
|
let mut extensions_mapping = HashMap::new();
|
||||||
|
extensions_mapping.insert(".".to_string(), Language::PlainText);
|
||||||
|
extensions_mapping.insert("txt".to_string(), Language::PlainText);
|
||||||
|
extensions_mapping.insert("rs".to_string(), Language::Rust);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
width: 1024,
|
||||||
|
height: 860,
|
||||||
|
scroll_speed: 10,
|
||||||
|
menu_height: 60,
|
||||||
|
theme: Theme::load(editor_config.current_theme().clone()),
|
||||||
|
editor_config,
|
||||||
|
extensions_mapping,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_speed(&self) -> i32 {
|
||||||
|
self.scroll_speed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> u32 {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_width(&mut self, w: u32) {
|
||||||
|
self.width = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_height(&mut self, h: u32) {
|
||||||
|
self.height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extensions_mapping(&self) -> &LanguageMapping {
|
||||||
|
&self.extensions_mapping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::lexer::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_language_mapping() {
|
||||||
|
let config = Config::new();
|
||||||
|
|
||||||
|
let mapping = config.extensions_mapping();
|
||||||
|
{
|
||||||
|
let mut keys: Vec<String> = mapping.keys().map(|s| s.to_string()).collect();
|
||||||
|
let mut expected: Vec<String> =
|
||||||
|
vec![".".to_string(), "txt".to_string(), "rs".to_string()];
|
||||||
|
keys.sort();
|
||||||
|
expected.sort();
|
||||||
|
assert_eq!(keys, expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut keys: Vec<Language> = mapping.values().map(|s| s.clone()).collect();
|
||||||
|
let mut expected: Vec<Language> =
|
||||||
|
vec![Language::PlainText, Language::PlainText, Language::Rust];
|
||||||
|
keys.sort();
|
||||||
|
expected.sort();
|
||||||
|
assert_eq!(keys, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,23 +6,28 @@ use std::path;
|
|||||||
|
|
||||||
pub fn create() {
|
pub fn create() {
|
||||||
if !themes_dir().exists() {
|
if !themes_dir().exists() {
|
||||||
fs::create_dir_all(&themes_dir())
|
let r = fs::create_dir_all(&themes_dir());
|
||||||
.unwrap_or_else(|_| panic!("Cannot create themes config directory"));
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot create themes config directory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fonts_dir().exists() {
|
if !fonts_dir().exists() {
|
||||||
fs::create_dir_all(&fonts_dir())
|
let r = fs::create_dir_all(&fonts_dir());
|
||||||
.unwrap_or_else(|_| panic!("Cannot create fonts config directory"));
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot create fonts config directory"));
|
||||||
write_default_fonts();
|
write_default_fonts();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !log_dir().exists() {
|
if !log_dir().exists() {
|
||||||
fs::create_dir_all(&log_dir()).unwrap_or_else(|_| panic!("Cannot create log directory"));
|
let r = fs::create_dir_all(&log_dir());
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot create log directory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !project_dir().exists() {
|
if !project_dir().exists() {
|
||||||
fs::create_dir_all(&project_dir())
|
let r = fs::create_dir_all(&project_dir());
|
||||||
.unwrap_or_else(|_| panic!("Cannot create project directory"));
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot create project directory"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,14 +36,16 @@ fn write_default_fonts() {
|
|||||||
let mut default_font_path = fonts_dir();
|
let mut default_font_path = fonts_dir();
|
||||||
default_font_path.push("DejaVuSansMono.ttf");
|
default_font_path.push("DejaVuSansMono.ttf");
|
||||||
let contents = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf");
|
let contents = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf");
|
||||||
fs::write(default_font_path, contents.to_vec())
|
let r = fs::write(default_font_path, contents.to_vec());
|
||||||
.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut default_font_path = fonts_dir();
|
let mut default_font_path = fonts_dir();
|
||||||
default_font_path.push("ElaineSans-Medium.ttf");
|
default_font_path.push("ElaineSans-Medium.ttf");
|
||||||
let contents = include_bytes!("../../assets/fonts/ElaineSans-Medium.ttf");
|
let contents = include_bytes!("../../assets/fonts/ElaineSans-Medium.ttf");
|
||||||
fs::write(default_font_path, contents.to_vec())
|
let r = fs::write(default_font_path, contents.to_vec());
|
||||||
.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
r.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
src/config/editor_config.rs
Normal file
44
src/config/editor_config.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use crate::config::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: 14,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,129 +1,16 @@
|
|||||||
use crate::lexer::Language;
|
use std::sync::{Arc, RwLock};
|
||||||
use crate::themes::Theme;
|
|
||||||
use dirs;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
mod creator;
|
pub mod config;
|
||||||
|
pub(crate) mod creator;
|
||||||
pub mod directories;
|
pub mod directories;
|
||||||
|
pub mod editor_config;
|
||||||
|
|
||||||
type LanguageMapping = HashMap<String, Language>;
|
pub use crate::config::config::*;
|
||||||
|
pub use crate::config::directories::*;
|
||||||
|
pub use crate::config::editor_config::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub type ConfigAccess = Arc<RwLock<Config>>;
|
||||||
pub struct EditorConfig {
|
|
||||||
character_size: u16,
|
pub trait ConfigHolder {
|
||||||
font_path: String,
|
fn config(&self) -> &ConfigAccess;
|
||||||
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: 14,
|
|
||||||
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,
|
|
||||||
scroll_speed: i32,
|
|
||||||
menu_height: u16,
|
|
||||||
editor_config: EditorConfig,
|
|
||||||
theme: Theme,
|
|
||||||
extensions_mapping: LanguageMapping,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
creator::create();
|
|
||||||
let editor_config = EditorConfig::new();
|
|
||||||
let mut extensions_mapping = HashMap::new();
|
|
||||||
extensions_mapping.insert(".".to_string(), Language::PlainText);
|
|
||||||
extensions_mapping.insert("txt".to_string(), Language::PlainText);
|
|
||||||
extensions_mapping.insert("rs".to_string(), Language::Rust);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
width: 1024,
|
|
||||||
height: 860,
|
|
||||||
scroll_speed: 10,
|
|
||||||
menu_height: 60,
|
|
||||||
theme: Theme::load(editor_config.current_theme().clone()),
|
|
||||||
editor_config,
|
|
||||||
extensions_mapping,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_speed(&self) -> i32 {
|
|
||||||
self.scroll_speed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> u32 {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_width(&mut self, w: u32) {
|
|
||||||
self.width = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&self) -> u32 {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_height(&mut self, h: u32) {
|
|
||||||
self.height = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extensions_mapping(&self) -> &LanguageMapping {
|
|
||||||
&self.extensions_mapping
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,17 @@ use std::ops::Deref;
|
|||||||
pub mod plain;
|
pub mod plain;
|
||||||
pub mod rust_lang;
|
pub mod rust_lang;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
PlainText,
|
PlainText,
|
||||||
Rust,
|
Rust,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
Whitespace { token: Token },
|
Whitespace { token: Token },
|
||||||
Keyword { token: Token },
|
Keyword { token: Token },
|
||||||
String { token: Token },
|
String { token: Token },
|
||||||
Number { token: Token },
|
|
||||||
Identifier { token: Token },
|
Identifier { token: Token },
|
||||||
Literal { token: Token },
|
Literal { token: Token },
|
||||||
Comment { token: Token },
|
Comment { token: Token },
|
||||||
@ -34,9 +33,6 @@ impl TokenType {
|
|||||||
TokenType::String { token } => TokenType::String {
|
TokenType::String { token } => TokenType::String {
|
||||||
token: token.move_to(line, character, start, end),
|
token: token.move_to(line, character, start, end),
|
||||||
},
|
},
|
||||||
TokenType::Number { token } => TokenType::Number {
|
|
||||||
token: token.move_to(line, character, start, end),
|
|
||||||
},
|
|
||||||
TokenType::Identifier { token } => TokenType::Identifier {
|
TokenType::Identifier { token } => TokenType::Identifier {
|
||||||
token: token.move_to(line, character, start, end),
|
token: token.move_to(line, character, start, end),
|
||||||
},
|
},
|
||||||
@ -78,7 +74,6 @@ impl Deref for TokenType {
|
|||||||
TokenType::Whitespace { token } => token,
|
TokenType::Whitespace { token } => token,
|
||||||
TokenType::Keyword { token } => token,
|
TokenType::Keyword { token } => token,
|
||||||
TokenType::String { token } => token,
|
TokenType::String { token } => token,
|
||||||
TokenType::Number { token } => token,
|
|
||||||
TokenType::Identifier { token } => token,
|
TokenType::Identifier { token } => token,
|
||||||
TokenType::Literal { token } => token,
|
TokenType::Literal { token } => token,
|
||||||
TokenType::Comment { token } => token,
|
TokenType::Comment { token } => token,
|
||||||
@ -88,7 +83,13 @@ impl Deref for TokenType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Span {
|
||||||
|
pub lo: usize,
|
||||||
|
pub hi: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
line: usize,
|
line: usize,
|
||||||
character: usize,
|
character: usize,
|
||||||
@ -97,12 +98,6 @@ pub struct Token {
|
|||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Span {
|
|
||||||
pub lo: usize,
|
|
||||||
pub hi: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self {
|
pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -157,3 +152,73 @@ pub fn parse(text: String, language: &Language) -> Vec<TokenType> {
|
|||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::lexer::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_plain() {
|
||||||
|
let buffer = "foo bar";
|
||||||
|
let language = &Language::PlainText;
|
||||||
|
let result = parse(buffer.to_string(), language);
|
||||||
|
assert_eq!(result.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_rust() {
|
||||||
|
let buffer = "foo bar";
|
||||||
|
let language = &Language::Rust;
|
||||||
|
let result = parse(buffer.to_string(), language);
|
||||||
|
assert_eq!(result.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_text() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.text();
|
||||||
|
let text: String = "a".to_string();
|
||||||
|
let expected = &text;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_line() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.line();
|
||||||
|
let expected = 1;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_character() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.character();
|
||||||
|
let expected = 2;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_start() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.start();
|
||||||
|
let expected = 3;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_end() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.end();
|
||||||
|
let expected = 4;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_value_for_move_to() {
|
||||||
|
let token = Token::new("a".to_string(), 1, 2, 3, 4);
|
||||||
|
let result = token.move_to(5, 6, 7, 8);
|
||||||
|
let expected = Token::new("a".to_string(), 5, 6, 7, 8);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -77,3 +77,140 @@ pub mod lexer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::lexer::plain::*;
|
||||||
|
use crate::lexer::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_simple_text() {
|
||||||
|
let code = "foo";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
}];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_long_text() {
|
||||||
|
let code = "foobarhelloworldexamplecomtesttest";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![TokenType::Identifier {
|
||||||
|
token: Token::new(
|
||||||
|
"foobarhelloworldexamplecomtesttest".to_string(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
34,
|
||||||
|
),
|
||||||
|
}];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_space() {
|
||||||
|
let code = "foo bar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 4, 4, 7),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_one_multi_character_space() {
|
||||||
|
let code = "foo bar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 4, 4, 5),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 5, 5, 6),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 6, 6, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 8, 8, 9),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 9, 9, 12),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_multiple_spaces() {
|
||||||
|
let code = "foo bar hello world";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 4, 4, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("hello".to_string(), 0, 8, 8, 13),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 13, 13, 14),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("world".to_string(), 0, 14, 14, 19),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_new_line() {
|
||||||
|
let code = "foo\nbar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new("\n".to_string(), 1, 0, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 1, 1, 4, 7),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,23 +11,23 @@ pub mod lexer {
|
|||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
r"(\d+|\d+\.\d+|'[\S]')" => (TokenType::Literal {
|
r"([0-9]+|[0-9]+\.[0-9]+|'[^']')" => (TokenType::Literal {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
r"[+-/*%=<>]" => (TokenType::Operator {
|
r"(->|[+-/*%=<>#])" => (TokenType::Operator {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
r"(:|::|\{|\}|\[|\]|,)" => (TokenType::Separator {
|
r"(:|::|\{|\}|\[|\]|;|,|\)|\()" => (TokenType::Separator {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
r"(let|fn|type|struct|pub|impl|for|self|Self|mod|use|enum)" => (TokenType::Keyword {
|
r"(let|fn|type|struct|pub|impl|for|self|Self|mod|use|enum|(iu)(8|16|32)|usize|bool)" => (TokenType::Keyword {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
r"[^ \t\r\n:+-/*,]+" => (TokenType::Identifier {
|
r"[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
}
|
}
|
||||||
@ -94,3 +94,383 @@ pub mod lexer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::lexer::rust_lang::*;
|
||||||
|
use crate::lexer::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_simple_text() {
|
||||||
|
let code = "foo";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
}];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_long_text() {
|
||||||
|
let code = "foobarhelloworldexamplecomtesttest";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![TokenType::Identifier {
|
||||||
|
token: Token::new(
|
||||||
|
"foobarhelloworldexamplecomtesttest".to_string(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
34,
|
||||||
|
),
|
||||||
|
}];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_space() {
|
||||||
|
let code = "foo bar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 4, 4, 7),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_one_multi_character_space() {
|
||||||
|
let code = "foo bar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 9),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 9, 9, 12),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_multiple_spaces() {
|
||||||
|
let code = "foo bar hello world";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 0, 4, 4, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("hello".to_string(), 0, 8, 8, 13),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 13, 13, 14),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("world".to_string(), 0, 14, 14, 19),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_text_with_new_line() {
|
||||||
|
let code = "foo\nbar";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new("\n".to_string(), 1, 0, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("bar".to_string(), 1, 1, 4, 7),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_keyword_let_and_char_literal() {
|
||||||
|
let code = "let a = 'b';";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Keyword {
|
||||||
|
token: Token::new("let".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("a".to_string(), 0, 4, 4, 5),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 5, 5, 6),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("=".to_string(), 0, 6, 6, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Literal {
|
||||||
|
token: Token::new("'b'".to_string(), 0, 8, 8, 11),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(";".to_string(), 0, 11, 11, 12),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_keyword_let_and_number_literal() {
|
||||||
|
let code = "let a = 684;";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Keyword {
|
||||||
|
token: Token::new("let".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("a".to_string(), 0, 4, 4, 5),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 5, 5, 6),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("=".to_string(), 0, 6, 6, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Literal {
|
||||||
|
token: Token::new("684".to_string(), 0, 8, 8, 11),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(";".to_string(), 0, 11, 11, 12),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_calculation() {
|
||||||
|
let code = "let a = b + 684;";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Keyword {
|
||||||
|
token: Token::new("let".to_string(), 0, 0, 0, 3),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 3, 3, 4),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("a".to_string(), 0, 4, 4, 5),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 5, 5, 6),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("=".to_string(), 0, 6, 6, 7),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("b".to_string(), 0, 8, 8, 9),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 9, 9, 10),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("+".to_string(), 0, 10, 10, 11),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 11, 11, 12),
|
||||||
|
},
|
||||||
|
TokenType::Literal {
|
||||||
|
token: Token::new("684".to_string(), 0, 12, 12, 15),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(";".to_string(), 0, 15, 15, 16),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_function() {
|
||||||
|
let code = r"fn foo(a: i32, b: i32) -> int {
|
||||||
|
a % b
|
||||||
|
}";
|
||||||
|
let lexer = lexer::Lexer::new(code);
|
||||||
|
let result: Vec<TokenType> = lexer.map(|pair| pair.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Keyword {
|
||||||
|
token: Token::new("fn".to_string(), 0, 0, 0, 2),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 2, 2, 3),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("foo".to_string(), 0, 3, 3, 6),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("(".to_string(), 0, 6, 6, 7),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("a".to_string(), 0, 7, 7, 8),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(":".to_string(), 0, 8, 8, 9),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 9, 9, 10),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("i32".to_string(), 0, 10, 10, 13),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new(",".to_string(), 0, 13, 13, 14),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 14, 14, 15),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("b".to_string(), 0, 15, 15, 16),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(":".to_string(), 0, 16, 16, 17),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 17, 17, 18),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("i32".to_string(), 0, 18, 18, 21),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(")".to_string(), 0, 21, 21, 22),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 22, 22, 23),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("->".to_string(), 0, 23, 23, 25),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 25, 25, 26),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("int".to_string(), 0, 26, 26, 29),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 29, 29, 30),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("{".to_string(), 0, 30, 30, 31),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new("\n".to_string(), 1, 0, 31, 32),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 1, 1, 32, 44),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("a".to_string(), 1, 13, 44, 45),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 1, 14, 45, 46),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("%".to_string(), 1, 15, 46, 47),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 1, 16, 47, 48),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("b".to_string(), 1, 17, 48, 49),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new("\n".to_string(), 2, 0, 49, 50),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 2, 1, 50, 58),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("}".to_string(), 2, 9, 58, 59),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_parse_derive() {
|
||||||
|
let buffer = "#[derive(Debug, Clone)]";
|
||||||
|
let result: Vec<TokenType> = lexer::Lexer::new(buffer).map(|p| p.0).collect();
|
||||||
|
let expected: Vec<TokenType> = vec![
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new("#".to_string(), 0, 0, 0, 1),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("[".to_string(), 0, 1, 1, 2),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("derive".to_string(), 0, 2, 2, 8),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("(".to_string(), 0, 8, 8, 9),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("Debug".to_string(), 0, 9, 9, 14),
|
||||||
|
},
|
||||||
|
TokenType::Operator {
|
||||||
|
token: Token::new(",".to_string(), 0, 14, 14, 15),
|
||||||
|
},
|
||||||
|
TokenType::Whitespace {
|
||||||
|
token: Token::new(" ".to_string(), 0, 15, 15, 16),
|
||||||
|
},
|
||||||
|
TokenType::Identifier {
|
||||||
|
token: Token::new("Clone".to_string(), 0, 16, 16, 21),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new(")".to_string(), 0, 21, 21, 22),
|
||||||
|
},
|
||||||
|
TokenType::Separator {
|
||||||
|
token: Token::new("]".to_string(), 0, 22, 22, 23),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
19
src/main.rs
19
src/main.rs
@ -13,6 +13,8 @@ extern crate serde_json;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate simplelog;
|
extern crate simplelog;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
use crate::app::Application;
|
use crate::app::Application;
|
||||||
use crate::config::directories::log_dir;
|
use crate::config::directories::log_dir;
|
||||||
@ -25,22 +27,27 @@ pub mod app;
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests;
|
||||||
pub mod themes;
|
pub mod themes;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
|
use simplelog::SharedLogger;
|
||||||
|
|
||||||
let mut log_file_path = log_dir();
|
let mut log_file_path = log_dir();
|
||||||
log_file_path.push("rider.log");
|
log_file_path.push("rider.log");
|
||||||
|
|
||||||
CombinedLogger::init(vec![
|
let mut outputs: Vec<Box<SharedLogger>> = vec![WriteLogger::new(
|
||||||
TermLogger::new(LevelFilter::Warn, Config::default()).unwrap(),
|
|
||||||
WriteLogger::new(
|
|
||||||
LevelFilter::Info,
|
LevelFilter::Info,
|
||||||
Config::default(),
|
Config::default(),
|
||||||
File::create(log_file_path).unwrap(),
|
File::create(log_file_path).unwrap(),
|
||||||
),
|
)];
|
||||||
])
|
if let Some(term) = TermLogger::new(LevelFilter::Warn, Config::default()) {
|
||||||
.unwrap();
|
outputs.push(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
CombinedLogger::init(outputs).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -2,6 +2,7 @@ use sdl2::image::LoadTexture;
|
|||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::render::{Texture, TextureCreator};
|
use sdl2::render::{Texture, TextureCreator};
|
||||||
use sdl2::ttf::{Font, Sdl2TtfContext};
|
use sdl2::ttf::{Font, Sdl2TtfContext};
|
||||||
|
use sdl2::video::WindowContext as WinCtxt;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
@ -9,6 +10,10 @@ use std::env;
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
//noinspection RsWrongLifetimeParametersNumber
|
||||||
|
pub type RcTex<'l> = Rc<Texture<'l>>;
|
||||||
|
pub type RcFont<'l> = Rc<Font<'l, 'static>>;
|
||||||
|
|
||||||
pub trait ResourceLoader<'l, R> {
|
pub trait ResourceLoader<'l, R> {
|
||||||
type Args: ?Sized;
|
type Args: ?Sized;
|
||||||
|
|
||||||
@ -70,6 +75,12 @@ impl<'a> From<&'a FontDetails> for FontDetails {
|
|||||||
pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator<T>>;
|
pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator<T>>;
|
||||||
pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>;
|
pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>;
|
||||||
|
|
||||||
|
pub trait ManagersHolder<'l> {
|
||||||
|
fn font_manager(&mut self) -> &mut FontManager<'l>;
|
||||||
|
|
||||||
|
fn texture_manager(&mut self) -> &mut TextureManager<'l, WinCtxt>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ResourceManager<'l, K, R, L>
|
pub struct ResourceManager<'l, K, R, L>
|
||||||
where
|
where
|
||||||
@ -132,9 +143,18 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l, T> TextureManager<'l, T> {
|
pub trait TextTextureManager<'l> {
|
||||||
//noinspection RsWrongLifetimeParametersNumber
|
//noinspection RsWrongLifetimeParametersNumber
|
||||||
pub fn load_text(
|
fn load_text(
|
||||||
|
&mut self,
|
||||||
|
details: &mut TextDetails,
|
||||||
|
font: &Rc<Font>,
|
||||||
|
) -> Result<Rc<Texture<'l>>, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> {
|
||||||
|
//noinspection RsWrongLifetimeParametersNumber
|
||||||
|
fn load_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
details: &mut TextDetails,
|
details: &mut TextDetails,
|
||||||
font: &Rc<Font>,
|
font: &Rc<Font>,
|
||||||
|
@ -1,90 +1,5 @@
|
|||||||
use crate::app::WindowCanvas;
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::renderer::managers::TextDetails;
|
|
||||||
use crate::renderer::managers::{FontManager, TextureManager};
|
|
||||||
use sdl2::rect::{Point, Rect};
|
|
||||||
use sdl2::render::{Texture, TextureCreator};
|
|
||||||
use sdl2::ttf::Sdl2TtfContext;
|
|
||||||
use sdl2::video::WindowContext;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::*;
|
|
||||||
|
|
||||||
pub mod managers;
|
pub mod managers;
|
||||||
|
pub mod renderer;
|
||||||
|
|
||||||
pub struct Renderer<'a> {
|
pub use crate::renderer::managers::*;
|
||||||
config: Arc<RwLock<Config>>,
|
pub use crate::renderer::renderer::*;
|
||||||
font_manager: FontManager<'a>,
|
|
||||||
texture_manager: TextureManager<'a, WindowContext>,
|
|
||||||
scroll: Point,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Renderer<'a> {
|
|
||||||
pub fn new(
|
|
||||||
config: Arc<RwLock<Config>>,
|
|
||||||
font_context: &'a Sdl2TtfContext,
|
|
||||||
texture_creator: &'a TextureCreator<WindowContext>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
config,
|
|
||||||
font_manager: FontManager::new(&font_context),
|
|
||||||
texture_manager: TextureManager::new(&texture_creator),
|
|
||||||
scroll: (0, 0).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(&self) -> &Arc<RwLock<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,
|
|
||||||
Some(src.clone()),
|
|
||||||
Some(dest.clone()),
|
|
||||||
0.0,
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = if let Ok(s) = surface {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let texture = self
|
|
||||||
.texture_manager
|
|
||||||
.loader()
|
|
||||||
.create_texture_from_surface(&surface);
|
|
||||||
let texture = if let Ok(t) = texture {
|
|
||||||
Rc::new(t)
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
45
src/renderer/renderer.rs
Normal file
45
src/renderer/renderer.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use crate::app::WindowCanvas as WC;
|
||||||
|
use crate::config::{Config, ConfigAccess, ConfigHolder};
|
||||||
|
use crate::renderer::managers::*;
|
||||||
|
use sdl2::rect::{Point, Rect};
|
||||||
|
use sdl2::render::{Texture, TextureCreator};
|
||||||
|
use sdl2::ttf::Sdl2TtfContext;
|
||||||
|
use sdl2::video::WindowContext as WinCtxt;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
pub struct Renderer<'l> {
|
||||||
|
config: ConfigAccess,
|
||||||
|
font_manager: FontManager<'l>,
|
||||||
|
texture_manager: TextureManager<'l, WinCtxt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Renderer<'l> {
|
||||||
|
pub fn new(
|
||||||
|
config: ConfigAccess,
|
||||||
|
font_context: &'l Sdl2TtfContext,
|
||||||
|
texture_creator: &'l TextureCreator<WinCtxt>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
font_manager: FontManager::new(&font_context),
|
||||||
|
texture_manager: TextureManager::new(&texture_creator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> ManagersHolder<'l> for Renderer<'l> {
|
||||||
|
fn font_manager(&mut self) -> &mut FontManager<'l> {
|
||||||
|
&mut self.font_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
fn texture_manager(&mut self) -> &mut TextureManager<'l, WinCtxt> {
|
||||||
|
&mut self.texture_manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> ConfigHolder for Renderer<'l> {
|
||||||
|
fn config(&self) -> &ConfigAccess {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
}
|
28
src/tests.rs
Normal file
28
src/tests.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
pub mod support {
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use sdl2::render::{Canvas, WindowCanvas};
|
||||||
|
use sdl2::*;
|
||||||
|
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
||||||
|
use std::borrow::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
pub fn build_config() -> Arc<RwLock<Config>> {
|
||||||
|
Arc::new(RwLock::new(Config::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_canvas() -> WindowCanvas {
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let video_subsystem = sdl_context.video().unwrap();
|
||||||
|
|
||||||
|
let window = video_subsystem
|
||||||
|
.window("Test", 1, 1)
|
||||||
|
.borderless()
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
window.into_canvas().accelerated().build().unwrap()
|
||||||
|
}
|
||||||
|
}
|
248
src/ui/caret.rs
248
src/ui/caret.rs
@ -1,248 +0,0 @@
|
|||||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::renderer::Renderer;
|
|
||||||
use crate::ui::text_character::TextCharacter;
|
|
||||||
use crate::ui::*;
|
|
||||||
use sdl2::pixels::Color;
|
|
||||||
use sdl2::rect::{Point, Rect};
|
|
||||||
use sdl2::render::Texture;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum CaretState {
|
|
||||||
Bright,
|
|
||||||
Blur,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum MoveDirection {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
}
|
|
||||||
|
|
||||||
//#[derive(Clone, Debug, PartialEq)]
|
|
||||||
//pub enum CaretLocation {
|
|
||||||
// FirstLineFirstCharacter,
|
|
||||||
// FirstLine(usize), // with character location
|
|
||||||
// LastLineFirstCharacter,
|
|
||||||
// LastLine(usize), // with character location
|
|
||||||
// FirstCharacter(usize),// with line number
|
|
||||||
// LastCharacter(usize), // with line number
|
|
||||||
// Other(usize, usize), // with line number and character number
|
|
||||||
//}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct CaretPosition {
|
|
||||||
text_position: usize,
|
|
||||||
line_number: usize,
|
|
||||||
line_position: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CaretPosition {
|
|
||||||
pub fn new(text_position: usize, line_number: usize, line_position: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
text_position,
|
|
||||||
line_number,
|
|
||||||
line_position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_position(&self) -> usize {
|
|
||||||
self.text_position.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_number(&self) -> usize {
|
|
||||||
self.line_number.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_position(&self) -> usize {
|
|
||||||
self.line_position.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.text_position = 0;
|
|
||||||
self.line_number = 0;
|
|
||||||
self.line_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text_position(&mut self, n: usize) {
|
|
||||||
self.text_position = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_line_number(&mut self, n: usize) {
|
|
||||||
self.line_number = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_line_position(&mut self, n: usize) {
|
|
||||||
self.line_position = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn moved(&self, text_position: i32, line_number: i32, line_position: i32) -> Self {
|
|
||||||
Self {
|
|
||||||
text_position: (self.text_position as i32 + text_position) as usize,
|
|
||||||
line_number: (self.line_number as i32 + line_number) as usize,
|
|
||||||
line_position: (self.line_position as i32 + line_position) as usize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct CaretRenderPosition {
|
|
||||||
dest: Rect,
|
|
||||||
reset_position: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct CaretColor {
|
|
||||||
bright: Color,
|
|
||||||
blur: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CaretColor {
|
|
||||||
pub fn new(bright: Color, blur: Color) -> Self {
|
|
||||||
Self { bright, blur }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bright(&self) -> &Color {
|
|
||||||
&self.bright
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blur(&self) -> &Color {
|
|
||||||
&self.blur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Caret {
|
|
||||||
pending: bool,
|
|
||||||
blink_delay: u8,
|
|
||||||
state: CaretState,
|
|
||||||
position: CaretPosition,
|
|
||||||
dest: Rect,
|
|
||||||
colors: CaretColor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Caret {
|
|
||||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
|
||||||
let read_config = config.read().unwrap();
|
|
||||||
let bright = read_config.theme().caret().bright().color().into();
|
|
||||||
let blur = read_config.theme().caret().blur().color().into();
|
|
||||||
Self {
|
|
||||||
state: CaretState::Bright,
|
|
||||||
blink_delay: 0,
|
|
||||||
dest: Rect::new(0, 0, 6, 0),
|
|
||||||
colors: CaretColor { bright, blur },
|
|
||||||
pending: true,
|
|
||||||
position: CaretPosition {
|
|
||||||
text_position: 0,
|
|
||||||
line_number: 0,
|
|
||||||
line_position: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_state(&mut self) {
|
|
||||||
self.state = if self.state == CaretState::Bright {
|
|
||||||
CaretState::Blur
|
|
||||||
} else {
|
|
||||||
CaretState::Bright
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_caret(&mut self) {
|
|
||||||
self.dest.set_x(0);
|
|
||||||
self.dest.set_y(0);
|
|
||||||
self.position.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_caret(&mut self, position: CaretPosition, pos: Point) {
|
|
||||||
self.position = position;
|
|
||||||
self.dest.set_x(pos.x());
|
|
||||||
self.dest.set_y(pos.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dest(&self) -> &Rect {
|
|
||||||
&self.dest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> &CaretPosition {
|
|
||||||
&self.position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Caret {
|
|
||||||
type Target = CaretPosition;
|
|
||||||
|
|
||||||
fn deref(&self) -> &<Self as Deref>::Target {
|
|
||||||
self.position()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for Caret {
|
|
||||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) -> UR {
|
|
||||||
let dest = match parent {
|
|
||||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
|
||||||
None => self.dest().clone(),
|
|
||||||
};
|
|
||||||
let start = Point::new(dest.x(), dest.y());
|
|
||||||
let end = Point::new(dest.x(), dest.y() + dest.height() as i32);
|
|
||||||
let color = match self.state {
|
|
||||||
CaretState::Bright => self.colors.bright(),
|
|
||||||
CaretState::Blur => self.colors.blur(),
|
|
||||||
}
|
|
||||||
.clone();
|
|
||||||
canvas.set_draw_color(color);
|
|
||||||
canvas
|
|
||||||
.draw_line(start, end)
|
|
||||||
.unwrap_or_else(|_| panic!("Failed to draw a caret"));
|
|
||||||
UR::NoOp
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
|
||||||
if !self.pending {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rect) = get_text_character_rect('W', renderer) {
|
|
||||||
self.dest.set_height(rect.height());
|
|
||||||
}
|
|
||||||
self.pending = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Update for Caret {
|
|
||||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
|
||||||
self.blink_delay += 1;
|
|
||||||
if self.blink_delay >= 30 {
|
|
||||||
self.blink_delay = 0;
|
|
||||||
self.toggle_state();
|
|
||||||
}
|
|
||||||
UR::NoOp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClickHandler for Caret {
|
|
||||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
|
||||||
UR::NoOp
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
|
||||||
is_in_rect(
|
|
||||||
point,
|
|
||||||
&match context {
|
|
||||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
|
||||||
_ => self.dest().clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderBox for Caret {
|
|
||||||
fn render_start_point(&self) -> Point {
|
|
||||||
self.dest().top_left()
|
|
||||||
}
|
|
||||||
}
|
|
322
src/ui/caret/caret.rs
Normal file
322
src/ui/caret/caret.rs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::ui::text_character::TextCharacter;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
|
use sdl2::rect::{Point, Rect};
|
||||||
|
use sdl2::render::Texture;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Caret {
|
||||||
|
pending: bool,
|
||||||
|
blink_delay: u8,
|
||||||
|
state: CaretState,
|
||||||
|
position: CaretPosition,
|
||||||
|
dest: Rect,
|
||||||
|
colors: CaretColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Caret {
|
||||||
|
pub fn new(config: ConfigAccess) -> Self {
|
||||||
|
let read_config = config.read().unwrap();
|
||||||
|
let bright = read_config.theme().caret().bright().color().into();
|
||||||
|
let blur = read_config.theme().caret().blur().color().into();
|
||||||
|
Self {
|
||||||
|
state: CaretState::Bright,
|
||||||
|
blink_delay: 0,
|
||||||
|
dest: Rect::new(0, 0, 6, 0),
|
||||||
|
colors: CaretColor::new(bright, blur),
|
||||||
|
pending: true,
|
||||||
|
position: CaretPosition::new(0, 0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_state(&mut self) {
|
||||||
|
self.state = match self.state {
|
||||||
|
CaretState::Bright => CaretState::Blur,
|
||||||
|
CaretState::Blur => CaretState::Bright,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_caret(&mut self) {
|
||||||
|
self.dest.set_x(0);
|
||||||
|
self.dest.set_y(0);
|
||||||
|
self.position.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_caret(&mut self, position: CaretPosition, pos: Point) {
|
||||||
|
self.position = position;
|
||||||
|
self.dest.set_x(pos.x());
|
||||||
|
self.dest.set_y(pos.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> &CaretPosition {
|
||||||
|
&self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> &CaretState {
|
||||||
|
&self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Caret {
|
||||||
|
type Target = CaretPosition;
|
||||||
|
|
||||||
|
fn deref(&self) -> &<Self as Deref>::Target {
|
||||||
|
self.position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Caret {
|
||||||
|
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) {
|
||||||
|
let dest = match parent {
|
||||||
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
|
None => self.dest().clone(),
|
||||||
|
};
|
||||||
|
let start = Point::new(dest.x(), dest.y());
|
||||||
|
let end = Point::new(dest.x(), dest.y() + dest.height() as i32);
|
||||||
|
let color = match self.state {
|
||||||
|
CaretState::Bright => self.colors.bright(),
|
||||||
|
CaretState::Blur => self.colors.blur(),
|
||||||
|
}
|
||||||
|
.clone();
|
||||||
|
canvas.set_draw_color(color);
|
||||||
|
canvas
|
||||||
|
.draw_line(start, end)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to draw a caret"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
if !self.pending {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rect) = get_text_character_rect('W', renderer) {
|
||||||
|
self.dest.set_height(rect.height());
|
||||||
|
}
|
||||||
|
self.pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update for Caret {
|
||||||
|
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
||||||
|
self.blink_delay += 1;
|
||||||
|
if self.blink_delay >= 30 {
|
||||||
|
self.blink_delay = 0;
|
||||||
|
self.toggle_state();
|
||||||
|
}
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClickHandler for Caret {
|
||||||
|
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
|
is_in_rect(
|
||||||
|
point,
|
||||||
|
&match context {
|
||||||
|
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||||
|
_ => self.dest().clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBox for Caret {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.dest().top_left()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_own_methods {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_move_caret() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
widget.move_caret(widget.moved(10, 21, 34), Point::new(10, 20));
|
||||||
|
let result = (
|
||||||
|
widget.text_position(),
|
||||||
|
widget.line_number(),
|
||||||
|
widget.line_position(),
|
||||||
|
widget.dest().clone(),
|
||||||
|
);
|
||||||
|
let expected = (10, 21, 34, Rect::new(10, 20, 6, 1));
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_reset() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
widget.reset_caret();
|
||||||
|
let result = (
|
||||||
|
widget.text_position(),
|
||||||
|
widget.line_number(),
|
||||||
|
widget.line_position(),
|
||||||
|
widget.dest().clone(),
|
||||||
|
);
|
||||||
|
let expected = (0, 0, 0, Rect::new(0, 0, 6, 1));
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_toggle_state() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
|
||||||
|
let old = widget.state().clone();
|
||||||
|
widget.toggle_state();
|
||||||
|
let new = widget.state().clone();
|
||||||
|
assert_ne!(old, new);
|
||||||
|
|
||||||
|
let old = widget.state().clone();
|
||||||
|
widget.toggle_state();
|
||||||
|
let new = widget.state().clone();
|
||||||
|
assert_ne!(old, new);
|
||||||
|
|
||||||
|
let old = widget.state().clone();
|
||||||
|
widget.toggle_state();
|
||||||
|
let new = widget.state().clone();
|
||||||
|
assert_ne!(old, new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_deref {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_deref_text_position() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0));
|
||||||
|
let result = widget.text_position();
|
||||||
|
let expected: usize = 10;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_deref_line_number() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0));
|
||||||
|
let result = widget.line_number();
|
||||||
|
let expected: usize = 21;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_deref_line_position() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
widget.move_caret(widget.moved(10, 21, 34), Point::new(0, 0));
|
||||||
|
let result = widget.line_position();
|
||||||
|
let expected: usize = 34;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_render_box {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_top_left_point() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = Caret::new(Arc::clone(&config));
|
||||||
|
let result = widget.render_start_point();
|
||||||
|
let expected = Point::new(0, 0);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_click_handler {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = Caret::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(9999, 9999);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = Caret::new(Arc::clone(&config));
|
||||||
|
|
||||||
|
let point = Point::new(0, 0);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = Caret::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(20, 30);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(9999, 9999));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = Caret::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(10, 10);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_on_click_do_nothing() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = Caret::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(12, 34);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(678, 293));
|
||||||
|
let result = widget.on_left_click(&point, &context);
|
||||||
|
let expected = UpdateResult::NoOp;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
43
src/ui/caret/caret_color.rs
Normal file
43
src/ui/caret/caret_color.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use sdl2::pixels::Color;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct CaretColor {
|
||||||
|
bright: Color,
|
||||||
|
blur: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CaretColor {
|
||||||
|
pub fn new(bright: Color, blur: Color) -> Self {
|
||||||
|
Self { bright, blur }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bright(&self) -> &Color {
|
||||||
|
&self.bright
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blur(&self) -> &Color {
|
||||||
|
&self.blur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_getters {
|
||||||
|
use super::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_bright() {
|
||||||
|
let target = CaretColor::new(Color::RGBA(1, 2, 3, 4), Color::RGBA(5, 6, 7, 8));
|
||||||
|
let result = target.bright().clone();
|
||||||
|
let expected = Color::RGBA(1, 2, 3, 4);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_blur() {
|
||||||
|
let target = CaretColor::new(Color::RGBA(1, 2, 3, 4), Color::RGBA(5, 6, 7, 8));
|
||||||
|
let result = target.blur().clone();
|
||||||
|
let expected = Color::RGBA(5, 6, 7, 8);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
123
src/ui/caret/caret_position.rs
Normal file
123
src/ui/caret/caret_position.rs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct CaretPosition {
|
||||||
|
text_position: usize,
|
||||||
|
line_number: usize,
|
||||||
|
line_position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CaretPosition {
|
||||||
|
pub fn new(text_position: usize, line_number: usize, line_position: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
text_position,
|
||||||
|
line_number,
|
||||||
|
line_position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_position(&self) -> usize {
|
||||||
|
self.text_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_number(&self) -> usize {
|
||||||
|
self.line_number
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_position(&self) -> usize {
|
||||||
|
self.line_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.text_position = 0;
|
||||||
|
self.line_number = 0;
|
||||||
|
self.line_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_text_position(&mut self, n: usize) {
|
||||||
|
self.text_position = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_line_number(&mut self, n: usize) {
|
||||||
|
self.line_number = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_line_position(&mut self, n: usize) {
|
||||||
|
self.line_position = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moved(&self, text_position: i32, line_number: i32, line_position: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
text_position: (self.text_position as i32 + text_position) as usize,
|
||||||
|
line_number: (self.line_number as i32 + line_number) as usize,
|
||||||
|
line_position: (self.line_position as i32 + line_position) as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_getters {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_text_position() {
|
||||||
|
let target = CaretPosition::new(1, 2, 3);
|
||||||
|
let result = target.text_position();
|
||||||
|
let expected = 1;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_line_number() {
|
||||||
|
let target = CaretPosition::new(1, 2, 3);
|
||||||
|
let result = target.line_number();
|
||||||
|
let expected = 2;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_line_position() {
|
||||||
|
let target = CaretPosition::new(1, 2, 3);
|
||||||
|
let result = target.line_position();
|
||||||
|
let expected = 3;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_moved() {
|
||||||
|
let target = CaretPosition::new(1, 2, 3);
|
||||||
|
let result = target.moved(1, 2, 3);
|
||||||
|
let expected = CaretPosition::new(2, 4, 6);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_setters {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_text_position() {
|
||||||
|
let mut target = CaretPosition::new(1, 2, 3);
|
||||||
|
target.set_text_position(100);
|
||||||
|
let result = target.text_position();
|
||||||
|
let expected = 100;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_line_number() {
|
||||||
|
let mut target = CaretPosition::new(1, 2, 3);
|
||||||
|
target.set_line_number(200);
|
||||||
|
let result = target.line_number();
|
||||||
|
let expected = 200;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_line_position() {
|
||||||
|
let mut target = CaretPosition::new(1, 2, 3);
|
||||||
|
target.set_line_position(300);
|
||||||
|
let result = target.line_position();
|
||||||
|
let expected = 300;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
21
src/ui/caret/mod.rs
Normal file
21
src/ui/caret/mod.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum CaretState {
|
||||||
|
Bright,
|
||||||
|
Blur,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum MoveDirection {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod caret;
|
||||||
|
pub mod caret_color;
|
||||||
|
pub mod caret_position;
|
||||||
|
|
||||||
|
pub use crate::ui::caret::caret::*;
|
||||||
|
pub use crate::ui::caret::caret_color::*;
|
||||||
|
pub use crate::ui::caret::caret_position::*;
|
@ -13,7 +13,7 @@ use crate::ui::*;
|
|||||||
pub struct EditorFile {
|
pub struct EditorFile {
|
||||||
path: String,
|
path: String,
|
||||||
sections: Vec<EditorFileSection>,
|
sections: Vec<EditorFileSection>,
|
||||||
render_position: Rect,
|
dest: Rect,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
config: Arc<RwLock<Config>>,
|
config: Arc<RwLock<Config>>,
|
||||||
line_height: u32,
|
line_height: u32,
|
||||||
@ -42,7 +42,7 @@ impl EditorFile {
|
|||||||
Self {
|
Self {
|
||||||
path,
|
path,
|
||||||
sections,
|
sections,
|
||||||
render_position,
|
dest: render_position,
|
||||||
buffer,
|
buffer,
|
||||||
config,
|
config,
|
||||||
line_height: 0,
|
line_height: 0,
|
||||||
@ -66,7 +66,7 @@ impl EditorFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_position(&self) -> &Rect {
|
pub fn render_position(&self) -> &Rect {
|
||||||
&self.render_position
|
&self.dest
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
||||||
@ -119,11 +119,10 @@ impl EditorFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for EditorFile {
|
impl Render for EditorFile {
|
||||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) {
|
||||||
for section in self.sections.iter() {
|
for section in self.sections.iter() {
|
||||||
section.render(canvas, renderer, parent);
|
section.render(canvas, renderer, parent);
|
||||||
}
|
}
|
||||||
UR::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
@ -179,6 +178,10 @@ impl ClickHandler for EditorFile {
|
|||||||
|
|
||||||
impl RenderBox for EditorFile {
|
impl RenderBox for EditorFile {
|
||||||
fn render_start_point(&self) -> Point {
|
fn render_start_point(&self) -> Point {
|
||||||
self.render_position.top_left()
|
self.dest.top_left()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,11 +101,10 @@ impl EditorFileSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for EditorFileSection {
|
impl Render for EditorFileSection {
|
||||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) {
|
||||||
for token in self.tokens.iter() {
|
for token in self.tokens.iter() {
|
||||||
token.render(canvas, renderer, parent);
|
token.render(canvas, renderer, parent);
|
||||||
}
|
}
|
||||||
UR::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::*;
|
||||||
use crate::lexer::TokenType;
|
use crate::lexer::TokenType;
|
||||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::*;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
@ -19,7 +19,6 @@ impl TokenType {
|
|||||||
&TokenType::Whitespace { .. } => ch.whitespace().color().into(),
|
&TokenType::Whitespace { .. } => ch.whitespace().color().into(),
|
||||||
&TokenType::Keyword { .. } => ch.keyword().color().into(),
|
&TokenType::Keyword { .. } => ch.keyword().color().into(),
|
||||||
&TokenType::String { .. } => ch.string().color().into(),
|
&TokenType::String { .. } => ch.string().color().into(),
|
||||||
&TokenType::Number { .. } => ch.number().color().into(),
|
|
||||||
&TokenType::Identifier { .. } => ch.identifier().color().into(),
|
&TokenType::Identifier { .. } => ch.identifier().color().into(),
|
||||||
&TokenType::Literal { .. } => ch.literal().color().into(),
|
&TokenType::Literal { .. } => ch.literal().color().into(),
|
||||||
&TokenType::Comment { .. } => ch.comment().color().into(),
|
&TokenType::Comment { .. } => ch.comment().color().into(),
|
||||||
@ -113,14 +112,13 @@ impl Render for EditorFileToken {
|
|||||||
* Must first create targets so even if new line appear renderer will know
|
* Must first create targets so even if new line appear renderer will know
|
||||||
* where move render starting point
|
* where move render starting point
|
||||||
*/
|
*/
|
||||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) {
|
||||||
if self.token_type.is_new_line() {
|
if self.token_type.is_new_line() {
|
||||||
return UR::NoOp;
|
return;
|
||||||
}
|
}
|
||||||
for text_character in self.characters.iter() {
|
for text_character in self.characters.iter() {
|
||||||
text_character.render(canvas, renderer, parent);
|
text_character.render(canvas, renderer, parent);
|
||||||
}
|
}
|
||||||
UR::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
@ -6,6 +6,7 @@ use std::sync::*;
|
|||||||
|
|
||||||
use crate::app::*;
|
use crate::app::*;
|
||||||
use crate::app::{UpdateResult as UR, WindowCanvas as WS};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WS};
|
||||||
|
use crate::config::*;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
|
|
||||||
pub struct FileEditor {
|
pub struct FileEditor {
|
||||||
@ -13,11 +14,11 @@ pub struct FileEditor {
|
|||||||
scroll: Point,
|
scroll: Point,
|
||||||
caret: Caret,
|
caret: Caret,
|
||||||
file: Option<EditorFile>,
|
file: Option<EditorFile>,
|
||||||
config: Arc<RwLock<Config>>,
|
config: ConfigAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileEditor {
|
impl FileEditor {
|
||||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
pub fn new(config: ConfigAccess) -> Self {
|
||||||
let dest = {
|
let dest = {
|
||||||
let c = config.read().unwrap();
|
let c = config.read().unwrap();
|
||||||
Rect::new(
|
Rect::new(
|
||||||
@ -36,10 +37,6 @@ impl FileEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config(&self) -> &Arc<RwLock<Config>> {
|
|
||||||
&self.config
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn caret(&self) -> &Caret {
|
pub fn caret(&self) -> &Caret {
|
||||||
&self.caret
|
&self.caret
|
||||||
}
|
}
|
||||||
@ -166,13 +163,13 @@ impl FileEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for FileEditor {
|
impl Render for FileEditor {
|
||||||
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _parent: Parent) {
|
||||||
canvas.set_clip_rect(self.dest.clone());
|
canvas.set_clip_rect(self.dest.clone());
|
||||||
match self.file() {
|
match self.file() {
|
||||||
Some(file) => file.render(canvas, renderer, Some(self)),
|
Some(file) => file.render(canvas, renderer, Some(self)),
|
||||||
_ => UR::NoOp,
|
_ => (),
|
||||||
};
|
};
|
||||||
self.caret.render(canvas, renderer, Some(self))
|
self.caret.render(canvas, renderer, Some(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
@ -229,6 +226,16 @@ impl RenderBox for FileEditor {
|
|||||||
fn render_start_point(&self) -> Point {
|
fn render_start_point(&self) -> Point {
|
||||||
self.dest.top_left() + self.scroll
|
self.dest.top_left() + self.scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigHolder for FileEditor {
|
||||||
|
fn config(&self) -> &ConfigAccess {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -256,35 +263,4 @@ mod tests {
|
|||||||
assert_eq!(file.path(), first_file.path());
|
assert_eq!(file.path(), first_file.path());
|
||||||
assert_eq!(file.buffer(), first_file.buffer());
|
assert_eq!(file.buffer(), first_file.buffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_text() {
|
|
||||||
let config = Arc::new(RwLock::new(Config::new()));
|
|
||||||
let sdl_context = sdl2::init().unwrap();
|
|
||||||
let video_subsystem = sdl_context.video().unwrap();
|
|
||||||
let window = video_subsystem
|
|
||||||
.window("Test", 1, 1)
|
|
||||||
.borderless()
|
|
||||||
.opengl()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let canvas = window.into_canvas().accelerated().build().unwrap();
|
|
||||||
let font_context = sdl2::ttf::init().unwrap();
|
|
||||||
let texture_creator = canvas.texture_creator();
|
|
||||||
let mut renderer = Renderer::new(config.clone(), &font_context, &texture_creator);
|
|
||||||
|
|
||||||
let mut editor = FileEditor::new(Arc::clone(&config));
|
|
||||||
let mut file = EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
|
||||||
file.prepare_ui(&mut renderer);
|
|
||||||
assert_eq!(editor.open_file(file).is_none(), true);
|
|
||||||
assert_eq!(editor.caret().position().text_position(), 0);
|
|
||||||
assert_eq!(editor.file().is_some(), true);
|
|
||||||
assert_eq!(editor.file().unwrap().sections().len(), 1);
|
|
||||||
assert_eq!(editor.file().unwrap().get_character_at(0).is_some(), true);
|
|
||||||
|
|
||||||
editor.insert_text("z".to_string(), &mut renderer);
|
|
||||||
assert_eq!(editor.caret().position().text_position(), 1);
|
|
||||||
assert_eq!(editor.file().is_some(), true);
|
|
||||||
assert_eq!(editor.file().unwrap().buffer(), "zfoo".to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::*;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::*;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
@ -11,33 +11,30 @@ pub struct MenuBar {
|
|||||||
border_color: Color,
|
border_color: Color,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
config: Arc<RwLock<Config>>,
|
config: ConfigAccess,
|
||||||
pending: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuBar {
|
impl MenuBar {
|
||||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
pub fn new(config: ConfigAccess) -> Self {
|
||||||
let background_color = { config.read().unwrap().theme().background().into() };
|
let (background_color, w, h): (Color, u32, u16) = {
|
||||||
|
let c = config.read().unwrap();
|
||||||
|
(c.theme().background().into(), c.width(), c.menu_height())
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
border_color: Color::RGB(10, 10, 10),
|
border_color: Color::RGB(10, 10, 10),
|
||||||
background_color,
|
background_color,
|
||||||
dest: Rect::new(0, 0, 0, 0),
|
dest: Rect::new(0, 0, w as u32, h as u32),
|
||||||
config,
|
config,
|
||||||
pending: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn background_color(&self) -> &Color {
|
pub fn background_color(&self) -> &Color {
|
||||||
&self.background_color
|
&self.background_color
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dest(&self) -> &Rect {
|
|
||||||
&self.dest
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for MenuBar {
|
impl Render for MenuBar {
|
||||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) {
|
||||||
canvas.set_clip_rect(self.dest.clone());
|
canvas.set_clip_rect(self.dest.clone());
|
||||||
canvas.set_draw_color(self.background_color.clone());
|
canvas.set_draw_color(self.background_color.clone());
|
||||||
canvas
|
canvas
|
||||||
@ -54,18 +51,12 @@ impl Render for MenuBar {
|
|||||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||||
|
|
||||||
UR::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, _renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, _renderer: &mut Renderer) {
|
||||||
if !self.pending {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let width = self.config.read().unwrap().width();
|
let width = self.config.read().unwrap().width();
|
||||||
let height = self.config.read().unwrap().menu_height() as u32;
|
let height = self.config.read().unwrap().menu_height() as u32;
|
||||||
self.dest = Rect::new(0, 0, width, height);
|
self.dest = Rect::new(0, 0, width, height);
|
||||||
self.pending = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,17 +70,15 @@ impl Update for MenuBar {
|
|||||||
|
|
||||||
impl ClickHandler for MenuBar {
|
impl ClickHandler for MenuBar {
|
||||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||||
unimplemented!()
|
UR::NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
is_in_rect(
|
let rect = match context.clone() {
|
||||||
point,
|
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||||
&match context {
|
|
||||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
|
||||||
_ => self.dest().clone(),
|
_ => self.dest().clone(),
|
||||||
},
|
};
|
||||||
)
|
is_in_rect(point, &rect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,4 +86,128 @@ impl RenderBox for MenuBar {
|
|||||||
fn render_start_point(&self) -> Point {
|
fn render_start_point(&self) -> Point {
|
||||||
self.dest.top_left()
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_getters {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_background_color() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let result = widget.background_color().clone();
|
||||||
|
let expected = Color::RGBA(18, 18, 18, 0);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_dest() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let (w, h) = {
|
||||||
|
let c = config.read().unwrap();
|
||||||
|
(c.width() as u32, c.menu_height() as u32)
|
||||||
|
};
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let result = widget.dest().clone();
|
||||||
|
let expected = Rect::new(0, 0, w, h);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_render_box {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_top_left_point() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let result = widget.render_start_point();
|
||||||
|
let expected = Point::new(0, 0);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_click_handler {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(9999, 9999);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(20, 30);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(20, 30);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(9999, 9999));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let (w, h) = {
|
||||||
|
(
|
||||||
|
config.read().unwrap().width(),
|
||||||
|
config.read().unwrap().menu_height(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(w as i32 + 120, h as i32 + 130);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(130, 140));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_on_click_do_nothing() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget = MenuBar::new(Arc::clone(&config));
|
||||||
|
let point = Point::new(12, 34);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(678, 293));
|
||||||
|
let result = widget.on_left_click(&point, &context);
|
||||||
|
let expected = UpdateResult::NoOp;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
120
src/ui/mod.rs
120
src/ui/mod.rs
@ -1,8 +1,8 @@
|
|||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
|
|
||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::*;
|
||||||
use crate::renderer::managers::FontDetails;
|
use crate::renderer::managers::*;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
pub mod caret;
|
pub mod caret;
|
||||||
@ -30,41 +30,32 @@ pub enum UpdateContext<'l> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
||||||
let start = rect.top_left();
|
rect.contains_point(point.clone())
|
||||||
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 fn get_text_character_rect(c: char, renderer: &mut Renderer) -> Option<Rect> {
|
#[inline]
|
||||||
let font_details = FontDetails::new(
|
pub fn build_font_details<T>(config_holder: &T) -> FontDetails
|
||||||
|
where
|
||||||
|
T: ConfigHolder,
|
||||||
|
{
|
||||||
|
let c = config_holder.config().read().unwrap();
|
||||||
|
FontDetails::new(
|
||||||
|
c.editor_config().font_path().as_str(),
|
||||||
|
c.editor_config().character_size().clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_text_character_rect<'l, T>(c: char, renderer: &mut T) -> Option<Rect>
|
||||||
|
where
|
||||||
|
T: ManagersHolder<'l> + ConfigHolder,
|
||||||
|
{
|
||||||
|
let font_details = build_font_details(renderer);
|
||||||
renderer
|
renderer
|
||||||
.config()
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.editor_config()
|
|
||||||
.font_path()
|
|
||||||
.as_str(),
|
|
||||||
renderer
|
|
||||||
.config()
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.editor_config()
|
|
||||||
.character_size()
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
let font = renderer
|
|
||||||
.font_manager()
|
.font_manager()
|
||||||
.load(&font_details)
|
.load(&font_details)
|
||||||
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
|
.ok()
|
||||||
|
.and_then(|font| font.size_of_char(c).ok())
|
||||||
if let Ok((width, height)) = font.size_of_char(c) {
|
.and_then(|(width, height)| Some(Rect::new(0, 0, width, height)))
|
||||||
Some(Rect::new(0, 0, width, height))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -73,26 +64,73 @@ pub fn move_render_point(p: Point, d: &Rect) -> Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Render {
|
pub trait Render {
|
||||||
fn render(
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent);
|
||||||
&self,
|
|
||||||
canvas: &mut WindowCanvas,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
parent: Parent,
|
|
||||||
) -> UpdateResult;
|
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer);
|
fn prepare_ui(&mut self, renderer: &mut Renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Update {
|
pub trait Update {
|
||||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult;
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ClickHandler {
|
pub trait ClickHandler {
|
||||||
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult;
|
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR;
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool;
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RenderBox {
|
pub trait RenderBox {
|
||||||
fn render_start_point(&self) -> Point;
|
fn render_start_point(&self) -> Point;
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tests::support;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
|
||||||
|
struct ConfigWrapper {
|
||||||
|
pub inner: ConfigAccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigHolder for ConfigWrapper {
|
||||||
|
fn config(&self) -> &ConfigAccess {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_true_if_inside_rect() {
|
||||||
|
let rect = Rect::new(10, 10, 30, 30);
|
||||||
|
let point = Point::new(20, 20);
|
||||||
|
assert_eq!(is_in_rect(&point, &rect), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_not_if_not_inside_rect() {
|
||||||
|
let rect = Rect::new(10, 10, 30, 30);
|
||||||
|
let point = Point::new(41, 41);
|
||||||
|
assert_eq!(is_in_rect(&point, &rect), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_moved_rect() {
|
||||||
|
let rect = Rect::new(10, 20, 30, 40);
|
||||||
|
let point = Point::new(11, 11);
|
||||||
|
assert_eq!(move_render_point(point, &rect), Rect::new(21, 31, 30, 40));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_build_font_details() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let wrapper = ConfigWrapper {
|
||||||
|
inner: config.clone(),
|
||||||
|
};
|
||||||
|
let details = build_font_details(&wrapper);
|
||||||
|
let c = config.read().unwrap();
|
||||||
|
assert_eq!(details.path, c.editor_config().font_path().to_string());
|
||||||
|
assert_eq!(details.size, c.editor_config().character_size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::*;
|
||||||
use crate::lexer::TokenType;
|
use crate::lexer::TokenType;
|
||||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
use crate::renderer::managers::*;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::*;
|
||||||
use crate::ui::caret::CaretPosition;
|
use crate::ui::caret::CaretPosition;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ pub struct TextCharacter {
|
|||||||
source: Rect,
|
source: Rect,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
color: Color,
|
color: Color,
|
||||||
config: Arc<RwLock<Config>>,
|
config: ConfigAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextCharacter {
|
impl TextCharacter {
|
||||||
@ -32,7 +32,7 @@ impl TextCharacter {
|
|||||||
line: usize,
|
line: usize,
|
||||||
last_in_line: bool,
|
last_in_line: bool,
|
||||||
color: Color,
|
color: Color,
|
||||||
config: Arc<RwLock<Config>>,
|
config: ConfigAccess,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text_character,
|
text_character,
|
||||||
@ -50,14 +50,18 @@ impl TextCharacter {
|
|||||||
self.last_in_line
|
self.last_in_line
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dest(&self) -> &Rect {
|
|
||||||
&self.dest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(&self) -> &Rect {
|
pub fn source(&self) -> &Rect {
|
||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_dest(&mut self, rect: &Rect) {
|
||||||
|
self.dest = rect.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_source(&mut self, rect: &Rect) {
|
||||||
|
self.source = rect.clone();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn color(&self) -> &Color {
|
pub fn color(&self) -> &Color {
|
||||||
&self.color
|
&self.color
|
||||||
}
|
}
|
||||||
@ -101,9 +105,9 @@ impl Render for TextCharacter {
|
|||||||
* Must first create targets so even if new line appear renderer will know
|
* Must first create targets so even if new line appear renderer will know
|
||||||
* where move render starting point
|
* where move render starting point
|
||||||
*/
|
*/
|
||||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) {
|
||||||
if self.is_new_line() {
|
if self.is_new_line() {
|
||||||
return UR::NoOp;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let font_details = {
|
let font_details = {
|
||||||
@ -127,20 +131,26 @@ impl Render for TextCharacter {
|
|||||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
};
|
};
|
||||||
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, &dest);
|
canvas
|
||||||
|
.copy_ex(
|
||||||
|
&texture,
|
||||||
|
Some(self.source.clone()),
|
||||||
|
Some(dest.clone()),
|
||||||
|
0.0,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
// let c = Color::RGB(255, 0, 0);
|
// let c = Color::RGB(255, 0, 0);
|
||||||
// canvas.set_draw_color(c);
|
// canvas.set_draw_color(c);
|
||||||
// canvas.draw_rect(dest.clone()).unwrap();
|
// canvas.draw_rect(dest.clone()).unwrap();
|
||||||
UR::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
let font_details = {
|
let font_details = build_font_details(renderer);
|
||||||
let config = renderer.config().read().unwrap();
|
|
||||||
let ec = config.editor_config();
|
|
||||||
FontDetails::new(ec.font_path().as_str(), ec.character_size().clone())
|
|
||||||
};
|
|
||||||
let font = renderer
|
let font = renderer
|
||||||
.font_manager()
|
.font_manager()
|
||||||
.load(&font_details)
|
.load(&font_details)
|
||||||
@ -151,8 +161,8 @@ impl Render for TextCharacter {
|
|||||||
c => c,
|
c => c,
|
||||||
};
|
};
|
||||||
if let Some(rect) = get_text_character_rect(c, renderer) {
|
if let Some(rect) = get_text_character_rect(c, renderer) {
|
||||||
self.source = rect.clone();
|
self.set_source(&rect);
|
||||||
self.dest = rect.clone();
|
self.set_dest(&rect);
|
||||||
}
|
}
|
||||||
let mut details = TextDetails {
|
let mut details = TextDetails {
|
||||||
text: self.text_character.to_string(),
|
text: self.text_character.to_string(),
|
||||||
@ -195,4 +205,311 @@ impl RenderBox for TextCharacter {
|
|||||||
fn render_start_point(&self) -> Point {
|
fn render_start_point(&self) -> Point {
|
||||||
self.dest.top_left()
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_getters {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_is_last_in_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.is_last_in_line(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_true_for_is_new_line_if_new_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.is_new_line(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_false_for_is_new_line_if_new_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget =
|
||||||
|
TextCharacter::new('W', 0, 0, true, Color::RGB(1, 12, 123), Arc::clone(&config));
|
||||||
|
assert_eq!(widget.is_new_line(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_position() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
1,
|
||||||
|
123,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.position(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
1,
|
||||||
|
123,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.line(), 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_text_character() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
87,
|
||||||
|
123,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.text_character(), '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_source() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.source(), &Rect::new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_dest() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_valid_color() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
Color::RGB(1, 12, 123),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
assert_eq!(widget.color(), &Color::RGB(1, 12, 123));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_own_methods {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_update_position_of_new_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let mut current = Rect::new(10, 23, 0, 0);
|
||||||
|
widget.update_position(&mut current);
|
||||||
|
assert_eq!(current, Rect::new(0, 103, 1, 1));
|
||||||
|
assert_eq!(widget.dest(), &Rect::new(0, 103, 30, 40));
|
||||||
|
assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_update_position_of_non_new_line() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('W', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let mut current = Rect::new(10, 23, 0, 0);
|
||||||
|
widget.update_position(&mut current);
|
||||||
|
assert_eq!(current, Rect::new(80, 23, 1, 1));
|
||||||
|
assert_eq!(widget.dest(), &Rect::new(10, 23, 70, 80));
|
||||||
|
assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_click_handler {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let point = Point::new(0, 0);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let point = Point::new(20, 30);
|
||||||
|
let context = UpdateContext::Nothing;
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refute_when_not_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let point = Point::new(20, 30);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(100, 100));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_when_click_target_because_parent() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let point = Point::new(120, 130);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(100, 100));
|
||||||
|
let result = widget.is_left_click_target(&point, &context);
|
||||||
|
assert_eq!(result, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_on_click_return_move_caret() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let position = 1233;
|
||||||
|
let line = 2893;
|
||||||
|
let mut widget = TextCharacter::new(
|
||||||
|
'\n',
|
||||||
|
position.clone(),
|
||||||
|
line.clone(),
|
||||||
|
true,
|
||||||
|
Color::RGB(0, 0, 0),
|
||||||
|
Arc::clone(&config),
|
||||||
|
);
|
||||||
|
let dest = Rect::new(10, 20, 30, 40);
|
||||||
|
widget.set_dest(&dest);
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
|
||||||
|
let point = Point::new(12, 34);
|
||||||
|
let context = UpdateContext::ParentPosition(Point::new(678, 293));
|
||||||
|
let result = widget.on_left_click(&point, &context);
|
||||||
|
let expected = UpdateResult::MoveCaret(dest, CaretPosition::new(position, line, 0));
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_render_box {
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn must_return_top_left_point() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let result = widget.render_start_point();
|
||||||
|
let expected = Point::new(10, 20);
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_update {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::renderer::*;
|
||||||
|
use crate::tests::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::pixels::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_do_nothing() {
|
||||||
|
let config = support::build_config();
|
||||||
|
let mut widget =
|
||||||
|
TextCharacter::new('\n', 0, 0, true, Color::RGB(0, 0, 0), Arc::clone(&config));
|
||||||
|
widget.set_dest(&Rect::new(10, 20, 30, 40));
|
||||||
|
widget.set_source(&Rect::new(50, 60, 70, 80));
|
||||||
|
let result = widget.update(
|
||||||
|
3234,
|
||||||
|
&UpdateContext::ParentPosition(Point::new(234, 234234)),
|
||||||
|
);
|
||||||
|
let expected = UpdateResult::NoOp;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user