diff --git a/editor/src/error.rs b/editor/src/error.rs index 9ad1f3e7f7..c3d2f53cbc 100644 --- a/editor/src/error.rs +++ b/editor/src/error.rs @@ -24,6 +24,8 @@ pub enum EdError { err_msg: String, backtrace: Backtrace, }, + #[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))] + MissingGlyphDims {}, } pub type EdResult = std::result::Result; diff --git a/editor/src/graphics/colors.rs b/editor/src/graphics/colors.rs index 063f215776..cce601fe75 100644 --- a/editor/src/graphics/colors.rs +++ b/editor/src/graphics/colors.rs @@ -1 +1,5 @@ pub const WHITE: [f32; 3] = [1.0, 1.0, 1.0]; +pub const TXT_COLOR: [f32; 4] = [0.4666, 0.2, 1.0, 1.0]; +pub const CODE_COLOR: [f32; 4] = [0.0, 0.05, 0.46, 1.0]; +pub const CARET_COLOR: [f32; 3] = WHITE; +pub const SELECT_COLOR: [f32; 3] = [0.45, 0.61, 1.0]; diff --git a/editor/src/graphics/mod.rs b/editor/src/graphics/mod.rs index 097746ef41..e5bb233ee6 100644 --- a/editor/src/graphics/mod.rs +++ b/editor/src/graphics/mod.rs @@ -1,3 +1,4 @@ pub mod colors; +pub mod style; pub mod lowlevel; pub mod primitives; diff --git a/editor/src/graphics/primitives/text.rs b/editor/src/graphics/primitives/text.rs index 379a754e4e..82da5571df 100644 --- a/editor/src/graphics/primitives/text.rs +++ b/editor/src/graphics/primitives/text.rs @@ -2,6 +2,8 @@ // by Benjamin Hansen, licensed under the MIT license use crate::graphics::primitives::rect::Rect; +use crate::graphics::colors::CODE_COLOR; +use crate::graphics::style::CODE_FONT_SIZE; use ab_glyph::{FontArc, Glyph, InvalidFont}; use cgmath::{Vector2, Vector4}; use itertools::Itertools; @@ -25,22 +27,47 @@ impl Default for Text { area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), color: (1.0, 1.0, 1.0, 1.0).into(), text: String::new(), - size: 16.0, + size: CODE_FONT_SIZE, visible: true, centered: false, } } } -// 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 { +// necessary to get dimensions for caret +pub fn example_code_glyph_rect(glyph_brush: &mut GlyphBrush<()>) -> Rect { + let code_text = Text { + position: (30.0, 90.0).into(),//TODO 30.0 90.0 should be an arg + area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), + color: CODE_COLOR.into(), + text: "a".to_owned(), + size: CODE_FONT_SIZE, + ..Default::default() + }; + + let layout = layout_from_text(&code_text); + + let section = section_from_text(&code_text, layout); + + let mut glyph_section_iter = glyph_brush.glyphs_custom_layout(section, &layout); + + if let Some(glyph) = glyph_section_iter.next() { + glyph_to_rect(glyph) + } else { + unreachable!(); + } +} + +fn layout_from_text(text: &Text) -> wgpu_glyph::Layout { + wgpu_glyph::Layout::default().h_align(if text.centered { wgpu_glyph::HorizontalAlign::Center } else { wgpu_glyph::HorizontalAlign::Left - }); + }) +} - let section = Section { +fn section_from_text(text: &Text, layout: wgpu_glyph::Layout) -> wgpu_glyph::Section { + Section { screen_position: text.position.into(), bounds: text.area_bounds.into(), layout, @@ -50,27 +77,21 @@ pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec) -> Vec> { + let layout = layout_from_text(text); + + let section = section_from_text(text, layout); glyph_brush.queue(section.clone()); let glyph_section_iter = glyph_brush.glyphs_custom_layout(section, &layout); glyph_section_iter - .map(|section_glyph| { - let position = section_glyph.glyph.position; - let px_scale = section_glyph.glyph.scale; - let width = glyph_width(§ion_glyph.glyph); - let height = px_scale.y; - let top_y = glyph_top_y(§ion_glyph.glyph); - - Rect { - top_left_coords: [position.x, top_y].into(), - width, - height, - color: [1.0, 1.0, 1.0], - } - }) + .map(glyph_to_rect) .group_by(|rect| rect.top_left_coords.y) .into_iter() .map(|(_y_coord, rect_group)| { @@ -94,6 +115,21 @@ pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec Rect { + let position = glyph.glyph.position; + let px_scale = glyph.glyph.scale; + let width = glyph_width(&glyph.glyph); + let height = px_scale.y; + let top_y = glyph_top_y(&glyph.glyph); + + Rect { + top_left_coords: [position.x, top_y].into(), + width, + height, + color: [1.0, 1.0, 1.0], + } +} + pub fn glyph_top_y(glyph: &Glyph) -> f32 { let height = glyph.scale.y; diff --git a/editor/src/graphics/style.rs b/editor/src/graphics/style.rs new file mode 100644 index 0000000000..7c4cbc15c2 --- /dev/null +++ b/editor/src/graphics/style.rs @@ -0,0 +1 @@ +pub const CODE_FONT_SIZE: f32 = 40.0; \ No newline at end of file diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 8835f556bc..df071a8743 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -8,17 +8,23 @@ // See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ -use crate::error::print_err; +use crate::error::{print_err, EdResult}; +use crate::error::EdError::MissingGlyphDims; +use crate::vec_result::{get_res}; use crate::graphics::lowlevel::buffer::create_rect_buffers; use crate::graphics::lowlevel::ortho::{init_ortho, update_ortho_buffer, OrthoResources}; use crate::graphics::lowlevel::vertex::Vertex; use crate::graphics::primitives::rect::Rect; -use crate::graphics::primitives::text::{build_glyph_brush, queue_text_draw, Text}; +use crate::graphics::primitives::text::{build_glyph_brush, queue_text_draw, Text, example_code_glyph_rect}; +use crate::graphics::colors::{TXT_COLOR, CODE_COLOR, CARET_COLOR}; +use crate::graphics::style::{CODE_FONT_SIZE}; use crate::selection::create_selection_rects; use crate::tea::{model, update}; +use crate::tea::model::Model; use crate::util::is_newline; +use model::{Position}; use bumpalo::Bump; -use model::Position; +use bumpalo::collections::Vec as BumpVec; use std::error::Error; use std::io; use std::path::Path; @@ -109,6 +115,7 @@ fn run_event_loop() -> Result<(), Box> { let is_animating = true; let mut ed_model = model::init_model(); + ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush)); let mut keyboard_modifiers = ModifiersState::empty(); let arena = Bump::new(); @@ -202,36 +209,22 @@ fn run_event_loop() -> Result<(), Box> { let glyph_bounds_rects = queue_all_text(&size, &ed_model.lines, ed_model.caret_pos, &mut glyph_brush); - if let Some(selection) = ed_model.selection_opt { - let selection_rects_res = - create_selection_rects(selection, &glyph_bounds_rects, &arena); - - 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 = begin_render_pass(&mut encoder, &frame.view); - - 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); - } - } - Err(e) => { - begin_render_pass(&mut encoder, &frame.view); - print_err(&e) //TODO draw error text on screen - } - } - } else { - begin_render_pass(&mut encoder, &frame.view); + // TODO too much args group them in Struct + match draw_all_rects( + &ed_model, + &glyph_bounds_rects, + &arena, + &mut encoder, + &frame.view, + &gpu_device, + &rect_pipeline, + &ortho + ) { + Ok(()) => (), + Err(e) => { + print_err(&e); + begin_render_pass(&mut encoder, &frame.view); + }, } // draw all text @@ -265,6 +258,47 @@ fn run_event_loop() -> Result<(), Box> { }) } +fn draw_all_rects( + ed_model: &Model, + glyph_bounds_rects: &Vec>, + arena: &Bump, + encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_pipeline: &wgpu::RenderPipeline, + ortho: &OrthoResources +) -> EdResult<()> { + let mut all_rects: BumpVec = BumpVec::new_in(arena); + + if let Some(selection) = ed_model.selection_opt { + let mut selection_rects = + create_selection_rects(selection, glyph_bounds_rects, &arena)?; + + all_rects.append(&mut selection_rects); + } + + all_rects.push( + make_caret_rect(ed_model.caret_pos, glyph_bounds_rects, ed_model.glyph_dim_rect_opt)? + ); + + 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_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); + + Ok(()) +} + fn begin_render_pass<'a>( encoder: &'a mut CommandEncoder, texture_view: &'a TextureView, @@ -287,6 +321,43 @@ fn begin_render_pass<'a>( }) } +fn make_caret_rect( + caret_pos: Position, + glyph_bound_rects: &[Vec], + glyph_dim_rect_opt: Option +) -> EdResult { + let mut glyph_rect = if let Some(glyph_dim_rect) = glyph_dim_rect_opt { + glyph_dim_rect + } else { + return Err(MissingGlyphDims {}); + }; + + let caret_y = glyph_rect.top_left_coords.y + (caret_pos.line as f32) * glyph_rect.height; + + if caret_pos.column > 0 && glyph_bound_rects.len() > caret_pos.line { + let indx = caret_pos.column - 1; + let glyph_rect_line = get_res(caret_pos.line, glyph_bound_rects)?; + + if glyph_rect_line.len() > indx { + glyph_rect = *get_res(indx, glyph_rect_line)?; + } + }; + + let caret_x = + if caret_pos.column == 0 { + glyph_rect.top_left_coords.x + } else { + glyph_rect.top_left_coords.x + glyph_rect.width + }; + + Ok(Rect { + top_left_coords: (caret_x, caret_y).into(), + height: glyph_rect.height, + width: 3.0, + color: CARET_COLOR + }) +} + fn make_rect_pipeline( gpu_device: &wgpu::Device, swap_chain_descr: &wgpu::SwapChainDescriptor, @@ -363,25 +434,25 @@ fn queue_all_text( let main_label = Text { position: (30.0, 30.0).into(), area_bounds, - color: (0.4666, 0.2, 1.0, 1.0).into(), + color: TXT_COLOR.into(), text: String::from("Enter some text:"), - size: 40.0, + size: CODE_FONT_SIZE, ..Default::default() }; let code_text = Text { - position: (30.0, 90.0).into(), + position: (30.0, 90.0).into(),//TODO 30 90 should be an arg area_bounds, - color: (0.0, 0.05, 0.46, 1.0).into(), + color: CODE_COLOR.into(), text: lines.join(""), - size: 40.0, + size: CODE_FONT_SIZE, ..Default::default() }; let caret_pos_label = Text { position: (30.0, 530.0).into(), area_bounds, - color: (0.4666, 0.2, 1.0, 1.0).into(), + color: TXT_COLOR.into(), text: format!("Ln {}, Col {}", caret_pos.line, caret_pos.column), size: 30.0, ..Default::default() diff --git a/editor/src/selection.rs b/editor/src/selection.rs index bd5228d3e4..42cf3ff323 100644 --- a/editor/src/selection.rs +++ b/editor/src/selection.rs @@ -72,7 +72,7 @@ pub fn create_selection_rects<'a>( top_left_coords, width, height, - color: colors::WHITE, + color: colors::SELECT_COLOR, }); Ok(all_rects) @@ -95,7 +95,7 @@ pub fn create_selection_rects<'a>( top_left_coords, width, height, - color: colors::WHITE, + color: colors::SELECT_COLOR, }); //middle lines @@ -120,7 +120,7 @@ pub fn create_selection_rects<'a>( top_left_coords, width, height, - color: colors::WHITE, + color: colors::SELECT_COLOR, }); } @@ -143,7 +143,7 @@ pub fn create_selection_rects<'a>( top_left_coords, width, height, - color: colors::WHITE, + color: colors::SELECT_COLOR, }); } diff --git a/editor/src/tea/model.rs b/editor/src/tea/model.rs index d7a314c03a..d0edb26e02 100644 --- a/editor/src/tea/model.rs +++ b/editor/src/tea/model.rs @@ -1,10 +1,12 @@ use std::cmp::Ordering; +use crate::graphics::primitives::rect::Rect; #[derive(Debug)] pub struct Model { pub lines: Vec, pub caret_pos: Position, pub selection_opt: Option, + pub glyph_dim_rect_opt: Option } pub fn init_model() -> Model { @@ -12,6 +14,7 @@ pub fn init_model() -> Model { lines: vec![String::new()], caret_pos: Position { line: 0, column: 0 }, selection_opt: None, + glyph_dim_rect_opt: None } }