Merge pull request #1212 from rtfeldman/tooltip

Type tooltip on selection
This commit is contained in:
Richard Feldman 2021-04-19 18:50:46 -04:00 committed by GitHub
commit 8975074518
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 225 additions and 40 deletions

View File

@ -37,12 +37,19 @@ pub struct EdModel<'a> {
pub has_focus: bool,
// Option<MarkNodeId>: MarkupNode that corresponds to caret position, Option because this MarkNodeId is only calculated when it needs to be used.
pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option<MarkNodeId>)>,
pub selected_expr2_tup: Option<(NodeId<Expr2>, MarkNodeId)>,
pub selected_expr_opt: Option<SelectedExpression>,
pub show_debug_view: bool,
// EdModel is dirty if it has changed since the previous render.
pub dirty: bool,
}
#[derive(Debug)]
pub struct SelectedExpression {
pub ast_node_id: NodeId<Expr2>,
pub mark_node_id: MarkNodeId,
pub type_str: String,
}
pub fn init_model<'a>(
code_str: &'a BumpString,
file_path: &'a Path,
@ -91,7 +98,7 @@ pub fn init_model<'a>(
glyph_dim_rect_opt: None,
has_focus: true,
caret_w_select_vec: NonEmpty::new((CaretWSelect::default(), None)),
selected_expr2_tup: None,
selected_expr_opt: None,
show_debug_view: false,
dirty: true,
})

View File

@ -9,6 +9,7 @@ use crate::editor::markup::nodes;
use crate::editor::markup::nodes::MarkupNode;
use crate::editor::mvc::app_update::InputOutcome;
use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_model::SelectedExpression;
use crate::editor::mvc::lookup_update::update_invalid_lookup;
use crate::editor::mvc::record_update::start_new_record;
use crate::editor::mvc::record_update::update_empty_record;
@ -47,7 +48,7 @@ impl<'a> EdModel<'a> {
caret_tup.0 = move_fun(&self.code_lines, caret_tup.0, modifiers)?;
caret_tup.1 = None;
}
self.selected_expr2_tup = None;
self.selected_expr_opt = None;
Ok(())
}
@ -147,8 +148,8 @@ impl<'a> EdModel<'a> {
// select all MarkupNodes that refer to specific ast node and its children.
pub fn select_expr(&mut self) -> EdResult<()> {
// include parent in selection if an `Expr2` was already selected
if let Some((_sel_expr2_id, mark_node_id)) = self.selected_expr2_tup {
let expr2_level_mark_node = self.markup_node_pool.get(mark_node_id);
if let Some(selected_expr) = &self.selected_expr_opt {
let expr2_level_mark_node = self.markup_node_pool.get(selected_expr.mark_node_id);
if let Some(parent_id) = expr2_level_mark_node.get_parent_id_opt() {
let parent_mark_node = self.markup_node_pool.get(parent_id);
@ -164,7 +165,11 @@ impl<'a> EdModel<'a> {
})?;
self.set_caret(expr_start_pos);
self.selected_expr2_tup = Some((ast_node_id, parent_id));
self.selected_expr_opt = Some(SelectedExpression {
ast_node_id,
mark_node_id: parent_id,
type_str: "Str".to_owned(), // TODO get this String from type inference
});
self.dirty = true;
}
@ -181,7 +186,11 @@ impl<'a> EdModel<'a> {
})?;
self.set_caret(expr_start_pos);
self.selected_expr2_tup = Some((ast_node_id, mark_node_id));
self.selected_expr_opt = Some(SelectedExpression {
ast_node_id,
mark_node_id,
type_str: "Str".to_owned(), // TODO get this String from type inference
});
self.dirty = true;
}
@ -226,32 +235,40 @@ impl<'a> EdModel<'a> {
}
fn replace_slected_expr_with_blank(&mut self) -> EdResult<()> {
if let Some((sel_expr2_id, mark_node_id)) = self.selected_expr2_tup {
let expr2_level_mark_node = self.markup_node_pool.get(mark_node_id);
let expr_mark_node_id_opt = if let Some(sel_expr) = &self.selected_expr_opt {
let expr2_level_mark_node = self.markup_node_pool.get(sel_expr.mark_node_id);
let blank_replacement = MarkupNode::Blank {
ast_node_id: sel_expr2_id,
ast_node_id: sel_expr.ast_node_id,
attributes: Attributes::new(),
syn_high_style: HighlightStyle::Blank,
parent_id_opt: expr2_level_mark_node.get_parent_id_opt(),
};
self.markup_node_pool
.replace_node(mark_node_id, blank_replacement);
.replace_node(sel_expr.mark_node_id, blank_replacement);
let active_selection = self.get_selection().context(MissingSelection {})?;
self.code_lines.del_selection(active_selection)?;
self.grid_node_map.del_selection(active_selection)?;
self.module.env.pool.set(sel_expr.ast_node_id, Expr2::Blank);
Some(sel_expr.mark_node_id)
} else {
None
};
// have to split the previous `if` up to prevent borrowing issues
if let Some(expr_mark_node_id) = expr_mark_node_id_opt {
let caret_pos = self.get_caret();
self.insert_between_line(
caret_pos.line,
caret_pos.column,
nodes::BLANK_PLACEHOLDER,
mark_node_id,
expr_mark_node_id,
)?;
self.module.env.pool.set(sel_expr2_id, Expr2::Blank)
}
self.set_sel_none();
@ -352,7 +369,7 @@ impl<'a> SelectableLines for EdModel<'a> {
fn set_sel_none(&mut self) {
self.caret_w_select_vec.first_mut().0.selection_opt = None;
self.selected_expr2_tup = None;
self.selected_expr_opt = None;
}
fn set_caret_w_sel(&mut self, caret_w_sel: CaretWSelect) {

View File

@ -1,6 +1,7 @@
use super::ed_model::EdModel;
use crate::editor::config::Config;
use crate::editor::ed_error::EdResult;
use crate::editor::mvc::ed_model::SelectedExpression;
use crate::editor::render_ast::build_code_graphics;
use crate::editor::render_debug::build_debug_graphics;
use crate::graphics::primitives::rect::Rect;
@ -8,6 +9,7 @@ use crate::ui::text::caret_w_select::make_caret_rect;
use crate::ui::text::caret_w_select::make_selection_rect;
use crate::ui::text::caret_w_select::CaretWSelect;
use crate::ui::text::selection::Selection;
use crate::ui::tooltip::ToolTip;
use crate::ui::ui_error::MissingGlyphDims;
use cgmath::Vector2;
use snafu::OptionExt;
@ -19,6 +21,32 @@ pub struct RenderedWgpu {
pub rects: Vec<Rect>,
}
impl RenderedWgpu {
pub fn new() -> Self {
Self {
text_sections: Vec::new(),
rects: Vec::new(),
}
}
pub fn add_text(&mut self, new_text_section: glyph_brush::OwnedSection) {
self.text_sections.push(new_text_section);
}
pub fn add_rect(&mut self, new_rect: Rect) {
self.rects.push(new_rect);
}
pub fn add_rects(&mut self, new_rects: Vec<Rect>) {
self.rects.extend(new_rects);
}
pub fn extend(&mut self, rendered_wgpu: RenderedWgpu) {
self.text_sections.extend(rendered_wgpu.text_sections);
self.rects.extend(rendered_wgpu.rects);
}
}
// create text and rectangles based on EdModel's markup_root
pub fn model_to_wgpu<'a>(
ed_model: &'a mut EdModel,
@ -28,9 +56,9 @@ pub fn model_to_wgpu<'a>(
) -> EdResult<RenderedWgpu> {
let glyph_dim_rect = ed_model.glyph_dim_rect_opt.context(MissingGlyphDims {})?;
let mut all_text_sections = Vec::new();
let mut all_rendered = RenderedWgpu::new();
let (code_section, mut rects) = build_code_graphics(
let rendered_code_graphics = build_code_graphics(
ed_model.markup_node_pool.get(ed_model.markup_root_id),
size,
txt_coords,
@ -39,7 +67,7 @@ pub fn model_to_wgpu<'a>(
&ed_model.markup_node_pool,
)?;
all_text_sections.push(code_section);
all_rendered.extend(rendered_code_graphics);
let caret_w_sel_vec = ed_model
.caret_w_select_vec
@ -47,28 +75,31 @@ pub fn model_to_wgpu<'a>(
.map(|(caret_w_sel, _)| *caret_w_sel)
.collect();
let mut sel_rects =
build_selection_graphics(caret_w_sel_vec, txt_coords, config, glyph_dim_rect)?;
let rendered_selection = build_selection_graphics(
caret_w_sel_vec,
&ed_model.selected_expr_opt,
txt_coords,
config,
glyph_dim_rect,
)?;
rects.append(&mut sel_rects);
all_rendered.extend(rendered_selection);
if ed_model.show_debug_view {
all_text_sections.push(build_debug_graphics(size, txt_coords, config, ed_model)?);
all_rendered.add_text(build_debug_graphics(size, txt_coords, config, ed_model)?);
}
Ok(RenderedWgpu {
text_sections: all_text_sections,
rects,
})
Ok(all_rendered)
}
pub fn build_selection_graphics(
caret_w_select_vec: Vec<CaretWSelect>,
selected_expr_opt: &Option<SelectedExpression>,
txt_coords: Vector2<f32>,
config: &Config,
glyph_dim_rect: Rect,
) -> EdResult<Vec<Rect>> {
let mut rects = Vec::new();
) -> EdResult<RenderedWgpu> {
let mut all_rendered = RenderedWgpu::new();
let char_width = glyph_dim_rect.width;
let char_height = glyph_dim_rect.height;
@ -90,16 +121,31 @@ pub fn build_selection_graphics(
let width =
((end_pos.column as f32) * char_width) - ((start_pos.column as f32) * char_width);
rects.push(make_selection_rect(
all_rendered.add_rect(make_selection_rect(
sel_rect_x,
sel_rect_y,
width,
&glyph_dim_rect,
&config.ed_theme.ui_theme,
));
// render tooltip showing type
if let Some(selected_expr) = selected_expr_opt {
let tooltip = ToolTip {
position_x: sel_rect_x,
position_y: sel_rect_y - glyph_dim_rect.height,
text: selected_expr.type_str.clone(),
};
let (tip_rect, tip_text) =
tooltip.render_tooltip(&glyph_dim_rect, &config.ed_theme.ui_theme);
all_rendered.add_rect(tip_rect);
all_rendered.add_text(tip_text);
}
}
rects.push(make_caret_rect(
all_rendered.add_rect(make_caret_rect(
top_left_x,
top_left_y,
&glyph_dim_rect,
@ -107,5 +153,5 @@ pub fn build_selection_graphics(
));
}
Ok(rects)
Ok(all_rendered)
}

View File

@ -1,4 +1,5 @@
use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER};
use crate::editor::mvc::ed_view::RenderedWgpu;
use crate::editor::slow_pool::SlowPool;
use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get};
use crate::graphics::primitives::rect::Rect;
@ -15,9 +16,10 @@ pub fn build_code_graphics<'a>(
config: &Config,
glyph_dim_rect: Rect,
markup_node_pool: &'a SlowPool,
) -> EdResult<(glyph_brush::OwnedSection, Vec<Rect>)> {
) -> EdResult<RenderedWgpu> {
let area_bounds = (size.width as f32, size.height as f32);
let layout = wgpu_glyph::Layout::default().h_align(wgpu_glyph::HorizontalAlign::Left);
let mut rendered_wgpu = RenderedWgpu::new();
let (glyph_text_vec, rects) = markup_to_wgpu(
markup_node,
@ -30,10 +32,17 @@ pub fn build_code_graphics<'a>(
markup_node_pool,
)?;
let section =
gr_text::section_from_glyph_text(glyph_text_vec, txt_coords.into(), area_bounds, layout);
let section = gr_text::owned_section_from_glyph_texts(
glyph_text_vec,
txt_coords.into(),
area_bounds,
layout,
);
Ok((section, rects))
rendered_wgpu.add_rects(rects);
rendered_wgpu.add_text(section);
Ok(rendered_wgpu)
}
struct CodeStyle<'a> {

View File

@ -47,7 +47,7 @@ pub fn build_debug_graphics(
.with_color(colors::to_slice(from_hsb(211, 80, 100)))
.with_scale(config.code_font_size);
let section = gr_text::section_from_glyph_text(
let section = gr_text::owned_section_from_glyph_texts(
vec![
grid_node_map_text,
code_lines_text,

View File

@ -1 +1 @@
pub const CODE_TXT_XY: (f32, f32) = (30.0, 30.0);
pub const CODE_TXT_XY: (f32, f32) = (40.0, 60.0);

View File

@ -16,7 +16,7 @@ pub struct EdTheme {
impl Default for EdTheme {
fn default() -> Self {
Self {
background: from_hsb(240, 10, 19),
background: from_hsb(240, 10, 19), // #2C2C35
syntax_high_map: default_highlight_map(),
ui_theme: UITheme::default(),
}

View File

@ -7,6 +7,7 @@ use crate::graphics::colors::RgbaTup;
use crate::graphics::style::DEFAULT_FONT_SIZE;
use ab_glyph::{FontArc, Glyph, InvalidFont};
use cgmath::{Vector2, Vector4};
use glyph_brush::OwnedSection;
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section};
#[derive(Debug)]
@ -83,7 +84,24 @@ fn section_from_text<'a>(
)
}
pub fn section_from_glyph_text(
pub fn owned_section_from_text(
text: &Text,
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> OwnedSection {
OwnedSection {
screen_position: text.position.into(),
bounds: text.area_bounds.into(),
layout,
..OwnedSection::default()
}
.add_text(
glyph_brush::OwnedText::new(text.text)
.with_color(Vector4::from(text.color))
.with_scale(text.size),
)
}
pub fn owned_section_from_glyph_texts(
text: Vec<glyph_brush::OwnedText>,
screen_position: (f32, f32),
area_bounds: (f32, f32),

View File

@ -1,4 +1,5 @@
pub mod text;
pub mod theme;
pub mod tooltip;
pub mod ui_error;
pub mod util;

View File

@ -1,4 +1,4 @@
use gr_colors::{from_hsba, RgbaTup};
use gr_colors::{from_hsb, from_hsba, RgbaTup};
use serde::{Deserialize, Serialize};
use crate::graphics::colors as gr_colors;
@ -13,6 +13,9 @@ pub struct UITheme {
pub text: RgbaTup,
pub caret: RgbaTup,
pub select_highlight: RgbaTup,
pub tooltip_bg: RgbaTup,
pub tooltip_text: RgbaTup,
pub default_font_size: f32,
}
impl Default for UITheme {
@ -23,6 +26,9 @@ impl Default for UITheme {
text: gr_colors::WHITE,
caret: gr_colors::WHITE,
select_highlight: from_hsba(240, 55, 100, 0.3),
tooltip_bg: from_hsb(240, 60, 50),
tooltip_text: gr_colors::WHITE,
default_font_size: 30.0,
}
}
}

81
editor/src/ui/tooltip.rs Normal file
View File

@ -0,0 +1,81 @@
use crate::graphics::primitives::rect::Rect;
use crate::graphics::primitives::text as gr_text;
use crate::graphics::primitives::text::layout_from_text;
use crate::graphics::primitives::text::Text;
use crate::ui::theme::UITheme;
pub struct ToolTip {
pub position_x: f32,
pub position_y: f32,
pub text: String,
}
impl ToolTip {
fn make_tooltip_rect(
&self,
width: f32,
height: f32,
height_padding: f32,
y_margin: f32,
ui_theme: &UITheme,
) -> Rect {
Rect {
top_left_coords: (
self.position_x,
self.position_y - (height_padding + y_margin),
)
.into(),
height: height + height_padding,
width,
color: ui_theme.tooltip_bg,
}
}
fn make_tooltip_text<'a>(
&'a self,
x_offset: f32,
y_offset: f32,
y_margin: f32,
ui_theme: &UITheme,
) -> Text<'a> {
Text {
position: (
self.position_x + x_offset,
self.position_y - (y_offset + y_margin),
)
.into(),
color: ui_theme.tooltip_text,
text: &self.text,
size: ui_theme.default_font_size,
..Default::default()
}
}
pub fn render_tooltip(
&self,
glyph_dim_rect: &Rect,
ui_theme: &UITheme,
) -> (Rect, glyph_brush::OwnedSection) {
let width_padding = glyph_dim_rect.height / 1.3;
let height_padding = width_padding / 1.3;
let text_x_offset = width_padding / 2.0;
let text_y_offset = height_padding / 2.0;
let y_margin = glyph_dim_rect.height / 4.0;
let text = self.make_tooltip_text(text_x_offset, text_y_offset, y_margin, ui_theme);
let text_layout = layout_from_text(&text);
let text_section = gr_text::owned_section_from_text(&text, text_layout);
let rect = self.make_tooltip_rect(
glyph_dim_rect.width * (text.text.len() as f32) + width_padding,
glyph_dim_rect.height,
height_padding,
y_margin,
ui_theme,
);
(rect, text_section)
}
}