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
|
||||
.idea
|
||||
log
|
||||
.rider
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::app::caret_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::renderer::Renderer;
|
||||
use crate::ui::caret::Caret;
|
||||
@ -19,9 +19,8 @@ use std::sync::Arc;
|
||||
pub struct AppState {
|
||||
menu_bar: MenuBar,
|
||||
files: Vec<EditorFile>,
|
||||
current_file: usize,
|
||||
caret: Caret,
|
||||
config: Rc<Config>,
|
||||
file_editor: FileEditor,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@ -29,19 +28,29 @@ impl AppState {
|
||||
Self {
|
||||
menu_bar: MenuBar::new(config.clone()),
|
||||
files: vec![],
|
||||
current_file: 0,
|
||||
caret: Caret::new(config.clone()),
|
||||
file_editor: FileEditor::new(
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if let Ok(buffer) = read_to_string(&file_path) {
|
||||
let file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
||||
self.current_file = self.files.len();
|
||||
self.files.push(file);
|
||||
let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
|
||||
file.prepare_ui(renderer);
|
||||
match self.file_editor.open_file(file) {
|
||||
Some(old) => self.files.push(old),
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to open file: {}", file_path);
|
||||
};
|
||||
@ -51,102 +60,55 @@ impl AppState {
|
||||
&self.config
|
||||
}
|
||||
|
||||
pub fn caret(&self) -> &Caret {
|
||||
&self.caret
|
||||
pub fn file_editor(&self) -> &FileEditor {
|
||||
&self.file_editor
|
||||
}
|
||||
|
||||
pub fn caret_mut(&mut self) -> &mut Caret {
|
||||
&mut self.caret
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||
&mut self.file_editor
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AppState {
|
||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
||||
self.menu_bar.render(canvas, renderer);
|
||||
if let Some(file) = self.current_file_mut() {
|
||||
file.render(canvas, renderer);
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _parent: Parent) -> UpdateResult {
|
||||
self.file_editor.render(canvas, renderer, None);
|
||||
self.menu_bar.render(canvas, renderer, None)
|
||||
}
|
||||
self.caret.render(canvas, renderer);
|
||||
UpdateResult::NoOp
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
self.menu_bar.prepare_ui(renderer);
|
||||
self.file_editor.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for AppState {
|
||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
||||
self.menu_bar.update(ticks);
|
||||
if let Some(file) = self.files.get_mut(self.current_file) {
|
||||
file.update(ticks);
|
||||
}
|
||||
self.caret.update(ticks);
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
self.menu_bar.update(ticks, context);
|
||||
self.file_editor.update(ticks, context);
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
||||
if self.menu_bar.is_left_click_target(point) {
|
||||
if self
|
||||
.menu_bar
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
|
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::ui::file::editor_file::EditorFile;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
pub fn move_caret_right(app_state: &mut AppState) {
|
||||
let file: &EditorFile = match app_state.current_file() {
|
||||
pub fn move_caret_right(file_editor: &mut FileEditor) {
|
||||
let file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
Some(f) => f,
|
||||
};
|
||||
let line = match file.get_character_at(app_state.caret().text_position()) {
|
||||
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) {
|
||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) {
|
||||
Some(text_character) => text_character,
|
||||
None => return,
|
||||
None => return, // EOF
|
||||
};
|
||||
let line = line - app_state.caret().line_number();
|
||||
let pos = app_state.caret().position().moved(1, line as i32, 0);
|
||||
let mut d: Rect = text_character.dest().clone();
|
||||
if text_character.is_new_line() && idx > 0 {
|
||||
let prev = match characters.get(idx - 1) {
|
||||
Some(c) => c,
|
||||
_ => return,
|
||||
let caret_rect = file_editor.caret().dest().clone();
|
||||
let pos = file_editor.caret().position();
|
||||
let (d, p): (Rect, CaretPosition) = match (
|
||||
c.is_last_in_line(),
|
||||
c.is_new_line(),
|
||||
c.dest().y() == caret_rect.y(),
|
||||
) {
|
||||
(true, true, false) => {
|
||||
let prev: TextCharacter = if c.position() != 0 {
|
||||
file.get_character_at(c.position() - 1).unwrap_or(c.clone())
|
||||
} else {
|
||||
c.clone()
|
||||
};
|
||||
d = prev.dest().clone();
|
||||
d.set_x(d.x() + d.width() as i32);
|
||||
let mut dest = prev.dest().clone();
|
||||
dest.set_x(dest.x() + dest.width() as i32);
|
||||
(dest, pos.moved(1, 0, 0))
|
||||
}
|
||||
app_state
|
||||
(false, true, false) => {
|
||||
let prev: TextCharacter = if c.position() != 0 {
|
||||
file.get_character_at(c.position() - 1).unwrap_or(c.clone())
|
||||
} else {
|
||||
c.clone()
|
||||
};
|
||||
let mut dest = prev.dest().clone();
|
||||
if !prev.is_new_line() {
|
||||
dest.set_x(dest.x() + dest.width() as i32);
|
||||
}
|
||||
(dest, pos.moved(1, 0, 0))
|
||||
}
|
||||
(true, false, false) => {
|
||||
// move after character, stay on current line
|
||||
(c.dest().clone(), pos.moved(1, 0, 0))
|
||||
}
|
||||
(true, false, true) => {
|
||||
// move to new line
|
||||
(c.dest().clone(), pos.moved(1, 0, 0))
|
||||
}
|
||||
_ => (c.dest().clone(), pos.moved(1, 0, 0)),
|
||||
};
|
||||
file_editor
|
||||
.caret_mut()
|
||||
.move_caret(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) {
|
||||
let _file: &EditorFile = match app_state.current_file() {
|
||||
pub fn move_caret_left(file_editor: &mut FileEditor) {
|
||||
let _file: &EditorFile = match file_editor.file() {
|
||||
None => return,
|
||||
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::ui::caret::Caret;
|
||||
use crate::ui::caret::CaretPosition;
|
||||
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;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::*;
|
||||
|
||||
fn get_character_at(app_state: &mut AppState, index: usize) -> Option<&TextCharacter> {
|
||||
match app_state.current_file() {
|
||||
None => return None,
|
||||
Some(f) => f,
|
||||
}
|
||||
.get_character_at(index)
|
||||
fn current_file_path(file_editor: &mut FileEditor) -> String {
|
||||
file_editor
|
||||
.file()
|
||||
.map_or_else(|| String::new(), |f| f.path())
|
||||
}
|
||||
|
||||
fn current_file_path(app_state: &mut AppState) -> String {
|
||||
match app_state.current_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() {
|
||||
pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
.buffer();
|
||||
let position: CaretPosition = app_state.caret().position().clone();
|
||||
let position: CaretPosition = file_editor.caret().position().clone();
|
||||
if position.text_position() == 0 {
|
||||
return;
|
||||
}
|
||||
@ -44,100 +30,107 @@ pub fn delete_front(app_state: &mut AppState) {
|
||||
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
||||
_ => position.moved(0, 0, 0),
|
||||
};
|
||||
|
||||
let move_to = match get_character_at(app_state, position.text_position()) {
|
||||
Some(character) => {
|
||||
let move_to = file_editor
|
||||
.file()
|
||||
.and_then(|f| f.get_character_at(file_editor.caret().text_position()))
|
||||
.and_then(|character| {
|
||||
let dest: &Rect = character.dest();
|
||||
Some((position, Point::new(dest.x(), dest.y())))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
});
|
||||
match move_to {
|
||||
Some((position, point)) => app_state.caret_mut().move_caret(position, point),
|
||||
None => app_state.caret_mut().reset_caret(),
|
||||
Some((position, point)) => file_editor.caret_mut().move_caret(position, point),
|
||||
None => file_editor.caret_mut().reset_caret(),
|
||||
};
|
||||
let new_file = EditorFile::new(
|
||||
current_file_path(app_state),
|
||||
let mut new_file = EditorFile::new(
|
||||
current_file_path(file_editor),
|
||||
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) {
|
||||
let file: &EditorFile = if let Some(file) = app_state.current_file() {
|
||||
pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let file: &EditorFile = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
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() {
|
||||
return;
|
||||
}
|
||||
buffer.remove(position);
|
||||
let new_file = EditorFile::new(file.path(), buffer, app_state.config().clone());
|
||||
app_state.replace_current_file(new_file);
|
||||
let mut new_file = EditorFile::new(file.path(), buffer, file_editor.config().clone());
|
||||
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) {
|
||||
let mut buffer: String = if let Some(file) = app_state.current_file() {
|
||||
file
|
||||
} else {
|
||||
pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) {
|
||||
let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer());
|
||||
if buffer.is_empty() {
|
||||
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,
|
||||
_ => return,
|
||||
};
|
||||
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() {
|
||||
buffer.insert(position.text_position(), character);
|
||||
if let Some(rect) = get_text_character_rect(character, renderer) {
|
||||
pos = pos + Point::new(rect.width() as i32, 0);
|
||||
position = position.moved(1, 0, 0);
|
||||
app_state.caret_mut().move_caret(position, pos.clone());
|
||||
file_editor.caret_mut().move_caret(position, pos.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let new_file = EditorFile::new(
|
||||
current_file_path(app_state),
|
||||
let mut new_file = EditorFile::new(
|
||||
file_editor.file().map_or(String::new(), |f| f.path()),
|
||||
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 insert_new_line(app_state: &mut AppState, renderer: &mut Renderer) {
|
||||
let mut buffer: String = if let Some(file) = app_state.current_file() {
|
||||
pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
|
||||
let mut buffer: String = if let Some(file) = file_editor.file() {
|
||||
file
|
||||
} else {
|
||||
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,
|
||||
_ => return,
|
||||
};
|
||||
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');
|
||||
if let Some(rect) = get_text_character_rect('\n', renderer) {
|
||||
pos = Point::new(
|
||||
app_state.config().editor_left_margin(),
|
||||
file_editor.config().editor_left_margin(),
|
||||
pos.y() + rect.height() as i32,
|
||||
);
|
||||
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(
|
||||
current_file_path(app_state),
|
||||
let mut new_file = EditorFile::new(
|
||||
current_file_path(file_editor),
|
||||
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 application;
|
||||
pub mod caret_manager;
|
||||
pub mod file_content_manager;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
pub use crate::app::app_state::*;
|
||||
pub use crate::app::application::*;
|
||||
pub use crate::app::caret_manager::*;
|
||||
pub use crate::app::file_content_manager::*;
|
||||
|
@ -19,6 +19,11 @@ pub fn create() {
|
||||
if !log_dir().exists() {
|
||||
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() {
|
||||
|
@ -26,3 +26,11 @@ pub fn config_dir() -> PathBuf {
|
||||
config_dir.push("rider");
|
||||
config_dir
|
||||
}
|
||||
|
||||
pub fn project_dir() -> PathBuf {
|
||||
let runtime = dirs::runtime_dir().unwrap();
|
||||
|
||||
let mut project_dir = runtime.clone();
|
||||
project_dir.push(".rider");
|
||||
project_dir
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ impl EditorConfig {
|
||||
pub struct Config {
|
||||
width: u32,
|
||||
height: u32,
|
||||
scroll_speed: i32,
|
||||
menu_height: u16,
|
||||
editor_config: EditorConfig,
|
||||
theme: Theme,
|
||||
@ -74,12 +75,18 @@ impl Config {
|
||||
Self {
|
||||
width: 1024,
|
||||
height: 860,
|
||||
scroll_speed: 10,
|
||||
menu_height: 60,
|
||||
theme: Theme::load(editor_config.current_theme().clone()),
|
||||
editor_config,
|
||||
extensions_mapping,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_speed(&self) -> i32 {
|
||||
self.scroll_speed
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||
pub mod plain;
|
||||
pub mod rust_lang;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Language {
|
||||
PlainText,
|
||||
Rust,
|
||||
|
@ -7,7 +7,7 @@ pub mod lexer {
|
||||
lexer! {
|
||||
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)
|
||||
}, text),
|
||||
|
||||
|
@ -10,16 +10,8 @@ pub struct CaretColor {
|
||||
impl Default for CaretColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bright: ThemeConfig::new(
|
||||
SerdeColor::new(0, 0, 0, 0),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
blur: ThemeConfig::new(
|
||||
SerdeColor::new(0, 0, 0, 0),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
bright: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
src/ui/caret.rs
111
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::renderer::Renderer;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
@ -10,7 +10,7 @@ use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum CaretState {
|
||||
pub enum CaretState {
|
||||
Bright,
|
||||
Blur,
|
||||
}
|
||||
@ -95,25 +95,6 @@ pub struct CaretRenderPosition {
|
||||
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)]
|
||||
pub struct CaretColor {
|
||||
bright: Color,
|
||||
@ -140,7 +121,7 @@ pub struct Caret {
|
||||
blink_delay: u8,
|
||||
state: CaretState,
|
||||
position: CaretPosition,
|
||||
render_position: CaretRenderPosition,
|
||||
dest: Rect,
|
||||
colors: CaretColor,
|
||||
}
|
||||
|
||||
@ -151,20 +132,7 @@ impl Caret {
|
||||
Self {
|
||||
state: CaretState::Bright,
|
||||
blink_delay: 0,
|
||||
render_position: CaretRenderPosition {
|
||||
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,
|
||||
),
|
||||
},
|
||||
dest: Rect::new(0, 0, 6, 0),
|
||||
colors: CaretColor { bright, blur },
|
||||
pending: true,
|
||||
position: CaretPosition {
|
||||
@ -172,7 +140,6 @@ impl Caret {
|
||||
line_number: 0,
|
||||
line_position: 0,
|
||||
},
|
||||
// config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,13 +152,19 @@ impl Caret {
|
||||
}
|
||||
|
||||
pub fn reset_caret(&mut self) {
|
||||
self.render_position.reset();
|
||||
self.dest.set_x(0);
|
||||
self.dest.set_y(0);
|
||||
self.position.reset();
|
||||
}
|
||||
|
||||
pub fn move_caret(&mut self, position: CaretPosition, pos: Point) {
|
||||
self.position = position;
|
||||
self.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 {
|
||||
@ -208,52 +181,66 @@ impl Deref for Caret {
|
||||
}
|
||||
|
||||
impl Render for Caret {
|
||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
||||
if self.pending {
|
||||
if let Some(rect) = get_text_character_rect('W', renderer) {
|
||||
let mut dest = self.render_position.dest().clone();
|
||||
dest.set_height(rect.height());
|
||||
let reset_position = dest.clone();
|
||||
self.render_position = CaretRenderPosition {
|
||||
dest,
|
||||
reset_position,
|
||||
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) -> UR {
|
||||
let dest = match parent {
|
||||
Some(parent) => move_render_point(parent.render_start_point(), self.dest()),
|
||||
None => self.dest().clone(),
|
||||
};
|
||||
}
|
||||
self.pending = false;
|
||||
}
|
||||
let dest = self.render_position.dest();
|
||||
let start = Point::new(dest.x(), dest.y());
|
||||
let end = Point::new(dest.x(), dest.y() + dest.height() as i32);
|
||||
let color = match self.state {
|
||||
CaretState::Bright => self.colors.bright(),
|
||||
CaretState::Blur => self.colors.blur(),
|
||||
};
|
||||
canvas.set_draw_color(color.clone());
|
||||
}
|
||||
.clone();
|
||||
canvas.set_draw_color(color);
|
||||
canvas
|
||||
.draw_line(start, end)
|
||||
.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 {
|
||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
||||
self.blink_delay += 1;
|
||||
if self.blink_delay >= 30 {
|
||||
self.blink_delay = 0;
|
||||
self.toggle_state();
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
UR::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for Caret {
|
||||
fn on_left_click(&mut self, _point: &Point) -> UpdateResult {
|
||||
// self.move_caret(Point::new(self.position.x(), self.position.y()));
|
||||
UpdateResult::NoOp
|
||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
||||
is_in_rect(point, &self.render_position.dest())
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for Caret {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest().top_left()
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
use sdl2::rect::{Point, Rect};
|
||||
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::renderer::Renderer;
|
||||
use crate::ui::file::editor_file_section::EditorFileSection;
|
||||
use crate::ui::text_character::TextCharacter;
|
||||
use crate::ui::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditorFile {
|
||||
path: String,
|
||||
sections: Vec<EditorFileSection>,
|
||||
render_position: Rect,
|
||||
buffer: String,
|
||||
config: Rc<Config>,
|
||||
line_height: u32,
|
||||
}
|
||||
|
||||
impl EditorFile {
|
||||
pub fn new(path: String, buffer: String, config: Rc<Config>) -> Self {
|
||||
use std::path::Path;
|
||||
|
||||
let p = Path::new(&path);
|
||||
let ext = match p.extension() {
|
||||
Some(s) => s.to_str().unwrap_or("txt"),
|
||||
None => "txt",
|
||||
}
|
||||
let ext = Path::new(&path)
|
||||
.extension()
|
||||
.and_then(|p| p.to_str())
|
||||
.map_or("txt", |s| s)
|
||||
.to_string();
|
||||
let sections = vec![EditorFileSection::new(buffer.clone(), ext, config.clone())];
|
||||
let x = config.editor_left_margin();
|
||||
@ -37,6 +37,7 @@ impl EditorFile {
|
||||
render_position: Rect::new(x, y, 0, 0),
|
||||
buffer,
|
||||
config,
|
||||
line_height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,10 +49,23 @@ impl EditorFile {
|
||||
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() {
|
||||
if let Some(text_character) = section.get_character_at(index) {
|
||||
return Some(text_character);
|
||||
let character = section.get_character_at(index);
|
||||
if character.is_some() {
|
||||
return character;
|
||||
}
|
||||
}
|
||||
None
|
||||
@ -59,7 +73,6 @@ impl EditorFile {
|
||||
|
||||
pub fn get_line(&self, line: &usize) -> Option<Vec<&TextCharacter>> {
|
||||
let mut vec: Vec<&TextCharacter> = vec![];
|
||||
|
||||
for section in self.sections.iter() {
|
||||
match section.get_line(line) {
|
||||
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) {
|
||||
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() {
|
||||
section.update_positions(&mut current);
|
||||
}
|
||||
@ -83,47 +111,66 @@ impl EditorFile {
|
||||
}
|
||||
|
||||
impl Render for EditorFile {
|
||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
||||
let mut res = UpdateResult::NoOp;
|
||||
for section in self.sections.iter_mut() {
|
||||
res = section.render(canvas, renderer);
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||
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() {
|
||||
section.prepare_ui(renderer);
|
||||
}
|
||||
if let Some(r) = get_text_character_rect('W', renderer) {
|
||||
self.line_height = r.height();
|
||||
}
|
||||
if res == UpdateResult::RefreshPositions {
|
||||
self.refresh_characters_position();
|
||||
for section in self.sections.iter_mut() {
|
||||
section.render(canvas, renderer);
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for EditorFile {
|
||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
||||
let mut result = UpdateResult::NoOp;
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||
let mut result = UR::NoOp;
|
||||
for section in self.sections.iter_mut() {
|
||||
result = section.update(ticks);
|
||||
result = section.update(ticks, context);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for EditorFile {
|
||||
fn on_left_click(&mut self, point: &Point) -> UpdateResult {
|
||||
for section in self.sections.iter_mut() {
|
||||
if section.is_left_click_target(point) {
|
||||
return section.on_left_click(point);
|
||||
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR {
|
||||
let mut index = -1;
|
||||
for (i, section) in self.sections.iter().enumerate() {
|
||||
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() {
|
||||
if section.is_left_click_target(point) {
|
||||
if section.is_left_click_target(point, &context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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 std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::app::{UpdateResult, WindowCanvas};
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::lexer::Language;
|
||||
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::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EditorFileSection {
|
||||
tokens: Vec<EditorFileToken>,
|
||||
language: Language,
|
||||
@ -27,9 +28,19 @@ impl EditorFileSection {
|
||||
let lexer_tokens = lexer::parse(buffer.clone(), language);
|
||||
|
||||
let mut tokens: Vec<EditorFileToken> = vec![];
|
||||
for token_type in lexer_tokens {
|
||||
let token = EditorFileToken::new(token_type, config.clone());
|
||||
tokens.push(token.clone());
|
||||
let mut iterator = lexer_tokens.iter().peekable();
|
||||
loop {
|
||||
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;
|
||||
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() {
|
||||
if let Some(text_character) = token.get_character_at(index) {
|
||||
return Some(text_character);
|
||||
let character = token.get_character_at(index);
|
||||
if character.is_some() {
|
||||
return character;
|
||||
}
|
||||
}
|
||||
None
|
||||
@ -68,47 +80,73 @@ impl EditorFileSection {
|
||||
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 {
|
||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
||||
let mut res = UpdateResult::NoOp;
|
||||
for character in self.tokens.iter_mut() {
|
||||
let r = character.render(canvas, renderer);
|
||||
if res == UpdateResult::NoOp {
|
||||
res = r;
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||
for token in self.tokens.iter() {
|
||||
token.render(canvas, renderer, parent);
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn prepare_ui(&mut self, renderer: &mut Renderer) {
|
||||
for token in self.tokens.iter_mut() {
|
||||
token.prepare_ui(renderer);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for EditorFileSection {
|
||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
||||
let mut result = UpdateResult::NoOp;
|
||||
for file_char in self.tokens.iter_mut() {
|
||||
result = file_char.update(ticks)
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||
let mut result = UR::NoOp;
|
||||
for token in self.tokens.iter_mut() {
|
||||
result = token.update(ticks, context)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
if token.is_left_click_target(point) {
|
||||
return token.on_left_click(point);
|
||||
if token.is_left_click_target(point, context) {
|
||||
return token.on_left_click(point, context);
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
||||
for token in self.tokens.iter() {
|
||||
if token.is_left_click_target(point) {
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::app::{UpdateResult, WindowCanvas};
|
||||
use crate::app::{UpdateResult as UR, WindowCanvas as WC};
|
||||
use crate::config::Config;
|
||||
use crate::lexer::TokenType;
|
||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||
use crate::renderer::Renderer;
|
||||
use crate::ui::text_character::*;
|
||||
use crate::ui::*;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
@ -11,32 +10,55 @@ use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
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 {
|
||||
last_in_line: bool,
|
||||
characters: Vec<TextCharacter>,
|
||||
token_type: TokenType,
|
||||
token_type: Rc<TokenType>,
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
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 {
|
||||
last_in_line,
|
||||
characters: vec![],
|
||||
token_type,
|
||||
token_type: Rc::new(token_type.clone()),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_last_in_line(&self) -> bool {
|
||||
self.last_in_line
|
||||
}
|
||||
|
||||
pub fn update_position(&mut self, current: &mut Rect) {
|
||||
for text_character in self.characters.iter_mut() {
|
||||
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() {
|
||||
if character.position() == index {
|
||||
return Some(&character);
|
||||
return Some(character.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
@ -70,32 +92,17 @@ impl EditorFileToken {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult {
|
||||
let config = renderer.config().theme().code_highlighting();
|
||||
let color: Color = match self.token_type {
|
||||
TokenType::Whitespace { .. } => config.whitespace().color().into(),
|
||||
TokenType::Keyword { .. } => config.keyword().color().into(),
|
||||
TokenType::String { .. } => config.string().color().into(),
|
||||
TokenType::Number { .. } => config.number().color().into(),
|
||||
TokenType::Identifier { .. } => config.identifier().color().into(),
|
||||
TokenType::Literal { .. } => config.literal().color().into(),
|
||||
TokenType::Comment { .. } => config.comment().color().into(),
|
||||
TokenType::Operator { .. } => config.operator().color().into(),
|
||||
TokenType::Separator { .. } => config.separator().color().into(),
|
||||
};
|
||||
for (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);
|
||||
pub fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> {
|
||||
let mut current: Option<&TextCharacter> = None;
|
||||
for text_character in self.characters.iter() {
|
||||
if !text_character.is_last_in_line() {
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateResult::RefreshPositions
|
||||
if text_character.line() == line {
|
||||
current = Some(text_character);
|
||||
}
|
||||
}
|
||||
current.map(|c| c.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,42 +111,60 @@ impl Render for EditorFileToken {
|
||||
* Must first create targets so even if new line appear renderer will know
|
||||
* where move render starting point
|
||||
*/
|
||||
fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult {
|
||||
if self.characters.is_empty() {
|
||||
return self.update_view(renderer);
|
||||
}
|
||||
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR {
|
||||
if self.token_type.is_new_line() {
|
||||
return UpdateResult::NoOp;
|
||||
return UR::NoOp;
|
||||
}
|
||||
for text_character in self.characters.iter_mut() {
|
||||
text_character.render(canvas, renderer);
|
||||
for text_character in self.characters.iter() {
|
||||
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 {
|
||||
fn update(&mut self, ticks: i32) -> UpdateResult {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||
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 {
|
||||
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() {
|
||||
if text_character.is_left_click_target(point) {
|
||||
return text_character.on_left_click(point);
|
||||
if text_character.is_left_click_target(point, context) {
|
||||
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() {
|
||||
if text_character.is_left_click_target(point) {
|
||||
if text_character.is_left_click_target(point, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
pub mod editor_file;
|
||||
pub mod editor_file_section;
|
||||
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;
|
||||
|
||||
pub struct MenuBar {
|
||||
border_color: Color,
|
||||
background_color: Color,
|
||||
dest: Rect,
|
||||
config: Rc<Config>,
|
||||
pending: bool,
|
||||
}
|
||||
|
||||
impl MenuBar {
|
||||
pub fn new(config: Rc<Config>) -> 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),
|
||||
config,
|
||||
pending: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,28 +35,66 @@ impl 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 height = self.config.menu_height() as u32;
|
||||
self.dest = Rect::new(0, 0, width, height);
|
||||
canvas.set_draw_color(self.background_color.clone());
|
||||
canvas.draw_rect(self.dest.clone()).unwrap();
|
||||
UpdateResult::NoOp
|
||||
self.pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for MenuBar {
|
||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
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!()
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
||||
is_in_rect(point, self.dest())
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for MenuBar {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,30 @@ use crate::renderer::Renderer;
|
||||
|
||||
pub mod caret;
|
||||
pub mod file;
|
||||
pub mod file_editor;
|
||||
pub mod menu_bar;
|
||||
pub mod project_tree;
|
||||
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 {
|
||||
let start = Point::new(rect.x(), rect.y());
|
||||
let start = rect.top_left();
|
||||
let end = Point::new(
|
||||
rect.x() + (rect.width() 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 {
|
||||
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 {
|
||||
fn update(&mut self, ticks: i32) -> UpdateResult;
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult;
|
||||
}
|
||||
|
||||
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::lexer::TokenType;
|
||||
use crate::renderer::managers::{FontDetails, TextDetails};
|
||||
@ -12,12 +12,12 @@ use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextCharacter {
|
||||
pending: bool,
|
||||
text_character: char,
|
||||
position: usize,
|
||||
line: usize,
|
||||
last_in_line: bool,
|
||||
source: Rect,
|
||||
dest: Rect,
|
||||
color: Color,
|
||||
@ -29,14 +29,15 @@ impl TextCharacter {
|
||||
text_character: char,
|
||||
position: usize,
|
||||
line: usize,
|
||||
last_in_line: bool,
|
||||
color: Color,
|
||||
config: Rc<Config>,
|
||||
) -> Self {
|
||||
Self {
|
||||
pending: true,
|
||||
text_character,
|
||||
position,
|
||||
line,
|
||||
last_in_line,
|
||||
source: Rect::new(0, 0, 0, 0),
|
||||
dest: Rect::new(0, 0, 0, 0),
|
||||
color,
|
||||
@ -44,6 +45,10 @@ impl TextCharacter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_last_in_line(&self) -> bool {
|
||||
self.last_in_line
|
||||
}
|
||||
|
||||
pub fn dest(&self) -> &Rect {
|
||||
&self.dest
|
||||
}
|
||||
@ -59,8 +64,10 @@ impl TextCharacter {
|
||||
pub fn update_position(&mut self, current: &mut Rect) {
|
||||
if self.is_new_line() {
|
||||
let y = self.source.height() as i32;
|
||||
current.set_x(self.config.editor_left_margin());
|
||||
current.set_x(0);
|
||||
current.set_y(current.y() + y);
|
||||
self.dest.set_x(current.x());
|
||||
self.dest.set_y(current.y());
|
||||
} else {
|
||||
self.dest.set_x(current.x());
|
||||
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 font_details =
|
||||
FontDetails::new(config.font_path().as_str(), config.character_size().clone());
|
||||
@ -96,83 +158,36 @@ impl TextCharacter {
|
||||
.texture_manager()
|
||||
.load_text(&mut details, &font)
|
||||
.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 {
|
||||
fn update(&mut self, _ticks: i32) -> UpdateResult {
|
||||
UpdateResult::NoOp
|
||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR {
|
||||
UR::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for TextCharacter {
|
||||
fn on_left_click(&mut self, _point: &Point) -> UpdateResult {
|
||||
UpdateResult::MoveCaret(
|
||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR {
|
||||
UR::MoveCaret(
|
||||
self.dest().clone(),
|
||||
CaretPosition::new(self.position(), self.line(), 0),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point) -> bool {
|
||||
is_in_rect(point, self.dest())
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
is_in_rect(
|
||||
point,
|
||||
&match context {
|
||||
&UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()),
|
||||
_ => self.dest().clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for TextCharacter {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user