mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
refactoring to separate UI folder for future UI framework
This commit is contained in:
parent
9dda23cf8b
commit
d289dc9f73
34 changed files with 900 additions and 2962 deletions
6
editor/src/editor/colors.rs
Normal file
6
editor/src/editor/colors.rs
Normal 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);
|
||||
|
|
@ -21,13 +21,6 @@ pub enum EdError {
|
|||
))]
|
||||
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))]
|
||||
InvalidSelection {
|
||||
err_msg: String,
|
||||
|
@ -49,9 +42,6 @@ pub enum EdError {
|
|||
len: usize,
|
||||
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>;
|
449
editor/src/editor/main.rs
Normal file
449
editor/src/editor/main.rs
Normal 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
6
editor/src/editor/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
pub mod main;
|
||||
mod ed_error;
|
||||
mod keyboard_input;
|
||||
mod render;
|
||||
mod colors;
|
|
@ -1,10 +1,15 @@
|
|||
use crate::error::EdResult;
|
||||
use crate::error::OutOfBounds;
|
||||
|
||||
use super::ed_error::{EdResult, OutOfBounds};
|
||||
use snafu::OptionExt;
|
||||
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> {
|
||||
let elt_ref = slice.get(index).context(OutOfBounds {
|
||||
index,
|
|
@ -2,471 +2,19 @@
|
|||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![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;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
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;
|
||||
mod graphics;
|
||||
mod lang;
|
||||
mod editor;
|
||||
mod ui;
|
||||
|
||||
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;
|
||||
|
||||
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<()> {
|
||||
//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);
|
||||
editor::main::launch(filepaths)
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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>;
|
|
@ -1,3 +1,4 @@
|
|||
pub mod error;
|
||||
pub mod model;
|
||||
pub mod update;
|
||||
pub mod ui_error;
|
||||
pub mod colors;
|
||||
pub mod text;
|
||||
mod util;
|
|
@ -1 +0,0 @@
|
|||
pub mod text;
|
|
@ -1,3 +0,0 @@
|
|||
pub mod selectable_text_m;
|
||||
pub mod selection;
|
||||
pub mod txt_pos;
|
|
@ -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>,
|
||||
}
|
|
@ -1,76 +1,31 @@
|
|||
// Adapted from https://github.com/cessen/ropey by Nathan Vegdahl, licensed under the MIT license
|
||||
|
||||
use crate::error::EdError::{FileOpenFailed, TextBufReadFailed};
|
||||
use crate::error::EdResult;
|
||||
use crate::error::OutOfBounds;
|
||||
use crate::mvc::ed_model::{Position, RawSelection};
|
||||
use crate::selection::validate_selection;
|
||||
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 crate::ui::text::caret_w_select::{CaretWSelect};
|
||||
use crate::ui::text::lines::{Lines, SelectableLines, MutSelectableLines};
|
||||
use bumpalo::collections::String as BumpString;
|
||||
use bumpalo::Bump;
|
||||
use ropey::Rope;
|
||||
use snafu::{ensure, OptionExt};
|
||||
use snafu::{ensure};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct TextBuffer {
|
||||
pub struct BigSelectableText {
|
||||
pub caret_w_select: CaretWSelect,
|
||||
pub text_rope: Rope,
|
||||
pub path_str: String,
|
||||
pub arena: Bump,
|
||||
}
|
||||
|
||||
impl TextBuffer {
|
||||
pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> {
|
||||
self.insert_str(caret_pos, &new_char.to_string())
|
||||
}
|
||||
impl BigSelectableText {
|
||||
|
||||
pub fn insert_str(&mut self, caret_pos: Position, new_str: &str) -> EdResult<()> {
|
||||
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<()> {
|
||||
fn check_bounds(&self, char_indx: usize) -> UIResult<()> {
|
||||
ensure!(
|
||||
char_indx <= self.text_rope.len_chars(),
|
||||
OutOfBounds {
|
||||
|
@ -83,46 +38,68 @@ impl TextBuffer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn line(&self, line_nr: usize) -> Option<&str> {
|
||||
if line_nr < self.nr_of_lines() {
|
||||
fn pos_to_char_indx(&self, pos: TextPos) -> usize {
|
||||
self.text_rope.line_to_char(pos.line) + pos.column
|
||||
}
|
||||
|
||||
fn char_indx_to_pos(&self, char_indx: usize) -> TextPos {
|
||||
let line = self.text_rope.char_to_line(char_indx);
|
||||
|
||||
let char_idx_line_start = self.pos_to_char_indx(TextPos { line, column: 0 });
|
||||
|
||||
let column = char_indx - char_idx_line_start;
|
||||
|
||||
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() {
|
||||
Some(line_str_ref)
|
||||
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);
|
||||
Some(arena_str_ref)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Ok(arena_str_ref)
|
||||
}
|
||||
|
||||
pub fn line_len(&self, line_nr: usize) -> Option<usize> {
|
||||
self.line(line_nr).map(|line| line.len())
|
||||
}
|
||||
|
||||
pub fn line_len_res(&self, line_nr: usize) -> EdResult<usize> {
|
||||
self.line_len(line_nr).context(OutOfBounds {
|
||||
index: line_nr,
|
||||
collection_name: "Rope",
|
||||
len: self.text_rope.len_lines(),
|
||||
})
|
||||
fn line_len(&self, line_nr: usize) -> UIResult<usize> {
|
||||
self.get_line(line_nr).map(|line| line.len())
|
||||
}
|
||||
|
||||
pub fn nr_of_lines(&self) -> usize {
|
||||
fn nr_of_lines(&self) -> usize {
|
||||
self.text_rope.len_lines()
|
||||
}
|
||||
|
||||
pub fn nr_of_chars(&self) -> usize {
|
||||
fn nr_of_chars(&self) -> usize {
|
||||
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
|
||||
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);
|
||||
|
||||
for line in self.text_rope.lines() {
|
||||
|
@ -131,40 +108,81 @@ impl TextBuffer {
|
|||
|
||||
lines
|
||||
}
|
||||
|
||||
fn pos_to_char_indx(&self, pos: Position) -> usize {
|
||||
self.text_rope.line_to_char(pos.line) + pos.column
|
||||
}
|
||||
|
||||
fn char_indx_to_pos(&self, char_indx: usize) -> Position {
|
||||
let line = self.text_rope.char_to_line(char_indx);
|
||||
impl SelectableLines for BigSelectableText {
|
||||
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)?;
|
||||
|
||||
let char_idx_line_start = self.pos_to_char_indx(Position { line, column: 0 });
|
||||
self.check_bounds(end_char_indx)?;
|
||||
|
||||
let column = char_indx - char_idx_line_start;
|
||||
let rope_slice = self.text_rope.slice(start_char_indx..end_char_indx);
|
||||
|
||||
Position { line, column }
|
||||
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 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 {
|
||||
fn last_text_pos(&self) -> TextPos {
|
||||
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 path_str = path_to_string(path);
|
||||
let arena = Bump::new();
|
||||
|
||||
Ok(TextBuffer {
|
||||
Ok(BigSelectableText {
|
||||
caret_w_select,
|
||||
text_rope,
|
||||
path_str,
|
||||
arena,
|
||||
|
@ -178,7 +196,7 @@ fn path_to_string(path: &Path) -> String {
|
|||
path_str
|
||||
}
|
||||
|
||||
fn rope_from_path(path: &Path) -> EdResult<Rope> {
|
||||
fn rope_from_path(path: &Path) -> UIResult<Rope> {
|
||||
match File::open(path) {
|
||||
Ok(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 {
|
||||
f.debug_struct("TextBuffer")
|
||||
f.debug_struct("BigSelectableText")
|
||||
.field("caret_w_select", &self.caret_w_select)
|
||||
.field("text_rope", &self.text_rope)
|
||||
.field("path_str", &self.path_str)
|
||||
.finish()
|
108
editor/src/ui/text/caret_w_select.rs
Normal file
108
editor/src/ui/text/caret_w_select.rs
Normal 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(())
|
||||
}
|
||||
|
||||
}
|
44
editor/src/ui/text/lines.rs
Normal file
44
editor/src/ui/text/lines.rs
Normal 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<()>;
|
||||
}
|
5
editor/src/ui/text/mod.rs
Normal file
5
editor/src/ui/text/mod.rs
Normal 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;
|
|
@ -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::Bump;
|
||||
use snafu::ensure;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RawSelection {
|
||||
pub start_pos: TxtPos,
|
||||
pub end_pos: TxtPos,
|
||||
pub start_pos: TextPos,
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub struct ValidSelection {
|
||||
pub selection: RawSelection,
|
||||
pub start_pos: TextPos,
|
||||
pub end_pos: TextPos,
|
||||
}
|
||||
|
||||
pub fn validate_selection(selection: RawSelection) -> EdResult<ValidSelection> {
|
||||
let RawSelection { start_pos, end_pos } = selection;
|
||||
pub fn validate_raw_sel(raw_sel: RawSelection) -> UIResult<ValidSelection> {
|
||||
validate_selection(raw_sel.start_pos, raw_sel.end_pos)
|
||||
}
|
||||
|
||||
pub fn validate_selection(start_pos: TextPos, end_pos: TextPos) -> UIResult<ValidSelection> {
|
||||
|
||||
ensure!(
|
||||
start_pos.line <= end_pos.line,
|
||||
|
@ -39,18 +66,22 @@ pub fn validate_selection(selection: RawSelection) -> EdResult<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>(
|
||||
raw_sel: RawSelection,
|
||||
text_buf: &TextBuffer,
|
||||
valid_sel: ValidSelection,
|
||||
lines: &dyn Lines,
|
||||
glyph_dim_rect: &Rect,
|
||||
arena: &'a Bump,
|
||||
) -> EdResult<BumpVec<'a, Rect>> {
|
||||
let valid_sel = validate_selection(raw_sel)?;
|
||||
let RawSelection { start_pos, end_pos } = valid_sel.selection;
|
||||
) -> UIResult<BumpVec<'a, Rect>> {
|
||||
let ValidSelection { start_pos, end_pos } = valid_sel;
|
||||
|
||||
let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena);
|
||||
|
||||
|
@ -73,7 +104,7 @@ pub fn create_selection_rects<'a>(
|
|||
Ok(all_rects)
|
||||
} else {
|
||||
// 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)
|
||||
- ((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;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -126,13 +157,14 @@ pub fn create_selection_rects<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod test_selection {
|
||||
use crate::error::{EdResult, OutOfBounds};
|
||||
use crate::mvc::ed_model::{Position, RawSelection};
|
||||
use crate::mvc::ed_update::{
|
||||
use crate::ui::ui_error::{UIResult, OutOfBounds};
|
||||
use crate::ui::text::text_pos::TextPos;
|
||||
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,
|
||||
};
|
||||
use crate::text_buffer::TextBuffer;
|
||||
use crate::vec_result::get_res;
|
||||
use crate::ui::text::big_selectable_text::BigSelectableText;
|
||||
use crate::ui::util::slice_get;
|
||||
use core::cmp::Ordering;
|
||||
use pest::Parser;
|
||||
use ropey::Rope;
|
||||
|
@ -146,14 +178,14 @@ pub mod test_selection {
|
|||
|
||||
// show selection and caret position as symbols in lines for easy testing
|
||||
pub fn convert_selection_to_dsl(
|
||||
raw_sel_opt: Option<RawSelection>,
|
||||
caret_pos: Position,
|
||||
selection_opt: Option<ValidSelection>,
|
||||
caret_pos: TextPos,
|
||||
lines: &mut [String],
|
||||
) -> EdResult<&[String]> {
|
||||
if let Some(raw_sel) = raw_sel_opt {
|
||||
) -> UIResult<&[String]> {
|
||||
if let Some(sel) = selection_opt {
|
||||
let mut to_insert = vec![
|
||||
(raw_sel.start_pos, '['),
|
||||
(raw_sel.end_pos, ']'),
|
||||
(sel.start_pos, '['),
|
||||
(sel.end_pos, ']'),
|
||||
(caret_pos, '|'),
|
||||
];
|
||||
let symbol_map: HashMap<char, usize> =
|
||||
|
@ -191,7 +223,7 @@ pub mod test_selection {
|
|||
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)?;
|
||||
line.insert(pos.column, insert_char);
|
||||
|
||||
|
@ -202,7 +234,7 @@ pub mod test_selection {
|
|||
fn get_mut_res<T>(
|
||||
index: usize,
|
||||
vec: &mut [T],
|
||||
) -> EdResult<&mut <usize as SliceIndex<[T]>>::Output> {
|
||||
) -> UIResult<&mut <usize as SliceIndex<[T]>>::Output> {
|
||||
let vec_len = vec.len();
|
||||
|
||||
let elt_ref = vec.get_mut(index).context(OutOfBounds {
|
||||
|
@ -252,7 +284,7 @@ pub mod test_selection {
|
|||
// Retrieve selection and position from formatted string
|
||||
pub fn convert_dsl_to_selection(
|
||||
lines: &[String],
|
||||
) -> Result<(Option<RawSelection>, Position), String> {
|
||||
) -> Result<(Option<ValidSelection>, TextPos), String> {
|
||||
let lines_str: String = lines.join("");
|
||||
|
||||
let parsed = LineParser::parse(Rule::linesWithSelect, &lines_str)
|
||||
|
@ -319,24 +351,28 @@ pub mod test_selection {
|
|||
|
||||
// Make sure return makes sense
|
||||
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() {
|
||||
Ok((None, caret_pos))
|
||||
} else if let Some((start_line, start_column)) = sel_start_opt {
|
||||
if let Some((end_line, end_column)) = sel_end_opt {
|
||||
Ok((
|
||||
Some(RawSelection {
|
||||
start_pos: Position {
|
||||
Ok(
|
||||
(
|
||||
Some(
|
||||
validate_selection(
|
||||
TextPos {
|
||||
line: start_line,
|
||||
column: start_column,
|
||||
},
|
||||
end_pos: Position {
|
||||
TextPos {
|
||||
line: end_line,
|
||||
column: end_column,
|
||||
},
|
||||
}),
|
||||
caret_pos,
|
||||
))
|
||||
).unwrap()
|
||||
),
|
||||
caret_pos
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Err("Selection end ']' was not found, but selection start '[' was. Bad input string.".to_owned())
|
||||
}
|
|
@ -2,27 +2,27 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TxtPos {
|
||||
pub struct TextPos {
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl Ord for TxtPos {
|
||||
impl Ord for TextPos {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(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> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TxtPos {
|
||||
impl PartialEq for TextPos {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(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
36
editor/src/ui/ui_error.rs
Normal 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>;
|
|
@ -1 +0,0 @@
|
|||
pub mod SelectableTextU;
|
|
@ -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
15
editor/src/ui/util.rs
Normal 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)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub fn is_newline(char_ref: &char) -> bool {
|
||||
let newline_codes = vec!['\u{d}', '\n'];
|
||||
|
||||
newline_codes.contains(char_ref)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue