refactoring to separate UI folder for future UI framework

This commit is contained in:
Anton-4 2021-02-13 19:41:20 +01:00
parent 9dda23cf8b
commit d289dc9f73
34 changed files with 900 additions and 2962 deletions

View file

@ -0,0 +1,6 @@
use crate::ui::colors::ColorTup;
pub const TXT_COLOR: ColorTup = (1.0, 1.0, 1.0, 1.0);
pub const CODE_COLOR: ColorTup = (0.21, 0.55, 0.83, 1.0);
pub const BG_COLOR: ColorTup = (0.11, 0.11, 0.13, 1.0);

View file

@ -21,13 +21,6 @@ pub enum EdError {
))] ))]
ClipboardInitFailed { err_msg: String }, ClipboardInitFailed { err_msg: String },
#[snafu(display(
"FileOpenFailed: failed to open file with path {} with the following error: {}.",
path_str,
err_msg
))]
FileOpenFailed { path_str: String, err_msg: String },
#[snafu(display("InvalidSelection: {}.", err_msg))] #[snafu(display("InvalidSelection: {}.", err_msg))]
InvalidSelection { InvalidSelection {
err_msg: String, err_msg: String,
@ -49,9 +42,6 @@ pub enum EdError {
len: usize, len: usize,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))]
TextBufReadFailed { path_str: String, err_msg: String },
} }
pub type EdResult<T, E = EdError> = std::result::Result<T, E>; pub type EdResult<T, E = EdError> = std::result::Result<T, E>;

449
editor/src/editor/main.rs Normal file
View file

@ -0,0 +1,449 @@
use crate::error::{print_err, EdResult};
use crate::graphics::colors::{CODE_COLOR, TXT_COLOR};
use crate::graphics::lowlevel::buffer::create_rect_buffers;
use crate::graphics::lowlevel::ortho::update_ortho_buffer;
use crate::graphics::lowlevel::pipelines;
use crate::graphics::primitives::text::{
build_glyph_brush, example_code_glyph_rect, queue_code_text_draw, queue_text_draw, Text,
};
use crate::graphics::style::CODE_FONT_SIZE;
use crate::graphics::style::CODE_TXT_XY;
use crate::mvc::app_model::AppModel;
use crate::mvc::ed_model::EdModel;
use crate::mvc::{app_update, ed_model, ed_view};
//use crate::resources::strings::NOTHING_OPENED;
use crate::vec_result::get_res;
use bumpalo::Bump;
use cgmath::Vector2;
use ed_model::Position;
use lang::{pool::Pool, scope::Scope};
use pipelines::RectResources;
use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, ModuleIds};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::error::Error;
use std::io;
use std::path::Path;
use wgpu::{CommandEncoder, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use winit::event;
use winit::event::{Event, ModifiersState};
use winit::event_loop::ControlFlow;
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, licensed under the MIT license
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
/// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> {
//TODO support using multiple filepaths
let first_path_opt = if !filepaths.is_empty() {
match get_res(0, filepaths) {
Ok(path_ref_ref) => Some(*path_ref_ref),
Err(e) => {
eprintln!("{}", e);
None
}
}
} else {
None
};
run_event_loop(first_path_opt).expect("Error running event loop");
Ok(())
}
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init();
// Open window and create a surface
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
.build(&event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
shader_validation: false,
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
};
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let ed_model_opt = if let Some(file_path) = file_path_opt {
let ed_model_res = ed_model::init_model(file_path);
match ed_model_res {
Ok(mut ed_model) => {
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
Some(ed_model)
}
Err(e) => {
print_err(&e);
None
}
}
} else {
None
};
let mut app_model = AppModel::init(ed_model_opt);
let mut keyboard_modifiers = ModifiersState::empty();
// This arena is never cleared and should only be used for allocations that occur rarely
let arena = Bump::new();
let mut rects_arena = Bump::new();
// Render loop
window.request_redraw();
event_loop.run(move |event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life!
if is_animating {
*control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait;
}
match event {
//Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
//Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
swap_chain = gpu_device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
}
//Received Character
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
if let Err(e) = app_update::handle_new_char(&ch, &mut app_model) {
print_err(&e)
}
}
//Keyboard Input
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let keydown_res = keyboard_input::handle_keydown(
input.state,
virtual_keycode,
keyboard_modifiers,
&mut app_model,
);
if let Err(e) = keydown_res {
print_err(&e)
}
}
}
}
}
//Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let frame = swap_chain
.get_current_frame()
.expect("Failed to acquire next SwapChainFrame")
.output;
if let Some(ed_model) = &app_model.ed_model_opt {
//TODO don't pass invisible lines
queue_editor_text(
&size,
&ed_model.text_buf.all_lines(&arena),
ed_model.caret_pos,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
} else {
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
let mut pool = Pool::with_capacity(12);
let mut var_store = VarStore::default();
let dep_idents = MutMap::default();
let mut module_ids = ModuleIds::default();
let exposed_ident_ids = IdentIds::default();
let home = module_ids.get_or_insert(&"Home".into());
let mut env = crate::lang::expr::Env::new(
home,
&arena,
&mut pool,
&mut var_store,
dep_idents,
&module_ids,
exposed_ident_ids,
);
let mut scope = Scope::new(home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let (expr2, _) = crate::lang::expr::str_to_expr2(
&arena, "True", &mut env, &mut scope, region,
)
.unwrap();
render::render_expr2(
&mut env,
&size,
&expr2,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
}
rects_arena.reset();
match draw_all_rects(
&app_model.ed_model_opt,
&rects_arena,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
) {
Ok(()) => (),
Err(e) => {
print_err(&e);
begin_render_pass(&mut encoder, &frame.view);
}
}
// draw all text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
staging_belt.finish();
cmd_queue.submit(Some(encoder.finish()));
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
})
}
fn draw_all_rects(
ed_model_opt: &Option<EdModel>,
arena: &Bump,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
) -> EdResult<()> {
if let Some(ed_model) = ed_model_opt {
let all_rects = ed_view::create_ed_rects(ed_model, arena)?;
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
} else {
// need to begin render pass to clear screen
begin_render_pass(encoder, texture_view);
}
Ok(())
}
fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
) -> RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
}],
depth_stencil_attachment: None,
})
}
// returns bounding boxes for every glyph
fn queue_editor_text(
size: &PhysicalSize<u32>,
editor_lines: &str,
caret_pos: Position,
code_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: code_coords,
area_bounds,
color: CODE_COLOR.into(),
text: editor_lines,
size: CODE_FONT_SIZE,
..Default::default()
};
let s = format!("Ln {}, Col {}", caret_pos.line, caret_pos.column);
let text = s.as_str();
let caret_pos_label = Text {
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
area_bounds,
color: TXT_COLOR.into(),
text,
size: 25.0,
..Default::default()
};
queue_text_draw(&caret_pos_label, glyph_brush);
queue_code_text_draw(&code_text, glyph_brush);
}
fn _queue_no_file_text(
size: &PhysicalSize<u32>,
text: &str,
text_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: text_coords,
area_bounds,
color: CODE_COLOR.into(),
text,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_text_draw(&code_text, glyph_brush);
}

