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
|
||||
log
|
||||
.rider
|
||||
.codecov
|
||||
cobertura.xml
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -416,6 +416,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"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)",
|
||||
"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)",
|
||||
|
@ -14,6 +14,7 @@ serde_derive = "*"
|
||||
log = "*"
|
||||
env_logger = "*"
|
||||
simplelog = "*"
|
||||
lazy_static = "*"
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.31.0"
|
||||
|
@ -18,8 +18,9 @@ Text editor in rust
|
||||
* [ ] Lock scroll when no available content
|
||||
* [ ] Config edit menu
|
||||
* [ ] Project tree
|
||||
* [ ] Cover `rider` with tests
|
||||
* [ ] Cover `rider` with tests at least 50%
|
||||
* [x] Handle resize window
|
||||
* [ ] Selection
|
||||
|
||||
### v1.1
|
||||
|
||||
@ -29,6 +30,8 @@ Text editor in rust
|
||||
* [ ] Git support
|
||||
* [ ] Context menu
|
||||
* [ ] Keep indent
|
||||
* [ ] Multi-selection
|
||||
* [ ] Cover `rider` with tests at least 75%
|
||||
|
||||
### v1.2
|
||||
* [ ] 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::file_content_manager;
|
||||
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::config::*;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::caret::Caret;
|
||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||
use crate::ui::caret::*;
|
||||
use crate::ui::file::editor_file::EditorFile;
|
||||
use crate::ui::file::*;
|
||||
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 {
|
||||
&self.file_editor
|
||||
}
|
||||
@ -62,9 +57,9 @@ impl 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.menu_bar.render(canvas, renderer, None)
|
||||
self.menu_bar.render(canvas, renderer, None);
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
@ -108,3 +103,9 @@ impl AppState {
|
||||
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::config::Config;
|
||||
pub use crate::config::{Config, ConfigAccess, ConfigHolder};
|
||||
pub use crate::renderer::Renderer;
|
||||
use crate::themes::*;
|
||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||
@ -251,8 +251,10 @@ impl Application {
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Arc<RwLock<Config>> {
|
||||
impl ConfigHolder for Application {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&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() {
|
||||
if !themes_dir().exists() {
|
||||
fs::create_dir_all(&themes_dir())
|
||||
.unwrap_or_else(|_| panic!("Cannot create themes config directory"));
|
||||
let r = fs::create_dir_all(&themes_dir());
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot create themes config directory"));
|
||||
}
|
||||
|
||||
if !fonts_dir().exists() {
|
||||
fs::create_dir_all(&fonts_dir())
|
||||
.unwrap_or_else(|_| panic!("Cannot create fonts config directory"));
|
||||
let r = fs::create_dir_all(&fonts_dir());
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot create fonts config directory"));
|
||||
write_default_fonts();
|
||||
}
|
||||
|
||||
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() {
|
||||
fs::create_dir_all(&project_dir())
|
||||
.unwrap_or_else(|_| panic!("Cannot create project directory"));
|
||||
let r = fs::create_dir_all(&project_dir());
|
||||
#[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();
|
||||
default_font_path.push("DejaVuSansMono.ttf");
|
||||
let contents = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf");
|
||||
fs::write(default_font_path, contents.to_vec())
|
||||
.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||
let r = fs::write(default_font_path, contents.to_vec());
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||
}
|
||||
{
|
||||
let mut default_font_path = fonts_dir();
|
||||
default_font_path.push("ElaineSans-Medium.ttf");
|
||||
let contents = include_bytes!("../../assets/fonts/ElaineSans-Medium.ttf");
|
||||
fs::write(default_font_path, contents.to_vec())
|
||||
.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||
let r = fs::write(default_font_path, contents.to_vec());
|
||||
#[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 crate::themes::Theme;
|
||||
use dirs;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
mod creator;
|
||||
pub mod config;
|
||||
pub(crate) mod creator;
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
pub type ConfigAccess = Arc<RwLock<Config>>;
|
||||
|
||||
pub trait ConfigHolder {
|
||||
fn config(&self) -> &ConfigAccess;
|
||||
}
|
||||
|
@ -3,18 +3,17 @@ use std::ops::Deref;
|
||||
pub mod plain;
|
||||
pub mod rust_lang;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
|
||||
pub enum Language {
|
||||
PlainText,
|
||||
Rust,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenType {
|
||||
Whitespace { token: Token },
|
||||
Keyword { token: Token },
|
||||
String { token: Token },
|
||||
Number { token: Token },
|
||||
Identifier { token: Token },
|
||||
Literal { token: Token },
|
||||
Comment { token: Token },
|
||||
@ -34,9 +33,6 @@ impl TokenType {
|
||||
TokenType::String { token } => TokenType::String {
|
||||
token: token.move_to(line, character, start, end),
|
||||
},
|
||||
TokenType::Number { token } => TokenType::Number {
|
||||
token: token.move_to(line, character, start, end),
|
||||
},
|
||||
TokenType::Identifier { token } => TokenType::Identifier {
|
||||
token: token.move_to(line, character, start, end),
|
||||
},
|
||||
@ -78,7 +74,6 @@ impl Deref for TokenType {
|
||||
TokenType::Whitespace { token } => token,
|
||||
TokenType::Keyword { token } => token,
|
||||
TokenType::String { token } => token,
|
||||
TokenType::Number { token } => token,
|
||||
TokenType::Identifier { token } => token,
|
||||
TokenType::Literal { 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 {
|
||||
line: usize,
|
||||
character: usize,
|
||||
@ -97,12 +98,6 @@ pub struct Token {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Span {
|
||||
pub lo: usize,
|
||||
pub hi: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(text: String, line: usize, character: usize, start: usize, end: usize) -> Self {
|
||||
Self {
|
||||
@ -157,3 +152,73 @@ pub fn parse(text: String, language: &Language) -> Vec<TokenType> {
|
||||
.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)
|
||||
}, 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)
|
||||
}, text),
|
||||
|
||||
r"[+-/*%=<>]" => (TokenType::Operator {
|
||||
r"(->|[+-/*%=<>#])" => (TokenType::Operator {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
|
||||
r"(:|::|\{|\}|\[|\]|,)" => (TokenType::Separator {
|
||||
r"(:|::|\{|\}|\[|\]|;|,|\)|\()" => (TokenType::Separator {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, 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)
|
||||
}, 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)
|
||||
}, 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);
|
||||
}
|
||||
}
|
||||
|
25
src/main.rs
25
src/main.rs
@ -13,6 +13,8 @@ extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use crate::app::Application;
|
||||
use crate::config::directories::log_dir;
|
||||
@ -25,22 +27,27 @@ pub mod app;
|
||||
pub mod config;
|
||||
pub mod lexer;
|
||||
pub mod renderer;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
pub mod themes;
|
||||
pub mod ui;
|
||||
|
||||
fn init_logger() {
|
||||
use simplelog::SharedLogger;
|
||||
|
||||
let mut log_file_path = log_dir();
|
||||
log_file_path.push("rider.log");
|
||||
|
||||
CombinedLogger::init(vec![
|
||||
TermLogger::new(LevelFilter::Warn, Config::default()).unwrap(),
|
||||
WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
File::create(log_file_path).unwrap(),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
let mut outputs: Vec<Box<SharedLogger>> = vec![WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
File::create(log_file_path).unwrap(),
|
||||
)];
|
||||
if let Some(term) = TermLogger::new(LevelFilter::Warn, Config::default()) {
|
||||
outputs.push(term);
|
||||
}
|
||||
|
||||
CombinedLogger::init(outputs).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -2,6 +2,7 @@ use sdl2::image::LoadTexture;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::render::{Texture, TextureCreator};
|
||||
use sdl2::ttf::{Font, Sdl2TtfContext};
|
||||
use sdl2::video::WindowContext as WinCtxt;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
#[allow(unused_imports)]
|
||||
@ -9,6 +10,10 @@ use std::env;
|
||||
use std::hash::Hash;
|
||||
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> {
|
||||
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 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)]
|
||||
pub struct ResourceManager<'l, K, R, L>
|
||||
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
|
||||
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,
|
||||
details: &mut TextDetails,
|
||||
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 renderer;
|
||||
|
||||
pub struct Renderer<'a> {
|
||||
config: Arc<RwLock<Config>>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
pub use crate::renderer::managers::*;
|
||||
pub use crate::renderer::renderer::*;
|
||||
|
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 {
|
||||
path: String,
|
||||
sections: Vec<EditorFileSection>,
|
||||
render_position: Rect,
|
||||
dest: Rect,
|
||||
buffer: String,
|
||||
config: Arc<RwLock<Config>>,
|
||||
line_height: u32,
|
||||
@ -42,7 +42,7 @@ impl EditorFile {
|
||||
Self {
|
||||
path,
|
||||
sections,
|
||||
render_position,
|
||||
dest: render_position,
|
||||
buffer,
|
||||
config,
|
||||
line_height: 0,
|
||||
@ -66,7 +66,7 @@ impl EditorFile {
|
||||
}
|
||||
|
||||
pub fn render_position(&self) -> &Rect {
|
||||
&self.render_position
|
||||
&self.dest
|
||||
}
|
||||
|
||||
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
||||
@ -119,11 +119,10 @@ impl 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() {
|
||||
section.render(canvas, renderer, parent);
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
@ -179,6 +178,10 @@ impl ClickHandler for EditorFile {
|
||||
|
||||
impl RenderBox for EditorFile {
|
||||
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 {
|
||||
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() {
|
||||
token.render(canvas, renderer, parent);
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::config::*;
|
||||
use crate::lexer::TokenType;
|
||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||
use crate::renderer::Renderer;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
@ -19,7 +19,6 @@ impl TokenType {
|
||||
&TokenType::Whitespace { .. } => ch.whitespace().color().into(),
|
||||
&TokenType::Keyword { .. } => ch.keyword().color().into(),
|
||||
&TokenType::String { .. } => ch.string().color().into(),
|
||||
&TokenType::Number { .. } => ch.number().color().into(),
|
||||
&TokenType::Identifier { .. } => ch.identifier().color().into(),
|
||||
&TokenType::Literal { .. } => ch.literal().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
|
||||
* 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() {
|
||||
return UR::NoOp;
|
||||
return;
|
||||
}
|
||||
for text_character in self.characters.iter() {
|
||||
text_character.render(canvas, renderer, parent);
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
|
@ -6,6 +6,7 @@ use std::sync::*;
|
||||
|
||||
use crate::app::*;
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WS};
|
||||
use crate::config::*;
|
||||
use crate::ui::*;
|
||||
|
||||
pub struct FileEditor {
|
||||
@ -13,11 +14,11 @@ pub struct FileEditor {
|
||||
scroll: Point,
|
||||
caret: Caret,
|
||||
file: Option<EditorFile>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
impl FileEditor {
|
||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
let dest = {
|
||||
let c = config.read().unwrap();
|
||||
Rect::new(
|
||||
@ -36,10 +37,6 @@ impl FileEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Arc<RwLock<Config>> {
|
||||
&self.config
|
||||
}
|
||||
|
||||
pub fn caret(&self) -> &Caret {
|
||||
&self.caret
|
||||
}
|
||||
@ -166,13 +163,13 @@ impl 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());
|
||||
match self.file() {
|
||||
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) {
|
||||
@ -229,6 +226,16 @@ impl RenderBox for FileEditor {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left() + self.scroll
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEditor {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -256,35 +263,4 @@ mod tests {
|
||||
assert_eq!(file.path(), first_file.path());
|
||||
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::config::Config;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::config::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
@ -11,33 +11,30 @@ pub struct MenuBar {
|
||||
border_color: Color,
|
||||
background_color: Color,
|
||||
dest: Rect,
|
||||
config: Arc<RwLock<Config>>,
|
||||
pending: bool,
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
impl MenuBar {
|
||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||
let background_color = { config.read().unwrap().theme().background().into() };
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
let (background_color, w, h): (Color, u32, u16) = {
|
||||
let c = config.read().unwrap();
|
||||
(c.theme().background().into(), c.width(), c.menu_height())
|
||||
};
|
||||
Self {
|
||||
border_color: Color::RGB(10, 10, 10),
|
||||
background_color,
|
||||
dest: Rect::new(0, 0, 0, 0),
|
||||
dest: Rect::new(0, 0, w as u32, h as u32),
|
||||
config,
|
||||
pending: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background_color(&self) -> &Color {
|
||||
&self.background_color
|
||||
}
|
||||
|
||||
pub fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
}
|
||||
}
|
||||
|
||||
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_draw_color(self.background_color.clone());
|
||||
canvas
|
||||
@ -54,18 +51,12 @@ impl Render for MenuBar {
|
||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, _renderer: &mut Renderer) {
|
||||
if !self.pending {
|
||||
return;
|
||||
}
|
||||
let width = self.config.read().unwrap().width();
|
||||
let height = self.config.read().unwrap().menu_height() as u32;
|
||||
self.dest = Rect::new(0, 0, width, height);
|
||||
self.pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,17 +70,15 @@ impl Update for MenuBar {
|
||||
|
||||
impl ClickHandler for MenuBar {
|
||||
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 {
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
},
|
||||
)
|
||||
let rect = match context.clone() {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
};
|
||||
is_in_rect(point, &rect)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,4 +86,128 @@ impl RenderBox for MenuBar {
|
||||
fn render_start_point(&self) -> Point {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
122
src/ui/mod.rs
122
src/ui/mod.rs
@ -1,8 +1,8 @@
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
use crate::app::{UpdateResult, WindowCanvas};
|
||||
use crate::config::Config;
|
||||
use crate::renderer::managers::FontDetails;
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::managers::*;
|
||||
use crate::renderer::Renderer;
|
||||
|
||||
pub mod caret;
|
||||
@ -30,41 +30,32 @@ pub enum UpdateContext<'l> {
|
||||
|
||||
#[inline]
|
||||
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
||||
let start = rect.top_left();
|
||||
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()
|
||||
rect.contains_point(point.clone())
|
||||
}
|
||||
|
||||
pub fn get_text_character_rect(c: char, renderer: &mut Renderer) -> Option<Rect> {
|
||||
let font_details = FontDetails::new(
|
||||
renderer
|
||||
.config()
|
||||
.read()
|
||||
.unwrap()
|
||||
.editor_config()
|
||||
.font_path()
|
||||
.as_str(),
|
||||
renderer
|
||||
.config()
|
||||
.read()
|
||||
.unwrap()
|
||||
.editor_config()
|
||||
.character_size()
|
||||
.clone(),
|
||||
);
|
||||
let font = renderer
|
||||
#[inline]
|
||||
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
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
|
||||
|
||||
if let Ok((width, height)) = font.size_of_char(c) {
|
||||
Some(Rect::new(0, 0, width, height))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.ok()
|
||||
.and_then(|font| font.size_of_char(c).ok())
|
||||
.and_then(|(width, height)| Some(Rect::new(0, 0, width, height)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -73,26 +64,73 @@ pub fn move_render_point(p: Point, d: &Rect) -> Rect {
|
||||
}
|
||||
|
||||
pub trait Render {
|
||||
fn render(
|
||||
&self,
|
||||
canvas: &mut WindowCanvas,
|
||||
renderer: &mut Renderer,
|
||||
parent: Parent,
|
||||
) -> UpdateResult;
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent);
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer);
|
||||
}
|
||||
|
||||
pub trait Update {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult;
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub trait RenderBox {
|
||||
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::config::Config;
|
||||
use crate::config::*;
|
||||
use crate::lexer::TokenType;
|
||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||
use crate::renderer::Renderer;
|
||||
use crate::renderer::managers::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::caret::CaretPosition;
|
||||
use crate::ui::*;
|
||||
|
||||
@ -22,7 +22,7 @@ pub struct TextCharacter {
|
||||
source: Rect,
|
||||
dest: Rect,
|
||||
color: Color,
|
||||
config: Arc<RwLock<Config>>,
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
impl TextCharacter {
|
||||
@ -32,7 +32,7 @@ impl TextCharacter {
|
||||
line: usize,
|
||||
last_in_line: bool,
|
||||
color: Color,
|
||||
config: Arc<RwLock<Config>>,
|
||||
config: ConfigAccess,
|
||||
) -> Self {
|
||||
Self {
|
||||
text_character,
|
||||
@ -50,14 +50,18 @@ impl TextCharacter {
|
||||
self.last_in_line
|
||||
}
|
||||
|
||||
pub fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &Rect {
|
||||
&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 {
|
||||
&self.color
|
||||
}
|
||||
@ -101,9 +105,9 @@ impl Render for TextCharacter {
|
||||
* Must first create targets so even if new line appear renderer will know
|
||||
* 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() {
|
||||
return UR::NoOp;
|
||||
return;
|
||||
}
|
||||
|
||||
let font_details = {
|
||||
@ -127,20 +131,26 @@ impl Render for TextCharacter {
|
||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||
};
|
||||
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);
|
||||
// canvas.set_draw_color(c);
|
||||
// canvas.draw_rect(dest.clone()).unwrap();
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
let font_details = {
|
||||
let config = renderer.config().read().unwrap();
|
||||
let ec = config.editor_config();
|
||||
FontDetails::new(ec.font_path().as_str(), ec.character_size().clone())
|
||||
};
|
||||
let font_details = build_font_details(renderer);
|
||||
|
||||
let font = renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
@ -151,8 +161,8 @@ impl Render for TextCharacter {
|
||||
c => c,
|
||||
};
|
||||
if let Some(rect) = get_text_character_rect(c, renderer) {
|
||||
self.source = rect.clone();
|
||||
self.dest = rect.clone();
|
||||
self.set_source(&rect);
|
||||
self.set_dest(&rect);
|
||||
}
|
||||
let mut details = TextDetails {
|
||||
text: self.text_character.to_string(),
|
||||
@ -195,4 +205,311 @@ impl RenderBox for TextCharacter {
|
||||
fn render_start_point(&self) -> Point {
|
||||
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