Add scrollbar (#1)
* Refactor code, prepare for scrollbar * Move file to file editor, fix rust lexer new line, refactor code, fix caret render position * Fix caret position at new lines * Scroll content
This commit is contained in:
parent
1617598fd4
commit
7d6a5887fa
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
.idea
|
.idea
|
||||||
log
|
log
|
||||||
|
.rider
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::app::caret_manager;
|
use crate::app::caret_manager;
|
||||||
use crate::app::file_content_manager;
|
use crate::app::file_content_manager;
|
||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::caret::Caret;
|
use crate::ui::caret::Caret;
|
||||||
@ -19,9 +19,8 @@ use std::sync::Arc;
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
menu_bar: MenuBar,
|
menu_bar: MenuBar,
|
||||||
files: Vec<EditorFile>,
|
files: Vec<EditorFile>,
|
||||||
current_file: usize,
|
|
||||||
caret: Caret,
|
|
||||||
config: Rc<Config>,
|
config: Rc<Config>,
|
||||||
|
file_editor: FileEditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@ -29,19 +28,29 @@ impl AppState {
|
|||||||
Self {
|
Self {
|
||||||
menu_bar: MenuBar::new(config.clone()),
|
menu_bar: MenuBar::new(config.clone()),
|
||||||
files: vec![],
|
files: vec![],
|
||||||
current_file: 0,
|
file_editor: FileEditor::new(
|
||||||
caret: Caret::new(config.clone()),
|
Rect::new(
|
||||||
|
config.editor_left_margin(),
|
||||||
|
config.editor_top_margin(),
|
||||||
|
config.width() - config.editor_left_margin() as u32,
|
||||||
|
config.height() - config.editor_top_margin() as u32,
|
||||||
|
),
|
||||||
|
config.clone(),
|
||||||
|
),
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_file(&mut self, file_path: String) {
|
pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) {
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
if let Ok(buffer) = read_to_string(&file_path) {
|
if let Ok(buffer) = read_to_string(&file_path) {
|
||||||
let file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
||||||
self.current_file = self.files.len();
|
file.prepare_ui(renderer);
|
||||||
self.files.push(file);
|
match self.file_editor.open_file(file) {
|
||||||
|
Some(old) => self.files.push(old),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Failed to open file: {}", file_path);
|
eprintln!("Failed to open file: {}", file_path);
|
||||||
};
|
};
|
||||||
@ -51,102 +60,55 @@ impl AppState {
|
|||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn caret(&self) -> &Caret {
|
pub fn file_editor(&self) -> &FileEditor {
|
||||||
&self.caret
|
&self.file_editor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn caret_mut(&mut self) -> &mut Caret {
|
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||||
&mut self.caret
|
&mut self.file_editor
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_file(&self) -> Option<&EditorFile> {
|
|
||||||
self.files.get(self.current_file)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_file_mut(&mut self) -> Option<&mut EditorFile> {
|
|
||||||
self.files.get_mut(self.current_file)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_editor_clicked(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
|
||||||
let current_file: &mut EditorFile = if let Some(current_file) = self.current_file_mut() {
|
|
||||||
current_file
|
|
||||||
} else {
|
|
||||||
return UpdateResult::NoOp;
|
|
||||||
};
|
|
||||||
if !current_file.is_left_click_target(point) {
|
|
||||||
return UpdateResult::NoOp;
|
|
||||||
}
|
|
||||||
video_subsystem.text_input().start();
|
|
||||||
match current_file.on_left_click(point) {
|
|
||||||
UpdateResult::MoveCaret(rect, position) => {
|
|
||||||
self.caret
|
|
||||||
.move_caret(position, Point::new(rect.x(), rect.y()));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_caret(&mut self, dir: MoveDirection) {
|
|
||||||
match dir {
|
|
||||||
MoveDirection::Left => {}
|
|
||||||
MoveDirection::Right => caret_manager::move_caret_right(self),
|
|
||||||
MoveDirection::Up => {}
|
|
||||||
MoveDirection::Down => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_front(&mut self) {
|
|
||||||
file_content_manager::delete_front(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_back(&mut self) {
|
|
||||||
file_content_manager::delete_back(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) {
|
|
||||||
file_content_manager::insert_text(self, text, renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_new_line(&mut self, renderer: &mut Renderer) {
|
|
||||||
file_content_manager::insert_new_line(self, renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_current_file(&mut self, file: EditorFile) {
|
|
||||||
self.files[self.current_file] = file;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AppState {
|
impl Render for AppState {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _parent: Parent) -> UpdateResult {
|
||||||
self.menu_bar.render(canvas, renderer);
|
self.file_editor.render(canvas, renderer, None);
|
||||||
if let Some(file) = self.current_file_mut() {
|
self.menu_bar.render(canvas, renderer, None)
|
||||||
file.render(canvas, renderer);
|
}
|
||||||
}
|
|
||||||
self.caret.render(canvas, renderer);
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
UpdateResult::NoOp
|
self.menu_bar.prepare_ui(renderer);
|
||||||
|
self.file_editor.prepare_ui(renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for AppState {
|
impl Update for AppState {
|
||||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||||
self.menu_bar.update(ticks);
|
self.menu_bar.update(ticks, context);
|
||||||
if let Some(file) = self.files.get_mut(self.current_file) {
|
self.file_editor.update(ticks, context);
|
||||||
file.update(ticks);
|
|
||||||
}
|
|
||||||
self.caret.update(ticks);
|
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
||||||
if self.menu_bar.is_left_click_target(point) {
|
if self
|
||||||
|
.menu_bar
|
||||||
|
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||||
|
{
|
||||||
video_subsystem.text_input().stop();
|
video_subsystem.text_input().stop();
|
||||||
return self.menu_bar.on_left_click(point);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.on_editor_clicked(point, video_subsystem);
|
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
234
src/app/application.rs
Normal file
234
src/app/application.rs
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
pub use crate::app::app_state::AppState;
|
||||||
|
pub use crate::config::Config;
|
||||||
|
pub use crate::renderer::Renderer;
|
||||||
|
use crate::themes::*;
|
||||||
|
use crate::ui::caret::{CaretPosition, MoveDirection};
|
||||||
|
use crate::ui::*;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use sdl2::event::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 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Task {
|
||||||
|
OpenFile { file_path: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Application {
|
||||||
|
config: Rc<Config>,
|
||||||
|
clear_color: Color,
|
||||||
|
sdl_context: Sdl,
|
||||||
|
canvas: WindowCanvas,
|
||||||
|
video_subsystem: VideoSubsystem,
|
||||||
|
tasks: Vec<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let config = Rc::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 = video_subsystem
|
||||||
|
.window("Rider", config.width(), config.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();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sdl_context,
|
||||||
|
video_subsystem,
|
||||||
|
canvas,
|
||||||
|
tasks: vec![],
|
||||||
|
clear_color: config.theme().background().into(),
|
||||||
|
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(self.config.clone());
|
||||||
|
let mut renderer = Renderer::new(self.config.clone(), &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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, None);
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateResult::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self) -> &Rc<Config> {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +1,64 @@
|
|||||||
use crate::app::AppState;
|
use crate::app::AppState;
|
||||||
use crate::ui::file::editor_file::EditorFile;
|
use crate::ui::*;
|
||||||
use crate::ui::text_character::TextCharacter;
|
|
||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
|
|
||||||
pub fn move_caret_right(app_state: &mut AppState) {
|
pub fn move_caret_right(file_editor: &mut FileEditor) {
|
||||||
let file: &EditorFile = match app_state.current_file() {
|
let file: &EditorFile = match file_editor.file() {
|
||||||
None => return,
|
None => return,
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
};
|
};
|
||||||
let line = match file.get_character_at(app_state.caret().text_position()) {
|
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) {
|
||||||
Some(t) if t.is_new_line() => app_state.caret().line_number().clone() + 1,
|
|
||||||
Some(_) => app_state.caret().line_number().clone(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let characters: Vec<&TextCharacter> = match file.get_line(&line) {
|
|
||||||
None => return,
|
|
||||||
Some(characters) => characters,
|
|
||||||
};
|
|
||||||
let mut idx = 0;
|
|
||||||
for (i, c) in characters.iter().enumerate() {
|
|
||||||
if c.position() == app_state.caret().text_position() {
|
|
||||||
idx = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let text_character: &TextCharacter = match characters.get(idx) {
|
|
||||||
Some(text_character) => text_character,
|
Some(text_character) => text_character,
|
||||||
None => return,
|
None => return, // EOF
|
||||||
};
|
};
|
||||||
let line = line - app_state.caret().line_number();
|
let caret_rect = file_editor.caret().dest().clone();
|
||||||
let pos = app_state.caret().position().moved(1, line as i32, 0);
|
let pos = file_editor.caret().position();
|
||||||
let mut d: Rect = text_character.dest().clone();
|
let (d, p): (Rect, CaretPosition) = match (
|
||||||
if text_character.is_new_line() && idx > 0 {
|
c.is_last_in_line(),
|
||||||
let prev = match characters.get(idx - 1) {
|
c.is_new_line(),
|
||||||
Some(c) => c,
|
c.dest().y() == caret_rect.y(),
|
||||||
_ => return,
|
) {
|
||||||
};
|
(true, true, false) => {
|
||||||
d = prev.dest().clone();
|
let prev: TextCharacter = if c.position() != 0 {
|
||||||
d.set_x(d.x() + d.width() as i32);
|
file.get_character_at(c.position() - 1).unwrap_or(c.clone())
|
||||||
}
|
} else {
|
||||||
app_state
|
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()
|
.caret_mut()
|
||||||
.move_caret(pos, Point::new(d.x(), d.y()));
|
.move_caret(p, Point::new(d.x(), d.y()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_caret_left(app_state: &mut AppState) {
|
pub fn move_caret_left(file_editor: &mut FileEditor) {
|
||||||
let _file: &EditorFile = match app_state.current_file() {
|
let _file: &EditorFile = match file_editor.file() {
|
||||||
None => return,
|
None => return,
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
};
|
};
|
||||||
let _line = app_state.caret().line_number();
|
let _line = file_editor.caret().line_number();
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,22 @@
|
|||||||
use crate::app::AppState;
|
use crate::app::*;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::caret::Caret;
|
use crate::ui::*;
|
||||||
use crate::ui::caret::CaretPosition;
|
use sdl2::rect::*;
|
||||||
use crate::ui::file::editor_file::EditorFile;
|
|
||||||
use crate::ui::get_text_character_rect;
|
|
||||||
use crate::ui::text_character::TextCharacter;
|
|
||||||
use sdl2::rect::Point;
|
|
||||||
use sdl2::rect::Rect;
|
|
||||||
|
|
||||||
fn get_character_at(app_state: &mut AppState, index: usize) -> Option<&TextCharacter> {
|
fn current_file_path(file_editor: &mut FileEditor) -> String {
|
||||||
match app_state.current_file() {
|
file_editor
|
||||||
None => return None,
|
.file()
|
||||||
Some(f) => f,
|
.map_or_else(|| String::new(), |f| f.path())
|
||||||
}
|
|
||||||
.get_character_at(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_file_path(app_state: &mut AppState) -> String {
|
pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||||
match app_state.current_file() {
|
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||||
Some(f) => f.path(),
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_front(app_state: &mut AppState) {
|
|
||||||
let mut buffer: String = if let Some(file) = app_state.current_file() {
|
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
.buffer();
|
.buffer();
|
||||||
let position: CaretPosition = app_state.caret().position().clone();
|
let position: CaretPosition = file_editor.caret().position().clone();
|
||||||
if position.text_position() == 0 {
|
if position.text_position() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -44,100 +30,107 @@ pub fn delete_front(app_state: &mut AppState) {
|
|||||||
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
||||||
_ => position.moved(0, 0, 0),
|
_ => position.moved(0, 0, 0),
|
||||||
};
|
};
|
||||||
|
let move_to = file_editor
|
||||||
let move_to = match get_character_at(app_state, position.text_position()) {
|
.file()
|
||||||
Some(character) => {
|
.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())))
|
Some((position, Point::new(dest.x(), dest.y())))
|
||||||
}
|
});
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
match move_to {
|
match move_to {
|
||||||
Some((position, point)) => app_state.caret_mut().move_caret(position, point),
|
Some((position, point)) => file_editor.caret_mut().move_caret(position, point),
|
||||||
None => app_state.caret_mut().reset_caret(),
|
None => file_editor.caret_mut().reset_caret(),
|
||||||
};
|
};
|
||||||
let new_file = EditorFile::new(
|
let mut new_file = EditorFile::new(
|
||||||
current_file_path(app_state),
|
current_file_path(file_editor),
|
||||||
buffer,
|
buffer,
|
||||||
app_state.config().clone(),
|
file_editor.config().clone(),
|
||||||
);
|
);
|
||||||
app_state.replace_current_file(new_file);
|
new_file.prepare_ui(renderer);
|
||||||
|
file_editor.replace_current_file(new_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_back(app_state: &mut AppState) {
|
pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||||
let file: &EditorFile = if let Some(file) = app_state.current_file() {
|
let file: &EditorFile = if let Some(file) = file_editor.file() {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut buffer: String = file.buffer();
|
let mut buffer: String = file.buffer();
|
||||||
let position: usize = app_state.caret().text_position();
|
let position: usize = file_editor.caret().text_position();
|
||||||
if position >= buffer.len() {
|
if position >= buffer.len() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
buffer.remove(position);
|
buffer.remove(position);
|
||||||
let new_file = EditorFile::new(file.path(), buffer, app_state.config().clone());
|
let mut new_file = EditorFile::new(file.path(), buffer, file_editor.config().clone());
|
||||||
app_state.replace_current_file(new_file);
|
new_file.prepare_ui(renderer);
|
||||||
|
file_editor.replace_current_file(new_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_text(app_state: &mut AppState, text: String, renderer: &mut Renderer) {
|
pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) {
|
||||||
let mut buffer: String = if let Some(file) = app_state.current_file() {
|
let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer());
|
||||||
file
|
if buffer.is_empty() {
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
.buffer();
|
|
||||||
let current = match get_character_at(app_state, app_state.caret().text_position()) {
|
let current = match file_editor
|
||||||
|
.file()
|
||||||
|
.and_then(|file| file.get_character_at(file_editor.caret().text_position()))
|
||||||
|
{
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
||||||
let mut position: CaretPosition = app_state.caret().position().clone();
|
let mut position: CaretPosition = file_editor.caret().position().clone();
|
||||||
for character in text.chars() {
|
for character in text.chars() {
|
||||||
buffer.insert(position.text_position(), character);
|
buffer.insert(position.text_position(), character);
|
||||||
if let Some(rect) = get_text_character_rect(character, renderer) {
|
if let Some(rect) = get_text_character_rect(character, renderer) {
|
||||||
pos = pos + Point::new(rect.width() as i32, 0);
|
pos = pos + Point::new(rect.width() as i32, 0);
|
||||||
position = position.moved(1, 0, 0);
|
position = position.moved(1, 0, 0);
|
||||||
app_state.caret_mut().move_caret(position, pos.clone());
|
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_file = EditorFile::new(
|
let mut new_file = EditorFile::new(
|
||||||
current_file_path(app_state),
|
file_editor.file().map_or(String::new(), |f| f.path()),
|
||||||
buffer,
|
buffer,
|
||||||
app_state.config().clone(),
|
file_editor.config().clone(),
|
||||||
);
|
);
|
||||||
|
new_file.prepare_ui(renderer);
|
||||||
app_state.replace_current_file(new_file);
|
file_editor.replace_current_file(new_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_new_line(app_state: &mut AppState, renderer: &mut Renderer) {
|
pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||||
let mut buffer: String = if let Some(file) = app_state.current_file() {
|
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
.buffer();
|
.buffer();
|
||||||
let current = match get_character_at(app_state, app_state.caret().text_position()) {
|
let current = match file_editor
|
||||||
|
.file()
|
||||||
|
.and_then(|file| file.get_character_at(file_editor.caret().text_position()))
|
||||||
|
{
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
let mut pos = Point::new(current.dest().x(), current.dest().y());
|
||||||
let mut position: CaretPosition = app_state.caret().position().clone();
|
let mut position: CaretPosition = file_editor.caret().position().clone();
|
||||||
buffer.insert(position.text_position(), '\n');
|
buffer.insert(position.text_position(), '\n');
|
||||||
if let Some(rect) = get_text_character_rect('\n', renderer) {
|
if let Some(rect) = get_text_character_rect('\n', renderer) {
|
||||||
pos = Point::new(
|
pos = Point::new(
|
||||||
app_state.config().editor_left_margin(),
|
file_editor.config().editor_left_margin(),
|
||||||
pos.y() + rect.height() as i32,
|
pos.y() + rect.height() as i32,
|
||||||
);
|
);
|
||||||
position = position.moved(0, 1, 0);
|
position = position.moved(0, 1, 0);
|
||||||
app_state.caret_mut().move_caret(position, pos.clone());
|
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_file = EditorFile::new(
|
let mut new_file = EditorFile::new(
|
||||||
current_file_path(app_state),
|
current_file_path(file_editor),
|
||||||
buffer,
|
buffer,
|
||||||
app_state.config().clone(),
|
file_editor.config().clone(),
|
||||||
);
|
);
|
||||||
app_state.replace_current_file(new_file);
|
new_file.prepare_ui(renderer);
|
||||||
|
file_editor.replace_current_file(new_file);
|
||||||
}
|
}
|
||||||
|
219
src/app/mod.rs
219
src/app/mod.rs
@ -1,218 +1,9 @@
|
|||||||
pub use crate::app::app_state::AppState;
|
|
||||||
pub use crate::config::Config;
|
|
||||||
pub use crate::renderer::Renderer;
|
|
||||||
use crate::themes::*;
|
|
||||||
use crate::ui::caret::{CaretPosition, MoveDirection};
|
|
||||||
use crate::ui::*;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use sdl2::event::Event;
|
|
||||||
use sdl2::hint;
|
|
||||||
use sdl2::keyboard::{Keycode, Mod};
|
|
||||||
use sdl2::mouse::MouseButton;
|
|
||||||
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 mod app_state;
|
pub mod app_state;
|
||||||
|
pub mod application;
|
||||||
pub mod caret_manager;
|
pub mod caret_manager;
|
||||||
pub mod file_content_manager;
|
pub mod file_content_manager;
|
||||||
|
|
||||||
pub type WindowCanvas = Canvas<Window>;
|
pub use crate::app::app_state::*;
|
||||||
|
pub use crate::app::application::*;
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
pub use crate::app::caret_manager::*;
|
||||||
pub enum UpdateResult {
|
pub use crate::app::file_content_manager::*;
|
||||||
NoOp,
|
|
||||||
Stop,
|
|
||||||
RefreshPositions,
|
|
||||||
MouseLeftClicked(Point),
|
|
||||||
MoveCaret(Rect, CaretPosition),
|
|
||||||
DeleteFront,
|
|
||||||
DeleteBack,
|
|
||||||
Input(String),
|
|
||||||
InsertNewLine,
|
|
||||||
MoveCaretLeft,
|
|
||||||
MoveCaretRight,
|
|
||||||
MoveCaretUp,
|
|
||||||
MoveCaretDown,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Task {
|
|
||||||
OpenFile { file_path: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Application {
|
|
||||||
config: Rc<Config>,
|
|
||||||
clear_color: Color,
|
|
||||||
sdl_context: Sdl,
|
|
||||||
canvas: WindowCanvas,
|
|
||||||
video_subsystem: VideoSubsystem,
|
|
||||||
tasks: Vec<Task>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let config = Rc::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 = video_subsystem
|
|
||||||
.window("Rider", config.width(), config.height())
|
|
||||||
.position_centered()
|
|
||||||
.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();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
sdl_context,
|
|
||||||
video_subsystem,
|
|
||||||
canvas,
|
|
||||||
tasks: vec![],
|
|
||||||
clear_color: config.theme().background().into(),
|
|
||||||
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(self.config.clone());
|
|
||||||
let mut renderer = Renderer::new(self.config.clone(), &font_context, &texture_creator);
|
|
||||||
|
|
||||||
'running: loop {
|
|
||||||
match self.handle_events(&mut event_pump) {
|
|
||||||
UpdateResult::Stop => break 'running,
|
|
||||||
UpdateResult::RefreshPositions => (),
|
|
||||||
UpdateResult::NoOp => (),
|
|
||||||
UpdateResult::MoveCaret(_, _pos) => (),
|
|
||||||
UpdateResult::MouseLeftClicked(point) => {
|
|
||||||
app_state.on_left_click(&point, &mut self.video_subsystem);
|
|
||||||
}
|
|
||||||
UpdateResult::DeleteFront => {
|
|
||||||
app_state.delete_front();
|
|
||||||
}
|
|
||||||
UpdateResult::DeleteBack => {
|
|
||||||
app_state.delete_back();
|
|
||||||
}
|
|
||||||
UpdateResult::Input(text) => {
|
|
||||||
app_state.insert_text(text, &mut renderer);
|
|
||||||
}
|
|
||||||
UpdateResult::InsertNewLine => {
|
|
||||||
app_state.insert_new_line(&mut renderer);
|
|
||||||
}
|
|
||||||
UpdateResult::MoveCaretLeft => {
|
|
||||||
app_state.move_caret(MoveDirection::Left);
|
|
||||||
}
|
|
||||||
UpdateResult::MoveCaretRight => {
|
|
||||||
app_state.move_caret(MoveDirection::Right);
|
|
||||||
}
|
|
||||||
UpdateResult::MoveCaretUp => {
|
|
||||||
app_state.move_caret(MoveDirection::Up);
|
|
||||||
}
|
|
||||||
UpdateResult::MoveCaretDown => {
|
|
||||||
app_state.move_caret(MoveDirection::Down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.tasks.clear();
|
|
||||||
|
|
||||||
self.clear();
|
|
||||||
|
|
||||||
app_state.update(timer.ticks() as i32);
|
|
||||||
app_state.render(&mut self.canvas, &mut renderer);
|
|
||||||
|
|
||||||
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, .. } => {
|
|
||||||
println!("text input: {}", text);
|
|
||||||
return UpdateResult::Input(text);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(&self) -> &Rc<Config> {
|
|
||||||
&self.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -19,6 +19,11 @@ pub fn create() {
|
|||||||
if !log_dir().exists() {
|
if !log_dir().exists() {
|
||||||
fs::create_dir_all(&log_dir()).unwrap_or_else(|_| panic!("Cannot create log directory"));
|
fs::create_dir_all(&log_dir()).unwrap_or_else(|_| panic!("Cannot create log directory"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !project_dir().exists() {
|
||||||
|
fs::create_dir_all(&project_dir())
|
||||||
|
.unwrap_or_else(|_| panic!("Cannot create project directory"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_default_fonts() {
|
fn write_default_fonts() {
|
||||||
|
@ -26,3 +26,11 @@ pub fn config_dir() -> PathBuf {
|
|||||||
config_dir.push("rider");
|
config_dir.push("rider");
|
||||||
config_dir
|
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
|
||||||
|
}
|
||||||
|
@ -56,6 +56,7 @@ impl EditorConfig {
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
scroll_speed: i32,
|
||||||
menu_height: u16,
|
menu_height: u16,
|
||||||
editor_config: EditorConfig,
|
editor_config: EditorConfig,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
@ -74,12 +75,18 @@ impl Config {
|
|||||||
Self {
|
Self {
|
||||||
width: 1024,
|
width: 1024,
|
||||||
height: 860,
|
height: 860,
|
||||||
|
scroll_speed: 10,
|
||||||
menu_height: 60,
|
menu_height: 60,
|
||||||
theme: Theme::load(editor_config.current_theme().clone()),
|
theme: Theme::load(editor_config.current_theme().clone()),
|
||||||
editor_config,
|
editor_config,
|
||||||
extensions_mapping,
|
extensions_mapping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn scroll_speed(&self) -> i32 {
|
||||||
|
self.scroll_speed
|
||||||
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> u32 {
|
pub fn width(&self) -> u32 {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
|||||||
pub mod plain;
|
pub mod plain;
|
||||||
pub mod rust_lang;
|
pub mod rust_lang;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
PlainText,
|
PlainText,
|
||||||
Rust,
|
Rust,
|
||||||
|
@ -7,7 +7,7 @@ pub mod lexer {
|
|||||||
lexer! {
|
lexer! {
|
||||||
fn next_token(text: 'a) -> (TokenType, &'a str);
|
fn next_token(text: 'a) -> (TokenType, &'a str);
|
||||||
|
|
||||||
r"( +|\t+|\n+)" => (TokenType::Whitespace {
|
r"( +|\t+|\n)" => (TokenType::Whitespace {
|
||||||
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
token: Token::new(text.to_string(), 0, 0, 0, 0)
|
||||||
}, text),
|
}, text),
|
||||||
|
|
||||||
|
@ -10,16 +10,8 @@ pub struct CaretColor {
|
|||||||
impl Default for CaretColor {
|
impl Default for CaretColor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bright: ThemeConfig::new(
|
bright: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||||
SerdeColor::new(0, 0, 0, 0),
|
blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
blur: ThemeConfig::new(
|
|
||||||
SerdeColor::new(0, 0, 0, 0),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
113
src/ui/caret.rs
113
src/ui/caret.rs
@ -1,4 +1,4 @@
|
|||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::text_character::TextCharacter;
|
use crate::ui::text_character::TextCharacter;
|
||||||
@ -10,7 +10,7 @@ use std::ops::Deref;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum CaretState {
|
pub enum CaretState {
|
||||||
Bright,
|
Bright,
|
||||||
Blur,
|
Blur,
|
||||||
}
|
}
|
||||||
@ -95,25 +95,6 @@ pub struct CaretRenderPosition {
|
|||||||
reset_position: Rect,
|
reset_position: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CaretRenderPosition {
|
|
||||||
pub fn dest(&self) -> &Rect {
|
|
||||||
&self.dest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_position(&self) -> &Rect {
|
|
||||||
&self.reset_position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.dest = self.reset_position.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_to(&mut self, p: &Point) {
|
|
||||||
self.dest.set_x(p.x());
|
|
||||||
self.dest.set_y(p.y());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct CaretColor {
|
pub struct CaretColor {
|
||||||
bright: Color,
|
bright: Color,
|
||||||
@ -140,7 +121,7 @@ pub struct Caret {
|
|||||||
blink_delay: u8,
|
blink_delay: u8,
|
||||||
state: CaretState,
|
state: CaretState,
|
||||||
position: CaretPosition,
|
position: CaretPosition,
|
||||||
render_position: CaretRenderPosition,
|
dest: Rect,
|
||||||
colors: CaretColor,
|
colors: CaretColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,20 +132,7 @@ impl Caret {
|
|||||||
Self {
|
Self {
|
||||||
state: CaretState::Bright,
|
state: CaretState::Bright,
|
||||||
blink_delay: 0,
|
blink_delay: 0,
|
||||||
render_position: CaretRenderPosition {
|
dest: Rect::new(0, 0, 6, 0),
|
||||||
dest: Rect::new(
|
|
||||||
config.editor_left_margin(),
|
|
||||||
config.editor_top_margin(),
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
reset_position: Rect::new(
|
|
||||||
config.editor_left_margin(),
|
|
||||||
config.editor_top_margin(),
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
colors: CaretColor { bright, blur },
|
colors: CaretColor { bright, blur },
|
||||||
pending: true,
|
pending: true,
|
||||||
position: CaretPosition {
|
position: CaretPosition {
|
||||||
@ -172,7 +140,6 @@ impl Caret {
|
|||||||
line_number: 0,
|
line_number: 0,
|
||||||
line_position: 0,
|
line_position: 0,
|
||||||
},
|
},
|
||||||
// config,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,13 +152,19 @@ impl Caret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_caret(&mut self) {
|
pub fn reset_caret(&mut self) {
|
||||||
self.render_position.reset();
|
self.dest.set_x(0);
|
||||||
|
self.dest.set_y(0);
|
||||||
self.position.reset();
|
self.position.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_caret(&mut self, position: CaretPosition, pos: Point) {
|
pub fn move_caret(&mut self, position: CaretPosition, pos: Point) {
|
||||||
self.position = position;
|
self.position = position;
|
||||||
self.render_position.move_to(&pos);
|
self.dest.set_x(pos.x());
|
||||||
|
self.dest.set_y(pos.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dest(&self) -> &Rect {
|
||||||
|
&self.dest
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(&self) -> &CaretPosition {
|
pub fn position(&self) -> &CaretPosition {
|
||||||
@ -208,52 +181,66 @@ impl Deref for Caret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Caret {
|
impl Render for Caret {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) -> UR {
|
||||||
if self.pending {
|
let dest = match parent {
|
||||||
if let Some(rect) = get_text_character_rect('W', renderer) {
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
let mut dest = self.render_position.dest().clone();
|
None => self.dest().clone(),
|
||||||
dest.set_height(rect.height());
|
};
|
||||||
let reset_position = dest.clone();
|
|
||||||
self.render_position = CaretRenderPosition {
|
|
||||||
dest,
|
|
||||||
reset_position,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self.pending = false;
|
|
||||||
}
|
|
||||||
let dest = self.render_position.dest();
|
|
||||||
let start = Point::new(dest.x(), dest.y());
|
let start = Point::new(dest.x(), dest.y());
|
||||||
let end = Point::new(dest.x(), dest.y() + dest.height() as i32);
|
let end = Point::new(dest.x(), dest.y() + dest.height() as i32);
|
||||||
let color = match self.state {
|
let color = match self.state {
|
||||||
CaretState::Bright => self.colors.bright(),
|
CaretState::Bright => self.colors.bright(),
|
||||||
CaretState::Blur => self.colors.blur(),
|
CaretState::Blur => self.colors.blur(),
|
||||||
};
|
}
|
||||||
canvas.set_draw_color(color.clone());
|
.clone();
|
||||||
|
canvas.set_draw_color(color);
|
||||||
canvas
|
canvas
|
||||||
.draw_line(start, end)
|
.draw_line(start, end)
|
||||||
.unwrap_or_else(|_| panic!("Failed to draw a caret"));
|
.unwrap_or_else(|_| panic!("Failed to draw a caret"));
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
if !self.pending {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rect) = get_text_character_rect('W', renderer) {
|
||||||
|
self.dest.set_height(rect.height());
|
||||||
|
}
|
||||||
|
self.pending = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for Caret {
|
impl Update for Caret {
|
||||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
||||||
self.blink_delay += 1;
|
self.blink_delay += 1;
|
||||||
if self.blink_delay >= 30 {
|
if self.blink_delay >= 30 {
|
||||||
self.blink_delay = 0;
|
self.blink_delay = 0;
|
||||||
self.toggle_state();
|
self.toggle_state();
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for Caret {
|
impl ClickHandler for Caret {
|
||||||
fn on_left_click(&mut self, _point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||||
// self.move_caret(Point::new(self.position.x(), self.position.y()));
|
UR::NoOp
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
is_in_rect(point, &self.render_position.dest())
|
is_in_rect(
|
||||||
|
point,
|
||||||
|
&match context {
|
||||||
|
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||||
|
_ => self.dest().clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBox for Caret {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.dest().top_left()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::file::editor_file_section::EditorFileSection;
|
use crate::ui::file::editor_file_section::EditorFileSection;
|
||||||
use crate::ui::text_character::TextCharacter;
|
use crate::ui::text_character::TextCharacter;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EditorFile {
|
pub struct EditorFile {
|
||||||
path: String,
|
path: String,
|
||||||
sections: Vec<EditorFileSection>,
|
sections: Vec<EditorFileSection>,
|
||||||
render_position: Rect,
|
render_position: Rect,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
config: Rc<Config>,
|
config: Rc<Config>,
|
||||||
|
line_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorFile {
|
impl EditorFile {
|
||||||
pub fn new(path: String, buffer: String, config: Rc<Config>) -> Self {
|
pub fn new(path: String, buffer: String, config: Rc<Config>) -> Self {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
let ext = Path::new(&path)
|
||||||
let p = Path::new(&path);
|
.extension()
|
||||||
let ext = match p.extension() {
|
.and_then(|p| p.to_str())
|
||||||
Some(s) => s.to_str().unwrap_or("txt"),
|
.map_or("txt", |s| s)
|
||||||
None => "txt",
|
.to_string();
|
||||||
}
|
|
||||||
.to_string();
|
|
||||||
let sections = vec![EditorFileSection::new(buffer.clone(), ext, config.clone())];
|
let sections = vec![EditorFileSection::new(buffer.clone(), ext, config.clone())];
|
||||||
let x = config.editor_left_margin();
|
let x = config.editor_left_margin();
|
||||||
let y = config.editor_top_margin();
|
let y = config.editor_top_margin();
|
||||||
@ -37,6 +37,7 @@ impl EditorFile {
|
|||||||
render_position: Rect::new(x, y, 0, 0),
|
render_position: Rect::new(x, y, 0, 0),
|
||||||
buffer,
|
buffer,
|
||||||
config,
|
config,
|
||||||
|
line_height: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,10 +49,23 @@ impl EditorFile {
|
|||||||
self.path.clone()
|
self.path.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> {
|
pub fn sections(&self) -> &Vec<EditorFileSection> {
|
||||||
|
&self.sections
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_height(&self) -> u32 {
|
||||||
|
self.line_height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_position(&self) -> &Rect {
|
||||||
|
&self.render_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
||||||
for section in self.sections.iter() {
|
for section in self.sections.iter() {
|
||||||
if let Some(text_character) = section.get_character_at(index) {
|
let character = section.get_character_at(index);
|
||||||
return Some(text_character);
|
if character.is_some() {
|
||||||
|
return character;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -59,7 +73,6 @@ impl EditorFile {
|
|||||||
|
|
||||||
pub fn get_line(&self, line: &usize) -> Option<Vec<&TextCharacter>> {
|
pub fn get_line(&self, line: &usize) -> Option<Vec<&TextCharacter>> {
|
||||||
let mut vec: Vec<&TextCharacter> = vec![];
|
let mut vec: Vec<&TextCharacter> = vec![];
|
||||||
|
|
||||||
for section in self.sections.iter() {
|
for section in self.sections.iter() {
|
||||||
match section.get_line(line) {
|
match section.get_line(line) {
|
||||||
Some(v) => vec.append(&mut v.clone()),
|
Some(v) => vec.append(&mut v.clone()),
|
||||||
@ -74,8 +87,23 @@ impl EditorFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> {
|
||||||
|
let mut current = None;
|
||||||
|
for section in self.sections.iter() {
|
||||||
|
let c = section.get_last_at_line(line);
|
||||||
|
if c.is_some() {
|
||||||
|
current = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_section_at_mut(&mut self, index: usize) -> Option<&mut EditorFileSection> {
|
||||||
|
self.sections.get_mut(index)
|
||||||
|
}
|
||||||
|
|
||||||
fn refresh_characters_position(&mut self) {
|
fn refresh_characters_position(&mut self) {
|
||||||
let mut current: Rect = self.render_position.clone();
|
let mut current: Rect = Rect::new(0, 0, 0, 0);
|
||||||
for section in self.sections.iter_mut() {
|
for section in self.sections.iter_mut() {
|
||||||
section.update_positions(&mut current);
|
section.update_positions(&mut current);
|
||||||
}
|
}
|
||||||
@ -83,47 +111,66 @@ impl EditorFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for EditorFile {
|
impl Render for EditorFile {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||||
let mut res = UpdateResult::NoOp;
|
for section in self.sections.iter() {
|
||||||
|
section.render(canvas, renderer, parent);
|
||||||
|
}
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
for section in self.sections.iter_mut() {
|
for section in self.sections.iter_mut() {
|
||||||
res = section.render(canvas, renderer);
|
section.prepare_ui(renderer);
|
||||||
}
|
}
|
||||||
if res == UpdateResult::RefreshPositions {
|
if let Some(r) = get_text_character_rect('W', renderer) {
|
||||||
self.refresh_characters_position();
|
self.line_height = r.height();
|
||||||
for section in self.sections.iter_mut() {
|
|
||||||
section.render(canvas, renderer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
self.refresh_characters_position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for EditorFile {
|
impl Update for EditorFile {
|
||||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||||
let mut result = UpdateResult::NoOp;
|
let mut result = UR::NoOp;
|
||||||
for section in self.sections.iter_mut() {
|
for section in self.sections.iter_mut() {
|
||||||
result = section.update(ticks);
|
result = section.update(ticks, context);
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for EditorFile {
|
impl ClickHandler for EditorFile {
|
||||||
fn on_left_click(&mut self, point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR {
|
||||||
for section in self.sections.iter_mut() {
|
let mut index = -1;
|
||||||
if section.is_left_click_target(point) {
|
for (i, section) in self.sections.iter().enumerate() {
|
||||||
return section.on_left_click(point);
|
if section.is_left_click_target(point, context) {
|
||||||
|
index = i as i32;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
if index >= 0 {
|
||||||
|
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||||
|
return self
|
||||||
|
.get_section_at_mut(index as usize)
|
||||||
|
.unwrap()
|
||||||
|
.on_left_click(point, &context);
|
||||||
|
}
|
||||||
|
UR::NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool {
|
||||||
|
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||||
for section in self.sections.iter() {
|
for section in self.sections.iter() {
|
||||||
if section.is_left_click_target(point) {
|
if section.is_left_click_target(point, &context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RenderBox for EditorFile {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.render_position.top_left()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
|
use std::cell::Cell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::lexer::Language;
|
use crate::lexer::Language;
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
@ -9,7 +10,7 @@ use crate::ui::file::editor_file_token::EditorFileToken;
|
|||||||
use crate::ui::text_character::TextCharacter;
|
use crate::ui::text_character::TextCharacter;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EditorFileSection {
|
pub struct EditorFileSection {
|
||||||
tokens: Vec<EditorFileToken>,
|
tokens: Vec<EditorFileToken>,
|
||||||
language: Language,
|
language: Language,
|
||||||
@ -27,9 +28,19 @@ impl EditorFileSection {
|
|||||||
let lexer_tokens = lexer::parse(buffer.clone(), language);
|
let lexer_tokens = lexer::parse(buffer.clone(), language);
|
||||||
|
|
||||||
let mut tokens: Vec<EditorFileToken> = vec![];
|
let mut tokens: Vec<EditorFileToken> = vec![];
|
||||||
for token_type in lexer_tokens {
|
let mut iterator = lexer_tokens.iter().peekable();
|
||||||
let token = EditorFileToken::new(token_type, config.clone());
|
loop {
|
||||||
tokens.push(token.clone());
|
let token_type = match iterator.next() {
|
||||||
|
Some(t) => t,
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
let next = iterator.peek();
|
||||||
|
let token = EditorFileToken::new(
|
||||||
|
token_type,
|
||||||
|
next.map_or(true, |t| t.is_new_line()),
|
||||||
|
config.clone(),
|
||||||
|
);
|
||||||
|
tokens.push(token);
|
||||||
}
|
}
|
||||||
let language = Language::PlainText;
|
let language = Language::PlainText;
|
||||||
Self {
|
Self {
|
||||||
@ -45,10 +56,11 @@ impl EditorFileSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> {
|
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
||||||
for token in self.tokens.iter() {
|
for token in self.tokens.iter() {
|
||||||
if let Some(text_character) = token.get_character_at(index) {
|
let character = token.get_character_at(index);
|
||||||
return Some(text_character);
|
if character.is_some() {
|
||||||
|
return character;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -68,46 +80,72 @@ impl EditorFileSection {
|
|||||||
Some(vec)
|
Some(vec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> {
|
||||||
|
let mut current: Option<TextCharacter> = None;
|
||||||
|
for token in self.tokens.iter() {
|
||||||
|
if !token.is_last_in_line() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let c = token.get_last_at_line(line);
|
||||||
|
if c.is_some() {
|
||||||
|
current = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for EditorFileSection {
|
impl Render for EditorFileSection {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||||
let mut res = UpdateResult::NoOp;
|
for token in self.tokens.iter() {
|
||||||
for character in self.tokens.iter_mut() {
|
token.render(canvas, renderer, parent);
|
||||||
let r = character.render(canvas, renderer);
|
}
|
||||||
if res == UpdateResult::NoOp {
|
UR::NoOp
|
||||||
res = r;
|
}
|
||||||
}
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
for token in self.tokens.iter_mut() {
|
||||||
|
token.prepare_ui(renderer);
|
||||||
}
|
}
|
||||||
res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for EditorFileSection {
|
impl Update for EditorFileSection {
|
||||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||||
let mut result = UpdateResult::NoOp;
|
let mut result = UR::NoOp;
|
||||||
for file_char in self.tokens.iter_mut() {
|
for token in self.tokens.iter_mut() {
|
||||||
result = file_char.update(ticks)
|
result = token.update(ticks, context)
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for EditorFileSection {
|
impl ClickHandler for EditorFileSection {
|
||||||
fn on_left_click(&mut self, point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR {
|
||||||
for token in self.tokens.iter_mut() {
|
for token in self.tokens.iter_mut() {
|
||||||
if token.is_left_click_target(point) {
|
if token.is_left_click_target(point, context) {
|
||||||
return token.on_left_click(point);
|
return token.on_left_click(point, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
for token in self.tokens.iter() {
|
let mut i = 0;
|
||||||
if token.is_left_click_target(point) {
|
loop {
|
||||||
return true;
|
if i == self.tokens.len() {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
match self.tokens.get(i) {
|
||||||
|
Some(token) => {
|
||||||
|
if token.is_left_click_target(point, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::lexer::TokenType;
|
use crate::lexer::TokenType;
|
||||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||||
use crate::renderer::Renderer;
|
use crate::renderer::Renderer;
|
||||||
use crate::ui::text_character::*;
|
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
@ -11,32 +10,55 @@ use sdl2::render::Texture;
|
|||||||
use sdl2::ttf::Font;
|
use sdl2::ttf::Font;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl TokenType {
|
||||||
|
pub fn to_color(&self, config: &Rc<Config>) -> Color {
|
||||||
|
let config = config.theme().code_highlighting();
|
||||||
|
match self {
|
||||||
|
&TokenType::Whitespace { .. } => config.whitespace().color().into(),
|
||||||
|
&TokenType::Keyword { .. } => config.keyword().color().into(),
|
||||||
|
&TokenType::String { .. } => config.string().color().into(),
|
||||||
|
&TokenType::Number { .. } => config.number().color().into(),
|
||||||
|
&TokenType::Identifier { .. } => config.identifier().color().into(),
|
||||||
|
&TokenType::Literal { .. } => config.literal().color().into(),
|
||||||
|
&TokenType::Comment { .. } => config.comment().color().into(),
|
||||||
|
&TokenType::Operator { .. } => config.operator().color().into(),
|
||||||
|
&TokenType::Separator { .. } => config.separator().color().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct EditorFileToken {
|
pub struct EditorFileToken {
|
||||||
|
last_in_line: bool,
|
||||||
characters: Vec<TextCharacter>,
|
characters: Vec<TextCharacter>,
|
||||||
token_type: TokenType,
|
token_type: Rc<TokenType>,
|
||||||
config: Rc<Config>,
|
config: Rc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorFileToken {
|
impl EditorFileToken {
|
||||||
pub fn new(token_type: TokenType, config: Rc<Config>) -> Self {
|
pub fn new(token_type: &TokenType, last_in_line: bool, config: Rc<Config>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
last_in_line,
|
||||||
characters: vec![],
|
characters: vec![],
|
||||||
token_type,
|
token_type: Rc::new(token_type.clone()),
|
||||||
config,
|
config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_last_in_line(&self) -> bool {
|
||||||
|
self.last_in_line
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_position(&mut self, current: &mut Rect) {
|
pub fn update_position(&mut self, current: &mut Rect) {
|
||||||
for text_character in self.characters.iter_mut() {
|
for text_character in self.characters.iter_mut() {
|
||||||
text_character.update_position(current);
|
text_character.update_position(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> {
|
pub fn get_character_at(&self, index: usize) -> Option<TextCharacter> {
|
||||||
for character in self.characters.iter() {
|
for character in self.characters.iter() {
|
||||||
if character.position() == index {
|
if character.position() == index {
|
||||||
return Some(&character);
|
return Some(character.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@ -70,32 +92,17 @@ impl EditorFileToken {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult {
|
pub fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> {
|
||||||
let config = renderer.config().theme().code_highlighting();
|
let mut current: Option<&TextCharacter> = None;
|
||||||
let color: Color = match self.token_type {
|
for text_character in self.characters.iter() {
|
||||||
TokenType::Whitespace { .. } => config.whitespace().color().into(),
|
if !text_character.is_last_in_line() {
|
||||||
TokenType::Keyword { .. } => config.keyword().color().into(),
|
continue;
|
||||||
TokenType::String { .. } => config.string().color().into(),
|
}
|
||||||
TokenType::Number { .. } => config.number().color().into(),
|
if text_character.line() == line {
|
||||||
TokenType::Identifier { .. } => config.identifier().color().into(),
|
current = Some(text_character);
|
||||||
TokenType::Literal { .. } => config.literal().color().into(),
|
}
|
||||||
TokenType::Comment { .. } => config.comment().color().into(),
|
|
||||||
TokenType::Operator { .. } => config.operator().color().into(),
|
|
||||||
TokenType::Separator { .. } => config.separator().color().into(),
|
|
||||||
};
|
|
||||||
for (index, c) in self.token_type.text().chars().enumerate() {
|
|
||||||
let mut text_character = TextCharacter::new(
|
|
||||||
c.clone(),
|
|
||||||
self.token_type.start() + index,
|
|
||||||
self.token_type.line(),
|
|
||||||
color,
|
|
||||||
self.config.clone(),
|
|
||||||
);
|
|
||||||
text_character.update_view(renderer);
|
|
||||||
self.characters.push(text_character);
|
|
||||||
}
|
}
|
||||||
|
current.map(|c| c.clone())
|
||||||
UpdateResult::RefreshPositions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,42 +111,60 @@ impl Render for EditorFileToken {
|
|||||||
* Must first create targets so even if new line appear renderer will know
|
* Must first create targets so even if new line appear renderer will know
|
||||||
* where move render starting point
|
* where move render starting point
|
||||||
*/
|
*/
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||||
if self.characters.is_empty() {
|
|
||||||
return self.update_view(renderer);
|
|
||||||
}
|
|
||||||
if self.token_type.is_new_line() {
|
if self.token_type.is_new_line() {
|
||||||
return UpdateResult::NoOp;
|
return UR::NoOp;
|
||||||
}
|
}
|
||||||
for text_character in self.characters.iter_mut() {
|
for text_character in self.characters.iter() {
|
||||||
text_character.render(canvas, renderer);
|
text_character.render(canvas, renderer, parent);
|
||||||
|
}
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
if !self.characters.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let color: Color = self.token_type.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();
|
||||||
|
let mut text_character: TextCharacter = TextCharacter::new(
|
||||||
|
c.clone(),
|
||||||
|
self.token_type.start() + index,
|
||||||
|
self.token_type.line(),
|
||||||
|
last_in_line,
|
||||||
|
color,
|
||||||
|
self.config.clone(),
|
||||||
|
);
|
||||||
|
text_character.prepare_ui(renderer);
|
||||||
|
self.characters.push(text_character);
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for EditorFileToken {
|
impl Update for EditorFileToken {
|
||||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||||
for text_character in self.characters.iter_mut() {
|
for text_character in self.characters.iter_mut() {
|
||||||
text_character.update(ticks);
|
text_character.update(ticks, context);
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for EditorFileToken {
|
impl ClickHandler for EditorFileToken {
|
||||||
fn on_left_click(&mut self, point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR {
|
||||||
for text_character in self.characters.iter_mut() {
|
for text_character in self.characters.iter_mut() {
|
||||||
if text_character.is_left_click_target(point) {
|
if text_character.is_left_click_target(point, context) {
|
||||||
return text_character.on_left_click(point);
|
return text_character.on_left_click(point, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
for text_character in self.characters.iter() {
|
for text_character in self.characters.iter() {
|
||||||
if text_character.is_left_click_target(point) {
|
if text_character.is_left_click_target(point, context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
pub mod editor_file;
|
pub mod editor_file;
|
||||||
pub mod editor_file_section;
|
pub mod editor_file_section;
|
||||||
pub mod editor_file_token;
|
pub mod editor_file_token;
|
||||||
|
|
||||||
|
pub use crate::ui::file::editor_file::*;
|
||||||
|
pub use crate::ui::file::editor_file_section::*;
|
||||||
|
pub use crate::ui::file::editor_file_token::*;
|
||||||
|
270
src/ui/file_editor.rs
Normal file
270
src/ui/file_editor.rs
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
use sdl2::rect::*;
|
||||||
|
use std::borrow::*;
|
||||||
|
use std::mem;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::app::{UpdateResult as UR, WindowCanvas as WS};
|
||||||
|
use crate::ui::*;
|
||||||
|
|
||||||
|
pub struct FileEditor {
|
||||||
|
dest: Rect,
|
||||||
|
scroll: Point,
|
||||||
|
caret: Caret,
|
||||||
|
file: Option<EditorFile>,
|
||||||
|
config: Rc<Config>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileEditor {
|
||||||
|
pub fn new(dest: Rect, config: Rc<Config>) -> Self {
|
||||||
|
Self {
|
||||||
|
dest,
|
||||||
|
scroll: Point::new(0, 0),
|
||||||
|
caret: Caret::new(config.clone()),
|
||||||
|
file: None,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self) -> &Rc<Config> {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn caret(&self) -> &Caret {
|
||||||
|
&self.caret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn caret_mut(&mut self) -> &mut Caret {
|
||||||
|
&mut self.caret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_file(&self) -> bool {
|
||||||
|
self.file.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drop_file(&mut self) -> Option<EditorFile> {
|
||||||
|
if self.has_file() {
|
||||||
|
let mut file = None;
|
||||||
|
mem::swap(&mut self.file, &mut file);
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_file(&mut self, file: EditorFile) -> Option<EditorFile> {
|
||||||
|
let mut file = Some(file);
|
||||||
|
mem::swap(&mut self.file, &mut file);
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file(&self) -> Option<&EditorFile> {
|
||||||
|
self.file.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_mut(&mut self) -> Option<&mut EditorFile> {
|
||||||
|
self.file.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_caret(&mut self, dir: MoveDirection) {
|
||||||
|
match dir {
|
||||||
|
MoveDirection::Left => {}
|
||||||
|
MoveDirection::Right => caret_manager::move_caret_right(self),
|
||||||
|
MoveDirection::Up => {}
|
||||||
|
MoveDirection::Down => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_front(&mut self, renderer: &mut Renderer) {
|
||||||
|
file_content_manager::delete_front(self, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_back(&mut self, renderer: &mut Renderer) {
|
||||||
|
file_content_manager::delete_back(self, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) {
|
||||||
|
file_content_manager::insert_text(self, text, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_new_line(&mut self, renderer: &mut Renderer) {
|
||||||
|
file_content_manager::insert_new_line(self, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_current_file(&mut self, file: EditorFile) {
|
||||||
|
self.open_file(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to(&mut self, x: i32, y: i32) {
|
||||||
|
self.scroll = self.scroll
|
||||||
|
+ Point::new(
|
||||||
|
self.config.scroll_speed() * x,
|
||||||
|
self.config.scroll_speed() * y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_text_character_clicked(&self, point: &Point) -> bool {
|
||||||
|
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||||
|
self.file()
|
||||||
|
.map_or(false, |file| file.is_left_click_target(point, &context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_editor_clicked(&self, point: &Point) -> bool {
|
||||||
|
self.dest
|
||||||
|
.contains_point(move_render_point(point.clone(), &self.dest).top_left())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_line_from_point(&self, point: &Point) -> i32 {
|
||||||
|
let file = match self.file() {
|
||||||
|
Some(f) => f,
|
||||||
|
_ => return 0,
|
||||||
|
};
|
||||||
|
let mut y = point.y() - self.render_start_point().y();
|
||||||
|
if y < 0 {
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
y / (file.line_height() as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_caret_to_end_of_line(&mut self, line: i32) {
|
||||||
|
let file = match self.file_mut() {
|
||||||
|
Some(f) => f,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let mut line = line;
|
||||||
|
while line >= 0 {
|
||||||
|
match file.get_last_at_line(line.clone() as usize) {
|
||||||
|
Some(text_character) => {
|
||||||
|
let rect = text_character.dest();
|
||||||
|
let position =
|
||||||
|
CaretPosition::new(text_character.position() + 1, line as usize, 0);
|
||||||
|
let p = if text_character.is_last_in_line() && text_character.is_new_line() {
|
||||||
|
rect.top_left()
|
||||||
|
} else {
|
||||||
|
rect.top_right()
|
||||||
|
};
|
||||||
|
self.caret.move_caret(position, p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
line -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for FileEditor {
|
||||||
|
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _parent: Parent) -> UR {
|
||||||
|
match self.file() {
|
||||||
|
Some(file) => file.render(canvas, renderer, Some(self)),
|
||||||
|
_ => UR::NoOp,
|
||||||
|
};
|
||||||
|
self.caret.render(canvas, renderer, Some(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
|
self.caret.prepare_ui(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update for FileEditor {
|
||||||
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||||
|
self.caret.update(ticks, context);
|
||||||
|
match self.file_mut() {
|
||||||
|
Some(file) => file.update(ticks, context),
|
||||||
|
_ => UR::NoOp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClickHandler for FileEditor {
|
||||||
|
fn on_left_click(&mut self, point: &Point, _context: &UpdateContext) -> UR {
|
||||||
|
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||||
|
|
||||||
|
if self.is_text_character_clicked(point) {
|
||||||
|
let file = if let Some(file) = self.file_mut() {
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
return UR::NoOp;
|
||||||
|
};
|
||||||
|
match file.on_left_click(point, &context) {
|
||||||
|
UR::MoveCaret(rect, position) => {
|
||||||
|
self.caret
|
||||||
|
.move_caret(position, Point::new(rect.x(), rect.y()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.set_caret_to_end_of_line(self.resolve_line_from_point(point));
|
||||||
|
}
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool {
|
||||||
|
self.is_text_character_clicked(point) || self.is_editor_clicked(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBox for FileEditor {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.dest.top_left() + self.scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::app::*;
|
||||||
|
use crate::ui::*;
|
||||||
|
use sdl2::rect::*;
|
||||||
|
use sdl2::*;
|
||||||
|
use std::borrow::*;
|
||||||
|
use std::rc::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replace_file() {
|
||||||
|
let config = Rc::new(Config::new());
|
||||||
|
let mut editor = FileEditor::new(Rect::new(0, 0, 100, 100), config.clone());
|
||||||
|
let first_file =
|
||||||
|
EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
||||||
|
let second_file =
|
||||||
|
EditorFile::new("./bar.txt".to_string(), "bar".to_string(), config.clone());
|
||||||
|
editor.open_file(first_file.clone());
|
||||||
|
let result = editor.open_file(second_file.clone());
|
||||||
|
assert_eq!(result.is_some(), true);
|
||||||
|
let file = result.as_ref().unwrap();
|
||||||
|
assert_eq!(file.path(), first_file.path());
|
||||||
|
assert_eq!(file.buffer(), first_file.buffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_text() {
|
||||||
|
let config = Rc::new(Config::new());
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
let video_subsystem = sdl_context.video().unwrap();
|
||||||
|
let window = video_subsystem
|
||||||
|
.window("Test", 1, 1)
|
||||||
|
.borderless()
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let canvas = window.into_canvas().accelerated().build().unwrap();
|
||||||
|
let font_context = sdl2::ttf::init().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
let mut renderer = Renderer::new(config.clone(), &font_context, &texture_creator);
|
||||||
|
|
||||||
|
let mut editor = FileEditor::new(Rect::new(0, 0, 100, 100), config.clone());
|
||||||
|
let mut file = EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
||||||
|
file.prepare_ui(&mut renderer);
|
||||||
|
assert_eq!(editor.open_file(file).is_none(), true);
|
||||||
|
assert_eq!(editor.caret().position().text_position(), 0);
|
||||||
|
assert_eq!(editor.file().is_some(), true);
|
||||||
|
assert_eq!(editor.file().unwrap().sections().len(), 1);
|
||||||
|
assert_eq!(editor.file().unwrap().get_character_at(0).is_some(), true);
|
||||||
|
|
||||||
|
editor.insert_text("z".to_string(), &mut renderer);
|
||||||
|
assert_eq!(editor.caret().position().text_position(), 1);
|
||||||
|
assert_eq!(editor.file().is_some(), true);
|
||||||
|
assert_eq!(editor.file().unwrap().buffer(), "zfoo".to_string());
|
||||||
|
}
|
||||||
|
}
|
@ -7,17 +7,21 @@ use sdl2::rect::{Point, Rect};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct MenuBar {
|
pub struct MenuBar {
|
||||||
|
border_color: Color,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
config: Rc<Config>,
|
config: Rc<Config>,
|
||||||
|
pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuBar {
|
impl MenuBar {
|
||||||
pub fn new(config: Rc<Config>) -> Self {
|
pub fn new(config: Rc<Config>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
background_color: Color::RGB(10, 10, 10),
|
border_color: Color::RGB(10, 10, 10),
|
||||||
|
background_color: config.theme().background().into(),
|
||||||
dest: Rect::new(0, 0, 0, 0),
|
dest: Rect::new(0, 0, 0, 0),
|
||||||
config,
|
config,
|
||||||
|
pending: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,28 +35,66 @@ impl MenuBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for MenuBar {
|
impl Render for MenuBar {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, _renderer: &mut Renderer) -> UpdateResult {
|
fn render(
|
||||||
|
&self,
|
||||||
|
canvas: &mut WindowCanvas,
|
||||||
|
_renderer: &mut Renderer,
|
||||||
|
parent: Parent,
|
||||||
|
) -> UpdateResult {
|
||||||
|
canvas.set_draw_color(self.background_color.clone());
|
||||||
|
canvas
|
||||||
|
.fill_rect(match parent {
|
||||||
|
None => self.dest.clone(),
|
||||||
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||||
|
|
||||||
|
canvas.set_draw_color(self.border_color.clone());
|
||||||
|
canvas
|
||||||
|
.draw_rect(match parent {
|
||||||
|
None => self.dest.clone(),
|
||||||
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to draw main menu background"));
|
||||||
|
|
||||||
|
UpdateResult::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, _renderer: &mut Renderer) {
|
||||||
|
if !self.pending {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let width = self.config.width();
|
let width = self.config.width();
|
||||||
let height = self.config.menu_height() as u32;
|
let height = self.config.menu_height() as u32;
|
||||||
self.dest = Rect::new(0, 0, width, height);
|
self.dest = Rect::new(0, 0, width, height);
|
||||||
canvas.set_draw_color(self.background_color.clone());
|
self.pending = false;
|
||||||
canvas.draw_rect(self.dest.clone()).unwrap();
|
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for MenuBar {
|
impl Update for MenuBar {
|
||||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for MenuBar {
|
impl ClickHandler for MenuBar {
|
||||||
fn on_left_click(&mut self, _point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
is_in_rect(point, self.dest())
|
is_in_rect(
|
||||||
|
point,
|
||||||
|
&match context {
|
||||||
|
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||||
|
_ => self.dest().clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBox for MenuBar {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,30 @@ use crate::renderer::Renderer;
|
|||||||
|
|
||||||
pub mod caret;
|
pub mod caret;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
pub mod file_editor;
|
||||||
pub mod menu_bar;
|
pub mod menu_bar;
|
||||||
|
pub mod project_tree;
|
||||||
pub mod text_character;
|
pub mod text_character;
|
||||||
|
|
||||||
|
pub use crate::ui::caret::*;
|
||||||
|
pub use crate::ui::file::*;
|
||||||
|
pub use crate::ui::file_editor::*;
|
||||||
|
pub use crate::ui::menu_bar::*;
|
||||||
|
pub use crate::ui::project_tree::*;
|
||||||
|
pub use crate::ui::text_character::*;
|
||||||
|
|
||||||
|
pub type Parent<'l> = Option<&'l RenderBox>;
|
||||||
|
pub type ParentMut<'l> = Option<&'l mut RenderBox>;
|
||||||
|
|
||||||
|
pub enum UpdateContext<'l> {
|
||||||
|
Nothing,
|
||||||
|
ParentPosition(Point),
|
||||||
|
CurrentFile(&'l mut EditorFile),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
pub fn is_in_rect(point: &Point, rect: &Rect) -> bool {
|
||||||
let start = Point::new(rect.x(), rect.y());
|
let start = rect.top_left();
|
||||||
let end = Point::new(
|
let end = Point::new(
|
||||||
rect.x() + (rect.width() as i32),
|
rect.x() + (rect.width() as i32),
|
||||||
rect.y() + (rect.height() as i32),
|
rect.y() + (rect.height() as i32),
|
||||||
@ -35,16 +54,32 @@ pub fn get_text_character_rect(c: char, renderer: &mut Renderer) -> Option<Rect>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn move_render_point(p: Point, d: &Rect) -> Rect {
|
||||||
|
Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height())
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Render {
|
pub trait Render {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult;
|
fn render(
|
||||||
|
&self,
|
||||||
|
canvas: &mut WindowCanvas,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
parent: Parent,
|
||||||
|
) -> UpdateResult;
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Update {
|
pub trait Update {
|
||||||
fn update(&mut self, ticks: i32) -> UpdateResult;
|
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ClickHandler {
|
pub trait ClickHandler {
|
||||||
fn on_left_click(&mut self, point: &Point) -> UpdateResult;
|
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult;
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool;
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RenderBox {
|
||||||
|
fn render_start_point(&self) -> Point;
|
||||||
}
|
}
|
||||||
|
1
src/ui/project_tree/mod.rs
Normal file
1
src/ui/project_tree/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
use crate::app::{UpdateResult, WindowCanvas};
|
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::lexer::TokenType;
|
use crate::lexer::TokenType;
|
||||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||||
@ -12,12 +12,12 @@ use sdl2::render::Texture;
|
|||||||
use sdl2::ttf::Font;
|
use sdl2::ttf::Font;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TextCharacter {
|
pub struct TextCharacter {
|
||||||
pending: bool,
|
|
||||||
text_character: char,
|
text_character: char,
|
||||||
position: usize,
|
position: usize,
|
||||||
line: usize,
|
line: usize,
|
||||||
|
last_in_line: bool,
|
||||||
source: Rect,
|
source: Rect,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
color: Color,
|
color: Color,
|
||||||
@ -29,14 +29,15 @@ impl TextCharacter {
|
|||||||
text_character: char,
|
text_character: char,
|
||||||
position: usize,
|
position: usize,
|
||||||
line: usize,
|
line: usize,
|
||||||
|
last_in_line: bool,
|
||||||
color: Color,
|
color: Color,
|
||||||
config: Rc<Config>,
|
config: Rc<Config>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pending: true,
|
|
||||||
text_character,
|
text_character,
|
||||||
position,
|
position,
|
||||||
line,
|
line,
|
||||||
|
last_in_line,
|
||||||
source: Rect::new(0, 0, 0, 0),
|
source: Rect::new(0, 0, 0, 0),
|
||||||
dest: Rect::new(0, 0, 0, 0),
|
dest: Rect::new(0, 0, 0, 0),
|
||||||
color,
|
color,
|
||||||
@ -44,6 +45,10 @@ impl TextCharacter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_last_in_line(&self) -> bool {
|
||||||
|
self.last_in_line
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dest(&self) -> &Rect {
|
pub fn dest(&self) -> &Rect {
|
||||||
&self.dest
|
&self.dest
|
||||||
}
|
}
|
||||||
@ -59,8 +64,10 @@ impl TextCharacter {
|
|||||||
pub fn update_position(&mut self, current: &mut Rect) {
|
pub fn update_position(&mut self, current: &mut Rect) {
|
||||||
if self.is_new_line() {
|
if self.is_new_line() {
|
||||||
let y = self.source.height() as i32;
|
let y = self.source.height() as i32;
|
||||||
current.set_x(self.config.editor_left_margin());
|
current.set_x(0);
|
||||||
current.set_y(current.y() + y);
|
current.set_y(current.y() + y);
|
||||||
|
self.dest.set_x(current.x());
|
||||||
|
self.dest.set_y(current.y());
|
||||||
} else {
|
} else {
|
||||||
self.dest.set_x(current.x());
|
self.dest.set_x(current.x());
|
||||||
self.dest.set_y(current.y());
|
self.dest.set_y(current.y());
|
||||||
@ -70,7 +77,62 @@ impl TextCharacter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult {
|
#[inline]
|
||||||
|
pub fn is_new_line(&self) -> bool {
|
||||||
|
self.text_character == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> usize {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&self) -> usize {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_character(&self) -> char {
|
||||||
|
self.text_character.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for TextCharacter {
|
||||||
|
/**
|
||||||
|
* Must first create targets so even if new line appear renderer will know
|
||||||
|
* where move render starting point
|
||||||
|
*/
|
||||||
|
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||||
|
if self.is_new_line() {
|
||||||
|
return UR::NoOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = renderer.config().editor_config();
|
||||||
|
let font_details =
|
||||||
|
FontDetails::new(config.font_path().as_str(), config.character_size().clone());
|
||||||
|
let font = renderer
|
||||||
|
.font_manager()
|
||||||
|
.load(&font_details)
|
||||||
|
.unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details));
|
||||||
|
|
||||||
|
let c = self.text_character.clone();
|
||||||
|
let mut details = TextDetails {
|
||||||
|
text: c.to_string(),
|
||||||
|
color: self.color.clone(),
|
||||||
|
font: font_details.clone(),
|
||||||
|
};
|
||||||
|
let dest = match parent {
|
||||||
|
None => self.dest.clone(),
|
||||||
|
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||||
|
};
|
||||||
|
if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) {
|
||||||
|
renderer.render_texture(canvas, &texture, &self.source, &dest);
|
||||||
|
}
|
||||||
|
// let c = Color::RGB(255, 0, 0);
|
||||||
|
// canvas.set_draw_color(c);
|
||||||
|
// canvas.draw_rect(dest.clone()).unwrap();
|
||||||
|
UR::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||||
let config = renderer.config().editor_config();
|
let config = renderer.config().editor_config();
|
||||||
let font_details =
|
let font_details =
|
||||||
FontDetails::new(config.font_path().as_str(), config.character_size().clone());
|
FontDetails::new(config.font_path().as_str(), config.character_size().clone());
|
||||||
@ -96,83 +158,36 @@ impl TextCharacter {
|
|||||||
.texture_manager()
|
.texture_manager()
|
||||||
.load_text(&mut details, &font)
|
.load_text(&mut details, &font)
|
||||||
.unwrap_or_else(|_| panic!("Could not create texture for {:?}", self.text_character));
|
.unwrap_or_else(|_| panic!("Could not create texture for {:?}", self.text_character));
|
||||||
|
|
||||||
self.pending = false;
|
|
||||||
UpdateResult::RefreshPositions
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_new_line(&self) -> bool {
|
|
||||||
self.text_character == '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_pending(&self) -> bool {
|
|
||||||
self.pending
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> usize {
|
|
||||||
self.position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line(&self) -> usize {
|
|
||||||
self.line
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_character(&self) -> char {
|
|
||||||
self.text_character.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for TextCharacter {
|
|
||||||
/**
|
|
||||||
* Must first create targets so even if new line appear renderer will know
|
|
||||||
* where move render starting point
|
|
||||||
*/
|
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
|
||||||
if self.is_pending() {
|
|
||||||
return self.update_view(renderer);
|
|
||||||
}
|
|
||||||
if self.is_new_line() {
|
|
||||||
return UpdateResult::NoOp;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = renderer.config().editor_config();
|
|
||||||
let font_details =
|
|
||||||
FontDetails::new(config.font_path().as_str(), config.character_size().clone());
|
|
||||||
let font = renderer
|
|
||||||
.font_manager()
|
|
||||||
.load(&font_details)
|
|
||||||
.unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details));
|
|
||||||
|
|
||||||
let c = self.text_character.clone();
|
|
||||||
let mut details = TextDetails {
|
|
||||||
text: c.to_string(),
|
|
||||||
color: self.color.clone(),
|
|
||||||
font: font_details.clone(),
|
|
||||||
};
|
|
||||||
if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) {
|
|
||||||
renderer.render_texture(canvas, &texture, &self.source, &self.dest);
|
|
||||||
}
|
|
||||||
UpdateResult::NoOp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Update for TextCharacter {
|
impl Update for TextCharacter {
|
||||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
||||||
UpdateResult::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClickHandler for TextCharacter {
|
impl ClickHandler for TextCharacter {
|
||||||
fn on_left_click(&mut self, _point: &Point) -> UpdateResult {
|
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||||
UpdateResult::MoveCaret(
|
UR::MoveCaret(
|
||||||
self.dest().clone(),
|
self.dest().clone(),
|
||||||
CaretPosition::new(self.position(), self.line(), 0),
|
CaretPosition::new(self.position(), self.line(), 0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||||
is_in_rect(point, self.dest())
|
is_in_rect(
|
||||||
|
point,
|
||||||
|
&match context {
|
||||||
|
&UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||||
|
_ => self.dest().clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBox for TextCharacter {
|
||||||
|
fn render_start_point(&self) -> Point {
|
||||||
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user