6
editor/src/editor/mod.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod main;
mod ed_error;
mod keyboard_input;
mod render;
mod colors;

View file

@ -1,10 +1,15 @@
use crate::error::EdResult;
use crate::error::OutOfBounds; use super::ed_error::{EdResult, OutOfBounds};
use snafu::OptionExt; use snafu::OptionExt;
use std::slice::SliceIndex; use std::slice::SliceIndex;
// replace vec methods that return Option with ones that return Result and proper Error pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n'];
newline_codes.contains(char_ref)
}
// replace vec methods that return Option with ones that return Result and proper Error
pub fn get_res<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> { pub fn get_res<T>(index: usize, slice: &[T]) -> EdResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds { let elt_ref = slice.get(index).context(OutOfBounds {
index, index,

View file

@ -2,471 +2,19 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
// Inspired by:
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, licensed under the MIT license
// https://github.com/cloudhead/rgx by Alexis Sellier, licensed under the MIT license
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
extern crate pest; extern crate pest;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
extern crate pest_derive; extern crate pest_derive;
use crate::error::{print_err, EdResult}; mod graphics;
use crate::graphics::colors::{CODE_COLOR, TXT_COLOR}; mod lang;
use crate::graphics::lowlevel::buffer::create_rect_buffers; mod editor;
use crate::graphics::lowlevel::ortho::update_ortho_buffer; mod ui;
use crate::graphics::lowlevel::pipelines;
use crate::graphics::primitives::text::{
build_glyph_brush, example_code_glyph_rect, queue_code_text_draw, queue_text_draw, Text,
};
use crate::graphics::style::CODE_FONT_SIZE;
use crate::graphics::style::CODE_TXT_XY;
use crate::mvc::app_model::AppModel;
use crate::mvc::ed_model::EdModel;
use crate::mvc::{app_update, ed_model, ed_view};
//use crate::resources::strings::NOTHING_OPENED;
use crate::vec_result::get_res;
use bumpalo::Bump;
use cgmath::Vector2;
use ed_model::Position;
use lang::{pool::Pool, scope::Scope};
use pipelines::RectResources;
use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, ModuleIds};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::error::Error;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use wgpu::{CommandEncoder, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use winit::event;
use winit::event::{Event, ModifiersState};
use winit::event_loop::ControlFlow;
pub mod error;
pub mod graphics;
mod keyboard_input;
pub mod lang;
mod mvc;
//pub mod mvc; // for benchmarking
mod ui;
mod resources;
mod selection;
mod text_buffer;
//pub mod text_buffer; // for benchmarking
mod render;
mod util;
mod vec_result;
/// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch.
pub fn launch(filepaths: &[&Path]) -> io::Result<()> { pub fn launch(filepaths: &[&Path]) -> io::Result<()> {
//TODO support using multiple filepaths editor::main::launch(filepaths)
let first_path_opt = if !filepaths.is_empty() {
match get_res(0, filepaths) {
Ok(path_ref_ref) => Some(*path_ref_ref),
Err(e) => {
eprintln!("{}", e);
None
}
}
} else {
None
};
run_event_loop(first_path_opt).expect("Error running event loop");
Ok(())
}
fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
env_logger::init();
// Open window and create a surface
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
.build(&event_loop)
.unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all());
let surface = unsafe { instance.create_surface(&window) };
// Initialize GPU
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
shader_validation: false,
},
None,
)
.await
.expect("Request device")
});
// Create staging belt and a local pool
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
let mut local_pool = futures::executor::LocalPool::new();
let local_spawner = local_pool.spawner();
// Prepare swap chain
let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
};
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true;
let ed_model_opt = if let Some(file_path) = file_path_opt {
let ed_model_res = ed_model::init_model(file_path);
match ed_model_res {
Ok(mut ed_model) => {
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
Some(ed_model)
}
Err(e) => {
print_err(&e);
None
}
}
} else {
None
};
let mut app_model = AppModel::init(ed_model_opt);
let mut keyboard_modifiers = ModifiersState::empty();
// This arena is never cleared and should only be used for allocations that occur rarely
let arena = Bump::new();
let mut rects_arena = Bump::new();
// Render loop
window.request_redraw();
event_loop.run(move |event, _, control_flow| {
// TODO dynamically switch this on/off depending on whether any
// animations are running. Should conserve CPU usage and battery life!
if is_animating {
*control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait;
}
match event {
//Close
Event::WindowEvent {
event: event::WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
//Resize
Event::WindowEvent {
event: event::WindowEvent::Resized(new_size),
..
} => {
size = new_size;
swap_chain = gpu_device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: render_format,
width: size.width,
height: size.height,
//Immediate may cause tearing, change present_mode if this becomes a problem
present_mode: wgpu::PresentMode::Immediate,
},
);
update_ortho_buffer(
size.width,
size.height,
&gpu_device,
&rect_resources.ortho.buffer,
&cmd_queue,
);
}
//Received Character
Event::WindowEvent {
event: event::WindowEvent::ReceivedCharacter(ch),
..
} => {
if let Err(e) = app_update::handle_new_char(&ch, &mut app_model) {
print_err(&e)
}
}
//Keyboard Input
Event::WindowEvent {
event: event::WindowEvent::KeyboardInput { input, .. },
..
} => {
if let Some(virtual_keycode) = input.virtual_keycode {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
if ed_model.has_focus {
let keydown_res = keyboard_input::handle_keydown(
input.state,
virtual_keycode,
keyboard_modifiers,
&mut app_model,
);
if let Err(e) = keydown_res {
print_err(&e)
}
}
}
}
}
//Modifiers Changed
Event::WindowEvent {
event: event::WindowEvent::ModifiersChanged(modifiers),
..
} => {
keyboard_modifiers = modifiers;
}
Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested { .. } => {
// Get a command encoder for the current frame
let mut encoder =
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Redraw"),
});
let frame = swap_chain
.get_current_frame()
.expect("Failed to acquire next SwapChainFrame")
.output;
if let Some(ed_model) = &app_model.ed_model_opt {
//TODO don't pass invisible lines
queue_editor_text(
&size,
&ed_model.text_buf.all_lines(&arena),
ed_model.caret_pos,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
} else {
// queue_no_file_text(&size, NOTHING_OPENED, CODE_TXT_XY.into(), &mut glyph_brush);
let mut pool = Pool::with_capacity(12);
let mut var_store = VarStore::default();
let dep_idents = MutMap::default();
let mut module_ids = ModuleIds::default();
let exposed_ident_ids = IdentIds::default();
let home = module_ids.get_or_insert(&"Home".into());
let mut env = crate::lang::expr::Env::new(
home,
&arena,
&mut pool,
&mut var_store,
dep_idents,
&module_ids,
exposed_ident_ids,
);
let mut scope = Scope::new(home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let (expr2, _) = crate::lang::expr::str_to_expr2(
&arena, "True", &mut env, &mut scope, region,
)
.unwrap();
render::render_expr2(
&mut env,
&size,
&expr2,
CODE_TXT_XY.into(),
&mut glyph_brush,
);
}
rects_arena.reset();
match draw_all_rects(
&app_model.ed_model_opt,
&rects_arena,
&mut encoder,
&frame.view,
&gpu_device,
&rect_resources,
) {
Ok(()) => (),
Err(e) => {
print_err(&e);
begin_render_pass(&mut encoder, &frame.view);
}
}
// draw all text
glyph_brush
.draw_queued(
&gpu_device,
&mut staging_belt,
&mut encoder,
&frame.view,
size.width,
size.height,
)
.expect("Draw queued");
staging_belt.finish();
cmd_queue.submit(Some(encoder.finish()));
// Recall unused staging buffers
use futures::task::SpawnExt;
local_spawner
.spawn(staging_belt.recall())
.expect("Recall staging belt");
local_pool.run_until_stalled();
}
_ => {
*control_flow = winit::event_loop::ControlFlow::Wait;
}
}
})
}
fn draw_all_rects(
ed_model_opt: &Option<EdModel>,
arena: &Bump,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
) -> EdResult<()> {
if let Some(ed_model) = ed_model_opt {
let all_rects = ed_view::create_ed_rects(ed_model, arena)?;
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
} else {
// need to begin render pass to clear screen
begin_render_pass(encoder, texture_view);
}
Ok(())
}
fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView,
) -> RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
}],
depth_stencil_attachment: None,
})
}
// returns bounding boxes for every glyph
fn queue_editor_text(
size: &PhysicalSize<u32>,
editor_lines: &str,
caret_pos: Position,
code_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: code_coords,
area_bounds,
color: CODE_COLOR.into(),
text: editor_lines,
size: CODE_FONT_SIZE,
..Default::default()
};
let s = format!("Ln {}, Col {}", caret_pos.line, caret_pos.column);
let text = s.as_str();
let caret_pos_label = Text {
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
area_bounds,
color: TXT_COLOR.into(),
text,
size: 25.0,
..Default::default()
};
queue_text_draw(&caret_pos_label, glyph_brush);
queue_code_text_draw(&code_text, glyph_brush);
}
fn _queue_no_file_text(
size: &PhysicalSize<u32>,
text: &str,
text_coords: Vector2<f32>,
glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into();
let code_text = Text {
position: text_coords,
area_bounds,
color: CODE_COLOR.into(),
text,
size: CODE_FONT_SIZE,
..Default::default()
};
queue_text_draw(&code_text, glyph_brush);
} }

