Gh 3/open file (#15)
* Remove unused imports Start FileSystem UI Fix formating Add textures for dir and file, implement prepare_ui, render, update and open directory Display choose file Display files and directories Format code Expand and collapse directories, open file Format code Fix calculating size of directory and displaying children Refactor render open file modal Format code Scroll file tree Format code Refactor open file modal Fix CI Fix some tests, add more tests, fix formatting Fix CI test run Fix CI test run * Add more tests * Add more tests * More tests * More tests * Fix caret position * Add simple string matching * Fixing add characters * Fix themes images * Simplify * Simplify * Simplify * Fix some problems * Fix race conditions in tests * Format code * Format code
@ -9,6 +9,7 @@ jobs:
|
||||
environment:
|
||||
CODECOV_TOKEN: "e58da505-19f2-481c-8068-e845cb36fbe4"
|
||||
TZ: "/usr/share/zoneinfo/Europe/Paris"
|
||||
rider-config: "1"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
@ -31,15 +32,23 @@ jobs:
|
||||
command: |
|
||||
rustup run nightly rustc --version --verbose
|
||||
rustup run nightly cargo --version --verbose
|
||||
rustup run nightly cargo build
|
||||
rustup run nightly cargo build --all
|
||||
mkdir -p ~/.local/bin
|
||||
cp $(pwd)/target/debug/rider-* $HOME/.local/bin
|
||||
export XDG_BIN_HOME=$HOME/.local/bin
|
||||
- run:
|
||||
name: Run rider-generator
|
||||
command: |
|
||||
export XDG_RUNTIME_DIR=$(pwd)
|
||||
export XDG_BIN_HOME=$HOME/.local/bin
|
||||
rustup run nightly cargo run -p rider-generator
|
||||
- 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
|
||||
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin || echo 0
|
||||
export XDG_RUNTIME_DIR=$(pwd)
|
||||
rustup run nightly cargo tarpaulin --ciserver circle-ci --out Xml
|
||||
export XDG_BIN_HOME=$HOME/.local/bin
|
||||
rustup run nightly cargo tarpaulin --all --ciserver circle-ci --out Xml
|
||||
- run:
|
||||
name: Upload Coverage
|
||||
command: |
|
||||
|
78
Cargo.lock
generated
@ -1,3 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.9"
|
||||
@ -413,13 +415,24 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rider"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rider-config 0.1.0",
|
||||
"sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rider-config"
|
||||
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)",
|
||||
"lazy_static 1.2.0 (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)",
|
||||
"rider-lexers 0.1.0",
|
||||
"rider-themes 0.1.0",
|
||||
"sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -427,6 +440,60 @@ dependencies = [
|
||||
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rider-editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rider-config 0.1.0",
|
||||
"rider-lexers 0.1.0",
|
||||
"rider-themes 0.1.0",
|
||||
"sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rider-generator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rider-config 0.1.0",
|
||||
"rider-themes 0.1.0",
|
||||
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rider-lexers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rider-themes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.13"
|
||||
@ -590,6 +657,14 @@ name = "utf8-ranges"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.6.0"
|
||||
@ -710,6 +785,7 @@ dependencies = [
|
||||
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
|
||||
"checksum uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dab5c5526c5caa3d106653401a267fed923e7046f35895ffcb5ca42db64942e6"
|
||||
"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
|
28
Cargo.toml
@ -4,17 +4,25 @@ version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"rider-generator",
|
||||
"rider-config",
|
||||
"rider-themes",
|
||||
"rider-lexers",
|
||||
"rider-editor"
|
||||
]
|
||||
default-members = [
|
||||
"rider-generator",
|
||||
"rider-config",
|
||||
"rider-themes",
|
||||
"rider-lexers",
|
||||
"rider-editor"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.5"
|
||||
plex = "*"
|
||||
dirs = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
log = "*"
|
||||
env_logger = "*"
|
||||
simplelog = "*"
|
||||
lazy_static = "*"
|
||||
rider-config = { version = "*", path = "./rider-config" }
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.31.0"
|
||||
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
BIN
assets/gear.jpg
Before Width: | Height: | Size: 51 KiB |
@ -1,53 +0,0 @@
|
||||
```railscasts
|
||||
|
||||
``comment
|
||||
#BC9358
|
||||
italic
|
||||
|
||||
``escaped character
|
||||
#509E4F
|
||||
|
||||
``class
|
||||
#FFF
|
||||
|
||||
``constant
|
||||
#FFF
|
||||
|
||||
``float
|
||||
#A4C260
|
||||
|
||||
``function
|
||||
#FFC56D
|
||||
|
||||
``global variable
|
||||
#D0CFFE
|
||||
|
||||
``integer
|
||||
#A4C260
|
||||
|
||||
``inline code
|
||||
#151515
|
||||
|
||||
``instance variable
|
||||
#D0CFFE
|
||||
|
||||
``doctype
|
||||
#E7BE69
|
||||
|
||||
``keyword
|
||||
#CB7832
|
||||
|
||||
``regex
|
||||
#A4C260
|
||||
|
||||
``string
|
||||
#A4C260
|
||||
|
||||
``symbol
|
||||
#6C9CBD
|
||||
|
||||
``html tag
|
||||
#E7BE69
|
||||
|
||||
``boolean
|
||||
#6C9CBD
|
23
rider-config/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "rider-config"
|
||||
version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rider-lexers = { path = "../rider-lexers", version = "0.1.0" }
|
||||
rider-themes = { path = "../rider-themes", version = "0.1.0" }
|
||||
rand = "0.5"
|
||||
plex = "*"
|
||||
dirs = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
log = "*"
|
||||
env_logger = "*"
|
||||
simplelog = "*"
|
||||
lazy_static = "*"
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.31.0"
|
||||
features = ["gfx", "image", "mixer", "ttf"]
|
242
rider-config/src/config.rs
Normal file
@ -0,0 +1,242 @@
|
||||
use crate::directories::*;
|
||||
use crate::EditorConfig;
|
||||
use crate::ScrollConfig;
|
||||
use rider_lexers::Language;
|
||||
use rider_themes::Theme;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
pub type LanguageMapping = HashMap<String, Language>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
width: u32,
|
||||
height: u32,
|
||||
menu_height: u16,
|
||||
editor_config: EditorConfig,
|
||||
theme: Theme,
|
||||
extensions_mapping: LanguageMapping,
|
||||
scroll: ScrollConfig,
|
||||
directories: Directories,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
let directories = Directories::new(None, None);
|
||||
let editor_config = EditorConfig::new(&directories);
|
||||
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,
|
||||
menu_height: 60,
|
||||
theme: Theme::default(),
|
||||
editor_config,
|
||||
extensions_mapping,
|
||||
scroll: ScrollConfig::new(),
|
||||
directories,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
i32::from(self.menu_height()) + i32::from(self.editor_config().margin_top())
|
||||
}
|
||||
|
||||
pub fn editor_left_margin(&self) -> i32 {
|
||||
i32::from(self.editor_config().margin_left())
|
||||
}
|
||||
|
||||
pub fn extensions_mapping(&self) -> &LanguageMapping {
|
||||
&self.extensions_mapping
|
||||
}
|
||||
|
||||
pub fn scroll(&self) -> &ScrollConfig {
|
||||
&self.scroll
|
||||
}
|
||||
|
||||
pub fn scroll_mut(&mut self) -> &mut ScrollConfig {
|
||||
&mut self.scroll
|
||||
}
|
||||
|
||||
pub fn directories(&self) -> &Directories {
|
||||
&self.directories
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme: String) {
|
||||
self.theme = self.load_theme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load_theme(&self, theme_name: String) -> Theme {
|
||||
let home_dir = dirs::config_dir().unwrap();
|
||||
let mut config_dir = home_dir.clone();
|
||||
config_dir.push("rider");
|
||||
fs::create_dir_all(&config_dir)
|
||||
.unwrap_or_else(|_| panic!("Cannot create config directory"));
|
||||
self.load_theme_content(format!("{}.json", theme_name).as_str())
|
||||
}
|
||||
|
||||
fn load_theme_content(&self, file_name: &str) -> Theme {
|
||||
let mut config_file = self.directories.themes_dir.clone();
|
||||
config_file.push(file_name);
|
||||
let contents = match fs::read_to_string(&config_file) {
|
||||
Ok(s) => s,
|
||||
Err(_) => fs::read_to_string(&config_file).unwrap_or_else(|_| "".to_owned()),
|
||||
};
|
||||
serde_json::from_str(&contents).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_scroll() {
|
||||
let config = Config::new();
|
||||
let result = config.scroll();
|
||||
let expected = ScrollConfig::new();
|
||||
assert_eq!(result.clone(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_scroll_mut() {
|
||||
let mut config = Config::new();
|
||||
let result = config.scroll_mut();
|
||||
result.set_margin_right(1236);
|
||||
let mut expected = ScrollConfig::new();
|
||||
expected.set_margin_right(1236);
|
||||
assert_eq!(result.clone(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_getters {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_width() {
|
||||
let config = Config::new();
|
||||
let result = config.width();
|
||||
let expected = 1024;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_height() {
|
||||
let config = Config::new();
|
||||
let result = config.height();
|
||||
let expected = 860;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn assert_editor_config() {
|
||||
// let config = Config::new();
|
||||
// let result = config.editor_config();
|
||||
// let expected = 1;
|
||||
// assert_eq!(result, expected);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn assert_theme() {
|
||||
// let config = Config::new();
|
||||
// let result = config.theme();
|
||||
// let expected = 1;
|
||||
// assert_eq!(result, expected);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn assert_menu_height() {
|
||||
let config = Config::new();
|
||||
let result = config.menu_height();
|
||||
let expected = 60;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_editor_top_margin() {
|
||||
let config = Config::new();
|
||||
let result = config.editor_top_margin();
|
||||
let expected = config.menu_height() as i32 + config.editor_config().margin_top() as i32;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_editor_left_margin() {
|
||||
let config = Config::new();
|
||||
let result = config.editor_left_margin();
|
||||
let expected = 10;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_extensions_mapping() {
|
||||
let config = Config::new();
|
||||
let mut result: Vec<String> = config
|
||||
.extensions_mapping()
|
||||
.keys()
|
||||
.map(|s| s.to_owned())
|
||||
.collect();
|
||||
result.sort();
|
||||
let mut expected: Vec<String> = vec!["rs".to_string(), "txt".to_string(), ".".to_string()];
|
||||
expected.sort();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
}
|
183
rider-config/src/directories.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use dirs;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Directories {
|
||||
pub log_dir: PathBuf,
|
||||
pub themes_dir: PathBuf,
|
||||
pub fonts_dir: PathBuf,
|
||||
pub config_dir: PathBuf,
|
||||
pub project_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Directories {
|
||||
pub fn new(config_dir: Option<String>, project_dir: Option<String>) -> Self {
|
||||
let path = match config_dir {
|
||||
Some(s) => s,
|
||||
None => dirs::config_dir().unwrap().to_str().unwrap().to_owned(),
|
||||
};
|
||||
let mut config_dir = PathBuf::new();
|
||||
config_dir.push(path);
|
||||
config_dir.push("rider");
|
||||
|
||||
let path = match project_dir {
|
||||
Some(s) => s,
|
||||
None => dirs::runtime_dir().unwrap().to_str().unwrap().to_owned(),
|
||||
};
|
||||
let mut project_dir = PathBuf::new();
|
||||
project_dir.push(path);
|
||||
project_dir.push(".rider");
|
||||
|
||||
Self {
|
||||
log_dir: log_dir(&config_dir),
|
||||
themes_dir: themes_dir(&config_dir),
|
||||
fonts_dir: fonts_dir(&config_dir),
|
||||
config_dir,
|
||||
project_dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_dir(config_dir: &PathBuf) -> PathBuf {
|
||||
let path = config_dir.to_str().unwrap().to_owned();
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path);
|
||||
path_buf.push("log");
|
||||
path_buf
|
||||
}
|
||||
|
||||
pub fn themes_dir(config_dir: &PathBuf) -> PathBuf {
|
||||
let path = config_dir.to_str().unwrap().to_owned();
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path);
|
||||
path_buf.push("themes");
|
||||
path_buf
|
||||
}
|
||||
|
||||
pub fn fonts_dir(config_dir: &PathBuf) -> PathBuf {
|
||||
let path = config_dir.to_str().unwrap().to_owned();
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path);
|
||||
path_buf.push("fonts");
|
||||
path_buf
|
||||
}
|
||||
|
||||
pub fn project_dir() -> PathBuf {
|
||||
let path = dirs::runtime_dir().unwrap().to_str().unwrap().to_owned();
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path);
|
||||
path_buf.push(".rider");
|
||||
path_buf
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn binaries_directory() -> Result<PathBuf, String> {
|
||||
let mut exec_dir = PathBuf::new();
|
||||
exec_dir.push(dirs::executable_dir().unwrap().clone());
|
||||
let mut rider_editor = exec_dir.clone();
|
||||
rider_editor.push("rider-editor");
|
||||
if rider_editor.exists() {
|
||||
return Ok(exec_dir);
|
||||
}
|
||||
|
||||
let path = dirs::runtime_dir().unwrap().to_str().unwrap().to_owned();
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path.clone());
|
||||
path_buf.push("rider-editor");
|
||||
if path_buf.exists() {
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.push(path);
|
||||
return Ok(path_buf);
|
||||
}
|
||||
|
||||
let mut current_dir = env::current_dir().unwrap();
|
||||
current_dir.push("target");
|
||||
current_dir.push("debug");
|
||||
let mut rider_editor = current_dir.clone();
|
||||
rider_editor.push("rider-editor");
|
||||
if rider_editor.exists() {
|
||||
return Ok(current_dir);
|
||||
}
|
||||
|
||||
let executable = dirs::executable_dir().unwrap();
|
||||
let mut rider_editor = executable.clone();
|
||||
rider_editor.push("rider-editor");
|
||||
if rider_editor.exists() {
|
||||
return Ok(executable);
|
||||
}
|
||||
|
||||
Err("Cannot find binaries!".to_string())
|
||||
}
|
||||
|
||||
pub fn get_binary_path(name: &str) -> Result<String, String> {
|
||||
if cfg!(test) {
|
||||
use std::fs;
|
||||
println!("#[cfg(test)]");
|
||||
|
||||
let mut current_dir = env::current_dir().unwrap();
|
||||
current_dir.push("target");
|
||||
current_dir.push("debug");
|
||||
let name = name.to_string().to_lowercase().replace("-", "_");
|
||||
println!(" name {:?}", name);
|
||||
current_dir.push(vec![name.clone(), "*".to_string()].join("-"));
|
||||
for entry in fs::read_dir(current_dir.to_str().unwrap()).unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(meta) = entry.metadata() {
|
||||
if meta.is_file() && !entry.path().ends_with(".d") {
|
||||
return Ok(entry.path().to_str().unwrap().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("Cannot find {:?}", name))
|
||||
} else {
|
||||
println!("#[cfg(not(test))]");
|
||||
let r = binaries_directory();
|
||||
let mut binaries: PathBuf = r.unwrap_or_else(|e| panic!(e));
|
||||
binaries.push(name.to_string());
|
||||
println!(" name {}", name);
|
||||
match binaries.to_str() {
|
||||
Some(s) => Ok(s.to_owned()),
|
||||
_ => Err(format!("Cannot find {:?}", name)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[test]
|
||||
fn assert_log_dir() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let path = directories.log_dir.clone();
|
||||
let expected: PathBuf = Path::new("/tmp/rider/log").into();
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_themes_dir() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let path = directories.themes_dir.clone();
|
||||
let expected: PathBuf = Path::new("/tmp/rider/themes").into();
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_fonts_dir() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let path = directories.fonts_dir.clone();
|
||||
let expected: PathBuf = Path::new("/tmp/rider/fonts").into();
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_config_dir() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let path = directories.config_dir.clone();
|
||||
let expected: PathBuf = Path::new("/tmp/rider").into();
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
}
|
93
rider-config/src/editor_config.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::directories::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(directories: &Directories) -> Self {
|
||||
let mut default_font_path = directories.fonts_dir.clone();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn assert_font_path() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let config = EditorConfig::new(&directories);
|
||||
let path = config.font_path().to_owned();
|
||||
let expected: String = "/tmp/rider/fonts/DejaVuSansMono.ttf".to_owned();
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_character_size() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let config = EditorConfig::new(&directories);
|
||||
let result = config.character_size();
|
||||
let expected: u16 = 14;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_current_theme() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let config = EditorConfig::new(&directories);
|
||||
let result = config.current_theme().to_owned();
|
||||
let expected = "railscasts".to_owned();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_margin_left() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let config = EditorConfig::new(&directories);
|
||||
let result = config.margin_left();
|
||||
let expected: u16 = 10;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_margin_top() {
|
||||
let directories = Directories::new(Some("/tmp".to_owned()), None);
|
||||
let config = EditorConfig::new(&directories);
|
||||
let result = config.margin_top();
|
||||
let expected: u16 = 10;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
extern crate rider_lexers;
|
||||
extern crate rider_themes;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub mod config;
|
||||
pub(crate) mod creator;
|
||||
pub mod directories;
|
||||
pub mod editor_config;
|
||||
pub mod scroll_config;
|
||||
|
||||
pub use crate::config::config::*;
|
||||
pub use crate::config::directories::*;
|
||||
pub use crate::config::editor_config::*;
|
||||
pub use crate::config::scroll_config::*;
|
||||
pub use crate::config::*;
|
||||
pub use crate::directories::*;
|
||||
pub use crate::editor_config::*;
|
||||
pub use crate::scroll_config::*;
|
||||
|
||||
pub type ConfigAccess = Arc<RwLock<Config>>;
|
||||
|
@ -39,6 +39,17 @@ impl ScrollConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScrollConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 4,
|
||||
margin_right: 5,
|
||||
speed: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -92,5 +103,4 @@ mod tests {
|
||||
let expected = 98;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
}
|
22
rider-editor/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "rider-editor"
|
||||
version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rider-config = { path = "../rider-config", version = "0.1.0" }
|
||||
rider-lexers = { path = "../rider-lexers", version = "0.1.0" }
|
||||
rider-themes = { path = "../rider-themes", version = "0.1.0" }
|
||||
rand = "0.5"
|
||||
dirs = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
log = "*"
|
||||
simplelog = "*"
|
||||
lazy_static = "*"
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.31.0"
|
||||
features = ["gfx", "image", "mixer", "ttf"]
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
185
rider-editor/src/app/app_state.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::*;
|
||||
use rider_config::*;
|
||||
use sdl2::rect::Point;
|
||||
use sdl2::VideoSubsystem as VS;
|
||||
use std::fs::read_to_string;
|
||||
use std::sync::*;
|
||||
|
||||
pub struct AppState {
|
||||
menu_bar: MenuBar,
|
||||
files: Vec<EditorFile>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
file_editor: FileEditor,
|
||||
open_file_modal: Option<OpenFile>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||
Self {
|
||||
menu_bar: MenuBar::new(Arc::clone(&config)),
|
||||
files: vec![],
|
||||
file_editor: FileEditor::new(Arc::clone(&config)),
|
||||
open_file_modal: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) {
|
||||
if let Ok(buffer) = read_to_string(&file_path) {
|
||||
let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
||||
file.prepare_ui(renderer);
|
||||
match self.file_editor.open_file(file) {
|
||||
Some(old) => self.files.push(old),
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to open file: {}", file_path);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) {
|
||||
match self.open_file_modal.as_mut() {
|
||||
Some(modal) => modal.open_directory(dir_path, renderer),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn file_editor(&self) -> &FileEditor {
|
||||
&self.file_editor
|
||||
}
|
||||
|
||||
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||
&mut self.file_editor
|
||||
}
|
||||
|
||||
pub fn set_open_file_modal(&mut self, modal: Option<OpenFile>) {
|
||||
self.open_file_modal = modal;
|
||||
}
|
||||
|
||||
pub fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
if let Some(modal) = self.open_file_modal.as_mut() {
|
||||
modal.scroll_by(x, y);
|
||||
} else {
|
||||
self.file_editor_mut().scroll_by(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file_modal(&self) -> Option<&OpenFile> {
|
||||
self.open_file_modal.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for AppState {
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) {
|
||||
self.file_editor
|
||||
.render(canvas, renderer, &RenderContext::Nothing);
|
||||
self.menu_bar
|
||||
.render(canvas, renderer, &RenderContext::Nothing);
|
||||
match self.open_file_modal.as_ref() {
|
||||
Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
self.menu_bar.prepare_ui(renderer);
|
||||
self.file_editor.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for AppState {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
let res = match self.open_file_modal.as_mut() {
|
||||
Some(modal) => modal.update(ticks, &UpdateContext::Nothing),
|
||||
_ => UpdateResult::NoOp,
|
||||
};
|
||||
if res != UpdateResult::NoOp {
|
||||
return res;
|
||||
}
|
||||
|
||||
self.menu_bar.update(ticks, context);
|
||||
self.file_editor.update(ticks, context);
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
||||
match self.open_file_modal.as_mut() {
|
||||
Some(modal) => return modal.on_left_click(point, &UpdateContext::Nothing),
|
||||
_ => (),
|
||||
};
|
||||
if self
|
||||
.menu_bar
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
video_subsystem.text_input().stop();
|
||||
return self.menu_bar.on_left_click(point, &UpdateContext::Nothing);
|
||||
} else if !self
|
||||
.file_editor
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
return UpdateResult::NoOp;
|
||||
} else {
|
||||
video_subsystem.text_input().start();
|
||||
self.file_editor
|
||||
.on_left_click(point, &UpdateContext::Nothing);
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
|
||||
pub fn is_left_click_target(&self, _point: &Point) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for AppState {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support;
|
||||
// use crate::ui::modal::open_file;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn must_return_none_for_default_file() {
|
||||
let config = support::build_config();
|
||||
let state = AppState::new(Arc::clone(&config));
|
||||
let file = state.file_editor().file();
|
||||
assert_eq!(file.is_none(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn must_scroll_file_when_no_modal() {
|
||||
let config = support::build_config();
|
||||
let mut state = AppState::new(Arc::clone(&config));
|
||||
let old_scroll = state.file_editor().scroll();
|
||||
state.set_open_file_modal(None);
|
||||
state.scroll_by(10, 10);
|
||||
assert_ne!(state.file_editor().scroll(), old_scroll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn must_scroll_modal_when_modal_was_set() {
|
||||
let config = support::build_config();
|
||||
let mut state = AppState::new(Arc::clone(&config));
|
||||
let modal = OpenFile::new("/".to_owned(), 100, 100, Arc::clone(&config));
|
||||
let file_scroll = state.file_editor().scroll();
|
||||
let old_scroll = state.file_editor().scroll();
|
||||
state.set_open_file_modal(Some(modal));
|
||||
state.scroll_by(10, 10);
|
||||
assert_eq!(state.file_editor().scroll(), file_scroll);
|
||||
assert_ne!(state.open_file_modal().unwrap().scroll(), old_scroll);
|
||||
}
|
||||
}
|
324
rider-editor/src/app/application.rs
Normal file
@ -0,0 +1,324 @@
|
||||
pub use crate::app::app_state::AppState;
|
||||
pub use crate::renderer::Renderer;
|
||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||
use crate::ui::*;
|
||||
pub use rider_config::{Config, ConfigAccess, ConfigHolder};
|
||||
use sdl2::event::*;
|
||||
use sdl2::hint;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::keyboard::Scancode;
|
||||
use sdl2::mouse::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Canvas;
|
||||
use sdl2::rwops::RWops;
|
||||
use sdl2::surface::Surface;
|
||||
use sdl2::video::Window;
|
||||
use sdl2::EventPump;
|
||||
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
pub type WindowCanvas = Canvas<Window>;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum UpdateResult {
|
||||
NoOp,
|
||||
Stop,
|
||||
RefreshPositions,
|
||||
MouseLeftClicked(Point),
|
||||
MoveCaret(Rect, CaretPosition),
|
||||
DeleteFront,
|
||||
DeleteBack,
|
||||
Input(String),
|
||||
InsertNewLine,
|
||||
MoveCaretLeft,
|
||||
MoveCaretRight,
|
||||
MoveCaretUp,
|
||||
MoveCaretDown,
|
||||
Scroll { x: i32, y: i32 },
|
||||
WindowResize { width: i32, height: i32 },
|
||||
RefreshFsTree,
|
||||
OpenFile(String),
|
||||
OpenDirectory(String),
|
||||
OpenFileModal,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub struct Application {
|
||||
config: Arc<RwLock<Config>>,
|
||||
clear_color: Color,
|
||||
sdl_context: Sdl,
|
||||
canvas: WindowCanvas,
|
||||
video_subsystem: VideoSubsystem,
|
||||
tasks: Vec<UpdateResult>,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Application {
|
||||
pub fn new() -> Self {
|
||||
let generator_path = rider_config::directories::get_binary_path("rider-generator")
|
||||
.unwrap_or_else(|e| panic!(e));
|
||||
Command::new(generator_path).status().unwrap();
|
||||
|
||||
let mut config = Config::new();
|
||||
config.set_theme(config.editor_config().current_theme().clone());
|
||||
let config = Arc::new(RwLock::new(config));
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
|
||||
hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1");
|
||||
hint::set("SDL_GL_MULTISAMPLESAMPLES", "8");
|
||||
hint::set("SDL_GL_ACCELERATED_VISUAL", "1");
|
||||
hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2");
|
||||
hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1");
|
||||
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let mut window: Window = {
|
||||
let c = config.read().unwrap();
|
||||
video_subsystem
|
||||
.window("Rider", c.width(), c.height())
|
||||
.position_centered()
|
||||
.resizable()
|
||||
.opengl()
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
let icon_bytes = include_bytes!("../../assets/images/gear-64x64.bmp").clone();
|
||||
let mut rw = RWops::from_bytes(&icon_bytes).unwrap();
|
||||
let mut icon = Surface::load_bmp_rw(&mut rw).unwrap();
|
||||
window.set_icon(&mut icon);
|
||||
|
||||
let canvas = window.into_canvas().accelerated().build().unwrap();
|
||||
let clear_color: Color = { config.read().unwrap().theme().background().into() };
|
||||
|
||||
Self {
|
||||
sdl_context,
|
||||
video_subsystem,
|
||||
canvas,
|
||||
tasks: vec![],
|
||||
clear_color,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap();
|
||||
let mut event_pump = self.sdl_context.event_pump().unwrap();
|
||||
let font_context = sdl2::ttf::init().unwrap();
|
||||
let texture_creator = self.canvas.texture_creator();
|
||||
let sleep_time = Duration::new(0, 1_000_000_000u32 / 60);
|
||||
let mut app_state = AppState::new(Arc::clone(&self.config));
|
||||
let mut renderer = Renderer::new(Arc::clone(&self.config), &font_context, &texture_creator);
|
||||
app_state.prepare_ui(&mut renderer);
|
||||
|
||||
'running: loop {
|
||||
self.handle_events(&mut event_pump);
|
||||
let mut new_tasks: Vec<UpdateResult> = vec![];
|
||||
for task in self.tasks.iter() {
|
||||
match task {
|
||||
UpdateResult::Stop => break 'running,
|
||||
UpdateResult::RefreshPositions => (),
|
||||
UpdateResult::NoOp => (),
|
||||
UpdateResult::MoveCaret(_, _pos) => (),
|
||||
UpdateResult::MouseLeftClicked(point) => {
|
||||
let res = app_state.on_left_click(&point, &mut self.video_subsystem);
|
||||
match res {
|
||||
UpdateResult::OpenDirectory(_) => new_tasks.push(res),
|
||||
UpdateResult::OpenFile(_) => {
|
||||
new_tasks.push(res);
|
||||
app_state.set_open_file_modal(None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
UpdateResult::DeleteFront => {
|
||||
app_state.file_editor_mut().delete_front(&mut renderer);
|
||||
}
|
||||
UpdateResult::DeleteBack => {
|
||||
app_state.file_editor_mut().delete_back(&mut renderer);
|
||||
}
|
||||
UpdateResult::Input(text) => {
|
||||
app_state
|
||||
.file_editor_mut()
|
||||
.insert_text(text.clone(), &mut renderer);
|
||||
}
|
||||
UpdateResult::InsertNewLine => {
|
||||
app_state.file_editor_mut().insert_new_line(&mut renderer);
|
||||
}
|
||||
UpdateResult::MoveCaretLeft => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Left);
|
||||
}
|
||||
UpdateResult::MoveCaretRight => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Right);
|
||||
}
|
||||
UpdateResult::MoveCaretUp => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Up);
|
||||
}
|
||||
UpdateResult::MoveCaretDown => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Down);
|
||||
}
|
||||
UpdateResult::Scroll { x, y } => {
|
||||
app_state.scroll_by(-x.clone(), -y.clone());
|
||||
}
|
||||
UpdateResult::WindowResize { width, height } => {
|
||||
let mut c = app_state.config().write().unwrap();
|
||||
let w = width.clone();
|
||||
let h = height.clone();
|
||||
if w > 0 {
|
||||
c.set_width(w as u32);
|
||||
}
|
||||
if h > 0 {
|
||||
c.set_height(h as u32);
|
||||
}
|
||||
}
|
||||
UpdateResult::RefreshFsTree => unimplemented!(),
|
||||
UpdateResult::OpenFile(file_path) => {
|
||||
app_state.open_file(file_path.clone(), &mut renderer);
|
||||
}
|
||||
UpdateResult::OpenDirectory(dir_path) => {
|
||||
app_state.open_directory(dir_path.clone(), &mut renderer);
|
||||
}
|
||||
UpdateResult::OpenFileModal => {
|
||||
use std::env;
|
||||
let pwd = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||
let mut modal =
|
||||
OpenFile::new(pwd.clone(), 400, 800, Arc::clone(&self.config));
|
||||
modal.prepare_ui(&mut renderer);
|
||||
modal.open_directory(pwd.clone(), &mut renderer);
|
||||
app_state.set_open_file_modal(Some(modal));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tasks = new_tasks;
|
||||
|
||||
self.clear();
|
||||
|
||||
app_state.update(timer.ticks() as i32, &UpdateContext::Nothing);
|
||||
app_state.render(&mut self.canvas, &mut renderer, &RenderContext::Nothing);
|
||||
|
||||
self.present();
|
||||
|
||||
if !cfg!(test) {
|
||||
sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file(&mut self, file_path: String) {
|
||||
self.tasks.push(UpdateResult::OpenFile(file_path));
|
||||
}
|
||||
|
||||
fn present(&mut self) {
|
||||
self.canvas.present();
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.canvas.set_draw_color(self.clear_color.clone());
|
||||
self.canvas.clear();
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, event_pump: &mut EventPump) {
|
||||
let left_control_pressed = event_pump
|
||||
.keyboard_state()
|
||||
.is_scancode_pressed(Scancode::LCtrl);
|
||||
let shift_pressed = event_pump
|
||||
.keyboard_state()
|
||||
.is_scancode_pressed(Scancode::LShift)
|
||||
|| event_pump
|
||||
.keyboard_state()
|
||||
.is_scancode_pressed(Scancode::RShift);
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => self.tasks.push(UpdateResult::Stop),
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn, x, y, ..
|
||||
} => match mouse_btn {
|
||||
MouseButton::Left => self
|
||||
.tasks
|
||||
.push(UpdateResult::MouseLeftClicked(Point::new(x, y))),
|
||||
_ => (),
|
||||
},
|
||||
Event::KeyDown { keycode, .. } => {
|
||||
let keycode = if keycode.is_some() {
|
||||
keycode.unwrap()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match keycode {
|
||||
Keycode::Backspace => {
|
||||
self.tasks.push(UpdateResult::DeleteFront);
|
||||
}
|
||||
Keycode::Delete => {
|
||||
self.tasks.push(UpdateResult::DeleteBack);
|
||||
}
|
||||
Keycode::KpEnter | Keycode::Return => {
|
||||
self.tasks.push(UpdateResult::InsertNewLine);
|
||||
}
|
||||
Keycode::Left => {
|
||||
self.tasks.push(UpdateResult::MoveCaretLeft);
|
||||
}
|
||||
Keycode::Right => {
|
||||
self.tasks.push(UpdateResult::MoveCaretRight);
|
||||
}
|
||||
Keycode::Up => {
|
||||
self.tasks.push(UpdateResult::MoveCaretUp);
|
||||
}
|
||||
Keycode::Down => {
|
||||
self.tasks.push(UpdateResult::MoveCaretDown);
|
||||
}
|
||||
Keycode::O => {
|
||||
if left_control_pressed && !shift_pressed {
|
||||
self.tasks.push(UpdateResult::OpenFileModal);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
Event::TextInput { text, .. } => {
|
||||
self.tasks.push(UpdateResult::Input(text));
|
||||
}
|
||||
Event::MouseWheel {
|
||||
direction, x, y, ..
|
||||
} => {
|
||||
match direction {
|
||||
MouseWheelDirection::Normal => {
|
||||
self.tasks.push(UpdateResult::Scroll { x, y });
|
||||
}
|
||||
MouseWheelDirection::Flipped => {
|
||||
self.tasks.push(UpdateResult::Scroll { x, y: -y });
|
||||
}
|
||||
_ => {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
self.tasks.push(UpdateResult::WindowResize {
|
||||
width: w,
|
||||
height: h,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl ConfigHolder for Application {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
67
rider-editor/src/app/caret_manager.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::Point;
|
||||
|
||||
pub fn move_caret_right(file_editor: &mut FileEditor) {
|
||||
let file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
Some(f) => f,
|
||||
};
|
||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) {
|
||||
Some(text_character) => text_character,
|
||||
None => return, // EOF
|
||||
};
|
||||
let pos = file_editor.caret().position();
|
||||
let d = c.dest().clone();
|
||||
let p = pos.moved(1, 0, 0);
|
||||
file_editor
|
||||
.caret_mut()
|
||||
.move_caret(p, Point::new(d.x(), d.y()));
|
||||
}
|
||||
|
||||
pub fn move_caret_left(file_editor: &mut FileEditor) {
|
||||
let file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
Some(f) => f,
|
||||
};
|
||||
if file_editor.caret().text_position() == 0 {
|
||||
return;
|
||||
}
|
||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() - 1) {
|
||||
Some(text_character) => text_character,
|
||||
None => return, // EOF
|
||||
};
|
||||
let pos = file_editor.caret().position();
|
||||
let d = c.dest().clone();
|
||||
let p = pos.moved(-1, 0, 0);
|
||||
file_editor
|
||||
.caret_mut()
|
||||
.move_caret(p, Point::new(d.x(), d.y()));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_move_right {
|
||||
use super::*;
|
||||
use crate::tests::support;
|
||||
|
||||
#[test]
|
||||
fn must_do_nothing() {
|
||||
let config = support::build_config();
|
||||
let mut editor = FileEditor::new(config);
|
||||
|
||||
assert_eq!(move_caret_right(&mut editor), ());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_move_left {
|
||||
use super::*;
|
||||
use crate::tests::support;
|
||||
|
||||
#[test]
|
||||
fn must_do_nothing() {
|
||||
let config = support::build_config();
|
||||
let mut editor = FileEditor::new(config);
|
||||
|
||||
assert_eq!(move_caret_left(&mut editor), ());
|
||||
}
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
use crate::app::*;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::sync::*;
|
||||
|
||||
fn current_file_path(file_editor: &mut FileEditor) -> String {
|
||||
pub fn current_file_path(file_editor: &mut FileEditor) -> String {
|
||||
file_editor
|
||||
.file()
|
||||
.map_or_else(|| String::new(), |f| f.path())
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
@ -35,7 +36,7 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
.file()
|
||||
.and_then(|f| f.get_character_at(file_editor.caret().text_position()))
|
||||
.and_then(|character| {
|
||||
let dest: &Rect = character.dest();
|
||||
let dest: Rect = character.dest();
|
||||
Some((position, Point::new(dest.x(), dest.y())))
|
||||
});
|
||||
match move_to {
|
||||
@ -51,6 +52,7 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
file_editor.replace_current_file(new_file);
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let file: &EditorFile = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
@ -68,6 +70,7 @@ pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
file_editor.replace_current_file(new_file);
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) {
|
||||
let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer());
|
||||
if buffer.is_empty() {
|
||||
@ -81,15 +84,19 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re
|
||||
Some(c) => c,
|
||||
_ => return,
|
||||
};
|
||||
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
||||
let mut pos = if current.is_new_line() {
|
||||
current.dest().top_left()
|
||||
+ Point::new(0, renderer.load_character_size('\n').height() as i32)
|
||||
} else {
|
||||
current.dest().top_left()
|
||||
};
|
||||
let mut position: CaretPosition = file_editor.caret().position().clone();
|
||||
for character in text.chars() {
|
||||
buffer.insert(position.text_position(), character);
|
||||
if let Some(rect) = get_text_character_rect(character, renderer) {
|
||||
pos = pos + Point::new(rect.width() as i32, 0);
|
||||
position = position.moved(1, 0, 0);
|
||||
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||
}
|
||||
let rect = renderer.load_character_size(character);
|
||||
pos = pos + Point::new(rect.width() as i32, 0);
|
||||
position = position.moved(1, 0, 0);
|
||||
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||
}
|
||||
|
||||
let mut new_file = EditorFile::new(
|
||||
@ -101,6 +108,7 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re
|
||||
file_editor.replace_current_file(new_file);
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
@ -115,17 +123,14 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
Some(c) => c,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
||||
let mut position: CaretPosition = file_editor.caret().position().clone();
|
||||
buffer.insert(position.text_position(), '\n');
|
||||
if let Some(rect) = get_text_character_rect('\n', renderer) {
|
||||
pos = Point::new(
|
||||
file_editor.config().read().unwrap().editor_left_margin(),
|
||||
pos.y() + rect.height() as i32,
|
||||
);
|
||||
position = position.moved(0, 1, 0);
|
||||
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||
}
|
||||
let rect = renderer.load_character_size('\n');
|
||||
pos = Point::new(0, pos.y() + rect.height() as i32);
|
||||
position = position.moved(0, 1, 0);
|
||||
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||
|
||||
let mut new_file = EditorFile::new(
|
||||
current_file_path(file_editor),
|
||||
@ -135,3 +140,31 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
new_file.prepare_ui(renderer);
|
||||
file_editor.replace_current_file(new_file);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support;
|
||||
|
||||
#[test]
|
||||
fn must_return_empty_string_when_no_file() {
|
||||
let config = support::build_config();
|
||||
let mut editor = FileEditor::new(config);
|
||||
let result = current_file_path(&mut editor);
|
||||
assert_eq!(result, String::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn must_return_path_string_when_file_was_set() {
|
||||
let config = support::build_config();
|
||||
let mut editor = FileEditor::new(Arc::clone(&config));
|
||||
let file = EditorFile::new(
|
||||
"/foo/bar".to_owned(),
|
||||
"hello world".to_owned(),
|
||||
Arc::clone(&config),
|
||||
);
|
||||
editor.open_file(file);
|
||||
let result = current_file_path(&mut editor);
|
||||
assert_eq!(result, "/foo/bar".to_owned());
|
||||
}
|
||||
}
|
57
rider-editor/src/main.rs
Normal file
@ -0,0 +1,57 @@
|
||||
extern crate dirs;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate rand;
|
||||
extern crate rider_config;
|
||||
extern crate rider_lexers;
|
||||
extern crate rider_themes;
|
||||
extern crate sdl2;
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate simplelog;
|
||||
|
||||
use crate::app::Application;
|
||||
use rider_config::directories::Directories;
|
||||
use simplelog::*;
|
||||
use std::fs::File;
|
||||
|
||||
pub mod app;
|
||||
pub mod renderer;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
pub mod ui;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn init_logger(directories: &Directories) {
|
||||
use simplelog::SharedLogger;
|
||||
|
||||
let mut log_file_path = directories.log_dir.clone();
|
||||
log_file_path.push("rider.log");
|
||||
|
||||
let mut outputs: Vec<Box<SharedLogger>> = vec![WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
File::create(log_file_path).unwrap(),
|
||||
)];
|
||||
let terminal_level = if cfg!(release) {
|
||||
LevelFilter::Trace
|
||||
} else {
|
||||
LevelFilter::Debug
|
||||
};
|
||||
if let Some(term) = TermLogger::new(terminal_level, Config::default()) {
|
||||
outputs.push(term);
|
||||
}
|
||||
|
||||
CombinedLogger::init(outputs).unwrap();
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn main() {
|
||||
let directories = Directories::new(None, None);
|
||||
let mut app = Application::new();
|
||||
app.init();
|
||||
init_logger(&directories);
|
||||
app.open_file("./test_files/test.rs".to_string());
|
||||
app.run();
|
||||
}
|
@ -2,7 +2,8 @@ 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 sdl2::video::WindowContext as WinCtxt;
|
||||
use rider_config::editor_config::EditorConfig;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
#[allow(unused_imports)]
|
||||
@ -10,22 +11,41 @@ use std::env;
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
//noinspection RsWrongLifetimeParametersNumber
|
||||
pub type RcTex<'l> = Rc<Texture<'l>>;
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub type RcFont<'l> = Rc<Font<'l, 'static>>;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct TextCharacterDetails {
|
||||
pub c: char,
|
||||
pub font_path: String,
|
||||
pub font_size: u16,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub trait ResourceLoader<'l, R> {
|
||||
type Args: ?Sized;
|
||||
|
||||
fn load(&'l self, data: &Self::Args) -> Result<R, String>;
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct FontDetails {
|
||||
pub path: String,
|
||||
pub size: u16,
|
||||
}
|
||||
|
||||
impl From<&EditorConfig> for FontDetails {
|
||||
fn from(config: &EditorConfig) -> Self {
|
||||
FontDetails::new(config.font_path().as_str(), config.character_size().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct TextDetails {
|
||||
pub text: String,
|
||||
@ -33,6 +53,7 @@ pub struct TextDetails {
|
||||
pub font: FontDetails,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl TextDetails {
|
||||
pub fn get_cache_key(&self) -> String {
|
||||
format!(
|
||||
@ -43,6 +64,7 @@ impl TextDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'a> From<&'a TextDetails> for TextDetails {
|
||||
fn from(details: &'a Self) -> Self {
|
||||
Self {
|
||||
@ -53,6 +75,7 @@ impl<'a> From<&'a TextDetails> for TextDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl FontDetails {
|
||||
pub fn new(path: &str, size: u16) -> FontDetails {
|
||||
Self {
|
||||
@ -62,6 +85,7 @@ impl FontDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'a> From<&'a FontDetails> for FontDetails {
|
||||
fn from(details: &'a FontDetails) -> Self {
|
||||
Self {
|
||||
@ -71,16 +95,21 @@ impl<'a> From<&'a FontDetails> for FontDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
//noinspection RsWrongLifetimeParametersNumber
|
||||
pub type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator<T>>;
|
||||
pub type TextureManager<'l> =
|
||||
ResourceManager<'l, String, Texture<'l>, TextureCreator<sdl2::video::WindowContext>>;
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub trait ManagersHolder<'l> {
|
||||
fn font_manager(&mut self) -> &mut FontManager<'l>;
|
||||
|
||||
fn texture_manager(&mut self) -> &mut TextureManager<'l, WinCtxt>;
|
||||
fn texture_manager(&mut self) -> &mut TextureManager<'l>;
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceManager<'l, K, R, L>
|
||||
where
|
||||
@ -91,6 +120,7 @@ where
|
||||
cache: HashMap<K, Rc<R>>,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l, K, R, L> ResourceManager<'l, K, R, L>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
@ -124,6 +154,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
//noinspection RsWrongLifetimeParametersNumber
|
||||
impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> {
|
||||
type Args = str;
|
||||
@ -134,6 +165,7 @@ impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
|
||||
type Args = FontDetails;
|
||||
|
||||
@ -143,6 +175,7 @@ impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub trait TextTextureManager<'l> {
|
||||
//noinspection RsWrongLifetimeParametersNumber
|
||||
fn load_text(
|
||||
@ -152,7 +185,8 @@ pub trait TextTextureManager<'l> {
|
||||
) -> Result<Rc<Texture<'l>>, String>;
|
||||
}
|
||||
|
||||
impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l> TextTextureManager<'l> for TextureManager<'l> {
|
||||
//noinspection RsWrongLifetimeParametersNumber
|
||||
fn load_text(
|
||||
&mut self,
|
||||
@ -169,9 +203,9 @@ impl<'l, T> TextTextureManager<'l> for TextureManager<'l, T> {
|
||||
let texture = self.loader.create_texture_from_surface(&surface).unwrap();
|
||||
let resource = Rc::new(texture);
|
||||
self.cache.insert(key, resource.clone());
|
||||
for c in details.text.chars() {
|
||||
info!("texture for '{:?}' created", c);
|
||||
}
|
||||
// for c in details.text.chars() {
|
||||
// info!("texture for '{:?}' created", c);
|
||||
// }
|
||||
Ok(resource)
|
||||
},
|
||||
Ok,
|
83
rider-editor/src/renderer/renderer.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::renderer::managers::*;
|
||||
use crate::ui::get_text_character_rect;
|
||||
use crate::ui::text_character::CharacterSizeManager;
|
||||
use rider_config::{ConfigAccess, ConfigHolder};
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::TextureCreator;
|
||||
use sdl2::ttf::Sdl2TtfContext;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub struct Renderer<'l> {
|
||||
config: ConfigAccess,
|
||||
font_manager: FontManager<'l>,
|
||||
texture_manager: TextureManager<'l>,
|
||||
character_sizes: HashMap<TextCharacterDetails, Rect>,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l> Renderer<'l> {
|
||||
pub fn new(
|
||||
config: ConfigAccess,
|
||||
font_context: &'l Sdl2TtfContext,
|
||||
texture_creator: &'l TextureCreator<sdl2::video::WindowContext>,
|
||||
) -> Self {
|
||||
let texture_manager = TextureManager::new(&texture_creator);
|
||||
let font_manager = FontManager::new(&font_context);
|
||||
Self {
|
||||
config,
|
||||
font_manager,
|
||||
texture_manager,
|
||||
character_sizes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn character_sizes_mut(&mut self) -> &mut HashMap<TextCharacterDetails, Rect> {
|
||||
&mut self.character_sizes
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> CharacterSizeManager for Renderer<'l> {
|
||||
fn load_character_size(&mut self, c: char) -> Rect {
|
||||
let (font_path, font_size) = {
|
||||
let config = self.config().read().unwrap();
|
||||
(
|
||||
config.editor_config().font_path().clone(),
|
||||
config.editor_config().character_size().clone(),
|
||||
)
|
||||
};
|
||||
let details = TextCharacterDetails {
|
||||
c: c.clone(),
|
||||
font_path,
|
||||
font_size,
|
||||
};
|
||||
self.character_sizes
|
||||
.get(&details)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
let size = get_text_character_rect(c, self).unwrap();
|
||||
self.character_sizes.insert(details.clone(), size.clone());
|
||||
Some(size)
|
||||
})
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
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> {
|
||||
&mut self.texture_manager
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l> ConfigHolder for Renderer<'l> {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
11
rider-editor/src/tests.rs
Normal file
@ -0,0 +1,11 @@
|
||||
#[cfg(test)]
|
||||
pub mod support {
|
||||
use rider_config::Config;
|
||||
use std::sync::*;
|
||||
|
||||
pub fn build_config() -> Arc<RwLock<Config>> {
|
||||
let mut config = Config::new();
|
||||
config.set_theme(config.editor_config().current_theme().clone());
|
||||
Arc::new(RwLock::new(config))
|
||||
}
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
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 rider_config::ConfigAccess;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Texture;
|
||||
use std::ops::Deref;
|
||||
use std::sync::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Caret {
|
||||
@ -70,13 +66,13 @@ impl Deref for Caret {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for Caret {
|
||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
|
||||
use std::borrow::*;
|
||||
use std::option::*;
|
||||
|
||||
let dest = match context.borrow() {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest().clone(),
|
||||
};
|
||||
let start = Point::new(dest.x(), dest.y());
|
||||
@ -121,11 +117,12 @@ impl ClickHandler for Caret {
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = self.dest();
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, &dest),
|
||||
_ => dest,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -136,18 +133,16 @@ impl RenderBox for Caret {
|
||||
self.dest().top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_own_methods {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -204,11 +199,9 @@ mod test_own_methods {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_deref {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::Point;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -244,11 +237,9 @@ mod test_deref {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::Point;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -264,11 +255,9 @@ mod test_render_box {
|
||||
#[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 sdl2::rect::Point;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
@ -23,7 +23,6 @@ impl CaretColor {
|
||||
#[cfg(test)]
|
||||
mod test_getters {
|
||||
use super::*;
|
||||
use sdl2::pixels::*;
|
||||
|
||||
#[test]
|
||||
fn assert_bright() {
|
@ -1,13 +1,12 @@
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::file::editor_file_section::EditorFileSection;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
use crate::ui::*;
|
||||
use rider_config::Config;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditorFile {
|
||||
@ -114,6 +113,7 @@ impl TextCollection for EditorFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for EditorFile {
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) {
|
||||
for section in self.sections.iter() {
|
||||
@ -196,21 +196,16 @@ impl RenderBox for EditorFile {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::app::*;
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::*;
|
||||
use std::borrow::*;
|
||||
use std::rc::*;
|
||||
use std::sync::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
#[test]
|
||||
fn assert_dest() {
|
@ -1,15 +1,14 @@
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::lexer::Language;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::file::editor_file_token::EditorFileToken;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
use crate::ui::*;
|
||||
use rider_config::Config;
|
||||
use rider_lexers;
|
||||
use rider_lexers::Language;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditorFileSection {
|
||||
@ -20,8 +19,6 @@ pub struct EditorFileSection {
|
||||
|
||||
impl EditorFileSection {
|
||||
pub fn new(buffer: String, ext: String, config: Arc<RwLock<Config>>) -> Self {
|
||||
use crate::lexer;
|
||||
|
||||
let language = config
|
||||
.read()
|
||||
.unwrap()
|
||||
@ -29,7 +26,7 @@ impl EditorFileSection {
|
||||
.get(ext.as_str())
|
||||
.unwrap_or(&Language::PlainText)
|
||||
.clone();
|
||||
let lexer_tokens = lexer::parse(buffer.clone(), &language);
|
||||
let lexer_tokens = rider_lexers::parse(buffer.clone(), language);
|
||||
|
||||
let mut tokens: Vec<EditorFileToken> = vec![];
|
||||
let mut iterator = lexer_tokens.iter().peekable();
|
||||
@ -128,6 +125,7 @@ impl TextCollection for EditorFileSection {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for EditorFileSection {
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) {
|
||||
for token in self.tokens.iter() {
|
@ -1,38 +1,17 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::lexer::TokenType;
|
||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use rider_config::Config;
|
||||
use rider_lexers::TokenType;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
impl TokenType {
|
||||
pub fn to_color(&self, config: &Arc<RwLock<Config>>) -> Color {
|
||||
let config = config.read().unwrap();
|
||||
let ch = config.theme().code_highlighting();
|
||||
match self {
|
||||
&TokenType::Whitespace { .. } => ch.whitespace().color().into(),
|
||||
&TokenType::Keyword { .. } => ch.keyword().color().into(),
|
||||
&TokenType::String { .. } => ch.string().color().into(),
|
||||
&TokenType::Identifier { .. } => ch.identifier().color().into(),
|
||||
&TokenType::Literal { .. } => ch.literal().color().into(),
|
||||
&TokenType::Comment { .. } => ch.comment().color().into(),
|
||||
&TokenType::Operator { .. } => ch.operator().color().into(),
|
||||
&TokenType::Separator { .. } => ch.separator().color().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditorFileToken {
|
||||
last_in_line: bool,
|
||||
characters: Vec<TextCharacter>,
|
||||
token_type: Rc<TokenType>,
|
||||
token_type: TokenType,
|
||||
config: Arc<RwLock<Config>>,
|
||||
}
|
||||
|
||||
@ -41,7 +20,7 @@ impl EditorFileToken {
|
||||
Self {
|
||||
last_in_line,
|
||||
characters: vec![],
|
||||
token_type: Rc::new(token_type.clone()),
|
||||
token_type: token_type.clone(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
@ -59,6 +38,21 @@ impl EditorFileToken {
|
||||
text_character.update_position(current);
|
||||
}
|
||||
}
|
||||
|
||||
fn token_to_color(&self, config: &Arc<RwLock<Config>>) -> Color {
|
||||
let config = config.read().unwrap();
|
||||
let ch = config.theme().code_highlighting();
|
||||
match &self.token_type {
|
||||
&TokenType::Whitespace { .. } => ch.whitespace().color().into(),
|
||||
&TokenType::Keyword { .. } => ch.keyword().color().into(),
|
||||
&TokenType::String { .. } => ch.string().color().into(),
|
||||
&TokenType::Identifier { .. } => ch.identifier().color().into(),
|
||||
&TokenType::Literal { .. } => ch.literal().color().into(),
|
||||
&TokenType::Comment { .. } => ch.comment().color().into(),
|
||||
&TokenType::Operator { .. } => ch.operator().color().into(),
|
||||
&TokenType::Separator { .. } => ch.separator().color().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextWidget for EditorFileToken {
|
||||
@ -129,6 +123,7 @@ impl TextCollection for EditorFileToken {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for EditorFileToken {
|
||||
/**
|
||||
* Must first create targets so even if new line appear renderer will know
|
||||
@ -147,7 +142,7 @@ impl Render for EditorFileToken {
|
||||
if !self.characters.is_empty() {
|
||||
return;
|
||||
}
|
||||
let color: Color = self.token_type.to_color(renderer.config());
|
||||
let color: Color = self.token_to_color(&renderer.config());
|
||||
let chars: Vec<char> = self.token_type.text().chars().collect();
|
||||
for (index, c) in chars.iter().enumerate() {
|
||||
let last_in_line = self.last_in_line && index + 1 == chars.len();
|
@ -1,15 +1,12 @@
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use std::borrow::*;
|
||||
use std::mem;
|
||||
use std::sync::*;
|
||||
|
||||
use crate::app::*;
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WS};
|
||||
use crate::ui::scroll_bar::horizontal_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::vertical_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::Scrollable;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::mem;
|
||||
use std::sync::*;
|
||||
|
||||
pub struct FileEditor {
|
||||
dest: Rect,
|
||||
@ -84,7 +81,7 @@ impl FileEditor {
|
||||
}
|
||||
|
||||
impl ScrollableView for FileEditor {
|
||||
fn scroll_to(&mut self, x: i32, y: i32) {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
@ -133,6 +130,8 @@ impl FileAccess for FileEditor {
|
||||
if let Some(f) = self.file.as_ref() {
|
||||
self.full_rect = f.full_rect();
|
||||
}
|
||||
self.vertical_scroll_bar.set_location(0);
|
||||
self.horizontal_scroll_bar.set_location(0);
|
||||
file
|
||||
}
|
||||
|
||||
@ -162,7 +161,7 @@ impl CaretAccess for FileEditor {
|
||||
|
||||
fn move_caret(&mut self, dir: MoveDirection) {
|
||||
match dir {
|
||||
MoveDirection::Left => {}
|
||||
MoveDirection::Left => caret_manager::move_caret_left(self),
|
||||
MoveDirection::Right => caret_manager::move_caret_right(self),
|
||||
MoveDirection::Up => {}
|
||||
MoveDirection::Down => {}
|
||||
@ -197,6 +196,7 @@ impl CaretAccess for FileEditor {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for FileEditor {
|
||||
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) {
|
||||
canvas.set_clip_rect(self.dest.clone());
|
||||
@ -215,12 +215,10 @@ impl Render for FileEditor {
|
||||
);
|
||||
self.vertical_scroll_bar.render(
|
||||
canvas,
|
||||
renderer,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
self.horizontal_scroll_bar.render(
|
||||
canvas,
|
||||
renderer,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
}
|
||||
@ -301,8 +299,8 @@ impl RenderBox for FileEditor {
|
||||
self.dest.top_left() + self.scroll()
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,12 +312,8 @@ impl ConfigHolder for FileEditor {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::app::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::*;
|
||||
use std::borrow::*;
|
||||
use std::rc::*;
|
||||
use rider_config::Config;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -344,10 +338,6 @@ mod test_config_holder {
|
||||
use crate::app::*;
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::*;
|
||||
use std::borrow::*;
|
||||
use std::rc::*;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -369,14 +359,9 @@ mod test_config_holder {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::app::*;
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::*;
|
||||
use std::borrow::*;
|
||||
use std::rc::*;
|
||||
use std::sync::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
impl FileEditor {
|
||||
pub fn set_full_rect(&mut self, r: Rect) {
|
||||
@ -421,7 +406,7 @@ mod test_render_box {
|
||||
widget.set_dest(Rect::new(x.clone(), y.clone(), 999, 999));
|
||||
widget.set_full_rect(Rect::new(0, 0, 99999, 99999));
|
||||
widget.update(1, &UpdateContext::Nothing);
|
||||
widget.scroll_to(30, 40);
|
||||
widget.scroll_by(30, 40);
|
||||
let result = widget.render_start_point().clone();
|
||||
let expected = Point::new(x - (ss * 30), y - (ss * 40));
|
||||
assert_eq!(result, expected);
|
@ -1,10 +1,9 @@
|
||||
pub use crate::ui::file_editor::file_editor::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::Point;
|
||||
|
||||
pub mod file_editor;
|
||||
|
||||
pub use crate::ui::file_editor::file_editor::*;
|
||||
|
||||
pub trait FileAccess {
|
||||
fn has_file(&self) -> bool;
|
||||
|
||||
@ -30,7 +29,7 @@ pub trait CaretAccess {
|
||||
}
|
||||
|
||||
pub trait ScrollableView {
|
||||
fn scroll_to(&mut self, x: i32, y: i32);
|
||||
fn scroll_by(&mut self, x: i32, y: i32);
|
||||
|
||||
fn scroll(&self) -> Point;
|
||||
}
|
432
rider-editor/src/ui/filesystem/directory.rs
Normal file
@ -0,0 +1,432 @@
|
||||
use crate::app::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::fs;
|
||||
use std::path;
|
||||
use std::sync::Arc;
|
||||
|
||||
const CHILD_MARGIN: i32 = 4;
|
||||
const DEFAULT_ICON_SIZE: u32 = 16;
|
||||
|
||||
pub struct DirectoryView {
|
||||
opened: bool,
|
||||
expanded: bool,
|
||||
name_width: u32,
|
||||
icon_width: u32,
|
||||
icon_height: u32,
|
||||
height: u32,
|
||||
path: String,
|
||||
files: Vec<FileEntry>,
|
||||
directories: Vec<DirectoryView>,
|
||||
pos: Point,
|
||||
source: Rect,
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
impl DirectoryView {
|
||||
pub fn new(path: String, config: ConfigAccess) -> Self {
|
||||
Self {
|
||||
opened: false,
|
||||
expanded: false,
|
||||
name_width: 0,
|
||||
icon_width: DEFAULT_ICON_SIZE,
|
||||
icon_height: DEFAULT_ICON_SIZE,
|
||||
height: 0,
|
||||
path,
|
||||
files: vec![],
|
||||
directories: vec![],
|
||||
pos: Point::new(0, 0),
|
||||
source: Rect::new(0, 0, 64, 64),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
pub fn dest(&self) -> Rect {
|
||||
match self.expanded {
|
||||
true => Rect::new(
|
||||
self.pos.x(),
|
||||
self.pos.y(),
|
||||
self.icon_width + self.name_width + NAME_MARGIN as u32,
|
||||
self.height,
|
||||
),
|
||||
false => Rect::new(
|
||||
self.pos.x(),
|
||||
self.pos.y(),
|
||||
self.icon_width + self.name_width + NAME_MARGIN as u32,
|
||||
self.icon_height,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &Rect {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) -> bool {
|
||||
match dir_path {
|
||||
_ if dir_path == self.path => {
|
||||
if !self.opened {
|
||||
self.opened = true;
|
||||
self.expanded = true;
|
||||
self.read_directory(renderer);
|
||||
} else {
|
||||
self.expanded = !self.expanded;
|
||||
}
|
||||
self.calculate_size(renderer);
|
||||
true
|
||||
}
|
||||
_ if dir_path.contains((self.path.clone() + "/").as_str()) => {
|
||||
if !self.opened {
|
||||
self.opened = true;
|
||||
self.expanded = true;
|
||||
self.read_directory(renderer);
|
||||
}
|
||||
for dir in self.directories.iter_mut() {
|
||||
if dir.open_directory(dir_path.clone(), renderer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.calculate_size(renderer);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
path::Path::new(&self.path)
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn name_width(&self) -> u32 {
|
||||
self.name_width
|
||||
}
|
||||
|
||||
pub fn icon_width(&self) -> u32 {
|
||||
self.icon_width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
match self.expanded {
|
||||
true => self.height,
|
||||
false => self.icon_height,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_directory(&mut self, renderer: &mut Renderer) {
|
||||
let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) {
|
||||
Ok(d) => d,
|
||||
_ => return,
|
||||
};
|
||||
for e in entries {
|
||||
let entry = match e {
|
||||
Ok(entry) => entry,
|
||||
_ => continue,
|
||||
};
|
||||
let meta = match entry.metadata() {
|
||||
Ok(meta) => meta,
|
||||
_ => continue,
|
||||
};
|
||||
if meta.is_dir() {
|
||||
let path = match entry.path().to_str() {
|
||||
Some(p) => p.to_string(),
|
||||
_ => continue,
|
||||
};
|
||||
let mut directory_view = DirectoryView::new(path, Arc::clone(&self.config));
|
||||
directory_view.prepare_ui(renderer);
|
||||
self.directories.push(directory_view);
|
||||
} else if meta.is_file() {
|
||||
let file_name = match entry.file_name().to_str() {
|
||||
Some(p) => p.to_string(),
|
||||
_ => continue,
|
||||
};
|
||||
let path = match entry.path().to_str() {
|
||||
Some(p) => p.to_string(),
|
||||
_ => continue,
|
||||
};
|
||||
let mut file_entry = FileEntry::new(file_name, path, Arc::clone(&self.config));
|
||||
file_entry.prepare_ui(renderer);
|
||||
self.files.push(file_entry);
|
||||
}
|
||||
}
|
||||
self.files.sort_by(|a, b| a.name().cmp(&b.name()));
|
||||
self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
|
||||
}
|
||||
|
||||
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let dir_texture_path = {
|
||||
let c = self.config.read().unwrap();
|
||||
let mut themes_dir = c.directories().themes_dir.clone();
|
||||
let path = c.theme().images().directory_icon();
|
||||
themes_dir.push(path);
|
||||
themes_dir.to_str().unwrap().to_owned()
|
||||
};
|
||||
let texture = renderer
|
||||
.texture_manager()
|
||||
.load(dir_texture_path.as_str())
|
||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
||||
|
||||
canvas
|
||||
.render_image(
|
||||
texture,
|
||||
self.source.clone(),
|
||||
Rect::new(dest.x(), dest.y(), self.icon_width, self.icon_height),
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
}
|
||||
|
||||
fn render_name<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let mut d = dest.clone();
|
||||
d.set_x(dest.x() + NAME_MARGIN);
|
||||
let font_details = build_font_details(self);
|
||||
let font = renderer.font_manager().load(&font_details).unwrap();
|
||||
let name = self.name();
|
||||
let config = self.config.read().unwrap();
|
||||
let text_color = config.theme().code_highlighting().title.color();
|
||||
|
||||
for c in name.chars() {
|
||||
let size = renderer.load_character_size(c.clone());
|
||||
let mut text_details = TextDetails {
|
||||
color: Color::RGBA(text_color.r, text_color.g, text_color.b, text_color.a),
|
||||
text: c.to_string(),
|
||||
font: font_details.clone(),
|
||||
};
|
||||
let text_texture = renderer
|
||||
.texture_manager()
|
||||
.load_text(&mut text_details, &font)
|
||||
.unwrap();
|
||||
d.set_width(size.width());
|
||||
d.set_height(size.height());
|
||||
|
||||
canvas
|
||||
.render_image(text_texture, self.source.clone(), d.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
d.set_x(d.x() + size.width() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_children<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
if !self.expanded {
|
||||
return;
|
||||
}
|
||||
let mut point = dest.top_left()
|
||||
+ Point::new(
|
||||
self.icon_width as i32 + CHILD_MARGIN,
|
||||
self.icon_height as i32 + CHILD_MARGIN,
|
||||
);
|
||||
for dir in self.directories.iter() {
|
||||
let context = RenderContext::RelativePosition(point.clone());
|
||||
dir.render(canvas, renderer, &context);
|
||||
point = point + Point::new(0, dir.height() as i32 + CHILD_MARGIN as i32);
|
||||
}
|
||||
for file in self.files.iter() {
|
||||
let context = RenderContext::RelativePosition(point.clone());
|
||||
file.render(canvas, renderer, &context);
|
||||
point = point + Point::new(0, file.height() as i32 + CHILD_MARGIN as i32);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_size(&mut self, renderer: &mut Renderer) {
|
||||
let size = renderer.load_character_size('W');
|
||||
self.height = size.height();
|
||||
self.icon_height = size.height();
|
||||
self.icon_width = size.height();
|
||||
self.name_width = 0;
|
||||
|
||||
for c in self.name().chars() {
|
||||
let size = renderer.load_character_size(c.clone());
|
||||
self.name_width += size.width();
|
||||
}
|
||||
|
||||
for dir in self.directories.iter_mut() {
|
||||
self.height = self.height + dir.height() + CHILD_MARGIN as u32;
|
||||
}
|
||||
for file in self.files.iter_mut() {
|
||||
self.height = self.height + file.height() + CHILD_MARGIN as u32;
|
||||
}
|
||||
}
|
||||
|
||||
fn name_and_icon_rect(&self) -> Rect {
|
||||
Rect::new(
|
||||
self.pos.x(),
|
||||
self.pos.y(),
|
||||
self.icon_width + self.name_width + NAME_MARGIN as u32,
|
||||
self.icon_height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for DirectoryView {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl DirectoryView {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
&RenderContext::RelativePosition(p) => p.clone(),
|
||||
_ => Point::new(0, 0),
|
||||
};
|
||||
let mut dest = move_render_point(move_point, &dest);
|
||||
self.render_icon::<T>(canvas, renderer, &mut dest);
|
||||
self.render_name::<T>(canvas, renderer, &mut dest.clone());
|
||||
self.render_children::<T>(canvas, renderer, &mut dest);
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
if self.opened {
|
||||
for dir in self.directories.iter_mut() {
|
||||
dir.prepare_ui(renderer);
|
||||
}
|
||||
for file in self.files.iter_mut() {
|
||||
file.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
self.calculate_size(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for DirectoryView {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
if !path::Path::new(&self.path).exists() {
|
||||
return UpdateResult::RefreshFsTree;
|
||||
}
|
||||
if self.opened {
|
||||
for dir in self.directories.iter_mut() {
|
||||
dir.update(ticks, context);
|
||||
}
|
||||
for file in self.files.iter_mut() {
|
||||
file.update(ticks, context);
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for DirectoryView {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.pos.clone()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
Rect::new(
|
||||
self.pos.x(),
|
||||
self.pos.y(),
|
||||
self.icon_width,
|
||||
self.icon_height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for DirectoryView {
|
||||
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult {
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
&UpdateContext::ParentPosition(p) => p.clone(),
|
||||
_ => Point::new(0, 0),
|
||||
};
|
||||
let dest = move_render_point(move_point.clone(), &dest);
|
||||
|
||||
// icon or name is target of click
|
||||
let icon_or_name = self.name_and_icon_rect();
|
||||
if move_render_point(move_point, &icon_or_name).contains_point(point.clone()) {
|
||||
return UpdateResult::OpenDirectory(self.path.clone());
|
||||
}
|
||||
|
||||
if !self.expanded {
|
||||
return UpdateResult::NoOp;
|
||||
}
|
||||
|
||||
let mut p = dest.top_left()
|
||||
+ Point::new(
|
||||
self.icon_width as i32 + CHILD_MARGIN,
|
||||
self.icon_height as i32 + CHILD_MARGIN,
|
||||
);
|
||||
for dir in self.directories.iter_mut() {
|
||||
let context = UpdateContext::ParentPosition(p.clone());
|
||||
if dir.is_left_click_target(&point, &context) {
|
||||
return dir.on_left_click(&point, &context);
|
||||
}
|
||||
p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN);
|
||||
}
|
||||
for file in self.files.iter_mut() {
|
||||
let context = UpdateContext::ParentPosition(p.clone());
|
||||
if file.is_left_click_target(&point, &context) {
|
||||
return file.on_left_click(&point, &context);
|
||||
}
|
||||
p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN);
|
||||
}
|
||||
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
UpdateContext::ParentPosition(p) => p.clone(),
|
||||
_ => Point::new(0, 0),
|
||||
};
|
||||
let dest = move_render_point(move_point.clone(), &dest);
|
||||
|
||||
// icon or name is target of click
|
||||
let name_and_icon_rect = self.name_and_icon_rect();
|
||||
if move_render_point(move_point.clone(), &name_and_icon_rect).contains_point(point.clone())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if !self.expanded {
|
||||
return false;
|
||||
}
|
||||
let mut p = dest.top_left()
|
||||
+ Point::new(
|
||||
self.icon_width as i32 + CHILD_MARGIN,
|
||||
self.icon_height as i32 + CHILD_MARGIN,
|
||||
);
|
||||
// subdirectory is target of click
|
||||
for dir in self.directories.iter() {
|
||||
let context = UpdateContext::ParentPosition(p.clone());
|
||||
if dir.is_left_click_target(&point, &context) {
|
||||
return true;
|
||||
}
|
||||
p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN);
|
||||
}
|
||||
// file inside directory is target of click
|
||||
for file in self.files.iter() {
|
||||
let context = UpdateContext::ParentPosition(p.clone());
|
||||
if file.is_left_click_target(&point, &context) {
|
||||
return true;
|
||||
}
|
||||
p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
204
rider-editor/src/ui/filesystem/file.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use crate::app::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::collections::HashMap;
|
||||
use std::path;
|
||||
|
||||
pub struct FileEntry {
|
||||
name_width: u32,
|
||||
icon_width: u32,
|
||||
height: u32,
|
||||
name: String,
|
||||
path: String,
|
||||
dest: Rect,
|
||||
source: Rect,
|
||||
config: ConfigAccess,
|
||||
char_sizes: HashMap<char, Rect>,
|
||||
}
|
||||
|
||||
impl FileEntry {
|
||||
pub fn new(name: String, path: String, config: ConfigAccess) -> Self {
|
||||
Self {
|
||||
name,
|
||||
path,
|
||||
name_width: 0,
|
||||
icon_width: 0,
|
||||
height: 0,
|
||||
dest: Rect::new(0, 0, 16, 16),
|
||||
source: Rect::new(0, 0, 64, 64),
|
||||
config,
|
||||
char_sizes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_width(&self) -> u32 {
|
||||
self.name_width
|
||||
}
|
||||
|
||||
pub fn icon_width(&self) -> u32 {
|
||||
self.icon_width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> String {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
pub fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &Rect {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn full_dest(&self) -> Rect {
|
||||
Rect::new(
|
||||
self.dest.x(),
|
||||
self.dest.y(),
|
||||
self.icon_width + NAME_MARGIN as u32 + self.name_width,
|
||||
self.height,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let dir_texture_path = {
|
||||
let c = self.config.read().unwrap();
|
||||
let mut themes_dir = c.directories().themes_dir.clone();
|
||||
let path = c.theme().images().file_icon();
|
||||
themes_dir.push(path);
|
||||
themes_dir.to_str().unwrap().to_owned()
|
||||
};
|
||||
let texture = renderer
|
||||
.texture_manager()
|
||||
.load(dir_texture_path.as_str())
|
||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
||||
dest.set_width(16);
|
||||
dest.set_height(16);
|
||||
canvas
|
||||
.render_image(texture, self.source.clone(), dest.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
}
|
||||
|
||||
fn render_name<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let mut d = dest.clone();
|
||||
d.set_x(dest.x() + NAME_MARGIN);
|
||||
|
||||
let font_details = build_font_details(self);
|
||||
let font = renderer.font_manager().load(&font_details).unwrap();
|
||||
let texture_manager = renderer.texture_manager();
|
||||
let name = self.name();
|
||||
|
||||
for c in name.chars() {
|
||||
let size = self
|
||||
.char_sizes
|
||||
.get(&c)
|
||||
.unwrap_or(&Rect::new(0, 0, 0, 0))
|
||||
.clone();
|
||||
let mut text_details = TextDetails {
|
||||
color: Color::RGBA(255, 255, 255, 0),
|
||||
text: c.to_string(),
|
||||
font: font_details.clone(),
|
||||
};
|
||||
let text_texture = texture_manager.load_text(&mut text_details, &font).unwrap();
|
||||
d.set_width(size.width());
|
||||
d.set_height(size.height());
|
||||
|
||||
canvas
|
||||
.render_image(text_texture, self.source.clone(), d.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
d.set_x(d.x() + size.width() as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEntry {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl FileEntry {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext)
|
||||
where
|
||||
T: RenderImage,
|
||||
{
|
||||
let mut dest = match context {
|
||||
&RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest.clone(),
|
||||
};
|
||||
self.render_icon(canvas, renderer, &mut dest);
|
||||
self.render_name(canvas, renderer, &mut dest.clone());
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
let w_rect = get_text_character_rect('W', renderer).unwrap();
|
||||
self.char_sizes.insert('W', w_rect.clone());
|
||||
self.height = w_rect.height();
|
||||
self.icon_width = w_rect.height();
|
||||
self.name_width = 0;
|
||||
|
||||
for c in self.name().chars() {
|
||||
let size = { get_text_character_rect(c.clone(), renderer).unwrap() };
|
||||
self.char_sizes.insert(c, size);
|
||||
self.name_width += size.width();
|
||||
}
|
||||
self.dest.set_width(w_rect.height());
|
||||
self.dest.set_height(w_rect.height());
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for FileEntry {
|
||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
|
||||
if !path::Path::new(&self.path).exists() {
|
||||
return UpdateResult::RefreshFsTree;
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for FileEntry {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for FileEntry {
|
||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult {
|
||||
UpdateResult::OpenFile(self.path.clone())
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = Rect::new(
|
||||
self.dest.x(),
|
||||
self.dest.y(),
|
||||
self.icon_width + self.name_width + NAME_MARGIN as u32,
|
||||
self.dest.height(),
|
||||
);
|
||||
let rect = match context {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &dest),
|
||||
_ => dest,
|
||||
};
|
||||
rect.contains_point(point.clone())
|
||||
}
|
||||
}
|
7
rider-editor/src/ui/filesystem/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub use crate::ui::filesystem::directory::*;
|
||||
pub use crate::ui::filesystem::file::*;
|
||||
|
||||
pub mod directory;
|
||||
pub mod file;
|
||||
|
||||
pub const NAME_MARGIN: i32 = 20;
|
@ -1,11 +1,9 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::*;
|
||||
use rider_config::ConfigAccess;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
pub struct MenuBar {
|
||||
border_color: Color,
|
||||
@ -16,12 +14,17 @@ pub struct MenuBar {
|
||||
|
||||
impl MenuBar {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
let (background_color, w, h): (Color, u32, u16) = {
|
||||
let (background_color, border_color, w, h): (Color, Color, u32, u16) = {
|
||||
let c = config.read().unwrap();
|
||||
(c.theme().background().into(), c.width(), c.menu_height())
|
||||
(
|
||||
c.theme().background().into(),
|
||||
c.theme().border_color().into(),
|
||||
c.width(),
|
||||
c.menu_height(),
|
||||
)
|
||||
};
|
||||
Self {
|
||||
border_color: Color::RGB(10, 10, 10),
|
||||
border_color,
|
||||
background_color,
|
||||
dest: Rect::new(0, 0, w as u32, h as u32),
|
||||
config,
|
||||
@ -33,6 +36,7 @@ impl MenuBar {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Render for MenuBar {
|
||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
|
||||
use std::borrow::*;
|
||||
@ -41,23 +45,23 @@ impl Render for MenuBar {
|
||||
canvas.set_draw_color(self.background_color.clone());
|
||||
canvas
|
||||
.fill_rect(match context.borrow() {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest.clone(),
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest(),
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||
|
||||
canvas.set_draw_color(self.border_color.clone());
|
||||
canvas.set_draw_color(self.border_color);
|
||||
canvas
|
||||
.draw_rect(match context.borrow() {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest.clone(),
|
||||
RenderContext::RelativePosition(p) => move_render_point((*p).clone(), &self.dest),
|
||||
_ => self.dest(),
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, _renderer: &mut Renderer) {
|
||||
let width = self.config.read().unwrap().width();
|
||||
let height = self.config.read().unwrap().menu_height() as u32;
|
||||
let height = u32::from(self.config.read().unwrap().menu_height());
|
||||
self.dest = Rect::new(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
@ -76,11 +80,11 @@ impl ClickHandler for MenuBar {
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let rect = match context.clone() {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
let rect = match *context {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest(),
|
||||
};
|
||||
is_in_rect(point, &rect)
|
||||
rect.contains_point(point.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,19 +93,17 @@ impl RenderBox for MenuBar {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
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 sdl2::pixels::Color;
|
||||
use sdl2::rect::Rect;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -129,11 +131,9 @@ mod test_getters {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::Point;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -149,11 +149,9 @@ mod test_render_box {
|
||||
#[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 sdl2::rect::Point;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
@ -1,14 +1,20 @@
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Texture;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::app::application::WindowCanvas;
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::managers::*;
|
||||
use crate::renderer::Renderer;
|
||||
use rider_config::*;
|
||||
|
||||
pub mod caret;
|
||||
pub mod file;
|
||||
pub mod file_editor;
|
||||
pub mod filesystem;
|
||||
pub mod menu_bar;
|
||||
pub mod modal;
|
||||
pub mod project_tree;
|
||||
pub mod scroll_bar;
|
||||
pub mod text_character;
|
||||
@ -16,7 +22,9 @@ pub mod text_character;
|
||||
pub use crate::ui::caret::*;
|
||||
pub use crate::ui::file::*;
|
||||
pub use crate::ui::file_editor::*;
|
||||
pub use crate::ui::filesystem::*;
|
||||
pub use crate::ui::menu_bar::*;
|
||||
pub use crate::ui::modal::*;
|
||||
pub use crate::ui::project_tree::*;
|
||||
pub use crate::ui::scroll_bar::*;
|
||||
pub use crate::ui::text_character::*;
|
||||
@ -34,6 +42,38 @@ pub enum RenderContext {
|
||||
RelativePosition(Point),
|
||||
}
|
||||
|
||||
pub trait RenderRect {
|
||||
fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub trait RenderBorder {
|
||||
fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub trait RenderImage {
|
||||
fn render_image(&mut self, tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String>;
|
||||
}
|
||||
|
||||
impl RenderRect for WindowCanvas {
|
||||
fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> {
|
||||
self.set_draw_color(color);
|
||||
self.fill_rect(rect)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBorder for WindowCanvas {
|
||||
fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> {
|
||||
self.set_draw_color(color);
|
||||
self.draw_rect(rect)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderImage for WindowCanvas {
|
||||
fn render_image(&mut self, tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String> {
|
||||
self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
||||
rect.contains_point(point.clone())
|
||||
@ -47,7 +87,7 @@ where
|
||||
let c = config_holder.config().read().unwrap();
|
||||
FontDetails::new(
|
||||
c.editor_config().font_path().as_str(),
|
||||
c.editor_config().character_size().clone(),
|
||||
c.editor_config().character_size(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -55,7 +95,7 @@ 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);
|
||||
let font_details = renderer.config().read().unwrap().editor_config().into();
|
||||
renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
@ -69,8 +109,9 @@ pub fn move_render_point(p: Point, d: &Rect) -> Rect {
|
||||
Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height())
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub trait Render {
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: &RenderContext);
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext);
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer);
|
||||
}
|
||||
@ -88,14 +129,13 @@ pub trait ClickHandler {
|
||||
pub trait RenderBox {
|
||||
fn render_start_point(&self) -> Point;
|
||||
|
||||
fn dest(&self) -> &Rect;
|
||||
fn dest(&self) -> Rect;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support;
|
||||
use sdl2::rect::*;
|
||||
|
||||
struct ConfigWrapper {
|
||||
pub inner: ConfigAccess,
|
3
rider-editor/src/ui/modal/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod open_file;
|
||||
|
||||
pub use crate::ui::modal::open_file::OpenFile;
|
235
rider-editor/src/ui/modal/open_file.rs
Normal file
@ -0,0 +1,235 @@
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::*;
|
||||
use crate::ui::{RenderContext as RC, UpdateContext as UC};
|
||||
use rider_config::ConfigAccess;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::sync::Arc;
|
||||
|
||||
const CONTENT_MARGIN_LEFT: i32 = 16;
|
||||
const CONTENT_MARGIN_TOP: i32 = 24;
|
||||
const DEFAULT_ICON_SIZE: u32 = 16;
|
||||
|
||||
pub struct OpenFile {
|
||||
root_path: String,
|
||||
directory_view: DirectoryView,
|
||||
vertical_scroll_bar: VerticalScrollBar,
|
||||
horizontal_scroll_bar: HorizontalScrollBar,
|
||||
dest: Rect,
|
||||
full_dest: Rect,
|
||||
background_color: Color,
|
||||
border_color: Color,
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
impl OpenFile {
|
||||
pub fn new(root_path: String, width: u32, height: u32, config: ConfigAccess) -> Self {
|
||||
let (window_width, window_height, background_color, border_color) = {
|
||||
let c = config.read().unwrap();
|
||||
let theme = c.theme();
|
||||
(
|
||||
c.width(),
|
||||
c.height(),
|
||||
theme.background().into(),
|
||||
theme.border_color().into(),
|
||||
)
|
||||
};
|
||||
Self {
|
||||
directory_view: DirectoryView::new(root_path.clone(), Arc::clone(&config)),
|
||||
vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)),
|
||||
horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)),
|
||||
root_path,
|
||||
dest: Rect::new(
|
||||
(window_width / 2) as i32 - (width / 2) as i32,
|
||||
(window_height / 2) as i32 - (height / 2) as i32,
|
||||
width,
|
||||
height,
|
||||
),
|
||||
full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE),
|
||||
background_color,
|
||||
border_color,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_path(&self) -> String {
|
||||
self.root_path.clone()
|
||||
}
|
||||
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) {
|
||||
self.directory_view.open_directory(dir_path, renderer);
|
||||
{
|
||||
let dest = self.directory_view.dest();
|
||||
let full_dest = Rect::new(
|
||||
dest.x(),
|
||||
dest.y(),
|
||||
dest.width() + (2 * CONTENT_MARGIN_LEFT as u32),
|
||||
dest.height() + (2 * CONTENT_MARGIN_TOP as u32),
|
||||
);
|
||||
self.full_dest = full_dest;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn full_rect(&self) -> &Rect {
|
||||
&self.full_dest
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableView for OpenFile {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
let value_y = read_config.scroll().speed() * y;
|
||||
let old_x = self.horizontal_scroll_bar.scroll_value();
|
||||
let old_y = self.vertical_scroll_bar.scroll_value();
|
||||
|
||||
if value_x + old_x >= 0 {
|
||||
self.horizontal_scroll_bar.scroll_to(value_x + old_x);
|
||||
if self.horizontal_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.horizontal_scroll_bar.scroll_to(old_x);
|
||||
}
|
||||
}
|
||||
if value_y + old_y >= 0 {
|
||||
self.vertical_scroll_bar.scroll_to(value_y + old_y);
|
||||
if self.vertical_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.vertical_scroll_bar.scroll_to(old_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Point {
|
||||
Point::new(
|
||||
-self.horizontal_scroll_bar.scroll_value(),
|
||||
-self.vertical_scroll_bar.scroll_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for OpenFile {
|
||||
fn update(&mut self, ticks: i32, context: &UC) -> UR {
|
||||
let (window_width, window_height, color, scroll_width, scroll_margin) = {
|
||||
let c = self.config.read().unwrap();
|
||||
(
|
||||
c.width(),
|
||||
c.height(),
|
||||
c.theme().background().into(),
|
||||
c.scroll().width(),
|
||||
c.scroll().margin_right(),
|
||||
)
|
||||
};
|
||||
|
||||
self.dest
|
||||
.set_x((window_width / 2) as i32 - (self.dest.width() / 2) as i32);
|
||||
self.dest
|
||||
.set_y((window_height / 2) as i32 - (self.dest.height() / 2) as i32);
|
||||
self.background_color = color;
|
||||
|
||||
// Scroll bars
|
||||
self.vertical_scroll_bar
|
||||
.set_full_size(self.full_dest.height()); // full dest
|
||||
self.vertical_scroll_bar.set_viewport(self.dest.height());
|
||||
self.vertical_scroll_bar
|
||||
.set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.vertical_scroll_bar.update(ticks, context);
|
||||
|
||||
self.horizontal_scroll_bar
|
||||
.set_full_size(self.full_dest.width()); // full dest
|
||||
self.horizontal_scroll_bar.set_viewport(self.dest.width());
|
||||
self.horizontal_scroll_bar
|
||||
.set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.horizontal_scroll_bar.update(ticks, context);
|
||||
|
||||
// End
|
||||
UR::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl OpenFile {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RC)
|
||||
where
|
||||
T: RenderRect + RenderBorder + RenderImage,
|
||||
{
|
||||
let dest = match context {
|
||||
RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest,
|
||||
};
|
||||
|
||||
// Background
|
||||
// canvas.set_clip_rect(dest.clone());
|
||||
canvas
|
||||
.render_rect(dest, self.background_color)
|
||||
.unwrap_or_else(|_| panic!("Failed to render open file modal background!"));
|
||||
canvas
|
||||
.render_border(dest, self.border_color)
|
||||
.unwrap_or_else(|_| panic!("Failed to render open file modal border!"));
|
||||
|
||||
let context = RC::RelativePosition(
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
||||
);
|
||||
|
||||
// directory tree
|
||||
self.directory_view.render(canvas, renderer, &context);
|
||||
|
||||
// Scroll bars
|
||||
self.vertical_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
self.horizontal_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
self.directory_view.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for OpenFile {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for OpenFile {
|
||||
fn on_left_click(&mut self, point: &Point, context: &UC) -> UR {
|
||||
let dest = match context {
|
||||
UC::ParentPosition(p) => move_render_point(*p, &self.dest),
|
||||
_ => self.dest,
|
||||
};
|
||||
let context = UC::ParentPosition(
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
||||
);
|
||||
let res = self.directory_view.on_left_click(point, &context);
|
||||
{
|
||||
let dest = self.directory_view.dest();
|
||||
let full_dest = Rect::new(
|
||||
dest.x(),
|
||||
dest.y(),
|
||||
dest.width() + (2 * CONTENT_MARGIN_LEFT as u32),
|
||||
dest.height() + (2 * CONTENT_MARGIN_TOP as u32),
|
||||
);
|
||||
self.full_dest = full_dest;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UC) -> bool {
|
||||
let dest = match context {
|
||||
UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest.clone(),
|
||||
};
|
||||
let context = UC::ParentPosition(
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
||||
);
|
||||
self.directory_view.is_left_click_target(point, &context);
|
||||
true
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::*;
|
||||
use crate::app::UpdateResult as UR;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use rider_config::ConfigAccess;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Rect;
|
||||
|
||||
pub struct HorizontalScrollBar {
|
||||
scroll_value: i32,
|
||||
@ -54,22 +53,26 @@ impl Update for HorizontalScrollBar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for HorizontalScrollBar {
|
||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl HorizontalScrollBar {
|
||||
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
|
||||
where
|
||||
T: RenderRect,
|
||||
{
|
||||
if self.full_width < self.viewport {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.set_draw_color(Color::RGBA(255, 255, 255, 0));
|
||||
canvas
|
||||
.fill_rect(match context {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect),
|
||||
_ => self.rect.clone(),
|
||||
})
|
||||
.render_rect(
|
||||
match context {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect),
|
||||
_ => self.rect.clone(),
|
||||
},
|
||||
Color::RGBA(255, 255, 255, 0),
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Failed to render vertical scroll back"));
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, _renderer: &mut Renderer) {}
|
||||
}
|
||||
|
||||
impl Scrollable for HorizontalScrollBar {
|
@ -1,9 +1,9 @@
|
||||
pub use crate::ui::scroll_bar::horizontal_scroll_bar::HorizontalScrollBar;
|
||||
pub use crate::ui::scroll_bar::vertical_scroll_bar::VerticalScrollBar;
|
||||
|
||||
pub mod horizontal_scroll_bar;
|
||||
pub mod vertical_scroll_bar;
|
||||
|
||||
use crate::ui::scroll_bar::horizontal_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::vertical_scroll_bar::*;
|
||||
|
||||
pub trait Scrollable {
|
||||
fn scroll_to(&mut self, n: i32);
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::*;
|
||||
use crate::app::UpdateResult as UR;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use rider_config::ConfigAccess;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Rect;
|
||||
|
||||
pub struct VerticalScrollBar {
|
||||
scroll_value: i32,
|
||||
@ -54,22 +53,26 @@ impl Update for VerticalScrollBar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for VerticalScrollBar {
|
||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl VerticalScrollBar {
|
||||
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
|
||||
where
|
||||
T: RenderBorder,
|
||||
{
|
||||
if self.full_height() < self.viewport() {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.set_draw_color(Color::RGBA(255, 255, 255, 0));
|
||||
canvas
|
||||
.fill_rect(match context {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect),
|
||||
_ => self.rect.clone(),
|
||||
})
|
||||
.render_border(
|
||||
match context {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.rect),
|
||||
_ => self.rect.clone(),
|
||||
},
|
||||
Color::RGBA(255, 255, 255, 0),
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Failed to render vertical scroll back"));
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, _renderer: &mut Renderer) {}
|
||||
}
|
||||
|
||||
impl Scrollable for VerticalScrollBar {
|
||||
@ -94,6 +97,9 @@ impl Scrollable for VerticalScrollBar {
|
||||
}
|
||||
|
||||
fn scrolled_part(&self) -> f64 {
|
||||
if self.full_height() <= self.viewport() {
|
||||
return 1.0;
|
||||
}
|
||||
self.scroll_value().abs() as f64 / (self.full_height() - self.viewport()) as f64
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::lexer::TokenType;
|
||||
use crate::renderer::managers::*;
|
||||
use crate::renderer::*;
|
||||
use crate::ui::caret::CaretPosition;
|
||||
use crate::ui::*;
|
||||
use rider_config::{ConfigAccess, ConfigHolder};
|
||||
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
pub trait CharacterSizeManager {
|
||||
fn load_character_size(&mut self, c: char) -> Rect;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextCharacter {
|
||||
@ -69,10 +68,10 @@ impl TextCharacter {
|
||||
pub fn update_position(&mut self, current: &mut Rect) {
|
||||
if self.is_new_line() {
|
||||
let y = self.source.height() as i32;
|
||||
current.set_x(0);
|
||||
current.set_y(current.y() + y);
|
||||
self.dest.set_x(current.x());
|
||||
self.dest.set_y(current.y());
|
||||
current.set_x(0);
|
||||
current.set_y(current.y() + y);
|
||||
} else {
|
||||
self.dest.set_x(current.x());
|
||||
self.dest.set_y(current.y());
|
||||
@ -100,25 +99,18 @@ impl TextCharacter {
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextCharacter {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl 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, context: &RenderContext) {
|
||||
pub fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) {
|
||||
if self.is_new_line() {
|
||||
return;
|
||||
}
|
||||
|
||||
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 = renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
.unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details));
|
||||
let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into();
|
||||
|
||||
let c = self.text_character.clone();
|
||||
let mut details = TextDetails {
|
||||
@ -127,9 +119,14 @@ impl Render for TextCharacter {
|
||||
font: font_details.clone(),
|
||||
};
|
||||
let dest = match context {
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest.clone(),
|
||||
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest(),
|
||||
};
|
||||
|
||||
let font = renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
.unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details));
|
||||
if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) {
|
||||
canvas
|
||||
.copy_ex(
|
||||
@ -148,27 +145,26 @@ impl Render for TextCharacter {
|
||||
// canvas.draw_rect(dest.clone()).unwrap();
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
let font_details = build_font_details(renderer);
|
||||
pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T)
|
||||
where
|
||||
T: ConfigHolder + CharacterSizeManager + ManagersHolder<'l>,
|
||||
{
|
||||
let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into();
|
||||
|
||||
let font = renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
|
||||
let rect = renderer.load_character_size(self.text_character);
|
||||
self.set_source(&rect);
|
||||
self.set_dest(&rect);
|
||||
|
||||
let c = match self.text_character {
|
||||
'\n' => 'W',
|
||||
c => c,
|
||||
};
|
||||
if let Some(rect) = get_text_character_rect(c, renderer) {
|
||||
self.set_source(&rect);
|
||||
self.set_dest(&rect);
|
||||
}
|
||||
let mut details = TextDetails {
|
||||
text: self.text_character.to_string(),
|
||||
color: self.color.clone(),
|
||||
font: font_details.clone(),
|
||||
};
|
||||
|
||||
let font = renderer
|
||||
.font_manager()
|
||||
.load(&font_details)
|
||||
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details));
|
||||
renderer
|
||||
.texture_manager()
|
||||
.load_text(&mut details, &font)
|
||||
@ -191,13 +187,11 @@ impl ClickHandler for TextCharacter {
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
},
|
||||
)
|
||||
let rect = match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, &self.dest),
|
||||
_ => self.dest(),
|
||||
};
|
||||
is_in_rect(point, &rect)
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,18 +200,17 @@ impl RenderBox for TextCharacter {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
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 sdl2::pixels::Color;
|
||||
use sdl2::rect::Rect;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
@ -323,7 +316,7 @@ mod test_getters {
|
||||
Color::RGB(1, 12, 123),
|
||||
Arc::clone(&config),
|
||||
);
|
||||
assert_eq!(widget.dest(), &Rect::new(0, 0, 0, 0));
|
||||
assert_eq!(widget.dest(), Rect::new(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -343,38 +336,48 @@ mod test_getters {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_own_methods {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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.dest(), Rect::new(10, 23, 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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'W',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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.dest(), Rect::new(10, 23, 70, 80));
|
||||
assert_eq!(widget.source(), &Rect::new(50, 60, 70, 80));
|
||||
}
|
||||
}
|
||||
@ -382,18 +385,22 @@ mod test_own_methods {
|
||||
#[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 sdl2::rect::{Point, 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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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);
|
||||
@ -405,8 +412,14 @@ mod test_click_handler {
|
||||
#[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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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);
|
||||
@ -418,8 +431,14 @@ mod test_click_handler {
|
||||
#[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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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);
|
||||
@ -431,8 +450,14 @@ mod test_click_handler {
|
||||
#[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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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);
|
||||
@ -451,7 +476,7 @@ mod test_click_handler {
|
||||
position.clone(),
|
||||
line.clone(),
|
||||
true,
|
||||
Color::RGB(0, 0, 0),
|
||||
sdl2::pixels::Color::RGB(0, 0, 0),
|
||||
Arc::clone(&config),
|
||||
);
|
||||
let dest = Rect::new(10, 20, 30, 40);
|
||||
@ -468,18 +493,22 @@ mod test_click_handler {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::{Point, 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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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();
|
||||
@ -491,18 +520,22 @@ mod test_render_box {
|
||||
#[cfg(test)]
|
||||
mod test_update {
|
||||
use crate::app::*;
|
||||
use crate::renderer::*;
|
||||
use crate::tests::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::*;
|
||||
use sdl2::rect::*;
|
||||
use sdl2::rect::{Point, 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));
|
||||
let mut widget = TextCharacter::new(
|
||||
'\n',
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
sdl2::pixels::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(
|
17
rider-generator/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "rider-generator"
|
||||
version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rider-config = { path = "../rider-config", version = "0.1.0" }
|
||||
rider-themes = { path = "../rider-themes", version = "0.1.0" }
|
||||
log = "*"
|
||||
simplelog = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
dirs = "*"
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
rand = "0.5"
|
BIN
rider-generator/assets/themes/default/images/directory-48x48.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 16 KiB |
BIN
rider-generator/assets/themes/default/images/directory-64x64.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
rider-generator/assets/themes/default/images/file-48x48.png
Normal file
After Width: | Height: | Size: 866 B |
BIN
rider-generator/assets/themes/default/images/file-512x512.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
rider-generator/assets/themes/default/images/file-64x64.png
Normal file
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 2.3 KiB |
BIN
rider-generator/assets/themes/railscasts/images/file-48x48.png
Normal file
After Width: | Height: | Size: 866 B |
BIN
rider-generator/assets/themes/railscasts/images/file-512x512.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
rider-generator/assets/themes/railscasts/images/file-64x64.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
102
rider-generator/src/config.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use crate::images;
|
||||
use rider_config::directories::*;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn create(directories: &Directories) -> std::io::Result<()> {
|
||||
if !directories.themes_dir.exists() {
|
||||
fs::create_dir_all(&directories.themes_dir)?;
|
||||
images::create(directories)?;
|
||||
}
|
||||
|
||||
if !directories.fonts_dir.exists() {
|
||||
fs::create_dir_all(&directories.fonts_dir)?;
|
||||
}
|
||||
write_default_fonts(directories)?;
|
||||
|
||||
if !directories.log_dir.exists() {
|
||||
fs::create_dir_all(&directories.log_dir)?;
|
||||
}
|
||||
|
||||
if !directories.project_dir.exists() {
|
||||
fs::create_dir_all(&directories.project_dir)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_default_fonts(directories: &Directories) -> std::io::Result<()> {
|
||||
let path = directories.fonts_dir.clone().to_str().unwrap().to_owned();
|
||||
let mut buf = PathBuf::new();
|
||||
buf.push(path);
|
||||
buf.push("DejaVuSansMono.ttf");
|
||||
if !buf.exists() {
|
||||
let contents = include_bytes!("../assets/fonts/DejaVuSansMono.ttf");
|
||||
fs::write(buf, contents.to_vec())?;
|
||||
}
|
||||
|
||||
let path = directories.fonts_dir.clone().to_str().unwrap().to_owned();
|
||||
let mut buf = PathBuf::new();
|
||||
buf.push(path);
|
||||
buf.push("ElaineSans-Medium.ttf");
|
||||
if !buf.exists() {
|
||||
let contents = include_bytes!("../assets/fonts/ElaineSans-Medium.ttf");
|
||||
fs::write(buf, contents.to_vec())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
fn join(a: String, b: String) -> String {
|
||||
vec![a, b].join("/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_fonts() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(join(test_path.clone(), "rider/fonts".to_owned()).as_str()).exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_log() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(join(test_path.clone(), "rider/log".to_owned()).as_str()).exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_themes() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
assert_eq!(
|
||||
Path::new(join(test_path.clone(), "rider/themes".to_owned()).as_str()).exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(join(test_path.clone(), "rider/themes".to_owned()).as_str()).exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
218
rider-generator/src/images.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use crate::write_bytes_to::write_bytes_to;
|
||||
use rider_config::directories::*;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn create(directories: &Directories) -> std::io::Result<()> {
|
||||
default_theme(directories)?;
|
||||
railscasts_theme(directories)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_default_directory_icon(dir: &PathBuf) -> std::io::Result<()> {
|
||||
let blob = include_bytes!("../assets/themes/default/images/directory-64x64.png");
|
||||
write_bytes_to(dir, "directory-64x64.png", blob)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_default_file_icon(dir: &PathBuf) -> std::io::Result<()> {
|
||||
let blob = include_bytes!("../assets/themes/default/images/file-64x64.png");
|
||||
write_bytes_to(dir, "file-64x64.png", blob)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_theme(directories: &Directories) -> std::io::Result<()> {
|
||||
let mut dir = PathBuf::new();
|
||||
dir.push(directories.themes_dir.clone());
|
||||
dir.push("default");
|
||||
dir.push("images");
|
||||
let r = create_dir_all(&dir);
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot create themes config directory"));
|
||||
|
||||
create_default_directory_icon(&dir)?;
|
||||
create_default_file_icon(&dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_railscasts_directory_icon(dir: &PathBuf) -> std::io::Result<()> {
|
||||
let blob = include_bytes!("../assets/themes/railscasts/images/directory-64x64.png");
|
||||
write_bytes_to(dir, "directory-64x64.png", blob)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_railscasts_file_icon(dir: &PathBuf) -> std::io::Result<()> {
|
||||
let blob = include_bytes!("../assets/themes/railscasts/images/file-64x64.png");
|
||||
write_bytes_to(dir, "file-64x64.png", blob)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn railscasts_theme(directories: &Directories) -> std::io::Result<()> {
|
||||
let mut dir = PathBuf::new();
|
||||
dir.push(directories.themes_dir.clone());
|
||||
dir.push("railscasts");
|
||||
dir.push("images");
|
||||
create_dir_all(&dir)?;
|
||||
create_railscasts_directory_icon(&dir)?;
|
||||
create_railscasts_file_icon(&dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
fn join(a: String, b: String) -> String {
|
||||
vec![a, b].join("/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
let themes_dir = join(test_path.clone(), "rider/themes".to_owned());
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"railscasts/images/directory-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"railscasts/images/file-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"default/images/directory-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"default/images/file-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"railscasts/images/directory-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"railscasts/images/file-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"default/images/directory-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
Path::new(
|
||||
join(
|
||||
themes_dir.clone(),
|
||||
"default/images/file-64x64.png".to_owned()
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
.exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_default_directory_icon() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let file_path: String = join(test_path.clone(), "directory-64x64.png".to_owned());
|
||||
let dir: PathBuf = test_path.into();
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), false);
|
||||
assert_eq!(create_default_directory_icon(&dir).is_ok(), true);
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_default_file_icon() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let file_path: String = join(test_path.clone(), "file-64x64.png".to_owned());
|
||||
let dir: PathBuf = test_path.into();
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), false);
|
||||
assert_eq!(create_default_file_icon(&dir).is_ok(), true);
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_railscasts_directory_icon() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let file_path: String = join(test_path.clone(), "directory-64x64.png".to_owned());
|
||||
let dir: PathBuf = test_path.into();
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), false);
|
||||
assert_eq!(create_railscasts_directory_icon(&dir).is_ok(), true);
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_railscasts_file_icon() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let file_path: String = join(test_path.clone(), "file-64x64.png".to_owned());
|
||||
let dir: PathBuf = test_path.into();
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), false);
|
||||
assert_eq!(create_railscasts_file_icon(&dir).is_ok(), true);
|
||||
assert_eq!(Path::new(file_path.as_str()).exists(), true);
|
||||
}
|
||||
}
|
120
rider-generator/src/main.rs
Normal file
@ -0,0 +1,120 @@
|
||||
extern crate dirs;
|
||||
extern crate log;
|
||||
extern crate rand;
|
||||
extern crate rider_config;
|
||||
extern crate rider_themes;
|
||||
extern crate serde;
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate simplelog;
|
||||
extern crate uuid;
|
||||
|
||||
use rider_config::directories::Directories;
|
||||
|
||||
pub mod config;
|
||||
pub mod images;
|
||||
pub mod themes;
|
||||
pub mod write_bytes_to;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let directories = Directories::new(None, None);
|
||||
config::create(&directories)?;
|
||||
themes::create(&directories)?;
|
||||
images::create(&directories)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::env::set_var;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
fn exists(dir: &String, sub: &str) -> bool {
|
||||
let joined = join(dir.clone(), sub.to_owned());
|
||||
Path::new(joined.as_str()).exists()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn join(a: String, b: String) -> String {
|
||||
vec![a, b].join("/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_main() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
let test_path = joined.as_str();
|
||||
create_dir_all(test_path.to_owned()).unwrap();
|
||||
|
||||
set_var("XDG_CONFIG_HOME", test_path);
|
||||
set_var("XDG_RUNTIME_DIR", test_path);
|
||||
|
||||
assert_eq!(exists(&test_path.to_owned(), ".rider"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&test_path.to_owned(), ".rider"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_fonts_dir() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(joined.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", joined.as_str().clone());
|
||||
set_var("XDG_RUNTIME_HOME", joined.as_str().clone());
|
||||
assert_eq!(exists(&joined, "rider/fonts"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&joined, "rider/fonts"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_log_dir() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(joined.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", joined.as_str().clone());
|
||||
set_var("XDG_RUNTIME_HOME", joined.as_str().clone());
|
||||
assert_eq!(exists(&joined, "rider/log"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&joined, "rider/log"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_themes_dir() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(joined.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", joined.as_str().clone());
|
||||
set_var("XDG_RUNTIME_HOME", joined.as_str().clone());
|
||||
assert_eq!(exists(&joined, "rider/themes"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&joined, "rider/themes"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_default_json() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(joined.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", joined.as_str().clone());
|
||||
set_var("XDG_RUNTIME_HOME", joined.as_str().clone());
|
||||
assert_eq!(exists(&joined, "rider/themes/default.json"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&joined, "rider/themes/default.json"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_railscasts_json() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let joined = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(joined.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", joined.as_str().clone());
|
||||
set_var("XDG_RUNTIME_HOME", joined.as_str().clone());
|
||||
assert_eq!(exists(&joined, "rider/themes/railscasts.json"), false);
|
||||
assert_eq!(main().is_ok(), true);
|
||||
assert_eq!(exists(&joined, "rider/themes/railscasts.json"), true);
|
||||
}
|
||||
}
|
81
rider-generator/src/themes.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::*;
|
||||
use rider_themes::predef::*;
|
||||
use rider_themes::Theme;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn create(directories: &Directories) -> std::io::Result<()> {
|
||||
fs::create_dir_all(directories.themes_dir.clone())?;
|
||||
for theme in default_styles() {
|
||||
write_theme(&theme, directories)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_theme(theme: &Theme, directories: &Directories) -> std::io::Result<()> {
|
||||
let mut theme_path = PathBuf::new();
|
||||
theme_path.push(directories.themes_dir.clone());
|
||||
theme_path.push(format!("{}.json", theme.name()));
|
||||
let contents = serde_json::to_string_pretty(&theme).unwrap();
|
||||
fs::write(&theme_path, contents.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_styles() -> Vec<Theme> {
|
||||
vec![default::build_theme(), railscasts::build_theme()]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn assert_default_styles() {
|
||||
assert_eq!(default_styles().len(), 2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn join(a: String, b: String) -> String {
|
||||
vec![a, b].join("/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_default() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
let rider_dir = join(test_path.clone(), "rider".to_owned());
|
||||
assert_eq!(
|
||||
Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_create_railscasts() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
let directories = Directories::new(Some(test_path.clone()), None);
|
||||
let rider_dir = join(test_path.clone(), "rider".to_owned());
|
||||
assert_eq!(
|
||||
Path::new(join(rider_dir.clone(), "themes/default.json".to_owned()).as_str()).exists(),
|
||||
false
|
||||
);
|
||||
assert_eq!(create(&directories).is_ok(), true);
|
||||
assert_eq!(
|
||||
Path::new(join(rider_dir.clone(), "themes/railscasts.json".to_owned()).as_str())
|
||||
.exists(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
34
rider-generator/src/write_bytes_to.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn write_bytes_to(dir: &PathBuf, file: &str, blob: &[u8]) -> std::io::Result<()> {
|
||||
let mut path = dir.clone();
|
||||
path.push(file);
|
||||
let mut f = File::create(path.to_str().unwrap())?;
|
||||
f.write_all(blob)?;
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::env::temp_dir;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn must_create_file() {
|
||||
let test_dir = temp_dir();
|
||||
let file_name = Uuid::new_v4().to_string();
|
||||
let blob: Vec<u8> = vec![1, 2, 3, 4];
|
||||
let res = write_bytes_to(&test_dir, file_name.as_str(), blob.as_slice());
|
||||
assert_eq!(res.is_ok(), true);
|
||||
|
||||
let mut test_file_path = test_dir.clone();
|
||||
test_file_path.push(file_name);
|
||||
let file_path = Path::new(&test_file_path);
|
||||
assert_eq!(file_path.exists(), true);
|
||||
}
|
||||
}
|
10
rider-lexers/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "rider-lexers"
|
||||
version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
plex = "*"
|
||||
log = "*"
|
||||
simplelog = "*"
|
@ -1,3 +1,6 @@
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub mod plain;
|
||||
@ -114,19 +117,19 @@ impl Token {
|
||||
}
|
||||
|
||||
pub fn line(&self) -> usize {
|
||||
self.line.clone()
|
||||
self.line
|
||||
}
|
||||
|
||||
pub fn character(&self) -> usize {
|
||||
self.character.clone()
|
||||
self.character
|
||||
}
|
||||
|
||||
pub fn start(&self) -> usize {
|
||||
self.start.clone()
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
self.end.clone()
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn move_to(&self, line: usize, character: usize, start: usize, end: usize) -> Self {
|
||||
@ -140,14 +143,14 @@ impl Token {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(text: String, language: &Language) -> Vec<TokenType> {
|
||||
pub fn parse(text: String, language: Language) -> Vec<TokenType> {
|
||||
match language {
|
||||
&Language::PlainText => plain::lexer::Lexer::new(text.as_str())
|
||||
.inspect(|tok| warn!("tok: {:?}", tok))
|
||||
Language::PlainText => plain::lexer::Lexer::new(text.as_str())
|
||||
// .inspect(|tok| warn!("tok: {:?}", tok))
|
||||
.map(|t| t.0)
|
||||
.collect(),
|
||||
&Language::Rust => rust_lang::lexer::Lexer::new(text.as_str())
|
||||
.inspect(|tok| warn!("tok: {:?}", tok))
|
||||
Language::Rust => rust_lang::lexer::Lexer::new(text.as_str())
|
||||
// .inspect(|tok| warn!("tok: {:?}", tok))
|
||||
.map(|t| t.0)
|
||||
.collect(),
|
||||
}
|
||||
@ -155,12 +158,13 @@ pub fn parse(text: String, language: &Language) -> Vec<TokenType> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::*;
|
||||
use super::*;
|
||||
use crate::Token;
|
||||
|
||||
#[test]
|
||||
fn must_parse_plain() {
|
||||
let buffer = "foo bar";
|
||||
let language = &Language::PlainText;
|
||||
let language = Language::PlainText;
|
||||
let result = parse(buffer.to_string(), language);
|
||||
assert_eq!(result.len(), 3);
|
||||
}
|
||||
@ -168,7 +172,7 @@ mod tests {
|
||||
#[test]
|
||||
fn must_parse_rust() {
|
||||
let buffer = "foo bar";
|
||||
let language = &Language::Rust;
|
||||
let language = Language::Rust;
|
||||
let result = parse(buffer.to_string(), language);
|
||||
assert_eq!(result.len(), 3);
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
use crate::lexer::{Token, TokenType};
|
||||
|
||||
pub mod lexer {
|
||||
use crate::lexer::{Span, Token, TokenType};
|
||||
use crate::{Span, Token, TokenType};
|
||||
use plex::lexer;
|
||||
|
||||
lexer! {
|
||||
@ -10,6 +8,7 @@ pub mod lexer {
|
||||
r"[ \t\r\n]" => (TokenType::Whitespace {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
|
||||
r"[^ \t\r\n]+" => (TokenType::Identifier {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
@ -37,31 +36,25 @@ pub mod lexer {
|
||||
type Item = (TokenType, Span);
|
||||
|
||||
fn next(&mut self) -> Option<(TokenType, Span)> {
|
||||
loop {
|
||||
let tok: (TokenType, &str) =
|
||||
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
|
||||
self.remaining = new_remaining;
|
||||
if token_type.is_new_line() {
|
||||
self.line += 1;
|
||||
self.character = text.len();
|
||||
} else {
|
||||
self.character += text.len();
|
||||
}
|
||||
(token_type, text)
|
||||
let tok: (TokenType, &str) =
|
||||
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
|
||||
self.remaining = new_remaining;
|
||||
if token_type.is_new_line() {
|
||||
self.line += 1;
|
||||
self.character = text.len();
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
match tok {
|
||||
(tok, text) => {
|
||||
let span = self.span_in(text);
|
||||
let token = tok.move_to(
|
||||
self.line.clone(),
|
||||
self.character - text.len(),
|
||||
span.lo.clone(),
|
||||
span.hi.clone(),
|
||||
);
|
||||
return Some((token, span));
|
||||
self.character += text.len();
|
||||
}
|
||||
(token_type, text)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
match tok {
|
||||
(tok, text) => {
|
||||
let span = self.span_in(text);
|
||||
let token =
|
||||
tok.move_to(self.line, self.character - text.len(), span.lo, span.hi);
|
||||
Some((token, span))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,8 +73,8 @@ pub mod lexer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::plain::*;
|
||||
use crate::lexer::*;
|
||||
use super::*;
|
||||
use crate::{Token, TokenType};
|
||||
|
||||
#[test]
|
||||
fn must_parse_simple_text() {
|
@ -1,7 +1,5 @@
|
||||
use crate::lexer::{Token, TokenType};
|
||||
|
||||
pub mod lexer {
|
||||
use crate::lexer::{Span, Token, TokenType};
|
||||
use crate::{Span, Token, TokenType};
|
||||
use plex::lexer;
|
||||
|
||||
lexer! {
|
||||
@ -11,6 +9,10 @@ pub mod lexer {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
|
||||
"(r\"|\")" => (TokenType::String {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
|
||||
r"([0-9]+|[0-9]+\.[0-9]+|'[^']')" => (TokenType::Literal {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
@ -30,6 +32,10 @@ pub mod lexer {
|
||||
r"[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
|
||||
r"'[^0-9 \t\r\n:+-/*,';<>=%()\[\]{}][^ \t\r\n:+-/*,';<>=%()\[\]{}]*" => (TokenType::Identifier {
|
||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||
}, text),
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
@ -54,31 +60,25 @@ pub mod lexer {
|
||||
type Item = (TokenType, Span);
|
||||
|
||||
fn next(&mut self) -> Option<(TokenType, Span)> {
|
||||
loop {
|
||||
let tok: (TokenType, &str) =
|
||||
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
|
||||
self.remaining = new_remaining;
|
||||
if token_type.is_new_line() {
|
||||
self.line += 1;
|
||||
self.character = text.len();
|
||||
} else {
|
||||
self.character += text.len();
|
||||
}
|
||||
(token_type, text)
|
||||
let tok: (TokenType, &str) =
|
||||
if let Some(((token_type, text), new_remaining)) = next_token(self.remaining) {
|
||||
self.remaining = new_remaining;
|
||||
(token_type, text)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
match tok {
|
||||
(tok, text) => {
|
||||
let line = self.line;
|
||||
if tok.is_new_line() {
|
||||
self.line += 1;
|
||||
self.character = text.len();
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
match tok {
|
||||
(tok, text) => {
|
||||
let span = self.span_in(text);
|
||||
let token = tok.move_to(
|
||||
self.line.clone(),
|
||||
self.character - text.len(),
|
||||
span.lo.clone(),
|
||||
span.hi.clone(),
|
||||
);
|
||||
return Some((token, span));
|
||||
self.character += text.len();
|
||||
}
|
||||
let span = self.span_in(text);
|
||||
let token = tok.move_to(line, self.character - text.len(), span.lo, span.hi);
|
||||
Some((token, span))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,8 +97,8 @@ pub mod lexer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::rust_lang::*;
|
||||
use crate::lexer::*;
|
||||
use super::*;
|
||||
use crate::{Token, TokenType};
|
||||
|
||||
#[test]
|
||||
fn must_parse_simple_text() {
|
||||
@ -207,7 +207,7 @@ mod tests {
|
||||
token: Token::new("foo".to_string(), 0, 0, 0, 3),
|
||||
},
|
||||
TokenType::Whitespace {
|
||||
token: Token::new("\n".to_string(), 1, 0, 3, 4),
|
||||
token: Token::new("\n".to_string(), 0, 0, 3, 4),
|
||||
},
|
||||
TokenType::Identifier {
|
||||
token: Token::new("bar".to_string(), 1, 1, 4, 7),
|
||||
@ -402,7 +402,7 @@ mod tests {
|
||||
token: Token::new("{".to_string(), 0, 30, 30, 31),
|
||||
},
|
||||
TokenType::Whitespace {
|
||||
token: Token::new("\n".to_string(), 1, 0, 31, 32),
|
||||
token: Token::new("\n".to_string(), 0, 0, 31, 32),
|
||||
},
|
||||
TokenType::Whitespace {
|
||||
token: Token::new(" ".to_string(), 1, 1, 32, 44),
|
||||
@ -423,7 +423,7 @@ mod tests {
|
||||
token: Token::new("b".to_string(), 1, 17, 48, 49),
|
||||
},
|
||||
TokenType::Whitespace {
|
||||
token: Token::new("\n".to_string(), 2, 0, 49, 50),
|
||||
token: Token::new("\n".to_string(), 1, 0, 49, 50),
|
||||
},
|
||||
TokenType::Whitespace {
|
||||
token: Token::new(" ".to_string(), 2, 1, 50, 58),
|
15
rider-themes/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "rider-themes"
|
||||
version = "0.1.0"
|
||||
authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
dirs = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.31.0"
|
||||
features = ["gfx", "image", "mixer", "ttf"]
|
@ -1,5 +1,5 @@
|
||||
use crate::themes::SerdeColor;
|
||||
use crate::themes::ThemeConfig;
|
||||
use crate::SerdeColor;
|
||||
use crate::ThemeConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct CaretColor {
|
||||
@ -10,7 +10,7 @@ pub struct CaretColor {
|
||||
impl Default for CaretColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bright: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
bright: ThemeConfig::new(SerdeColor::new(120, 120, 120, 0), false, false),
|
||||
blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
}
|
||||
}
|
273
rider-themes/src/code_highlighting_color.rs
Normal file
@ -0,0 +1,273 @@
|
||||
use crate::SerdeColor;
|
||||
use crate::ThemeConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct CodeHighlightingColor {
|
||||
pub comment: ThemeConfig,
|
||||
pub constant: ThemeConfig,
|
||||
pub error: ThemeConfig,
|
||||
pub warning: ThemeConfig,
|
||||
pub identifier: ThemeConfig,
|
||||
pub keyword: ThemeConfig,
|
||||
pub literal: ThemeConfig,
|
||||
pub number: ThemeConfig,
|
||||
pub operator: ThemeConfig,
|
||||
pub separator: ThemeConfig,
|
||||
pub statement: ThemeConfig,
|
||||
pub string: ThemeConfig,
|
||||
pub title: ThemeConfig,
|
||||
pub type_: ThemeConfig,
|
||||
pub todo: ThemeConfig,
|
||||
pub pre_proc: ThemeConfig,
|
||||
pub special: ThemeConfig,
|
||||
pub whitespace: ThemeConfig,
|
||||
}
|
||||
|
||||
impl Default for CodeHighlightingColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
comment: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
constant: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
error: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
warning: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
identifier: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
keyword: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
literal: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
number: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
operator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
separator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
statement: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
string: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
title: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
type_: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
todo: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
pre_proc: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
special: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
whitespace: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeHighlightingColor {
|
||||
pub fn comment(&self) -> &ThemeConfig {
|
||||
&self.comment
|
||||
}
|
||||
|
||||
pub fn constant(&self) -> &ThemeConfig {
|
||||
&self.constant
|
||||
}
|
||||
|
||||
pub fn error(&self) -> &ThemeConfig {
|
||||
&self.error
|
||||
}
|
||||
|
||||
pub fn warning(&self) -> &ThemeConfig {
|
||||
&self.warning
|
||||
}
|
||||
|
||||
pub fn identifier(&self) -> &ThemeConfig {
|
||||
&self.identifier
|
||||
}
|
||||
|
||||
pub fn keyword(&self) -> &ThemeConfig {
|
||||
&self.keyword
|
||||
}
|
||||
|
||||
pub fn literal(&self) -> &ThemeConfig {
|
||||
&self.literal
|
||||
}
|
||||
|
||||
pub fn number(&self) -> &ThemeConfig {
|
||||
&self.number
|
||||
}
|
||||
|
||||
pub fn operator(&self) -> &ThemeConfig {
|
||||
&self.operator
|
||||
}
|
||||
|
||||
pub fn separator(&self) -> &ThemeConfig {
|
||||
&self.separator
|
||||
}
|
||||
|
||||
pub fn statement(&self) -> &ThemeConfig {
|
||||
&self.statement
|
||||
}
|
||||
|
||||
pub fn string(&self) -> &ThemeConfig {
|
||||
&self.string
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &ThemeConfig {
|
||||
&self.title
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> &ThemeConfig {
|
||||
&self.type_
|
||||
}
|
||||
|
||||
pub fn todo(&self) -> &ThemeConfig {
|
||||
&self.todo
|
||||
}
|
||||
|
||||
pub fn pre_proc(&self) -> &ThemeConfig {
|
||||
&self.pre_proc
|
||||
}
|
||||
|
||||
pub fn special(&self) -> &ThemeConfig {
|
||||
&self.special
|
||||
}
|
||||
|
||||
pub fn whitespace(&self) -> &ThemeConfig {
|
||||
&self.whitespace
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_comment() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.comment().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_constant() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.constant().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_error() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.error().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_warning() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.warning().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_identifier() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.identifier().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_keyword() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.keyword().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_literal() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.literal().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_number() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.number().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_operator() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.operator().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_separator() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.separator().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_statement() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.statement().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_string() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.string().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_title() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.title().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_type_() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.type_().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_todo() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.todo().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_pre_proc() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.pre_proc().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_special() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.special().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_whitespace() {
|
||||
let target = CodeHighlightingColor::default();
|
||||
let result = target.whitespace().clone();
|
||||
let expected = ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::themes::SerdeColor;
|
||||
use crate::themes::ThemeConfig;
|
||||
use crate::SerdeColor;
|
||||
use crate::ThemeConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct DiffColor {
|
||||
@ -12,9 +12,9 @@ pub struct DiffColor {
|
||||
impl Default for DiffColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
add: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
delete: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
change: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
add: ThemeConfig::new(SerdeColor::new(0, 200, 0, 0), false, false),
|
||||
delete: ThemeConfig::new(SerdeColor::new(200, 0, 0, 0), false, false),
|
||||
change: ThemeConfig::new(SerdeColor::new(0, 0, 200, 0), false, false),
|
||||
text: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
}
|
||||
}
|
51
rider-themes/src/images.rs
Normal file
@ -0,0 +1,51 @@
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct ThemeImages {
|
||||
directory_icon: String,
|
||||
file_icon: String,
|
||||
}
|
||||
|
||||
impl ThemeImages {
|
||||
pub fn new(directory_icon: String, file_icon: String) -> Self {
|
||||
Self {
|
||||
file_icon,
|
||||
directory_icon,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn directory_icon(&self) -> String {
|
||||
self.directory_icon.clone()
|
||||
}
|
||||
|
||||
pub fn file_icon(&self) -> String {
|
||||
self.file_icon.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThemeImages {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
directory_icon: "default/images/directory-64x64.png".to_string(),
|
||||
file_icon: "default/images/file-64x64.png".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_directory_icon() {
|
||||
let config = ThemeImages::new("foo".to_owned(), "bar".to_owned());
|
||||
let result = config.directory_icon();
|
||||
let expected = "foo".to_owned();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
#[test]
|
||||
fn assert_file_icon() {
|
||||
let config = ThemeImages::new("foo".to_owned(), "bar".to_owned());
|
||||
let result = config.file_icon();
|
||||
let expected = "bar".to_owned();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
21
rider-themes/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod caret_color;
|
||||
pub mod code_highlighting_color;
|
||||
pub mod diff_color;
|
||||
pub mod images;
|
||||
pub mod predef;
|
||||
pub mod serde_color;
|
||||
pub mod theme;
|
||||
pub mod theme_config;
|
||||
|
||||
pub use crate::caret_color::CaretColor;
|
||||
pub use crate::code_highlighting_color::CodeHighlightingColor;
|
||||
pub use crate::diff_color::DiffColor;
|
||||
pub use crate::images::ThemeImages;
|
||||
pub use crate::serde_color::SerdeColor;
|
||||
pub use crate::theme::Theme;
|
||||
pub use crate::theme_config::ThemeConfig;
|
@ -1,4 +1,4 @@
|
||||
use crate::themes::Theme;
|
||||
use crate::Theme;
|
||||
|
||||
pub fn build_theme() -> Theme {
|
||||
Theme::default()
|
@ -1,14 +1,16 @@
|
||||
use crate::themes::caret_color::CaretColor;
|
||||
use crate::themes::CodeHighlightingColor;
|
||||
use crate::themes::DiffColor;
|
||||
use crate::themes::SerdeColor;
|
||||
use crate::themes::Theme;
|
||||
use crate::themes::ThemeConfig;
|
||||
use crate::caret_color::CaretColor;
|
||||
use crate::CodeHighlightingColor;
|
||||
use crate::DiffColor;
|
||||
use crate::SerdeColor;
|
||||
use crate::Theme;
|
||||
use crate::ThemeConfig;
|
||||
use crate::ThemeImages;
|
||||
|
||||
pub fn build_theme() -> Theme {
|
||||
Theme::new(
|
||||
"railscasts".to_string(),
|
||||
SerdeColor::new(18, 18, 18, 0),
|
||||
SerdeColor::new(200, 200, 200, 0),
|
||||
CaretColor::new(
|
||||
ThemeConfig::new(SerdeColor::new(121, 121, 121, 0), false, false),
|
||||
ThemeConfig::new(SerdeColor::new(21, 21, 21, 0), false, false),
|
||||
@ -39,5 +41,9 @@ pub fn build_theme() -> Theme {
|
||||
ThemeConfig::new(SerdeColor::new(135, 0, 135, 0), false, false),
|
||||
ThemeConfig::new(SerdeColor::new(18, 18, 18, 0), false, false),
|
||||
),
|
||||
ThemeImages::new(
|
||||
"railscasts/images/directory-64x64.png".to_owned(),
|
||||
"railscasts/images/file-64x64.png".to_owned(),
|
||||
),
|
||||
)
|
||||
}
|
49
rider-themes/src/serde_color.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use sdl2::pixels::Color;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct SerdeColor {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl SerdeColor {
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for &SerdeColor {
|
||||
fn into(self) -> Color {
|
||||
Color {
|
||||
r: self.r,
|
||||
g: self.g,
|
||||
b: self.b,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use sdl2::pixels::Color;
|
||||
|
||||
#[test]
|
||||
fn must_cast_serde_color_to_color() {
|
||||
let target = SerdeColor::new(12, 34, 56, 78);
|
||||
let color: Color = (&target).into();
|
||||
let expected = Color::RGBA(12, 34, 56, 78);
|
||||
assert_eq!(color, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn must_assign_to_proper_fields() {
|
||||
let color = SerdeColor::new(12, 34, 56, 78);
|
||||
assert_eq!(color.r, 12);
|
||||
assert_eq!(color.g, 34);
|
||||
assert_eq!(color.b, 56);
|
||||
assert_eq!(color.a, 78);
|
||||
}
|
||||
}
|
141
rider-themes/src/theme.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use crate::CaretColor;
|
||||
use crate::CodeHighlightingColor;
|
||||
use crate::DiffColor;
|
||||
use crate::SerdeColor;
|
||||
use crate::ThemeImages;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct Theme {
|
||||
name: String,
|
||||
background: SerdeColor,
|
||||
border_color: SerdeColor,
|
||||
caret: CaretColor,
|
||||
code_highlighting: CodeHighlightingColor,
|
||||
diff: DiffColor,
|
||||
images: ThemeImages,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "default".to_string(),
|
||||
background: SerdeColor::new(255, 255, 255, 0),
|
||||
border_color: SerdeColor::new(0, 0, 0, 0),
|
||||
caret: CaretColor::default(),
|
||||
code_highlighting: CodeHighlightingColor::default(),
|
||||
diff: DiffColor::default(),
|
||||
images: ThemeImages::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn new(
|
||||
name: String,
|
||||
background: SerdeColor,
|
||||
border_color: SerdeColor,
|
||||
caret: CaretColor,
|
||||
code_highlighting: CodeHighlightingColor,
|
||||
diff: DiffColor,
|
||||
images: ThemeImages,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
background,
|
||||
border_color,
|
||||
caret,
|
||||
code_highlighting,
|
||||
diff,
|
||||
images,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn background(&self) -> &SerdeColor {
|
||||
&self.background
|
||||
}
|
||||
|
||||
pub fn border_color(&self) -> &SerdeColor {
|
||||
&self.border_color
|
||||
}
|
||||
|
||||
pub fn caret(&self) -> &CaretColor {
|
||||
&self.caret
|
||||
}
|
||||
|
||||
pub fn diff(&self) -> &DiffColor {
|
||||
&self.diff
|
||||
}
|
||||
|
||||
pub fn code_highlighting(&self) -> &CodeHighlightingColor {
|
||||
&self.code_highlighting
|
||||
}
|
||||
|
||||
pub fn images(&self) -> &ThemeImages {
|
||||
&self.images
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_name() {
|
||||
let target = Theme::default();
|
||||
let result = target.name().clone();
|
||||
let expected = "default".to_owned();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_background() {
|
||||
let target = Theme::default();
|
||||
let result = target.background().clone();
|
||||
let expected = SerdeColor::new(255, 255, 255, 0);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_border_color() {
|
||||
let target = Theme::default();
|
||||
let result = target.border_color().clone();
|
||||
let expected = SerdeColor::new(0, 0, 0, 0);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_caret() {
|
||||
let target = Theme::default();
|
||||
let result = target.caret().clone();
|
||||
let expected = CaretColor::default();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_diff() {
|
||||
let target = Theme::default();
|
||||
let result = target.diff().clone();
|
||||
let expected = DiffColor::default();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_code_highlighting() {
|
||||
let target = Theme::default();
|
||||
let result = target.code_highlighting().clone();
|
||||
let expected = CodeHighlightingColor::default();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_images() {
|
||||
let target = Theme::default();
|
||||
let result = target.images().clone();
|
||||
let expected = ThemeImages::default();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
59
rider-themes/src/theme_config.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use crate::SerdeColor;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct ThemeConfig {
|
||||
color: SerdeColor,
|
||||
italic: bool,
|
||||
bold: bool,
|
||||
}
|
||||
|
||||
impl ThemeConfig {
|
||||
pub fn new(color: SerdeColor, italic: bool, bold: bool) -> Self {
|
||||
Self {
|
||||
color,
|
||||
italic,
|
||||
bold,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(&self) -> &SerdeColor {
|
||||
&self.color
|
||||
}
|
||||
|
||||
pub fn italic(&self) -> bool {
|
||||
self.italic
|
||||
}
|
||||
|
||||
pub fn bold(&self) -> bool {
|
||||
self.bold
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_color() {
|
||||
let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), true, false);
|
||||
let result = target.color().clone();
|
||||
let expected = SerdeColor::new(29, 20, 45, 72);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_italic() {
|
||||
let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), true, false);
|
||||
let result = target.italic();
|
||||
let expected = true;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_bold() {
|
||||
let target = ThemeConfig::new(SerdeColor::new(29, 20, 45, 72), false, true);
|
||||
let result = target.bold();
|
||||
let expected = true;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
7
scripts/test.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
cargo test -p rider-generator
|
||||
cargo test -p rider-config
|
||||
cargo test -p rider-themes
|
||||
cargo test -p rider-lexers
|
||||
cargo test -p rider-editor
|
@ -1,113 +0,0 @@
|
||||
use crate::app::caret_manager;
|
||||
use crate::app::file_content_manager;
|
||||
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||
use crate::config::*;
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::caret::*;
|
||||
use crate::ui::file::editor_file::EditorFile;
|
||||
use crate::ui::file::*;
|
||||
use crate::ui::menu_bar::MenuBar;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::VideoSubsystem as VS;
|
||||
use std::boxed::Box;
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
pub struct AppState {
|
||||
menu_bar: MenuBar,
|
||||
files: Vec<EditorFile>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
file_editor: FileEditor,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||
Self {
|
||||
menu_bar: MenuBar::new(Arc::clone(&config)),
|
||||
files: vec![],
|
||||
file_editor: FileEditor::new(Arc::clone(&config)),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) {
|
||||
use std::fs::read_to_string;
|
||||
|
||||
if let Ok(buffer) = read_to_string(&file_path) {
|
||||
let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
||||
file.prepare_ui(renderer);
|
||||
match self.file_editor.open_file(file) {
|
||||
Some(old) => self.files.push(old),
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to open file: {}", file_path);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn file_editor(&self) -> &FileEditor {
|
||||
&self.file_editor
|
||||
}
|
||||
|
||||
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||
&mut self.file_editor
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AppState {
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) {
|
||||
self.file_editor
|
||||
.render(canvas, renderer, &RenderContext::Nothing);
|
||||
self.menu_bar
|
||||
.render(canvas, renderer, &RenderContext::Nothing);
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
self.menu_bar.prepare_ui(renderer);
|
||||
self.file_editor.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for AppState {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
self.menu_bar.update(ticks, context);
|
||||
self.file_editor.update(ticks, context);
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
||||
if self
|
||||
.menu_bar
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
video_subsystem.text_input().stop();
|
||||
return self.menu_bar.on_left_click(point, &UpdateContext::Nothing);
|
||||
} else {
|
||||
if !self
|
||||
.file_editor
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
return UpdateResult::NoOp;
|
||||
} else {
|
||||
video_subsystem.text_input().start();
|
||||
self.file_editor
|
||||
.on_left_click(point, &UpdateContext::Nothing);
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
|
||||
pub fn is_left_click_target(&self, _point: &Point) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for AppState {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
pub use crate::app::app_state::AppState;
|
||||
pub use crate::config::{Config, ConfigAccess, ConfigHolder};
|
||||
pub use crate::renderer::Renderer;
|
||||
use crate::themes::*;
|
||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||
use crate::ui::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use sdl2::event::*;
|
||||
use sdl2::hint;
|
||||
use sdl2::keyboard::{Keycode, Mod};
|
||||
use sdl2::mouse::*;
|
||||
use sdl2::pixels::{Color, PixelFormatEnum};
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Canvas;
|
||||
use sdl2::rwops::RWops;
|
||||
use sdl2::surface::Surface;
|
||||
use sdl2::ttf::Sdl2TtfContext;
|
||||
use sdl2::video::Window;
|
||||
use sdl2::EventPump;
|
||||
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
||||
|
||||
pub type WindowCanvas = Canvas<Window>;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum UpdateResult {
|
||||
NoOp,
|
||||
Stop,
|
||||
RefreshPositions,
|
||||
MouseLeftClicked(Point),
|
||||
MoveCaret(Rect, CaretPosition),
|
||||
DeleteFront,
|
||||
DeleteBack,
|
||||
Input(String),
|
||||
InsertNewLine,
|
||||
MoveCaretLeft,
|
||||
MoveCaretRight,
|
||||
MoveCaretUp,
|
||||
MoveCaretDown,
|
||||
Scroll { x: i32, y: i32 },
|
||||
WindowResize { width: i32, height: i32 },
|
||||
}
|
||||
|
||||
pub enum Task {
|
||||
OpenFile { file_path: String },
|
||||
}
|
||||
|
||||
pub struct Application {
|
||||
config: Arc<RwLock<Config>>,
|
||||
clear_color: Color,
|
||||
sdl_context: Sdl,
|
||||
canvas: WindowCanvas,
|
||||
video_subsystem: VideoSubsystem,
|
||||
tasks: Vec<Task>,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn new() -> Self {
|
||||
let config = Arc::new(RwLock::new(Config::new()));
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
|
||||
hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1");
|
||||
hint::set("SDL_GL_MULTISAMPLESAMPLES", "8");
|
||||
hint::set("SDL_GL_ACCELERATED_VISUAL", "1");
|
||||
hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2");
|
||||
hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1");
|
||||
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let mut window: Window = {
|
||||
let c = config.read().unwrap();
|
||||
video_subsystem
|
||||
.window("Rider", c.width(), c.height())
|
||||
.position_centered()
|
||||
.resizable()
|
||||
.opengl()
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
let icon_bytes = include_bytes!("../../assets/gear-64x64.bmp").clone();
|
||||
let mut rw = RWops::from_bytes(&icon_bytes).unwrap();
|
||||
let mut icon = Surface::load_bmp_rw(&mut rw).unwrap();
|
||||
window.set_icon(&mut icon);
|
||||
|
||||
let canvas = window.into_canvas().accelerated().build().unwrap();
|
||||
let clear_color: Color = { config.read().unwrap().theme().background().into() };
|
||||
|
||||
Self {
|
||||
sdl_context,
|
||||
video_subsystem,
|
||||
canvas,
|
||||
tasks: vec![],
|
||||
clear_color,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap();
|
||||
let mut event_pump = self.sdl_context.event_pump().unwrap();
|
||||
let font_context = sdl2::ttf::init().unwrap();
|
||||
let texture_creator = self.canvas.texture_creator();
|
||||
let sleep_time = Duration::new(0, 1_000_000_000u32 / 60);
|
||||
let mut app_state = AppState::new(Arc::clone(&self.config));
|
||||
let mut renderer = Renderer::new(Arc::clone(&self.config), &font_context, &texture_creator);
|
||||
app_state.prepare_ui(&mut renderer);
|
||||
|
||||
'running: loop {
|
||||
match self.handle_events(&mut event_pump) {
|
||||
UpdateResult::Stop => break 'running,
|
||||
UpdateResult::RefreshPositions => (),
|
||||
UpdateResult::NoOp => (),
|
||||
UpdateResult::MoveCaret(_, _pos) => (),
|
||||
UpdateResult::MouseLeftClicked(point) => {
|
||||
app_state.on_left_click(&point, &mut self.video_subsystem);
|
||||
}
|
||||
UpdateResult::DeleteFront => {
|
||||
app_state.file_editor_mut().delete_front(&mut renderer);
|
||||
}
|
||||
UpdateResult::DeleteBack => {
|
||||
app_state.file_editor_mut().delete_back(&mut renderer);
|
||||
}
|
||||
UpdateResult::Input(text) => {
|
||||
app_state.file_editor_mut().insert_text(text, &mut renderer);
|
||||
}
|
||||
UpdateResult::InsertNewLine => {
|
||||
app_state.file_editor_mut().insert_new_line(&mut renderer);
|
||||
}
|
||||
UpdateResult::MoveCaretLeft => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Left);
|
||||
}
|
||||
UpdateResult::MoveCaretRight => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Right);
|
||||
}
|
||||
UpdateResult::MoveCaretUp => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Up);
|
||||
}
|
||||
UpdateResult::MoveCaretDown => {
|
||||
app_state.file_editor_mut().move_caret(MoveDirection::Down);
|
||||
}
|
||||
UpdateResult::Scroll { x, y } => {
|
||||
app_state.file_editor_mut().scroll_to(-x, -y);
|
||||
}
|
||||
UpdateResult::WindowResize { width, height } => {
|
||||
let mut c = app_state.config().write().unwrap();
|
||||
if width > 0 {
|
||||
c.set_width(width as u32);
|
||||
}
|
||||
if height > 0 {
|
||||
c.set_height(height as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
for task in self.tasks.iter() {
|
||||
match task {
|
||||
Task::OpenFile { file_path } => {
|
||||
use crate::ui::file::editor_file::*;
|
||||
app_state.open_file(file_path.clone(), &mut renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tasks.clear();
|
||||
|
||||
self.clear();
|
||||
|
||||
app_state.update(timer.ticks() as i32, &UpdateContext::Nothing);
|
||||
app_state.render(&mut self.canvas, &mut renderer, &RenderContext::Nothing);
|
||||
|
||||
self.present();
|
||||
sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file(&mut self, file_path: String) {
|
||||
self.tasks.push(Task::OpenFile { file_path });
|
||||
}
|
||||
|
||||
fn present(&mut self) {
|
||||
self.canvas.present();
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.canvas.set_draw_color(self.clear_color.clone());
|
||||
self.canvas.clear();
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, event_pump: &mut EventPump) -> UpdateResult {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => return UpdateResult::Stop,
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn, x, y, ..
|
||||
} => match mouse_btn {
|
||||
MouseButton::Left => return UpdateResult::MouseLeftClicked(Point::new(x, y)),
|
||||
_ => (),
|
||||
},
|
||||
Event::KeyDown { keycode, .. } => {
|
||||
let keycode = if keycode.is_some() {
|
||||
keycode.unwrap()
|
||||
} else {
|
||||
return UpdateResult::NoOp;
|
||||
};
|
||||
match keycode {
|
||||
Keycode::Backspace => return UpdateResult::DeleteFront,
|
||||
Keycode::Delete => return UpdateResult::DeleteBack,
|
||||
Keycode::KpEnter | Keycode::Return => return UpdateResult::InsertNewLine,
|
||||
Keycode::Left => return UpdateResult::MoveCaretLeft,
|
||||
Keycode::Right => return UpdateResult::MoveCaretRight,
|
||||
Keycode::Up => return UpdateResult::MoveCaretUp,
|
||||
Keycode::Down => return UpdateResult::MoveCaretDown,
|
||||
_ => UpdateResult::NoOp,
|
||||
};
|
||||
}
|
||||
Event::TextInput { text, .. } => {
|
||||
return UpdateResult::Input(text);
|
||||
}
|
||||
Event::MouseWheel {
|
||||
direction, x, y, ..
|
||||
} => {
|
||||
match direction {
|
||||
MouseWheelDirection::Normal => {
|
||||
return UpdateResult::Scroll { x, y };
|
||||
}
|
||||
MouseWheelDirection::Flipped => {
|
||||
return UpdateResult::Scroll { x, y: -y };
|
||||
}
|
||||
_ => {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
return UpdateResult::WindowResize {
|
||||
width: w,
|
||||
height: h,
|
||||
};
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for Application {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
use crate::app::AppState;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
pub fn move_caret_right(file_editor: &mut FileEditor) {
|
||||
let file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
Some(f) => f,
|
||||
};
|
||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) {
|
||||
Some(text_character) => text_character,
|
||||
None => return, // EOF
|
||||
};
|
||||
let caret_rect = file_editor.caret().dest().clone();
|
||||
let pos = file_editor.caret().position();
|
||||
let (d, p): (Rect, CaretPosition) = match (
|
||||
c.is_last_in_line(),
|
||||
c.is_new_line(),
|
||||
c.dest().y() == caret_rect.y(),
|
||||
) {
|
||||
(true, true, false) => {
|
||||
let prev: TextCharacter = if c.position() != 0 {
|
||||
file.get_character_at(c.position() - 1).unwrap_or(c.clone())
|
||||
} else {
|
||||
c.clone()
|
||||
};
|
||||
let mut dest = prev.dest().clone();
|
||||
dest.set_x(dest.x() + dest.width() as i32);
|
||||
(dest, pos.moved(1, 0, 0))
|
||||
}
|
||||
(false, true, false) => {
|
||||
let prev: TextCharacter = if c.position() != 0 {
|
||||
file.get_character_at(c.position() - 1).unwrap_or(c.clone())
|
||||
} else {
|
||||
c.clone()
|
||||
};
|
||||
let mut dest = prev.dest().clone();
|
||||
if !prev.is_new_line() {
|
||||
dest.set_x(dest.x() + dest.width() as i32);
|
||||
}
|
||||
(dest, pos.moved(1, 0, 0))
|
||||
}
|
||||
(true, false, false) => {
|
||||
// move after character, stay on current line
|
||||
(c.dest().clone(), pos.moved(1, 0, 0))
|
||||
}
|
||||
(true, false, true) => {
|
||||
// move to new line
|
||||
(c.dest().clone(), pos.moved(1, 0, 0))
|
||||
}
|
||||
_ => (c.dest().clone(), pos.moved(1, 0, 0)),
|
||||
};
|
||||
file_editor
|
||||
.caret_mut()
|
||||
.move_caret(p, Point::new(d.x(), d.y()));
|
||||
}
|
||||
|
||||
pub fn move_caret_left(file_editor: &mut FileEditor) {
|
||||
let _file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
Some(f) => f,
|
||||
};
|
||||
let _line = file_editor.caret().line_number();
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
use crate::config::creator;
|
||||
use crate::config::EditorConfig;
|
||||
use crate::config::ScrollConfig;
|
||||
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,
|
||||
menu_height: u16,
|
||||
editor_config: EditorConfig,
|
||||
theme: Theme,
|
||||
extensions_mapping: LanguageMapping,
|
||||
scroll: ScrollConfig,
|
||||
}
|
||||
|
||||
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,
|
||||
menu_height: 60,
|
||||
theme: Theme::load(editor_config.current_theme().clone()),
|
||||
editor_config,
|
||||
extensions_mapping,
|
||||
scroll: ScrollConfig::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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 fn scroll(&self) -> &ScrollConfig {
|
||||
&self.scroll
|
||||
}
|
||||
|
||||
pub fn scroll_mut(&mut self) -> &mut ScrollConfig {
|
||||
&mut self.scroll
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_scroll() {
|
||||
let config = Config::new();
|
||||
let result = config.scroll();
|
||||
let expected = ScrollConfig::new();
|
||||
assert_eq!(result.clone(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_scroll_mut() {
|
||||
let mut config = Config::new();
|
||||
let result = config.scroll_mut();
|
||||
result.set_margin_right(1236);
|
||||
let mut expected = ScrollConfig::new();
|
||||
expected.set_margin_right(1236);
|
||||
assert_eq!(result.clone(), expected);
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use crate::config::directories::*;
|
||||
use crate::themes::config_creator;
|
||||
use dirs;
|
||||
use std::fs;
|
||||
use std::path;
|
||||
|
||||
pub fn create() {
|
||||
if !themes_dir().exists() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
let r = fs::create_dir_all(&project_dir());
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot create project directory"));
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
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");
|
||||
let r = fs::write(default_font_path, contents.to_vec());
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
r.unwrap_or_else(|_| panic!("Cannot write default font file!"));
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
use dirs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn log_dir() -> PathBuf {
|
||||
let mut log_dir = config_dir();
|
||||
log_dir.push("log");
|
||||
log_dir
|
||||
}
|
||||
|
||||
pub fn themes_dir() -> PathBuf {
|
||||
let mut themes_dir = config_dir();
|
||||
themes_dir.push("themes");
|
||||
themes_dir
|
||||
}
|
||||
|
||||
pub fn fonts_dir() -> PathBuf {
|
||||
let mut fonts_dir = config_dir();
|
||||
fonts_dir.push("fonts");
|
||||
fonts_dir
|
||||
}
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
let home_dir = dirs::config_dir().unwrap();
|
||||
|
||||
let mut config_dir = home_dir.clone();
|
||||
config_dir.push("rider");
|
||||
config_dir
|
||||
}
|
||||
|
||||
pub fn project_dir() -> PathBuf {
|
||||
let runtime = dirs::runtime_dir().unwrap();
|
||||
|
||||
let mut project_dir = runtime.clone();
|
||||
project_dir.push(".rider");
|
||||
project_dir
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
106
src/main.rs
@ -1,59 +1,53 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
extern crate dirs;
|
||||
extern crate plex;
|
||||
extern crate rand;
|
||||
extern crate sdl2;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
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;
|
||||
use log::Level;
|
||||
use simplelog::*;
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::File;
|
||||
|
||||
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");
|
||||
|
||||
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();
|
||||
}
|
||||
extern crate rider_config;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut app = Application::new();
|
||||
app.init();
|
||||
init_logger();
|
||||
app.open_file("./assets/examples/test.rs".to_string());
|
||||
app.run();
|
||||
let generator = rider_config::directories::get_binary_path("rider-generator").unwrap();
|
||||
println!("generator will be {:?}", generator);
|
||||
Command::new(generator).status().unwrap();
|
||||
|
||||
let editor = rider_config::directories::get_binary_path("rider-editor").unwrap();
|
||||
println!("editor will be {:?}", editor);
|
||||
Command::new(editor).status().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::env::set_var;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
fn exists(dir: &String, sub: &str) -> bool {
|
||||
Path::new(join(dir.clone(), sub.to_owned()).as_str()).exists()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn join(a: String, b: String) -> String {
|
||||
vec![a, b].join("/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_main() {
|
||||
let uniq = Uuid::new_v4();
|
||||
let test_path = join("/tmp/rider-tests".to_owned(), uniq.to_string());
|
||||
create_dir_all(test_path.clone()).unwrap();
|
||||
set_var("XDG_CONFIG_HOME", test_path.as_str());
|
||||
set_var("XDG_RUNTIME_DIR", test_path.as_str());
|
||||
let rider_dir = join(test_path.clone(), "rider".to_owned());
|
||||
assert_eq!(exists(&rider_dir, "themes"), false);
|
||||
assert_eq!(exists(&rider_dir, "log"), false);
|
||||
assert_eq!(exists(&test_path, ".rider"), false);
|
||||
assert_eq!(exists(&rider_dir, "themes/default.json"), false);
|
||||
assert_eq!(exists(&rider_dir, "themes/railscasts.json"), false);
|
||||
main();
|
||||
assert_eq!(exists(&rider_dir, "fonts"), true);
|
||||
assert_eq!(exists(&rider_dir, "log"), true);
|
||||
assert_eq!(exists(&rider_dir, "themes"), true);
|
||||
assert_eq!(exists(&test_path, ".rider"), true);
|
||||
assert_eq!(exists(&rider_dir, "themes/default.json"), true);
|
||||
assert_eq!(exists(&rider_dir, "themes/railscasts.json"), true);
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
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
@ -1,28 +0,0 @@
|
||||
#[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()
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
use crate::themes::SerdeColor;
|
||||
use crate::themes::ThemeConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct CodeHighlightingColor {
|
||||
pub comment: ThemeConfig,
|
||||
pub constant: ThemeConfig,
|
||||
pub error: ThemeConfig,
|
||||
pub warning: ThemeConfig,
|
||||
pub identifier: ThemeConfig,
|
||||
pub keyword: ThemeConfig,
|
||||
pub literal: ThemeConfig,
|
||||
pub number: ThemeConfig,
|
||||
pub operator: ThemeConfig,
|
||||
pub separator: ThemeConfig,
|
||||
pub statement: ThemeConfig,
|
||||
pub string: ThemeConfig,
|
||||
pub title: ThemeConfig,
|
||||
pub type_: ThemeConfig,
|
||||
pub todo: ThemeConfig,
|
||||
pub pre_proc: ThemeConfig,
|
||||
pub special: ThemeConfig,
|
||||
pub whitespace: ThemeConfig,
|
||||
}
|
||||
|
||||
impl Default for CodeHighlightingColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
comment: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
constant: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
error: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
warning: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
identifier: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
keyword: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
literal: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
number: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
operator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
separator: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
statement: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
string: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
title: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
type_: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
todo: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
pre_proc: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
special: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
whitespace: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeHighlightingColor {
|
||||
pub fn comment(&self) -> &ThemeConfig {
|
||||
&self.comment
|
||||
}
|
||||
|
||||
pub fn constant(&self) -> &ThemeConfig {
|
||||
&self.constant
|
||||
}
|
||||
|
||||
pub fn error(&self) -> &ThemeConfig {
|
||||
&self.error
|
||||
}
|
||||
|
||||
pub fn warning(&self) -> &ThemeConfig {
|
||||
&self.warning
|
||||
}
|
||||
|
||||
pub fn identifier(&self) -> &ThemeConfig {
|
||||
&self.identifier
|
||||
}
|
||||
|
||||
pub fn keyword(&self) -> &ThemeConfig {
|
||||
&self.keyword
|
||||
}
|
||||
|
||||
pub fn literal(&self) -> &ThemeConfig {
|
||||
&self.literal
|
||||
}
|
||||
|
||||
pub fn number(&self) -> &ThemeConfig {
|
||||
&self.number
|
||||
}
|
||||
|
||||
pub fn operator(&self) -> &ThemeConfig {
|
||||
&self.operator
|
||||
}
|
||||
|
||||
pub fn separator(&self) -> &ThemeConfig {
|
||||
&self.separator
|
||||
}
|
||||
|
||||
pub fn statement(&self) -> &ThemeConfig {
|
||||
&self.statement
|
||||
}
|
||||
|
||||
pub fn string(&self) -> &ThemeConfig {
|
||||
&self.string
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &ThemeConfig {
|
||||
&self.title
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> &ThemeConfig {
|
||||
&self.type_
|
||||
}
|
||||
|
||||
pub fn todo(&self) -> &ThemeConfig {
|
||||
&self.todo
|
||||
}
|
||||
|
||||
pub fn pre_proc(&self) -> &ThemeConfig {
|
||||
&self.pre_proc
|
||||
}
|
||||
|
||||
pub fn special(&self) -> &ThemeConfig {
|
||||
&self.special
|
||||
}
|
||||
|
||||
pub fn whitespace(&self) -> &ThemeConfig {
|
||||
&self.whitespace
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
use crate::config::directories::*;
|
||||
use crate::themes::predef::*;
|
||||
use crate::themes::*;
|
||||
use dirs;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn create() {
|
||||
fs::create_dir_all(themes_dir())
|
||||
.unwrap_or_else(|_| panic!("Cannot create theme config directory"));
|
||||
for theme in default_styles() {
|
||||
write_theme(&theme);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_theme(theme: &Theme) {
|
||||
let mut theme_path = themes_dir();
|
||||
theme_path.push(format!("{}.json", theme.name()));
|
||||
let contents = serde_json::to_string_pretty(&theme).unwrap();
|
||||
fs::write(&theme_path, contents.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to crate theme config file"));
|
||||
}
|
||||
|
||||
fn default_styles() -> Vec<Theme> {
|
||||
vec![default::build_theme(), railscasts::build_theme()]
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
use crate::config::directories::*;
|
||||
use sdl2::pixels::Color;
|
||||
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
|
||||
use serde_json;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod caret_color;
|
||||
pub mod code_highlighting_color;
|
||||
pub mod config_creator;
|
||||
pub mod diff_color;
|
||||
pub mod predef;
|
||||
pub mod serde_color;
|
||||
pub mod theme;
|
||||
pub mod theme_config;
|
||||
|
||||
pub use crate::themes::caret_color::CaretColor;
|
||||
pub use crate::themes::code_highlighting_color::CodeHighlightingColor;
|
||||
pub use crate::themes::diff_color::DiffColor;
|
||||
pub use crate::themes::serde_color::SerdeColor;
|
||||
pub use crate::themes::theme::Theme;
|
||||
pub use crate::themes::theme_config::ThemeConfig;
|
@ -1,26 +0,0 @@
|
||||
use sdl2::pixels::Color;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct SerdeColor {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl SerdeColor {
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Color> for &SerdeColor {
|
||||
fn into(self) -> Color {
|
||||
Color {
|
||||
r: self.r,
|
||||
g: self.g,
|
||||
b: self.b,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
use crate::config::directories::themes_dir;
|
||||
use crate::themes::CaretColor;
|
||||
use crate::themes::CodeHighlightingColor;
|
||||
use crate::themes::DiffColor;
|
||||
use crate::themes::SerdeColor;
|
||||
use dirs;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
pub struct Theme {
|
||||
name: String,
|
||||
background: SerdeColor,
|
||||
caret: CaretColor,
|
||||
code_highlighting: CodeHighlightingColor,
|
||||
diff: DiffColor,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
use crate::themes::config_creator;
|
||||
Self {
|
||||
name: "default".to_string(),
|
||||
background: SerdeColor::new(255, 255, 255, 0),
|
||||
caret: CaretColor::default(),
|
||||
code_highlighting: CodeHighlightingColor::default(),
|
||||
diff: DiffColor::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn new(
|
||||
name: String,
|
||||
background: SerdeColor,
|
||||
caret: CaretColor,
|
||||
code_highlighting: CodeHighlightingColor,
|
||||
diff: DiffColor,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
background,
|
||||
caret,
|
||||
code_highlighting,
|
||||
diff,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn background(&self) -> &SerdeColor {
|
||||
&self.background
|
||||
}
|
||||
|
||||
pub fn caret(&self) -> &CaretColor {
|
||||
&self.caret
|
||||
}
|
||||
|
||||
pub fn diff(&self) -> &DiffColor {
|
||||
&self.diff
|
||||
}
|
||||
|
||||
pub fn code_highlighting(&self) -> &CodeHighlightingColor {
|
||||
&self.code_highlighting
|
||||
}
|
||||
|
||||
pub fn load(theme_name: String) -> Self {
|
||||
let home_dir = dirs::config_dir().unwrap();
|
||||
let mut config_dir = home_dir.clone();
|
||||
config_dir.push("rider");
|
||||
fs::create_dir_all(&config_dir)
|
||||
.unwrap_or_else(|_| panic!("Cannot create config directory"));
|
||||
Self::load_content(format!("{}.json", theme_name).as_str())
|
||||
}
|
||||
|
||||
fn load_content(file_name: &str) -> Theme {
|
||||
let mut config_file = themes_dir();
|
||||
config_file.push(file_name);
|
||||
let contents = match fs::read_to_string(&config_file) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
use crate::themes::config_creator;
|
||||
config_creator::create();
|
||||
fs::read_to_string(&config_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to load theme config file"))
|
||||
}
|
||||
};
|
||||
serde_json::from_str(&contents).unwrap_or_default()
|
||||
}
|
||||
}
|