diff --git a/Cargo.lock b/Cargo.lock index aabd2bbf8b..ed51ac0659 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2649,6 +2649,7 @@ dependencies = [ "indoc", "inkwell", "inlinable_string", + "itertools", "libc", "log", "maplit", diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 4e1e639c8f..160be3ae00 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -62,6 +62,7 @@ env_logger = "0.7" futures = "0.3" wgpu_glyph = "0.10" cgmath = "0.17.0" +itertools = "0.9.0" [dependencies.bytemuck] version = "1.4" diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index d1f163b1cf..ecc9f8e6a4 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -73,8 +73,12 @@ These are potentially inspirational resources for the editor's design. * Voice input: * Good for accessibility. * https://www.youtube.com/watch?v=Ffa3cXM7bjc is interesting for inspiration. - * Describe actions instead of using complicated shortcuts. * Could be efficient way to communicate with smart assistant. + * Describe actions to execute them, examples: + * Add latest datetime package to dependencies. + * Generate unit test for this function. + * Show edit history for this function. + ## General Thoughts/Ideas diff --git a/editor/src/colors.rs b/editor/src/colors.rs new file mode 100644 index 0000000000..304ccd7f5e --- /dev/null +++ b/editor/src/colors.rs @@ -0,0 +1,2 @@ + +pub const WHITE: [f32; 3] = [1.0, 1.0, 1.0]; \ No newline at end of file diff --git a/editor/src/error.rs b/editor/src/error.rs new file mode 100644 index 0000000000..b2138187a5 --- /dev/null +++ b/editor/src/error.rs @@ -0,0 +1,12 @@ +use std::{error::Error, fmt}; + +#[derive(Debug)] +pub struct OutOfBounds; + +impl Error for OutOfBounds {} + +impl fmt::Display for OutOfBounds { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TODO proper error") + } +} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index e02e01de99..27692e4b50 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -19,6 +19,8 @@ use std::path::Path; use winit::event; use winit::event::{Event, ModifiersState}; use winit::event_loop::ControlFlow; +use error::{OutOfBounds}; +use vec_result::{get_res}; pub mod ast; mod buffer; @@ -34,6 +36,9 @@ pub mod text; mod types; mod util; mod vertex; +mod colors; +pub mod error; +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. @@ -107,7 +112,7 @@ fn run_event_loop() -> Result<(), Box> { let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let is_animating = true; - let mut text_state = "A".to_owned(); + let mut text_state = "".to_owned();//String::new(); let mut keyboard_modifiers = ModifiersState::empty(); // Render loop @@ -201,29 +206,35 @@ fn run_event_loop() -> Result<(), Box> { &mut glyph_brush, ); - if !glyph_bounds_rects.is_empty() { - let rect_buffers = create_rect_buffers( - &gpu_device, - &mut encoder, - &glyph_bounds_rects, - ); + let selection_rects_res = create_selection_rects(1, 10, 2, 10, &glyph_bounds_rects); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: &frame.view, - resolve_target: None, - ops: wgpu::Operations::default(), - }], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&rect_pipeline); - render_pass.set_bind_group(0, &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); - - drop(render_pass); + match selection_rects_res { + Ok(selection_rects) => + if !selection_rects.is_empty() { + let rect_buffers = create_rect_buffers( + &gpu_device, + &mut encoder, + &selection_rects, + ); + + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + ops: wgpu::Operations::default(), + }], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&rect_pipeline); + render_pass.set_bind_group(0, &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); + + drop(render_pass); + }, + Err(e) => println!("{:?}", e) //TODO draw error text on screen } // draw all text @@ -257,6 +268,83 @@ fn run_event_loop() -> Result<(), Box> { }) } + +fn create_selection_rects( + start_line: usize, + pos_in_start_line: usize, + stop_line: usize, + pos_in_stop_line: usize, + glyph_bound_rects: &Vec> +) -> Result, OutOfBounds> { + //TODO assert start_line <= stop_line, if start_line == stop_line => pos_in_start_line <= pos_in_stop_line + + let mut all_rects = Vec::new(); + + if start_line == stop_line { + let start_glyph_rect = + get_res( + pos_in_start_line, + get_res(start_line, glyph_bound_rects)? + )?; + + let stop_glyph_rect = + get_res( + pos_in_stop_line, + get_res(stop_line, glyph_bound_rects)? + )?; + + let top_left_coords = + start_glyph_rect.top_left_coords; + + let height = start_glyph_rect.height; + let width = (stop_glyph_rect.top_left_coords.x - start_glyph_rect.top_left_coords.x) + stop_glyph_rect.width; + + all_rects.push( + Rect { + top_left_coords, + width, + height, + color: colors::WHITE + } + ); + + Ok(all_rects) + } else { + let start_line = get_res(start_line, glyph_bound_rects)?; + + let start_glyph_rect = + get_res( + pos_in_start_line, + start_line + )?; + + let stop_glyph_rect = + get_res( + start_line.len() - 1, + start_line + )?; + + let top_left_coords = + start_glyph_rect.top_left_coords; + + let height = start_glyph_rect.height; + let width = (stop_glyph_rect.top_left_coords.x - start_glyph_rect.top_left_coords.x) + stop_glyph_rect.width; + + all_rects.push( + Rect { + top_left_coords, + width, + height, + color: colors::WHITE + } + ); + + //TODO loop rects if necessary and stop line rect + + Ok(all_rects) + } +} + fn make_rect_pipeline( gpu_device: &wgpu::Device, swap_chain_descr: &wgpu::SwapChainDescriptor, @@ -321,11 +409,12 @@ fn create_render_pipeline( }) } +// returns bounding boxes for every glyph fn queue_all_text( size: &winit::dpi::PhysicalSize, text_state: &str, glyph_brush: &mut wgpu_glyph::GlyphBrush<()>, -) -> Vec { +) -> Vec> { let area_bounds = (size.width as f32, size.height as f32).into(); let main_label = Text { diff --git a/editor/src/text.rs b/editor/src/text.rs index d73a7ff24a..faef0d0817 100644 --- a/editor/src/text.rs +++ b/editor/src/text.rs @@ -1,10 +1,11 @@ // Adapted from https://github.com/sotrh/learn-wgpu // by Benjamin Hansen, licensed under the MIT license -use ab_glyph::{FontArc, InvalidFont}; +use ab_glyph::{FontArc, InvalidFont, Glyph}; use cgmath::{Vector2, Vector4}; use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section}; use crate::rect::Rect; +use itertools::Itertools; #[derive(Debug)] pub struct Text { @@ -31,7 +32,8 @@ impl Default for Text { } } -pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec { +// returns bounding boxes for every glyph +pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec> { let layout = wgpu_glyph::Layout::default().h_align(if text.centered { wgpu_glyph::HorizontalAlign::Center } else { @@ -57,17 +59,31 @@ pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec f32 { + let height = glyph.scale.y; + + glyph.position.y - height * 0.75 +} + +pub fn glyph_width(glyph: &Glyph) -> f32 { + glyph.scale.x * 0.5 } pub fn build_glyph_brush( diff --git a/editor/src/vec_result.rs b/editor/src/vec_result.rs new file mode 100644 index 0000000000..3e634e5d18 --- /dev/null +++ b/editor/src/vec_result.rs @@ -0,0 +1,14 @@ + +use crate::error::{OutOfBounds}; +use std::slice::SliceIndex; + +// replace vec methods that return Option with Result and proper Error + +pub fn get_res(indx: usize, vec: &Vec) -> Result<&>::Output, OutOfBounds> { + match vec.get(indx) { + Some(elt) => + Ok(elt), + None => + Err(OutOfBounds {}) + } +} \ No newline at end of file