File diff suppressed because it is too large Load diff

View file

@ -1,20 +0,0 @@
use snafu::{Backtrace, ErrorCompat, Snafu};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum UIError {
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
collection_name,
len
))]
OutOfBounds {
index: usize,
collection_name: String,
len: usize,
backtrace: Backtrace,
},
}
pub type UIResult<T, E = UIError> = std::result::Result<T, E>;

View file

@ -1,3 +1,4 @@
pub mod error; pub mod ui_error;
pub mod model; pub mod colors;
pub mod update; pub mod text;
mod util;

View file

@ -1 +0,0 @@
pub mod text;

View file

@ -1,3 +0,0 @@
pub mod selectable_text_m;
pub mod selection;
pub mod txt_pos;

View file

@ -1,48 +0,0 @@
use crate::ui::error::UIResult;
use super::txt_pos::TxtPos;
use std::fmt;
impl std::fmt::Display for RawSelection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}",
self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column
)
}
}
trait SelectableLines {
fn get_line(&self, line_nr: usize) -> UIResult<&str>;
fn line_len(&self, line_nr: usize) -> UIResult<usize>;
fn nr_of_lines(&self) -> usize;
fn nr_of_chars(&self) -> usize;
// TODO use pool allocation here
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
fn last_TxtPos(&self) -> TxtPos;
fn get_selection(&self, raw_sel: RawSelection) -> UIResult<&str>;
}
trait MutSelectableLines {
fn insert_char(&mut self, caret_pos: TxtPos, new_char: &char) -> UIResult<()>;
fn insert_str(&mut self, caret_pos: TxtPos, new_str: &str) -> UIResult<()>;
fn pop_char(&mut self, caret_pos: TxtPos);
fn del_selection(&mut self, raw_sel: RawSelection) -> UIResult<()>;
}
#[derive(Debug)]
pub struct SelectableText {
pub lines: SelectableLines,
pub caret_pos: TxtPos,
pub selection_opt: Option<RawSelection>,
}

View file

@ -1,76 +1,31 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license // Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::error::EdError::{FileOpenFailed, TextBufReadFailed}; use crate::ui::ui_error::UIError::{FileOpenFailed, TextBufReadFailed};
use crate::error::EdResult; use crate::ui::ui_error::UIResult;
use crate::error::OutOfBounds; use crate::ui::ui_error::OutOfBounds;
use crate::mvc::ed_model::{Position, RawSelection}; use crate::ui::text::text_pos::{TextPos};
use crate::selection::validate_selection; use crate::ui::text::selection::{ValidSelection};
use crate::ui::text::caret_w_select::{CaretWSelect};
use crate::ui::text::lines::{Lines, SelectableLines, MutSelectableLines};
use bumpalo::collections::String as BumpString; use bumpalo::collections::String as BumpString;
use bumpalo::Bump; use bumpalo::Bump;
use ropey::Rope; use ropey::Rope;
use snafu::{ensure, OptionExt}; use snafu::{ensure};
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
pub struct TextBuffer { pub struct BigSelectableText {
pub caret_w_select: CaretWSelect,
pub text_rope: Rope, pub text_rope: Rope,
pub path_str: String, pub path_str: String,
pub arena: Bump, pub arena: Bump,
} }
impl TextBuffer { impl BigSelectableText {
pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> {
self.insert_str(caret_pos, &new_char.to_string())
}
pub fn insert_str(&mut self, caret_pos: Position, new_str: &str) -> EdResult<()> { fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
let char_indx = self.pos_to_char_indx(caret_pos);
self.check_bounds(char_indx)?;
self.text_rope.insert(char_indx, new_str);
Ok(())
}
pub fn pop_char(&mut self, caret_pos: Position) {
let char_indx = self.pos_to_char_indx(caret_pos);
if (char_indx > 0) && char_indx <= self.text_rope.len_chars() {
self.text_rope.remove((char_indx - 1)..char_indx);
}
}
pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> {
let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?;
self.check_bounds(end_char_indx)?;
self.text_rope.remove(start_char_indx..end_char_indx);
Ok(())
}
pub fn get_selection(&self, raw_sel: RawSelection) -> EdResult<&str> {
let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?;
self.check_bounds(end_char_indx)?;
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() {
Ok(line_str_ref)
} else {
// happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(arena_str_ref)
}
}
fn check_bounds(&self, char_indx: usize) -> EdResult<()> {
ensure!( ensure!(
char_indx <= self.text_rope.len_chars(), char_indx <= self.text_rope.len_chars(),
OutOfBounds { OutOfBounds {
@ -83,46 +38,68 @@ impl TextBuffer {
Ok(()) Ok(())
} }
pub fn line(&self, line_nr: usize) -> Option<&str> { fn pos_to_char_indx(&self, pos: TextPos) -> usize {
if line_nr < self.nr_of_lines() { self.text_rope.line_to_char(pos.line) + pos.column
let rope_slice = self.text_rope.line(line_nr); }
if let Some(line_str_ref) = rope_slice.as_str() { fn char_indx_to_pos(&self, char_indx: usize) -> TextPos {
Some(line_str_ref) let line = self.text_rope.char_to_line(char_indx);
} else {
// happens very rarely let char_idx_line_start = self.pos_to_char_indx(TextPos { line, column: 0 });
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str); let column = char_indx - char_idx_line_start;
Some(arena_str_ref)
TextPos { line, column }
}
fn sel_to_tup(&self, val_sel: ValidSelection) -> UIResult<(usize, usize)> {
let start_char_indx = self.pos_to_char_indx(val_sel.start_pos);
let end_char_indx = self.pos_to_char_indx(val_sel.end_pos);
Ok((start_char_indx, end_char_indx))
}
}
impl Lines for BigSelectableText {
fn get_line(&self, line_nr: usize) -> UIResult<&str> {
ensure!(
line_nr < self.nr_of_lines(),
OutOfBounds {
index: line_nr,
collection_name: "BigSelectableText",
len: self.nr_of_lines(),
} }
);
let rope_slice = self.text_rope.line(line_nr);
if let Some(line_str_ref) = rope_slice.as_str() {
Ok(line_str_ref)
} else { } else {
None // happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(arena_str_ref)
} }
} }
pub fn line_len(&self, line_nr: usize) -> Option<usize> { fn line_len(&self, line_nr: usize) -> UIResult<usize> {
self.line(line_nr).map(|line| line.len()) self.get_line(line_nr).map(|line| line.len())
} }
pub fn line_len_res(&self, line_nr: usize) -> EdResult<usize> { fn nr_of_lines(&self) -> usize {
self.line_len(line_nr).context(OutOfBounds {
index: line_nr,
collection_name: "Rope",
len: self.text_rope.len_lines(),
})
}
pub fn nr_of_lines(&self) -> usize {
self.text_rope.len_lines() self.text_rope.len_lines()
} }
pub fn nr_of_chars(&self) -> usize { fn nr_of_chars(&self) -> usize {
self.text_rope.len_chars() self.text_rope.len_chars()
} }
// expensive function, don't use it if it can be done with a specialized, more efficient function
// TODO use pool allocation here // TODO use pool allocation here
pub fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> { // expensive function, don't use it if it can be done with a specialized, more efficient function
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a> {
let mut lines = BumpString::with_capacity_in(self.text_rope.len_chars(), arena); let mut lines = BumpString::with_capacity_in(self.text_rope.len_chars(), arena);
for line in self.text_rope.lines() { for line in self.text_rope.lines() {
@ -131,40 +108,81 @@ impl TextBuffer {
lines lines
} }
}
fn pos_to_char_indx(&self, pos: Position) -> usize { impl SelectableLines for BigSelectableText {
self.text_rope.line_to_char(pos.line) + pos.column fn get_selection(&self) -> UIResult<Option<&str>> {
if let Some(val_sel) = self.caret_w_select.selection_opt {
let (start_char_indx, end_char_indx) = self.sel_to_tup(val_sel)?;
self.check_bounds(end_char_indx)?;
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
if let Some(line_str_ref) = rope_slice.as_str() {
Ok(Some(line_str_ref))
} else {
// happens very rarely
let line_str = rope_slice.chunks().collect::<String>();
let arena_str_ref = self.arena.alloc(line_str);
Ok(Some(arena_str_ref))
}
} else {
Ok(None)
}
} }
fn char_indx_to_pos(&self, char_indx: usize) -> Position { fn last_text_pos(&self) -> TextPos {
let line = self.text_rope.char_to_line(char_indx);
let char_idx_line_start = self.pos_to_char_indx(Position { line, column: 0 });
let column = char_indx - char_idx_line_start;
Position { line, column }
}
fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> {
let valid_sel = validate_selection(raw_sel)?;
let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos);
let end_char_indx = self.pos_to_char_indx(valid_sel.selection.end_pos);
Ok((start_char_indx, end_char_indx))
}
pub fn last_position(&self) -> Position {
self.char_indx_to_pos(self.nr_of_chars()) self.char_indx_to_pos(self.nr_of_chars())
} }
} }
pub fn from_path(path: &Path) -> EdResult<TextBuffer> { impl MutSelectableLines for BigSelectableText {
fn insert_char(&mut self, caret_pos: TextPos, new_char: &char) -> UIResult<()> {
self.insert_str(caret_pos, &new_char.to_string())
}
fn insert_str(&mut self, caret_pos: TextPos, new_str: &str) -> UIResult<()> {
let char_indx = self.pos_to_char_indx(caret_pos);
self.check_bounds(char_indx)?;
self.text_rope.insert(char_indx, new_str);
Ok(())
}
fn pop_char(&mut self, caret_pos: TextPos) {
let char_indx = self.pos_to_char_indx(caret_pos);
if (char_indx > 0) && char_indx <= self.text_rope.len_chars() {
self.text_rope.remove((char_indx - 1)..char_indx);
}
}
fn del_selection(&mut self) -> UIResult<()> {
if let Some(valid_sel) = self.caret_w_select.selection_opt {
let (start_char_indx, end_char_indx) = self.sel_to_tup(valid_sel)?;
self.check_bounds(end_char_indx)?;
self.text_rope.remove(start_char_indx..end_char_indx);
// TODO adjust caret pos aftere delete
}
Ok(())
}
}
pub fn from_path(path: &Path) -> UIResult<BigSelectableText> {
let caret_w_select = CaretWSelect::default();
let text_rope = rope_from_path(path)?; let text_rope = rope_from_path(path)?;
let path_str = path_to_string(path); let path_str = path_to_string(path);
let arena = Bump::new(); let arena = Bump::new();
Ok(TextBuffer { Ok(BigSelectableText {
caret_w_select,
text_rope, text_rope,
path_str, path_str,
arena, arena,
@ -178,7 +196,7 @@ fn path_to_string(path: &Path) -> String {
path_str path_str
} }
fn rope_from_path(path: &Path) -> EdResult<Rope> { fn rope_from_path(path: &Path) -> UIResult<Rope> {
match File::open(path) { match File::open(path) {
Ok(file) => { Ok(file) => {
let buf_reader = &mut io::BufReader::new(file); let buf_reader = &mut io::BufReader::new(file);
@ -197,9 +215,11 @@ fn rope_from_path(path: &Path) -> EdResult<Rope> {
} }
} }
impl fmt::Debug for TextBuffer { // need to explicitly omit arena
impl fmt::Debug for BigSelectableText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TextBuffer") f.debug_struct("BigSelectableText")
.field("caret_w_select", &self.caret_w_select)
.field("text_rope", &self.text_rope) .field("text_rope", &self.text_rope)
.field("path_str", &self.path_str) .field("path_str", &self.path_str)
.finish() .finish()

View file

@ -0,0 +1,108 @@
use crate::ui::ui_error::UIResult;
use super::text_pos::TextPos;
use super::selection::{ValidSelection};
use winit::event::{ModifiersState};
use super::selection::validate_selection;
#[derive(Debug)]
pub struct CaretWSelect {
pub caret_pos: TextPos,
pub selection_opt: Option<ValidSelection>,
}
fn mk_some_sel(start_pos: TextPos, end_pos: TextPos) -> UIResult<Option<ValidSelection>> {
Ok(
Some(
validate_selection(start_pos, end_pos)?
)
)
}
impl Default for CaretWSelect {
fn default() -> Self {
Self {
caret_pos: TextPos {
line: 0,
column: 0
},
selection_opt: None
}
}
}
impl CaretWSelect {
pub fn move_caret_w_mods(&mut self, new_pos: TextPos, mods: &ModifiersState) -> UIResult<()> {
let caret_pos = self.caret_pos;
// one does not simply move the caret
let valid_sel_opt =
if new_pos != caret_pos {
if mods.shift() {
if let Some(old_sel) = self.selection_opt {
if new_pos < old_sel.start_pos {
if caret_pos > old_sel.start_pos {
mk_some_sel(
new_pos,
old_sel.start_pos
)?
} else {
mk_some_sel(
new_pos,
old_sel.end_pos
)?
}
} else if new_pos > old_sel.end_pos {
if caret_pos < old_sel.end_pos {
mk_some_sel(
old_sel.end_pos,
new_pos
)?
} else {
mk_some_sel(
old_sel.start_pos,
new_pos
)?
}
} else if new_pos > caret_pos {
mk_some_sel(
new_pos,
old_sel.end_pos
)?
} else if new_pos < caret_pos {
mk_some_sel(
old_sel.start_pos,
new_pos
)?
} else {
// TODO should this return none?
None
}
} else if new_pos < self.caret_pos {
mk_some_sel(
new_pos,
caret_pos
)?
} else {
mk_some_sel(
caret_pos,
new_pos
)?
}
} else {
None
}
} else {
self.selection_opt
};
self.caret_pos = new_pos;
self.selection_opt = valid_sel_opt;
Ok(())
}
}

View file

@ -0,0 +1,44 @@
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
use crate::ui::ui_error::UIError::{FileOpenFailed, TextBufReadFailed};
use crate::ui::ui_error::UIResult;
use crate::ui::ui_error::OutOfBounds;
use crate::ui::text::text_pos::{TextPos};
use crate::ui::text::selection::{ValidSelection};
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use ropey::Rope;
use snafu::{ensure, OptionExt};
use std::fmt;
use std::fs::File;
use std::io;
use std::path::Path;
pub trait Lines {
fn get_line(&self, line_nr: usize) -> UIResult<&str>;
fn line_len(&self, line_nr: usize) -> UIResult<usize>;
fn nr_of_lines(&self) -> usize;
fn nr_of_chars(&self) -> usize;
// TODO use pool allocation here
fn all_lines<'a>(&self, arena: &'a Bump) -> BumpString<'a>;
}
pub trait SelectableLines {
fn get_selection(&self) -> UIResult<Option<&str>>;
fn last_text_pos(&self) -> TextPos;
}
pub trait MutSelectableLines {
fn insert_char(&mut self, caret_pos: TextPos, new_char: &char) -> UIResult<()>;
fn insert_str(&mut self, caret_pos: TextPos, new_str: &str) -> UIResult<()>;
fn pop_char(&mut self, caret_pos: TextPos);
fn del_selection(&mut self) -> UIResult<()>;
}

View file

@ -0,0 +1,5 @@
pub mod caret_w_select;
pub mod selection;
pub mod text_pos;
pub mod big_selectable_text;
pub mod lines;

View file

@ -1,21 +1,48 @@
use crate::ui::error::{UIResult, InvalidSelection}; use crate::ui::ui_error::{UIResult, InvalidSelection};
use crate::ui::colors;
use super::text_pos::TextPos;
use super::lines::Lines;
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use snafu::ensure; use snafu::ensure;
use std::fmt;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct RawSelection { pub struct RawSelection {
pub start_pos: TxtPos, pub start_pos: TextPos,
pub end_pos: TxtPos, pub end_pos: TextPos,
}
impl RawSelection {
pub fn make_raw_sel(start_pos: TextPos, end_pos: TextPos) -> RawSelection {
RawSelection {
start_pos,
end_pos
}
}
}
impl std::fmt::Display for RawSelection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"RawSelection: start_pos: line:{} col:{}, end_pos: line:{} col:{}",
self.start_pos.line, self.start_pos.column, self.end_pos.line, self.end_pos.column
)
}
} }
//using the "parse don't validate" pattern //using the "parse don't validate" pattern
#[derive(Debug)]
pub struct ValidSelection { pub struct ValidSelection {
pub selection: RawSelection, pub start_pos: TextPos,
pub end_pos: TextPos,
} }
pub fn validate_selection(selection: RawSelection) -> EdResult<ValidSelection> { pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<ValidSelection> {
let RawSelection { start_pos, end_pos } = selection; validate_selection(raw_sel.start_pos, raw_sel.end_pos)
}
pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<ValidSelection> {
ensure!( ensure!(
start_pos.line <= end_pos.line, start_pos.line <= end_pos.line,
@ -39,18 +66,22 @@ pub fn validate_selection(selection: RawSelection) -> EdResult<ValidSelection> {
); );
Ok(ValidSelection { Ok(ValidSelection {
selection: RawSelection { start_pos, end_pos }, start_pos, end_pos,
}) })
} }
use bumpalo::Bump;
use crate::graphics::primitives::rect::Rect;
use crate::ui::text::lines::Lines;
pub fn create_selection_rects<'a>( pub fn create_selection_rects<'a>(
raw_sel: RawSelection, valid_sel: ValidSelection,
text_buf: &TextBuffer, lines: &dyn Lines,
glyph_dim_rect: &Rect, glyph_dim_rect: &Rect,
arena: &'a Bump, arena: &'a Bump,
) -> EdResult<BumpVec<'a, Rect>> { ) -> UIResult<BumpVec<'a, Rect>> {
let valid_sel = validate_selection(raw_sel)?; let ValidSelection { start_pos, end_pos } = valid_sel;
let RawSelection { start_pos, end_pos } = valid_sel.selection;
let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena); let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena);
@ -73,7 +104,7 @@ pub fn create_selection_rects<'a>(
Ok(all_rects) Ok(all_rects)
} else { } else {
// first line // first line
let end_col = text_buf.line_len_res(start_pos.line)?; let end_col = lines.line_len(start_pos.line)?;
let width = ((end_col as f32) * glyph_dim_rect.width) let width = ((end_col as f32) * glyph_dim_rect.width)
- ((start_pos.column as f32) * glyph_dim_rect.width); - ((start_pos.column as f32) * glyph_dim_rect.width);
@ -91,7 +122,7 @@ pub fn create_selection_rects<'a>(
let first_mid_line = start_pos.line + 1; let first_mid_line = start_pos.line + 1;
for i in first_mid_line..(first_mid_line + nr_mid_lines) { for i in first_mid_line..(first_mid_line + nr_mid_lines) {
let mid_line_len = text_buf.line_len_res(i)?; let mid_line_len = lines.line_len(i)?;
let width = (mid_line_len as f32) * glyph_dim_rect.width; let width = (mid_line_len as f32) * glyph_dim_rect.width;
@ -126,13 +157,14 @@ pub fn create_selection_rects<'a>(
#[cfg(test)] #[cfg(test)]
pub mod test_selection { pub mod test_selection {
use crate::error::{EdResult, OutOfBounds}; use crate::ui::ui_error::{UIResult, OutOfBounds};
use crate::mvc::ed_model::{Position, RawSelection}; use crate::ui::text::text_pos::TextPos;
use crate::mvc::ed_update::{ use crate::ui::text::selection::{ValidSelection, validate_selection};
use crate::editor::mvc::ed_update::{
move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun, move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun,
}; };
use crate::text_buffer::TextBuffer; use crate::ui::text::big_selectable_text::BigSelectableText;
use crate::vec_result::get_res; use crate::ui::util::slice_get;
use core::cmp::Ordering; use core::cmp::Ordering;
use pest::Parser; use pest::Parser;
use ropey::Rope; use ropey::Rope;
@ -146,14 +178,14 @@ pub mod test_selection {
// show selection and caret position as symbols in lines for easy testing // show selection and caret position as symbols in lines for easy testing
pub fn convert_selection_to_dsl( pub fn convert_selection_to_dsl(
raw_sel_opt: Option<RawSelection>, selection_opt: Option<ValidSelection>,
caret_pos: Position, caret_pos: TextPos,
lines: &mut [String], lines: &mut [String],
) -> EdResult<&[String]> { ) -> UIResult<&[String]> {
if let Some(raw_sel) = raw_sel_opt { if let Some(sel) = selection_opt {
let mut to_insert = vec![ let mut to_insert = vec![
(raw_sel.start_pos, '['), (sel.start_pos, '['),
(raw_sel.end_pos, ']'), (sel.end_pos, ']'),
(caret_pos, '|'), (caret_pos, '|'),
]; ];
let symbol_map: HashMap<char, usize> = let symbol_map: HashMap<char, usize> =
@ -191,7 +223,7 @@ pub mod test_selection {
Ok(lines) Ok(lines)
} }
fn insert_at_pos(lines: &mut [String], pos: Position, insert_char: char) -> EdResult<()> { fn insert_at_pos(lines: &mut [String], pos: TextPos, insert_char: char) -> UIResult<()> {
let line = get_mut_res(pos.line, lines)?; let line = get_mut_res(pos.line, lines)?;
line.insert(pos.column, insert_char); line.insert(pos.column, insert_char);
@ -202,7 +234,7 @@ pub mod test_selection {
fn get_mut_res<T>( fn get_mut_res<T>(
index: usize, index: usize,
vec: &mut [T], vec: &mut [T],
) -> EdResult<&mut <usize as SliceIndex<[T]>>::Output> { ) -> UIResult<&mut <usize as SliceIndex<[T]>>::Output> {
let vec_len = vec.len(); let vec_len = vec.len();
let elt_ref = vec.get_mut(index).context(OutOfBounds { let elt_ref = vec.get_mut(index).context(OutOfBounds {
@ -252,7 +284,7 @@ pub mod test_selection {
// Retrieve selection and position from formatted string // Retrieve selection and position from formatted string
pub fn convert_dsl_to_selection( pub fn convert_dsl_to_selection(
lines: &[String], lines: &[String],
) -> Result<(Option<RawSelection>, Position), String> { ) -> Result<(Option<ValidSelection>, TextPos), String> {
let lines_str: String = lines.join(""); let lines_str: String = lines.join("");
let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str) let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str)
@ -319,24 +351,28 @@ pub mod test_selection {
// Make sure return makes sense // Make sure return makes sense
if let Some((line, column)) = caret_opt { if let Some((line, column)) = caret_opt {
let caret_pos = Position { line, column }; let caret_pos = TextPos { line, column };
if sel_start_opt.is_none() && sel_end_opt.is_none() { if sel_start_opt.is_none() && sel_end_opt.is_none() {
Ok((None, caret_pos)) Ok((None, caret_pos))
} else if let Some((start_line, start_column)) = sel_start_opt { } else if let Some((start_line, start_column)) = sel_start_opt {
if let Some((end_line, end_column)) = sel_end_opt { if let Some((end_line, end_column)) = sel_end_opt {
Ok(( Ok(
Some(RawSelection { (
start_pos: Position { Some(
line: start_line, validate_selection(
column: start_column, TextPos {
}, line: start_line,
end_pos: Position { column: start_column,
line: end_line, },
column: end_column, TextPos {
}, line: end_line,
}), column: end_column,
caret_pos, },
)) ).unwrap()
),
caret_pos
)
)
} else { } else {
Err("Selection end ']' was not found, but selection start '[' was. Bad input string.".to_owned()) Err("Selection end ']' was not found, but selection start '[' was. Bad input string.".to_owned())
} }

View file

@ -2,27 +2,27 @@
use std::cmp::Ordering; use std::cmp::Ordering;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TxtPos { pub struct TextPos {
pub line: usize, pub line: usize,
pub column: usize, pub column: usize,
} }
impl Ord for TxtPos { impl Ord for TextPos {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
(self.line, self.column).cmp(&(other.line, other.column)) (self.line, self.column).cmp(&(other.line, other.column))
} }
} }
impl PartialOrd for TxtPos { impl PartialOrd for TextPos {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl PartialEq for TxtPos { impl PartialEq for TextPos {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
(self.line, self.column) == (other.line, other.column) (self.line, self.column) == (other.line, other.column)
} }
} }
impl Eq for TxtPos {} impl Eq for TextPos {}

36
editor/src/ui/ui_error.rs Normal file
View file

@ -0,0 +1,36 @@
use snafu::{Backtrace, ErrorCompat, Snafu};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum UIError {
#[snafu(display(
"OutOfBounds: index {} was out of bounds for {} with length {}.",
index,
collection_name,
len
))]
OutOfBounds {
index: usize,
collection_name: String,
len: usize,
backtrace: Backtrace,
},
#[snafu(display("InvalidSelection: {}.", err_msg))]
InvalidSelection {
err_msg: String,
backtrace: Backtrace,
},
#[snafu(display(
"FileOpenFailed: failed to open file with path {} with the following error: {}.",
path_str,
err_msg
))]
FileOpenFailed { path_str: String, err_msg: String },
#[snafu(display("TextBufReadFailed: the file {} could be opened but we encountered the following error while trying to read it: {}.", path_str, err_msg))]
TextBufReadFailed { path_str: String, err_msg: String },
}
pub type UIResult<T, E = UIError> = std::result::Result<T, E>;

View file

@ -1 +0,0 @@
pub mod SelectableTextU;

View file

@ -1,62 +0,0 @@
impl SelectableText {
pub fn move_caret_w_mods(&mut self, new_pos: Position, mods: &ModifiersState) {
let caret_pos = self.caret_pos;
// one does not simply move the caret
if new_pos != caret_pos {
if mods.shift() {
if let Some(selection) = self.selection_opt {
if new_pos < selection.start_pos {
if caret_pos > selection.start_pos {
self.set_selection(
new_pos,
selection.start_pos
)
} else {
self.set_selection(
new_pos,
selection.end_pos
)
}
} else if new_pos > selection.end_pos {
if caret_pos < selection.end_pos {
self.set_selection(
selection.end_pos,
new_pos
)
} else {
self.set_selection(
selection.start_pos,
new_pos
)
}
} else if new_pos > caret_pos {
self.set_selection(
new_pos,
selection.end_pos
)
} else if new_pos < caret_pos {
self.set_selection(
selection.start_pos,
new_pos
)
}
} else if new_pos < self.caret_pos {
self.set_selection(
new_pos,
caret_pos
)
} else {
self.set_selection(
caret_pos,
new_pos
)
}
} else {
self.selection_opt = None;
}
self.caret_pos = new_pos;
}
}
}

15
editor/src/ui/util.rs Normal file
View file

@ -0,0 +1,15 @@
use super::ui_error::{UIResult, OutOfBounds};
use snafu::OptionExt;
use std::slice::SliceIndex;
// replace vec methods that return Option with ones that return Result and proper Error
pub fn slice_get<T>(index: usize, slice: &[T]) -> UIResult<&<usize as SliceIndex<[T]>>::Output> {
let elt_ref = slice.get(index).context(OutOfBounds {
index,
collection_name: "Slice",
len: slice.len(),
})?;
Ok(elt_ref)
}

View file

@ -1,5 +0,0 @@
pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}', '\n'];
newline_codes.contains(char_ref)
}