use crate::background::*;
use crate::scrollshadow::*;
use crate::scrollview::*;
use crate::textbuffer::*;
use crate::textcursor::*;
use crate::tokentype::*;
use wrflib::*;
static SHADER_INDENT_LINES: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
uniform indent_sel: float;
instance color: vec4;
instance indent_id: float;
fn pixel() -> vec4 {
let col = color;
let thickness = 0.8 + dpi_dilate * 0.5;
if indent_id == indent_sel {
col *= vec4(1., 1., 1., 1.);
thickness *= 1.3;
}
else {
col *= vec4(0.75, 0.75, 0.75, 0.75);
}
let df = Df::viewport(pos * rect_size);
df.move_to(1., -1.);
df.line_to(1., rect_size.y + 1.);
return df.stroke(col, thickness);
}"#
),
],
..Shader::DEFAULT
};
static SHADER_CURSOR: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
uniform blink: float;
instance color: vec4;
fn pixel() -> vec4 {
if blink<0.5 {
return vec4(color.rgb * color.a, color.a);
}
else {
return vec4(0., 0., 0., 0.);
}
}"#
),
],
..Shader::DEFAULT
};
static SHADER_SELECTION: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
instance color: vec4;
instance prev_x: float;
instance prev_w: float;
instance next_x: float;
instance next_w: float;
const gloopiness: float = 8.;
const border_radius: float = 2.;
fn vertex() -> vec4 { // custom vertex shader because we widen the draweable area a bit for the gloopiness
let shift: vec2 = -draw_scroll;
let clipped: vec2 = clamp(
geom * vec2(rect_size.x + 16., rect_size.y) + rect_pos + shift - vec2(8., 0.),
draw_clip.xy,
draw_clip.zw
);
pos = (clipped - shift - rect_pos) / rect_size;
return camera_projection * (camera_view *
vec4(clipped.x, clipped.y, draw_depth + draw_zbias, 1.));
}
fn pixel() -> vec4 {
let df = Df::viewport(pos * rect_size);
df.box(0., 0., rect_size.x, rect_size.y, border_radius);
if prev_w > 0. {
df.box(prev_x, -rect_size.y, prev_w, rect_size.y, border_radius);
df.gloop(gloopiness);
}
if next_w > 0. {
df.box(next_x, rect_size.y, next_w, rect_size.y, border_radius);
df.gloop(gloopiness);
}
//df_shape *= cos(pos.x*8.)+cos(pos.y*16.);
return df.fill(color);
}"#
),
],
..Shader::DEFAULT
};
static SHADER_PAREN_PAIR: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
instance color: vec4;
fn pixel() -> vec4 {
let df = Df::viewport(pos * rect_size);
df.rect(0., rect_size.y - 1.5 - dpi_dilate, rect_size.x, 1.5 + dpi_dilate);
return df.fill(color);
}"#
),
],
..Shader::DEFAULT
};
static SHADER_SEARCH_MARKER: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
instance color: vec4;
fn pixel() -> vec4 {
let pos2 = vec2(pos.x, pos.y + 0.03 * sin(pos.x * rect_size.x));
let df = Df::viewport(pos2 * rect_size);
df.move_to(0., rect_size.y - 1.);
df.line_to(rect_size.x, rect_size.y - 1.);
return df.stroke(vec4(171.0/255.0,99.0/255.0,99.0/255.0,1.0), 0.8);
}"#
),
],
..Shader::DEFAULT
};
static SHADER_MESSAGE_MARKER: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
QuadIns::SHADER,
code_fragment!(
r#"
instance color: vec4;
fn pixel() -> vec4 {
let pos2 = vec2(pos.x, pos.y + 0.03 * sin(pos.x * rect_size.x));
let df = Df::viewport(pos2 * rect_size);
df.move_to(0., rect_size.y - 1.);
df.line_to(rect_size.x, rect_size.y - 1.);
return df.stroke(color, 0.8);
}"#
),
],
..Shader::DEFAULT
};
#[derive(Clone)]
#[repr(C)]
struct ColorBackgroundIns {
quad: QuadIns,
color: Vec4,
}
pub struct ColorBackground {
shader: &'static Shader,
color: Vec4,
}
impl ColorBackground {
fn new(shader: &'static Shader) -> Self {
Self { shader, color: Default::default() }
}
fn draw_quad_abs(&self, cx: &mut Cx, rect: Rect) {
cx.add_instances(self.shader, &[ColorBackgroundIns { quad: QuadIns::from_rect(rect), color: self.color }]);
}
}
#[derive(Clone)]
#[repr(C)]
struct IndentLinesIns {
quad: QuadIns,
color: Vec4,
indent_id: f32,
}
pub struct IndentLines {
indent_sel: f32,
area: Area,
instances: Vec<IndentLinesIns>,
}
impl IndentLines {
fn new() -> Self {
Self { area: Area::Empty, indent_sel: Default::default(), instances: Default::default() }
}
fn write_uniforms(&self, cx: &mut Cx) {
self.area.write_user_uniforms(cx, self.indent_sel);
}
fn set_indent_sel(&mut self, cx: &mut Cx, v: f32) {
self.indent_sel = v;
self.write_uniforms(cx);
}
fn clear(&mut self) {
self.instances.clear();
}
fn make_quad_rel(&mut self, cx: &mut Cx, rect: Rect, color: Vec4, indent_id: f32) {
self.instances.push(IndentLinesIns { quad: QuadIns::from_rect(rect.translate(cx.get_box_origin())), color, indent_id });
self.write_uniforms(cx)
}
fn draw(&mut self, cx: &mut Cx) {
self.area = cx.add_instances(&SHADER_INDENT_LINES, &self.instances);
self.write_uniforms(cx)
}
}
#[derive(Clone)]
#[repr(C)]
pub struct CursorIns {
base: QuadIns,
color: Vec4,
}
pub struct Cursor {
area: Area,
blink: f32,
}
impl Cursor {
fn new() -> Self {
Self { area: Area::Empty, blink: 0.0 }
}
fn write_uniforms(&self, cx: &mut Cx) {
self.area.write_user_uniforms(cx, self.blink);
}
fn set_blink(&mut self, cx: &mut Cx, v: f32) {
self.blink = v;
self.write_uniforms(cx);
}
fn draw_quad_rel(&mut self, cx: &mut Cx, rect: Rect) {
self.area = cx.add_instances(
&SHADER_CURSOR,
&[CursorIns {
base: QuadIns::from_rect(rect.translate(cx.get_box_origin())).with_draw_depth(1.3),
color: COLOR_CURSOR,
}],
);
self.write_uniforms(cx)
}
}
#[derive(Clone, Default)]
#[repr(C)]
pub struct SelectionIns {
base: QuadIns,
color: Vec4,
prev_x: f32,
prev_w: f32,
next_x: f32,
next_w: f32,
}
pub struct TextEditor {
pub component_base: ComponentBase,
pub view: ScrollView,
pub gutter_bg: Background,
pub cursor: Cursor,
pub cursor_row: Background,
pub paren_pair: ColorBackground,
pub indent_lines: IndentLines,
pub message_marker: ColorBackground,
pub search_marker: ColorBackground,
pub cursors: TextCursorSet,
pub text_area: Area,
pub text_glyphs: Vec<TextIns>,
pub current_font_scale: f32,
pub open_font_scale: f32,
pub folded_font_scale: f32,
pub line_number_width: f32,
pub line_number_click_margin: f32,
pub draw_line_numbers: bool,
pub top_padding: f32,
pub cursor_blink_speed: f64,
pub _undo_id: u64,
pub highlight_area_on: bool,
pub mark_unmatched_parens: bool,
pub draw_cursor_row: bool,
pub search_markers_bypass: Vec<TextCursor>,
pub folding_depth: usize,
pub colors: CodeEditorColors,
pub read_only: bool,
pub multiline: bool,
pub line_number_offset: usize,
pub _scroll_pos_on_load: Option<Vec2>,
pub _set_key_focus_on_load: bool,
pub _set_last_cursor: Option<((usize, usize), bool)>,
pub _line_number_chunk: Vec<char>,
pub _scroll_pos: Vec2,
pub _last_finger_move: Option<Vec2>,
pub _paren_stack: Vec<ParenItem>,
pub _indent_stack: Vec<(Vec4, f32)>,
pub _indent_id_alloc: f32,
pub _indent_line_inst: Area,
pub _bg_inst: Option<InstanceRangeArea>,
pub _last_indent_color: Vec4,
pub _line_geometry: Vec<LineGeom>,
pub _anim_select: Vec<AnimSelect>,
pub _visible_lines: usize,
pub _select_scroll: Option<SelectScroll>,
pub _grid_select_corner: Option<TextPos>,
pub _is_row_select: bool,
pub _last_cursor_pos: TextPos,
pub _anim_font_scale: f32,
pub _line_largest_font: f32,
pub _anim_folding: AnimFolding,
pub _monospace_size: Vec2,
pub _monospace_base: Vec2,
pub _tokens_on_line: usize,
pub _line_was_folded: bool,
pub _final_fill_height: f32,
pub _draw_cursors: DrawCursors,
pub _draw_search: DrawCursors,
pub _draw_messages: DrawCursors,
pub _cursor_blink_timer: Timer,
pub _cursor_blink_flipflop: f32,
pub _highlight_visibility: f32,
pub _last_tabs: usize,
pub _newline_tabs: usize,
pub _last_lag_mutation_id: u32,
pub _line_number_glyphs: Vec<TextIns>,
}
#[derive(Clone, PartialEq)]
pub enum TextEditorEvent {
None,
CursorMove,
LagChange,
Change,
KeyFocus,
KeyFocusLost,
Escape,
Return,
Search(String),
Decl(String),
}
#[derive(Default, Clone)]
pub struct CodeEditorColors {
indent_line_unknown: Vec4,
indent_line_fn: Vec4,
indent_line_typedef: Vec4,
indent_line_looping: Vec4,
indent_line_flow: Vec4,
paren_pair_match: Vec4,
paren_pair_fail: Vec4,
message_marker_error: Vec4,
message_marker_warning: Vec4,
message_marker_log: Vec4,
line_number_normal: Vec4,
line_number_highlight: Vec4,
whitespace: Vec4,
keyword: Vec4,
flow: Vec4,
looping: Vec4,
identifier: Vec4,
call: Vec4,
type_name: Vec4,
theme_name: Vec4,
string: Vec4,
number: Vec4,
comment: Vec4,
doc_comment: Vec4,
paren_d1: Vec4,
paren_d2: Vec4,
operator: Vec4,
delimiter: Vec4,
unexpected: Vec4,
warning: Vec4,
error: Vec4,
defocus: Vec4,
}
const COLOR_GUTTER_BG: Vec4 = vec4(30.0 / 255.0, 30.0 / 255.0, 30.0 / 255.0, 1.0);
const COLOR_INDENT_LINE_UNKNOWN: Vec4 = vec4(85.0 / 255.0, 85.0 / 255.0, 85.0 / 255.0, 1.0);
const COLOR_INDENT_LINE_FN: Vec4 = vec4(220.0 / 255.0, 220.0 / 255.0, 174.0 / 255.0, 1.0);
const COLOR_INDENT_LINE_TYPEDEF: Vec4 = vec4(91.0 / 255.0, 155.0 / 255.0, 211.0 / 255.0, 1.0);
const COLOR_INDENT_LINE_LOOPING: Vec4 = vec4(1.0, 140.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_INDENT_LINE_FLOW: Vec4 = vec4(196.0 / 255.0, 133.0 / 255.0, 190.0 / 255.0, 1.0);
const COLOR_SELECTION: Vec4 = vec4(41.0 / 255.0, 78.0 / 255.0, 117.0 / 255.0, 1.0);
const COLOR_SELECTION_DEFOCUS: Vec4 = vec4(75.0 / 255.0, 75.0 / 255.0, 75.0 / 255.0, 1.0);
const COLOR_CURSOR: Vec4 = vec4(176.0 / 255.0, 176.0 / 255.0, 176.0 / 255.0, 1.0);
const COLOR_CURSOR_ROW: Vec4 = vec4(45.0 / 255.0, 45.0 / 255.0, 45.0 / 255.0, 1.0);
const COLOR_PAREN_PAIR_MATCH: Vec4 = vec4(1.0, 1.0, 1.0, 1.0);
const COLOR_PAREN_PAIR_FAIL: Vec4 = vec4(1.0, 0.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_MESSAGE_MARKER_ERROR: Vec4 = vec4(200.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_MESSAGE_MARKER_WARNING: Vec4 = vec4(0.0 / 255.0, 200.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_MESSAGE_MARKER_LOG: Vec4 = vec4(200.0 / 255.0, 200.0 / 255.0, 200.0 / 255.0, 1.0);
const COLOR_SEARCH_MARKER: Vec4 = vec4(128.0 / 255.0, 64.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_LINE_NUMBER_NORMAL: Vec4 = vec4(136.0 / 255.0, 136.0 / 255.0, 136.0 / 255.0, 1.0);
const COLOR_LINE_NUMBER_HIGHLIGHT: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_WHITESPACE: Vec4 = vec4(110.0 / 255.0, 110.0 / 255.0, 110.0 / 255.0, 1.0);
const COLOR_KEYWORD: Vec4 = vec4(91.0 / 255.0, 155.0 / 255.0, 211.0 / 255.0, 1.0);
const COLOR_FLOW: Vec4 = vec4(196.0 / 255.0, 133.0 / 255.0, 190.0 / 255.0, 1.0);
const COLOR_LOOPING: Vec4 = vec4(1.0, 140.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_IDENTIFIER: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_CALL: Vec4 = vec4(220.0 / 255.0, 220.0 / 255.0, 174.0 / 255.0, 1.0);
const COLOR_TYPE_NAME: Vec4 = vec4(86.0 / 255.0, 201.0 / 255.0, 177.0 / 255.0, 1.0);
const COLOR_THEME_NAME: Vec4 = vec4(204.0 / 255.0, 145.0 / 255.0, 123.0 / 255.0, 1.0);
const COLOR_STRING: Vec4 = vec4(204.0 / 255.0, 145.0 / 255.0, 123.0 / 255.0, 1.0);
const COLOR_NUMBER: Vec4 = vec4(182.0 / 255.0, 206.0 / 255.0, 170.0 / 255.0, 1.0);
const COLOR_COMMENT: Vec4 = vec4(99.0 / 255.0, 141.0 / 255.0, 84.0 / 255.0, 1.0);
const COLOR_DOC_COMMENT: Vec4 = vec4(120.0 / 255.0, 171.0 / 255.0, 104.0 / 255.0, 1.0);
const COLOR_PAREN_D1: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_PAREN_D2: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_OPERATOR: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_DELIMITER: Vec4 = vec4(212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0);
const COLOR_UNEXPECTED: Vec4 = vec4(1.0, 0.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_WARNING: Vec4 = vec4(225.0 / 255.0, 229.0 / 255.0, 112.0 / 255.0, 1.0);
const COLOR_ERROR: Vec4 = vec4(254.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0);
const COLOR_DEFOCUS: Vec4 = vec4(128.0 / 255.0, 128.0 / 255.0, 128.0 / 255.0, 1.0);
impl Default for TextEditor {
fn default() -> Self {
Self {
component_base: Default::default(),
read_only: false,
multiline: true,
cursors: TextCursorSet::default(),
indent_lines: IndentLines::new(),
view: ScrollView::new_standard_hv(),
gutter_bg: Background::default().with_draw_depth(1.4),
colors: CodeEditorColors::default(),
cursor: Cursor::new(),
cursor_row: Background::default(),
paren_pair: ColorBackground::new(&SHADER_PAREN_PAIR),
message_marker: ColorBackground::new(&SHADER_MESSAGE_MARKER),
search_marker: ColorBackground::new(&SHADER_SEARCH_MARKER),
text_area: Area::Empty,
text_glyphs: vec![],
current_font_scale: 1.0,
open_font_scale: 1.0,
folded_font_scale: 0.07,
line_number_width: 45.,
line_number_click_margin: 10.,
draw_line_numbers: true,
cursor_blink_speed: 0.5,
top_padding: 27.,
mark_unmatched_parens: true,
highlight_area_on: true,
draw_cursor_row: true,
line_number_offset: 0,
search_markers_bypass: Vec::new(),
_scroll_pos_on_load: None,
_set_key_focus_on_load: false,
_set_last_cursor: None,
_monospace_size: Vec2::default(),
_monospace_base: Vec2::default(),
_last_finger_move: None,
_tokens_on_line: 0,
_line_was_folded: false,
_scroll_pos: Vec2::default(),
_visible_lines: 0,
_undo_id: 0,
_line_geometry: Vec::new(),
_anim_select: Vec::new(),
_grid_select_corner: None,
_is_row_select: false,
_highlight_visibility: 0.,
_bg_inst: None,
_line_number_chunk: Vec::new(),
_anim_font_scale: 1.0,
_line_largest_font: 0.,
_final_fill_height: 0.,
folding_depth: 2,
_anim_folding: AnimFolding { state: AnimFoldingState::Open, focussed_line: 0, did_animate: false },
_select_scroll: None,
_draw_cursors: DrawCursors::default(),
_draw_search: DrawCursors::default(),
_draw_messages: DrawCursors::default(),
_paren_stack: Vec::new(),
_indent_stack: Vec::new(),
_indent_id_alloc: 0.0,
_indent_line_inst: Area::Empty,
_last_cursor_pos: TextPos::zero(),
_last_indent_color: Vec4::default(),
_cursor_blink_timer: Timer::empty(),
_cursor_blink_flipflop: 0.,
_last_lag_mutation_id: 0,
_last_tabs: 0,
_newline_tabs: 0,
_line_number_glyphs: Vec::new(),
}
}
}
impl TextEditor {
pub fn apply_style(&mut self) {
self.colors.indent_line_unknown = COLOR_INDENT_LINE_UNKNOWN;
self.colors.indent_line_fn = COLOR_INDENT_LINE_FN;
self.colors.indent_line_typedef = COLOR_INDENT_LINE_TYPEDEF;
self.colors.indent_line_looping = COLOR_INDENT_LINE_LOOPING;
self.colors.indent_line_flow = COLOR_INDENT_LINE_FLOW;
self.search_marker.color = COLOR_SEARCH_MARKER;
self.colors.paren_pair_match = COLOR_PAREN_PAIR_MATCH;
self.colors.paren_pair_fail = COLOR_PAREN_PAIR_FAIL;
self.colors.message_marker_error = COLOR_MESSAGE_MARKER_ERROR;
self.colors.message_marker_warning = COLOR_MESSAGE_MARKER_WARNING;
self.colors.message_marker_log = COLOR_MESSAGE_MARKER_LOG;
self.colors.line_number_normal = COLOR_LINE_NUMBER_NORMAL;
self.colors.line_number_highlight = COLOR_LINE_NUMBER_HIGHLIGHT;
self.colors.whitespace = COLOR_WHITESPACE;
self.colors.keyword = COLOR_KEYWORD;
self.colors.flow = COLOR_FLOW;
self.colors.looping = COLOR_LOOPING;
self.colors.identifier = COLOR_IDENTIFIER;
self.colors.call = COLOR_CALL;
self.colors.type_name = COLOR_TYPE_NAME;
self.colors.theme_name = COLOR_THEME_NAME;
self.colors.string = COLOR_STRING;
self.colors.number = COLOR_NUMBER;
self.colors.comment = COLOR_COMMENT;
self.colors.doc_comment = COLOR_DOC_COMMENT;
self.colors.paren_d1 = COLOR_PAREN_D1;
self.colors.paren_d2 = COLOR_PAREN_D2;
self.colors.operator = COLOR_OPERATOR;
self.colors.delimiter = COLOR_DELIMITER;
self.colors.unexpected = COLOR_UNEXPECTED;
self.colors.warning = COLOR_WARNING;
self.colors.error = COLOR_ERROR;
self.colors.defocus = COLOR_DEFOCUS;
}
fn reset_cursor_blinker(&mut self, cx: &mut Cx) {
cx.stop_timer(&mut self._cursor_blink_timer);
self._cursor_blink_timer = cx.start_timer(self.cursor_blink_speed * 0.5, false);
self._cursor_blink_flipflop = 0.;
self.cursor.set_blink(cx, self._cursor_blink_flipflop);
}
fn handle_finger_down(&mut self, cx: &mut Cx, fe: &FingerDownEvent, text_buffer: &mut TextBuffer) {
cx.set_down_mouse_cursor(MouseCursor::Text);
self.set_key_focus(cx);
self._undo_id += 1;
let offset;
if fe.rel.x < self.line_number_width - self.line_number_click_margin {
offset = self.compute_offset_from_ypos(cx, fe.abs.y, text_buffer, false);
let range = text_buffer.get_nearest_line_range(offset);
self.cursors.set_last_clamp_range(range);
self._is_row_select = true;
} else {
offset = if let Some(o) = TextIns::closest_offset(cx, &self.text_area, fe.abs, TEXT_STYLE_MONO.line_spacing) {
o
} else {
return;
};
match fe.tap_count {
1 => {}
2 => {
if let Some((coffset, len)) = TextCursorSet::get_nearest_token_chunk(offset, text_buffer) {
self.cursors.set_last_clamp_range((coffset, len));
}
}
3 => {
if let Some((coffset, len)) = TextCursorSet::get_nearest_token_chunk(offset, text_buffer) {
let (start, line_len) = text_buffer.get_nearest_line_range(offset);
let mut chunk_offset = coffset;
let mut chunk_len = len;
if start < chunk_offset {
chunk_len += chunk_offset - start;
chunk_offset = start;
if line_len > chunk_len {
chunk_len = line_len;
}
}
self.cursors.set_last_clamp_range((chunk_offset, chunk_len));
} else {
let range = text_buffer.get_nearest_line_range(offset);
self.cursors.set_last_clamp_range(range);
}
}
_ => {
}
}
}
if fe.modifiers.shift {
if fe.modifiers.logo || fe.modifiers.control {
let pos = self.compute_grid_text_pos_from_abs(cx, fe.abs);
self._grid_select_corner = Some(self.cursors.grid_select_corner(pos, text_buffer));
self.cursors.grid_select(self._grid_select_corner.unwrap(), pos, text_buffer);
if self.cursors.set.is_empty() {
self.cursors.clear_and_set_last_cursor_head_and_tail(offset, offset, text_buffer);
}
} else {
self.cursors.clear_and_set_last_cursor_head(offset, text_buffer);
}
} else {
if fe.modifiers.logo || fe.modifiers.control {
self.cursors.add_last_cursor_head_and_tail(offset, offset, text_buffer);
} else {
self.cursors.clear_and_set_last_cursor_head_and_tail(offset, offset, text_buffer);
}
}
cx.request_draw();
self._last_finger_move = Some(fe.abs);
self.reset_cursor_blinker(cx);
}
fn handle_finger_move(&mut self, cx: &mut Cx, fe: &FingerMoveEvent, text_buffer: &mut TextBuffer) {
let cursor_moved = if let Some(grid_select_corner) = self._grid_select_corner {
let pos = self.compute_grid_text_pos_from_abs(cx, fe.abs);
self.cursors.grid_select(grid_select_corner, pos, text_buffer)
} else if self._is_row_select {
let offset = self.compute_offset_from_ypos(cx, fe.abs.y, text_buffer, true);
self.cursors.set_last_cursor_head(offset, text_buffer)
} else if let Some(offset) = TextIns::closest_offset(cx, &self.text_area, fe.abs, TEXT_STYLE_MONO.line_spacing) {
self.cursors.set_last_cursor_head(offset, text_buffer)
} else {
false
};
self._last_finger_move = Some(fe.abs);
let repaint_scroll = self.check_select_scroll_dynamics(fe);
if repaint_scroll || cursor_moved {
cx.request_draw();
}
if cursor_moved {
self.reset_cursor_blinker(cx);
}
}
fn handle_finger_up(&mut self, cx: &mut Cx, _fe: &FingerUpEvent, _text_buffer: &mut TextBuffer) {
self.cursors.clear_last_clamp_range();
self._select_scroll = None;
self._last_finger_move = None;
self._grid_select_corner = None;
self._is_row_select = false;
self.reset_cursor_blinker(cx);
}
fn handle_key_down(&mut self, cx: &mut Cx, ke: &KeyEvent, text_buffer: &mut TextBuffer) -> bool {
let cursor_moved = match ke.key_code {
KeyCode::KeyE => {
if ke.modifiers.logo || ke.modifiers.control {
let pos = self.cursors.get_last_cursor_head();
let mut moved = false;
for result in text_buffer.markers.search_cursors.iter().rev() {
if result.head < pos {
if ke.modifiers.shift {
self.cursors.add_last_cursor_head_and_tail(result.head, result.tail, text_buffer);
} else {
self.cursors.set_last_cursor_head_and_tail(result.head, result.tail, text_buffer);
}
moved = true;
break;
}
}
moved
} else {
false
}
}
KeyCode::KeyD => {
if ke.modifiers.logo || ke.modifiers.control {
let pos = self.cursors.get_last_cursor_head();
let mut moved = false;
for result in text_buffer.markers.search_cursors.iter() {
if result.tail > pos {
if ke.modifiers.shift {
self.cursors.add_last_cursor_head_and_tail(result.head, result.tail, text_buffer);
} else {
self.cursors.set_last_cursor_head_and_tail(result.head, result.tail, text_buffer);
}
moved = true;
break;
}
}
moved
} else {
false
}
}
KeyCode::ArrowUp => {
if !self.multiline || ke.modifiers.logo || ke.modifiers.control {
false
} else {
if self._anim_folding.state.is_folded() && self.cursors.set.len() == 1 {
let delta = self.compute_next_unfolded_line_up(text_buffer);
self.cursors.move_up(delta, ke.modifiers.shift, text_buffer);
} else {
self.cursors.move_up(1, ke.modifiers.shift, text_buffer);
}
self._undo_id += 1;
true
}
}
KeyCode::ArrowDown => {
if !self.multiline || ke.modifiers.logo || ke.modifiers.control {
false
} else {
if self._anim_folding.state.is_folded() && self.cursors.set.len() == 1 {
let delta = self.compute_next_unfolded_line_down(text_buffer);
self.cursors.move_down(delta, ke.modifiers.shift, text_buffer);
} else {
self.cursors.move_down(1, ke.modifiers.shift, text_buffer);
}
self._undo_id += 1;
true
}
}
KeyCode::ArrowLeft => {
if ke.modifiers.logo || ke.modifiers.control {
self.cursors.move_left_nearest_token(ke.modifiers.shift, text_buffer)
} else {
self.cursors.move_left(1, ke.modifiers.shift, text_buffer);
}
self._undo_id += 1;
true
}
KeyCode::ArrowRight => {
if ke.modifiers.logo || ke.modifiers.control {
self.cursors.move_right_nearest_token(ke.modifiers.shift, text_buffer)
} else {
self.cursors.move_right(1, ke.modifiers.shift, text_buffer);
}
self._undo_id += 1;
true
}
KeyCode::PageUp => {
self.cursors.move_up(self._visible_lines.max(5) - 4, ke.modifiers.shift, text_buffer);
self._undo_id += 1;
true
}
KeyCode::PageDown => {
self.cursors.move_down(self._visible_lines.max(5) - 4, ke.modifiers.shift, text_buffer);
self._undo_id += 1;
true
}
KeyCode::Home => {
self.cursors.move_home(ke.modifiers.shift, text_buffer);
self._undo_id += 1;
true
}
KeyCode::End => {
self.cursors.move_end(ke.modifiers.shift, text_buffer);
self._undo_id += 1;
true
}
KeyCode::Backspace => {
if !self.read_only {
self.cursors.backspace(text_buffer, self._undo_id);
true
} else {
false
}
}
KeyCode::Delete => {
if !self.read_only {
self.cursors.delete(text_buffer);
true
} else {
false
}
}
KeyCode::KeyZ => {
if !self.read_only {
if ke.modifiers.logo || ke.modifiers.control {
if ke.modifiers.shift {
text_buffer.redo(true, &mut self.cursors);
true
} else {
text_buffer.undo(true, &mut self.cursors);
true
}
} else {
false
}
} else {
false
}
}
KeyCode::KeyX => {
if !self.read_only && (ke.modifiers.logo || ke.modifiers.control) {
self.cursors.replace_text("", text_buffer, None);
true
} else {
false
}
}
KeyCode::KeyA => {
if ke.modifiers.logo || ke.modifiers.control {
self.cursors.select_all(text_buffer);
cx.request_draw();
false
} else {
false
}
}
KeyCode::Alt => {
self.start_code_folding(cx, text_buffer);
false
}
KeyCode::Tab => {
if !self.read_only {
if ke.modifiers.shift {
self.cursors.remove_tab(text_buffer, 4);
} else {
self.cursors.insert_tab(text_buffer, " ");
}
true
} else {
false
}
}
KeyCode::Return => {
if !self.read_only && self.multiline {
if !ke.modifiers.control && !ke.modifiers.logo {
self.cursors.insert_newline_with_indent(text_buffer);
}
true
} else {
false
}
}
_ => false,
};
if cursor_moved {
self.scroll_last_cursor_visible(cx, text_buffer, 0.);
cx.request_draw();
self.reset_cursor_blinker(cx);
}
cursor_moved
}
fn handle_text_input(&mut self, cx: &mut Cx, te: &TextInputEvent, text_buffer: &mut TextBuffer) {
if te.replace_last {
text_buffer.undo(false, &mut self.cursors);
}
if !te.was_paste && te.input.len() == 1 {
match te.input.chars().next().unwrap() {
'(' => {
self.cursors.insert_around("(", ")", text_buffer);
}
'[' => {
self.cursors.insert_around("[", "]", text_buffer);
}
'{' => {
self.cursors.insert_around("{", "}", text_buffer);
}
'"' => {
self.cursors.insert_around("\"", "\"", text_buffer);
}
')' => {
self.cursors.overwrite_if_exists_or_deindent(")", 4, text_buffer);
}
']' => {
self.cursors.overwrite_if_exists_or_deindent("]", 4, text_buffer);
}
'}' => {
self.cursors.overwrite_if_exists_or_deindent("}", 4, text_buffer);
}
_ => {
self.cursors.replace_text(&te.input, text_buffer, None);
}
}
} else if !self.multiline {
let replaced = te.input.replace('\n', "");
self.cursors.replace_text(&replaced, text_buffer, None);
} else {
self.cursors.replace_text(&te.input, text_buffer, None);
}
self.scroll_last_cursor_visible(cx, text_buffer, 0.);
cx.request_draw();
self.reset_cursor_blinker(cx);
cx.send_signal(text_buffer.signal, TextBuffer::STATUS_DATA_UPDATE);
}
pub fn handle_live_replace(
&mut self,
cx: &mut Cx,
range: (usize, usize),
what: &str,
text_buffer: &mut TextBuffer,
group: u64,
) -> bool {
self.cursors.clear_and_set_last_cursor_head_and_tail(range.1, range.0, text_buffer);
self.cursors.replace_text(what, text_buffer, Some(TextUndoGrouping::LiveEdit(group)));
self.scroll_last_cursor_visible(cx, text_buffer, 0.);
cx.request_draw();
self.reset_cursor_blinker(cx);
false
}
pub fn handle(&mut self, cx: &mut Cx, event: &mut Event, text_buffer: &mut TextBuffer) -> TextEditorEvent {
if self.view.handle(cx, event) {
if let Some(last_finger_move) = self._last_finger_move {
if let Some(grid_select_corner) = self._grid_select_corner {
let pos = self.compute_grid_text_pos_from_abs(cx, last_finger_move);
self.cursors.grid_select(grid_select_corner, pos, text_buffer);
} else if let Some(offset) =
TextIns::closest_offset(cx, &self.text_area, last_finger_move, TEXT_STYLE_MONO.line_spacing)
{
self.cursors.set_last_cursor_head(offset, text_buffer);
}
}
cx.request_draw();
}
let last_mutation_id = text_buffer.mutation_id;
match event {
Event::Timer(te) => {
if self._cursor_blink_timer.is_timer(te) {
if self.has_key_focus(cx) {
self._cursor_blink_timer = cx.start_timer(self.cursor_blink_speed, false);
}
self._cursor_blink_flipflop = 1.0 - self._cursor_blink_flipflop;
self._undo_id += 1;
self._highlight_visibility = 1.0;
self.cursor.set_blink(cx, self._cursor_blink_flipflop);
if self._last_lag_mutation_id != text_buffer.mutation_id {
let was_filechange = self._last_lag_mutation_id != 0;
self._last_lag_mutation_id = text_buffer.mutation_id;
if was_filechange {
return TextEditorEvent::LagChange;
}
}
}
}
Event::Signal(se) => {
if let Some(statusses) = se.signals.get(&text_buffer.signal) {
for status in statusses {
if *status == TextBuffer::STATUS_MESSAGE_UPDATE
|| *status == TextBuffer::STATUS_SEARCH_UPDATE
|| *status == TextBuffer::STATUS_DATA_UPDATE
{
cx.request_draw();
} else if *status == TextBuffer::STATUS_KEYBOARD_UPDATE {
if let Some(KeyCode::Alt) = &text_buffer.keyboard.key_down {
self.start_code_folding(cx, text_buffer);
}
if let Some(KeyCode::Alt) = &text_buffer.keyboard.key_up {
self.start_code_unfolding(cx, text_buffer);
}
}
}
}
}
_ => (),
}
let mut cursor_moved = false;
match event.hits(cx, &self.component_base, HitOpt::default()) {
Event::KeyFocus(_kf) => {
self.reset_cursor_blinker(cx);
cx.request_draw();
return TextEditorEvent::KeyFocus;
}
Event::KeyFocusLost(_kf) => {
cx.request_draw();
return TextEditorEvent::KeyFocusLost;
}
Event::FingerDown(fe) => {
self.handle_finger_down(cx, &fe, text_buffer);
}
Event::FingerHover(_fe) => {
cx.set_hover_mouse_cursor(MouseCursor::Text);
}
Event::FingerUp(fe) => {
self.handle_finger_up(cx, &fe, text_buffer);
}
Event::FingerMove(fe) => {
self.handle_finger_move(cx, &fe, text_buffer);
}
Event::KeyDown(ke) => {
if ke.key_code == KeyCode::Return && !self.read_only && !self.multiline {
return TextEditorEvent::Return;
}
if ke.key_code == KeyCode::Escape {
let pos = self.cursors.get_last_cursor_head();
self.cursors.clear_and_set_last_cursor_head_and_tail(pos, pos, text_buffer);
return TextEditorEvent::Escape;
}
if ke.key_code == KeyCode::KeyF && (ke.modifiers.logo || ke.modifiers.control) {
let search = self.cursors.get_ident_around_last_cursor_and_set(text_buffer);
return TextEditorEvent::Search(search);
}
if ke.key_code == KeyCode::KeyS && (ke.modifiers.logo || ke.modifiers.control) {
let search = self.cursors.get_ident_around_last_cursor_and_set(text_buffer);
return TextEditorEvent::Decl(search);
}
cursor_moved = self.handle_key_down(cx, &ke, text_buffer);
}
Event::KeyUp(ke) => {
match ke.key_code {
KeyCode::Alt => {
self.start_code_unfolding(cx, text_buffer);
}
_ => (),
}
self.reset_cursor_blinker(cx);
}
Event::TextInput(te) => {
if !self.read_only {
self.handle_text_input(cx, &te, text_buffer);
}
}
Event::TextCopy => {
cx.copy_text_to_clipboard(&self.cursors.get_all_as_string(text_buffer));
}
_ => (),
};
if last_mutation_id != text_buffer.mutation_id {
TextEditorEvent::Change
} else if cursor_moved {
TextEditorEvent::CursorMove
} else {
TextEditorEvent::None
}
}
pub fn has_key_focus(&self, cx: &Cx) -> bool {
cx.has_key_focus(Some(&self.component_base))
}
pub fn set_key_focus(&mut self, cx: &mut Cx) {
if self.view.area() == Area::Empty {
self._set_key_focus_on_load = true;
return;
}
cx.set_key_focus(Some(&self.component_base));
self.reset_cursor_blinker(cx);
}
pub fn begin_draw_objects(&mut self) {
self.text_glyphs.clear();
self.indent_lines.clear();
self._line_number_glyphs.clear();
}
pub fn end_draw_objects(&mut self, cx: &mut Cx) {
self.text_area = TextIns::draw_glyphs(
cx,
&self.text_glyphs,
&DrawGlyphsProps { text_style: TEXT_STYLE_MONO, ..DrawGlyphsProps::default() },
);
self.indent_lines.draw(cx);
}
pub fn init_draw_state(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
self._monospace_base = TextIns::get_monospace_base(cx, &TEXT_STYLE_MONO);
self.set_font_scale(cx, self.open_font_scale);
self._draw_cursors = DrawCursors::default();
self._draw_messages = DrawCursors::default();
self._draw_search = DrawCursors::default();
self._tokens_on_line = 0;
self._visible_lines = 0;
self._newline_tabs = 0;
self._last_tabs = 0;
self._indent_stack.truncate(0);
self._indent_id_alloc = 1.0;
self._paren_stack.truncate(0);
self._draw_cursors.set_next(&self.cursors.set);
self._draw_search.set_next(if !self.search_markers_bypass.is_empty() {
&self.search_markers_bypass
} else {
&text_buffer.markers.search_cursors
});
self._line_geometry.truncate(0);
self._line_largest_font = TEXT_STYLE_MONO.font_size;
self._last_indent_color = self.colors.indent_line_unknown;
cx.move_draw_pos(self.line_number_width, self.top_padding);
}
pub fn begin_text_editor(&mut self, cx: &mut Cx, text_buffer: &TextBuffer, override_layout_size: Option<LayoutSize>) {
if let Some(layout_size) = override_layout_size {
self.view.begin_view(cx, layout_size);
cx.begin_row(layout_size.width, layout_size.height);
} else {
self.view.begin_view(cx, LayoutSize::FILL);
cx.begin_row(Width::Compute, Height::Compute);
};
self.apply_style();
if self._set_key_focus_on_load {
self._set_key_focus_on_load = false;
self.set_key_focus(cx);
}
self.begin_draw_objects();
if let Some(select_scroll) = &mut self._select_scroll {
let scroll_pos = self.view.get_scroll_pos(cx);
if self
.view
.set_scroll_pos(cx, Vec2 { x: scroll_pos.x + select_scroll.delta.x, y: scroll_pos.y + select_scroll.delta.y })
{
cx.request_draw();
} else {
select_scroll.at_end = true;
}
}
if text_buffer.markers.mutation_id != text_buffer.mutation_id {
self._draw_messages.term(&text_buffer.markers.message_cursors);
} else {
self._draw_messages.set_next(&text_buffer.markers.message_cursors);
}
self._last_cursor_pos = self.cursors.get_last_cursor_text_pos(text_buffer);
self.cursors.clamp_to_text_buffer(text_buffer);
self.do_folding_animation_step(cx);
self.init_draw_state(cx, text_buffer);
self._scroll_pos = self.view.get_scroll_pos(cx);
}
fn do_folding_animation_step(&mut self, cx: &mut Cx) {
let anim_folding = &mut self._anim_folding;
if anim_folding.state.is_animating() {
anim_folding.state.next_anim_step();
if anim_folding.state.is_animating() {
cx.request_draw();
}
anim_folding.did_animate = true;
} else {
anim_folding.did_animate = false;
}
self._anim_font_scale = anim_folding.state.get_font_size(self.open_font_scale, self.folded_font_scale);
if self._anim_folding.did_animate {
let mut ypos = self.top_padding;
let mut ypos_at_line = ypos;
let focus_line = self._anim_folding.focussed_line;
if focus_line < self._line_geometry.len() {
for (line, geom) in self._line_geometry.iter().enumerate() {
if focus_line == line {
ypos_at_line = ypos;
}
ypos += if geom.was_folded {
self._monospace_base.y * TEXT_STYLE_MONO.font_size * self._anim_font_scale
} else {
self._monospace_base.y * TEXT_STYLE_MONO.font_size
}
}
ypos += self._final_fill_height;
let dy = self._line_geometry[focus_line].walk.y - ypos_at_line;
let sv = self.view.get_scroll_view_total();
self.view.set_scroll_view_total(cx, Vec2 { x: sv.x, y: ypos });
let scroll_pos = self.view.get_scroll_pos(cx);
self.view.set_scroll_pos(cx, Vec2 { x: scroll_pos.x, y: scroll_pos.y - dy });
}
}
}
fn line_is_visible(&self, cx: &mut Cx, min_height: f32, scroll: Vec2) -> bool {
let pos = cx.get_draw_pos();
let vy = cx.get_box_origin().y + scroll.y;
let vh = cx.get_height_total();
!(pos.y > vy + vh || pos.y + min_height < vy)
}
fn draw_new_line(&mut self, cx: &mut Cx) {
let relative_offset = cx.get_draw_pos() - cx.get_box_origin();
let line_geom = LineGeom {
walk: relative_offset,
font_size: self._line_largest_font,
was_folded: self._line_was_folded,
indent_id: if let Some((_, id)) = self._indent_stack.last() { *id } else { 0. },
};
let origin = cx.get_box_origin();
if self.draw_line_numbers && self.line_is_visible(cx, self._monospace_size.y, self._scroll_pos) {
let mut line_number_text = String::with_capacity(6);
let line_num = self._line_geometry.len() + 1 + self.line_number_offset;
let mut scale = 10000;
let mut fill = false;
loop {
let digit = ((line_num / scale) % 10) as u8;
if digit != 0 {
fill = true;
}
if fill {
line_number_text.push((48 + digit) as char);
} else {
line_number_text.push(' ');
}
if scale <= 1 {
break;
}
scale /= 10;
}
let draw_str_props = TextInsProps {
wrapping: Wrapping::Line,
text_style: TEXT_STYLE_MONO,
font_scale: self.current_font_scale,
draw_depth: 1.5,
color: if line_num == self._last_cursor_pos.row + 1 {
self.colors.line_number_highlight
} else {
self.colors.line_number_normal
},
..TextInsProps::DEFAULT
};
let chunk_width = self._monospace_size.x * 5.0;
self._line_number_glyphs.extend(TextIns::generate_2d_glyphs(
&draw_str_props.text_style,
&cx.fonts_data,
cx.current_dpi_factor,
draw_str_props.font_scale,
draw_str_props.draw_depth,
draw_str_props.color,
vec2(
origin.x + (self.line_number_width - chunk_width - self.line_number_click_margin),
origin.y + line_geom.walk.y,
),
0,
&line_number_text.chars().collect::<Vec<char>>(),
|_, _, _, _| 0.0,
));
}
cx.draw_new_line_min_height(self._monospace_size.y);
cx.move_draw_pos(self.line_number_width, 0.);
self._tokens_on_line = 0;
self._draw_cursors.process_newline();
self._draw_messages.process_newline();
self._line_geometry.push(line_geom);
self._line_largest_font = TEXT_STYLE_MONO.font_size;
}
fn draw_indent_lines(&mut self, cx: &mut Cx, geom_y: f32, tabs: usize) {
let y_pos = geom_y - cx.get_box_origin().y;
let tab_variable_width = self._monospace_base.x * 4. * TEXT_STYLE_MONO.font_size * self._anim_font_scale;
let tab_fixed_width = self._monospace_base.x * 4. * TEXT_STYLE_MONO.font_size;
let mut off = self.line_number_width;
for i in 0..tabs {
let (indent_color, indent_id) =
if i < self._indent_stack.len() { self._indent_stack[i] } else { (self.colors.indent_line_unknown, 0.) };
let tab_width = if i < self.folding_depth { tab_fixed_width } else { tab_variable_width };
self.indent_lines.make_quad_rel(
cx,
Rect { pos: vec2(off, y_pos), size: vec2(tab_width, self._monospace_size.y) },
indent_color,
indent_id,
);
off += tab_width;
}
}
pub fn draw_chunk(
&mut self,
cx: &mut Cx,
token_chunks_index: usize,
flat_text: &[char],
token_chunk: &TokenChunk,
markers: &TextBufferMarkers,
) {
if token_chunk.len == 0 {
return;
}
let token_type = token_chunk.token_type;
let chunk = &flat_text[token_chunk.offset..(token_chunk.offset + token_chunk.len)]; let offset = token_chunk.offset; let next_char = token_chunk.next;
if token_type == TokenType::ParenOpen {
self.draw_paren_open(token_chunks_index, offset, next_char, chunk);
}
if self._tokens_on_line == 0 {
let font_scale = match token_type {
TokenType::Whitespace => {
let tabs = chunk.len() >> 2;
while tabs > self._indent_stack.len() {
self._indent_stack.push((self._last_indent_color, self._indent_id_alloc));
self._indent_id_alloc += 1.0;
}
while tabs < self._indent_stack.len() {
self._indent_stack.pop();
if let Some(indent) = self._indent_stack.last() {
self._last_indent_color = indent.0;
}
}
if tabs >= self.folding_depth || next_char == '\n' {
let dx = (self._monospace_base.x * TEXT_STYLE_MONO.font_size * 4. * (self.folding_depth as f32))
- (self._monospace_base.x
* TEXT_STYLE_MONO.font_size
* self._anim_font_scale
* 4.
* (self.folding_depth as f32));
cx.move_draw_pos(dx, 0.0);
self._line_was_folded = true;
self._anim_font_scale
} else {
self._line_was_folded = false;
self.open_font_scale
}
}
TokenType::Newline
| TokenType::CommentLine
| TokenType::CommentChunk
| TokenType::CommentMultiBegin
| TokenType::CommentMultiEnd
| TokenType::Hash => {
self._line_was_folded = true;
self._anim_font_scale
}
_ => {
self._indent_stack.truncate(0);
self._line_was_folded = false;
self.open_font_scale
}
};
self.set_font_scale(cx, font_scale);
}
if self._tokens_on_line < 4 {
match token_type {
TokenType::Flow => {
self._last_indent_color = self.colors.indent_line_flow;
}
TokenType::Looping => {
self._last_indent_color = self.colors.indent_line_looping;
}
TokenType::TypeDef => {
self._last_indent_color = self.colors.indent_line_typedef;
}
TokenType::Fn | TokenType::Call | TokenType::Macro => {
self._last_indent_color = self.colors.indent_line_fn;
}
_ => (),
}
}
if let Some(geom) = self.move_cursor_right_no_wrap(
cx,
self._monospace_size.x * (chunk.len() as f32),
self._monospace_size.y,
self._scroll_pos,
) {
let mut mark_spaces = 0.0;
let color = match token_type {
TokenType::Whitespace => {
if self._tokens_on_line == 0 && chunk[0] == ' ' {
let tabs = chunk.len() >> 2;
self._last_tabs = tabs;
self._newline_tabs = tabs;
self.draw_indent_lines(cx, geom.pos.y, tabs);
} else if next_char == '\n' {
mark_spaces = 1.0;
}
self.colors.whitespace
}
TokenType::Newline => {
if self._tokens_on_line == 0 {
self._newline_tabs = 0;
self.draw_indent_lines(cx, geom.pos.y, self._last_tabs);
} else {
self._last_tabs = self._newline_tabs;
self._newline_tabs = 0;
}
self.colors.whitespace
}
TokenType::BuiltinType => self.colors.keyword,
TokenType::Keyword => self.colors.keyword,
TokenType::Bool => self.colors.keyword,
TokenType::Error => self.colors.error,
TokenType::Warning => self.colors.warning,
TokenType::Defocus => self.colors.defocus,
TokenType::Flow => self.colors.flow,
TokenType::Looping => self.colors.looping,
TokenType::TypeDef => self.colors.keyword,
TokenType::Impl => self.colors.keyword,
TokenType::Fn => self.colors.keyword,
TokenType::Identifier => self.colors.identifier,
TokenType::Macro | TokenType::Call => self.colors.call,
TokenType::TypeName => self.colors.type_name,
TokenType::ThemeName => self.colors.theme_name,
TokenType::Color => self.colors.string,
TokenType::Regex => self.colors.string,
TokenType::String => self.colors.string,
TokenType::Number => self.colors.number,
TokenType::StringMultiBegin => self.colors.string,
TokenType::StringChunk => self.colors.string,
TokenType::StringMultiEnd => self.colors.string,
TokenType::CommentMultiBegin => self.colors.comment,
TokenType::CommentMultiEnd => self.colors.comment,
TokenType::CommentLine => self.colors.comment,
TokenType::CommentChunk => self.colors.comment,
TokenType::ParenOpen => {
let depth = self._paren_stack.len();
self._paren_stack.last_mut().unwrap().geom_open = Some(geom);
match depth % 2 {
0 => self.colors.paren_d1,
_ => self.colors.paren_d2,
}
}
TokenType::ParenClose => {
if let Some(paren) = self._paren_stack.last_mut() {
paren.geom_close = Some(geom);
} else if self.mark_unmatched_parens {
self.paren_pair.color = self.colors.paren_pair_fail;
self.paren_pair.draw_quad_abs(cx, geom);
}
let depth = self._paren_stack.len();
match depth % 2 {
0 => self.colors.paren_d1,
_ => self.colors.paren_d2,
}
}
TokenType::Operator => self.colors.operator,
TokenType::Namespace => self.colors.operator,
TokenType::Hash => self.colors.operator,
TokenType::Delimiter => self.colors.delimiter,
TokenType::Colon => self.colors.delimiter,
TokenType::Splat => self.colors.operator,
TokenType::Eof => self.colors.unexpected,
TokenType::Unexpected => self.colors.unexpected,
};
if self._tokens_on_line == 0 {
self._visible_lines += 1;
}
let cursors = &self.cursors.set;
let last_cursor = self.cursors.last_cursor;
let draw_cursors = &mut self._draw_cursors;
let draw_messages = &mut self._draw_messages;
let draw_search = &mut self._draw_search;
let height = self._monospace_size.y;
let search_cursors =
if !self.search_markers_bypass.is_empty() { &self.search_markers_bypass } else { &markers.search_cursors };
let z = 2.0; if !search_cursors.is_empty() {
let char_callback = |ch, offset, x, w| {
draw_search.mark_text_select_only(search_cursors, offset, x, geom.pos.y, w, height);
draw_messages.mark_text_select_only(&markers.message_cursors, offset, x, geom.pos.y, w, height);
draw_cursors.mark_text_with_cursor(cursors, ch, offset, x, geom.pos.y, w, height, z, last_cursor, mark_spaces)
};
self.text_glyphs.extend(TextIns::generate_2d_glyphs(
&TEXT_STYLE_MONO,
&cx.fonts_data,
cx.current_dpi_factor,
self.current_font_scale,
0.,
color,
geom.pos,
offset,
chunk,
char_callback,
));
} else {
let char_callback = |ch, offset, x, w| {
draw_messages.mark_text_select_only(&markers.message_cursors, offset, x, geom.pos.y, w, height);
draw_cursors.mark_text_with_cursor(cursors, ch, offset, x, geom.pos.y, w, height, z, last_cursor, mark_spaces)
};
self.text_glyphs.extend(TextIns::generate_2d_glyphs(
&TEXT_STYLE_MONO,
&cx.fonts_data,
cx.current_dpi_factor,
self.current_font_scale,
0.,
color,
geom.pos,
offset,
chunk,
char_callback,
));
};
}
self._tokens_on_line += 1;
if token_chunk.token_type == TokenType::ParenClose {
self.draw_paren_close(cx, token_chunks_index, offset, next_char, chunk);
} else if token_type == TokenType::Newline {
self.draw_new_line(cx);
}
}
fn move_cursor_right_no_wrap(&self, cx: &mut Cx, w: f32, h: f32, scroll: Vec2) -> Option<Rect> {
let pos = cx.get_draw_pos();
cx.add_box(LayoutSize::new(Width::Fix(w), Height::Fix(h)));
let origin = cx.get_box_origin();
let vx = origin.x + scroll.x;
let vy = origin.y + scroll.y;
let vw = cx.get_width_total();
let vh = cx.get_height_total();
if pos.x > vx + vw || pos.x + w < vx || pos.y > vy + vh || pos.y + h < vy {
None
} else {
Some(Rect { pos: vec2(pos.x, pos.y), size: vec2(w, h) })
}
}
fn draw_paren_open(&mut self, token_chunks_index: usize, offset: usize, next_char: char, chunk: &[char]) {
let marked = if let Some(pos) = self.cursors.get_last_cursor_singular() {
pos == offset || pos == offset + 1 && next_char != '(' && next_char != '{' && next_char != '['
} else {
false
};
self._paren_stack.push(ParenItem {
pair_start: token_chunks_index, geom_open: None,
geom_close: None,
marked,
exp_paren: chunk[0],
});
}
fn draw_paren_close(&mut self, cx: &mut Cx, token_chunks_index: usize, offset: usize, next_char: char, chunk: &[char]) {
if self._paren_stack.is_empty() {
return;
}
let last = self._paren_stack.pop().unwrap();
if last.geom_open.is_none() && last.geom_close.is_none() {
return;
}
if !self.has_key_focus(cx) {
return;
}
if let Some(pos) = self.cursors.get_last_cursor_singular() {
if self.mark_unmatched_parens {
let fail = if last.exp_paren == '(' && chunk[0] != ')'
|| last.exp_paren == '[' && chunk[0] != ']'
|| last.exp_paren == '{' && chunk[0] != '}'
{
self.paren_pair.color = self.colors.paren_pair_fail;
true
} else {
self.paren_pair.color = self.colors.paren_pair_match;
false
};
if fail
|| pos == offset
|| pos == offset + 1 && next_char != ')' && next_char != '}' && next_char != ']'
|| last.marked
{
if last.pair_start + 1 == token_chunks_index && last.geom_open.is_some() && last.geom_close.is_some() {
let geom_open = last.geom_open.unwrap();
let geom_close = last.geom_open.unwrap();
let geom =
Rect { pos: geom_open.pos, size: vec2(geom_open.size.x + geom_close.size.x, geom_close.size.y) };
self.paren_pair.draw_quad_abs(cx, geom);
} else {
if let Some(rc) = last.geom_open {
self.paren_pair.draw_quad_abs(cx, rc);
}
if let Some(rc) = last.geom_close {
self.paren_pair.draw_quad_abs(cx, rc);
}
}
}
};
}
}
fn draw_paren_unmatched(&mut self, cx: &mut Cx) {
if !self.mark_unmatched_parens {
return;
}
while !self._paren_stack.is_empty() {
let last = self._paren_stack.pop().unwrap();
if self.has_key_focus(cx) && last.geom_open.is_some() {
self.paren_pair.color = self.colors.paren_pair_fail;
if let Some(rc) = last.geom_open {
self.paren_pair.draw_quad_abs(cx, rc);
}
}
}
}
pub fn end_text_editor(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
if self.multiline {
self.draw_new_line(cx);
}
self.place_ime_and_draw_cursor_row(cx);
self.draw_selections(cx);
self.draw_message_markers(cx, text_buffer);
self.draw_search_markers(cx);
self.draw_paren_unmatched(cx);
self.end_draw_objects(cx);
self.draw_cursors(cx);
if self.draw_line_numbers {
self.gutter_bg.draw_with_scroll_sticky(
cx,
Rect { pos: cx.get_box_origin(), size: vec2(self.line_number_width, cx.get_height_total()) },
COLOR_GUTTER_BG,
);
}
TextIns::draw_glyphs_with_scroll_sticky(cx, &self._line_number_glyphs, &TEXT_STYLE_MONO, true, false);
self._final_fill_height = cx.get_height_total() - self._monospace_size.y;
self.draw_shadows(cx);
self.do_selection_scrolling(cx, text_buffer);
self.set_indent_line_highlight_id(cx);
cx.end_row();
self.view.end_view(cx);
self.component_base.register_component_area(cx, self.view.area());
if let Some(((head, tail), at_top)) = self._set_last_cursor {
self._set_last_cursor = None;
self._scroll_pos_on_load = None;
self.cursors.clear_and_set_last_cursor_head_and_tail(head, tail, text_buffer);
if at_top {
self.scroll_last_cursor_top(cx, text_buffer);
} else {
self.scroll_last_cursor_visible(cx, text_buffer, self._final_fill_height * 0.8);
}
cx.request_draw();
} else if let Some(scroll_pos_on_load) = self._scroll_pos_on_load {
self.view.set_scroll_pos(cx, scroll_pos_on_load);
self._scroll_pos_on_load = None;
}
}
pub fn set_last_cursor(&mut self, cx: &mut Cx, cursor: (usize, usize), at_top: bool) {
self._set_last_cursor = Some((cursor, at_top));
cx.request_draw();
}
fn draw_cursors(&mut self, cx: &mut Cx) {
if self.has_key_focus(cx) {
let origin = cx.get_box_origin();
self.cursor.blink = self._cursor_blink_flipflop;
for rc in &self._draw_cursors.cursors {
self.cursor.draw_quad_rel(cx, Rect { pos: vec2(rc.x, rc.y) - origin, size: vec2(rc.w, rc.h) });
}
}
}
fn draw_shadows(&mut self, cx: &mut Cx) {
ScrollShadow::draw_shadow_left_at(
cx,
Rect { pos: vec2(self.line_number_width, 0.), size: vec2(0., cx.get_height_total()) },
1.0,
);
ScrollShadow::draw_shadow_top(cx, 1.0);
}
fn draw_message_markers(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
let origin = cx.get_box_origin();
let message_markers = &mut self._draw_messages.selections;
for mark in message_markers {
let body = &text_buffer.markers.message_bodies[mark.index];
self.message_marker.color = match body.level {
TextBufferMessageLevel::Warning => self.colors.message_marker_warning,
TextBufferMessageLevel::Error => self.colors.message_marker_error,
TextBufferMessageLevel::Log => self.colors.message_marker_log,
};
self.message_marker.draw_quad_abs(cx, Rect { pos: cx.get_box_origin() + mark.rc.pos - origin, size: mark.rc.size });
}
}
pub fn draw_search_markers(&mut self, cx: &mut Cx) {
let origin = cx.get_box_origin();
for mark in &self._draw_search.selections {
self.search_marker.draw_quad_abs(cx, Rect { pos: cx.get_box_origin() + mark.rc.pos - origin, size: mark.rc.size });
}
}
pub fn draw_selections(&mut self, cx: &mut Cx) {
let color = if self.has_key_focus(cx) { COLOR_SELECTION } else { COLOR_SELECTION_DEFOCUS };
let sel = &mut self._draw_cursors.selections;
let data: Vec<SelectionIns> = (0..sel.len())
.map(|i| {
let cur = &sel[i];
let mut selection = SelectionIns {
base: QuadIns::from_rect(Rect { pos: cur.rc.pos, size: cur.rc.size }),
color,
..SelectionIns::default()
};
if i > 0 && sel[i - 1].index == cur.index {
let p_rc = &sel[i - 1].rc;
selection.prev_x = p_rc.pos.x - cur.rc.pos.x;
selection.prev_w = p_rc.size.x;
} else {
selection.prev_x = 0.;
selection.prev_w = -1.;
}
if i < sel.len() - 1 && sel[i + 1].index == cur.index {
let n_rc = &sel[i + 1].rc;
selection.next_x = n_rc.pos.x - cur.rc.pos.x;
selection.next_w = n_rc.size.x;
} else {
selection.next_x = 0.;
selection.next_w = -1.;
}
selection
})
.collect();
cx.add_instances(&SHADER_SELECTION, &data);
}
fn place_ime_and_draw_cursor_row(&mut self, cx: &mut Cx) {
if let Some(last_cursor) = self._draw_cursors.last_cursor {
let rc = self._draw_cursors.cursors[last_cursor];
if self.cursors.get_last_cursor_singular().is_some() {
if self.draw_cursor_row {
self.cursor_row.draw(
cx,
Rect {
pos: vec2(self.line_number_width + cx.get_box_origin().x, rc.y),
size: vec2(cx.get_width_total().max(cx.get_box_bounds().x) - self.line_number_width, rc.h),
},
COLOR_CURSOR_ROW,
);
}
}
if cx.has_key_focus(Some(&self.component_base)) {
let scroll_pos = self.view.get_scroll_pos(cx);
cx.show_text_ime(rc.x - scroll_pos.x, rc.y - scroll_pos.y);
} else {
cx.hide_text_ime();
}
}
}
fn do_selection_scrolling(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
if let Some(select_scroll) = self._select_scroll.clone() {
if let Some(grid_select_corner) = self._grid_select_corner {
let pos = self.compute_grid_text_pos_from_abs(cx, select_scroll.abs);
self.cursors.grid_select(grid_select_corner, pos, text_buffer);
} else if let Some(offset) =
TextIns::closest_offset(cx, &self.text_area, select_scroll.abs, TEXT_STYLE_MONO.line_spacing)
{
self.cursors.set_last_cursor_head(offset, text_buffer);
}
if select_scroll.at_end {
self._select_scroll = None;
}
cx.request_draw();
}
}
fn _do_selection_animations(&mut self, cx: &mut Cx) {
if !self._anim_folding.state.is_animating() {
let sel = &mut self._draw_cursors.selections;
let mut anim_select_any = false;
for (i, cur) in sel.iter_mut().enumerate() {
let start_time = if self._select_scroll.is_none() && self._last_finger_move.is_some() { 1. } else { 0. };
if i < self._anim_select.len() && cur.rc.pos.y < self._anim_select[i].ypos {
self._anim_select.insert(i, AnimSelect { time: start_time, invert: true, ypos: cur.rc.pos.y });
}
let (wtime, htime, invert) = if i < self._anim_select.len() {
let len = self._anim_select.len() - 1;
let anim = &mut self._anim_select[i];
anim.ypos = cur.rc.pos.y;
if anim.time <= 0.0001 {
anim.time = 0.0
} else {
anim.time *= 0.55;
anim_select_any = true;
}
if i == len {
(anim.time, anim.time, i == 0 && anim.invert)
} else {
(anim.time, 0., i == 0 && anim.invert)
}
} else {
self._anim_select.push(AnimSelect { time: start_time, invert: i == 0, ypos: cur.rc.pos.y });
anim_select_any = true;
(start_time, start_time, false)
};
let wtime = 1.0 - wtime as f32;
let htime = 1.0 - htime as f32;
if invert {
cur.rc.size.x *= wtime;
cur.rc.size.y *= htime;
} else {
cur.rc.pos.x += cur.rc.size.x * (1. - wtime);
cur.rc.size.x *= wtime;
cur.rc.size.y *= htime;
}
}
self._anim_select.truncate(sel.len());
if anim_select_any {
cx.request_draw();
}
}
}
fn set_indent_line_highlight_id(&mut self, cx: &mut Cx) {
if !self._indent_line_inst.is_empty() {
let indent_id = if self.cursors.is_last_cursor_singular() && self._last_cursor_pos.row < self._line_geometry.len() {
self._line_geometry[self._last_cursor_pos.row].indent_id
} else {
0.
};
self.indent_lines.set_indent_sel(cx, indent_id);
}
}
fn set_font_scale(&mut self, _cx: &Cx, font_scale: f32) {
self.current_font_scale = font_scale;
if font_scale > self._line_largest_font {
self._line_largest_font = font_scale;
}
self._monospace_size.x = self._monospace_base.x * TEXT_STYLE_MONO.font_size * font_scale;
self._monospace_size.y = self._monospace_base.y * TEXT_STYLE_MONO.font_size * font_scale;
}
pub fn reset_cursors(&mut self) {
self.cursors = TextCursorSet::default();
}
fn scroll_last_cursor_visible(&mut self, cx: &mut Cx, text_buffer: &TextBuffer, height_pad: f32) {
if self.cursors.last_cursor >= self.cursors.set.len() {
panic!("LAST CURSOR INVALID");
}
let pos = self.cursors.get_last_cursor_text_pos(text_buffer);
if !self._line_geometry.is_empty() {
let row = pos.row.min(self._line_geometry.len() - 1);
if row < self._line_geometry.len() {
let geom = &self._line_geometry[row];
let mono_size = Vec2 { x: self._monospace_base.x * geom.font_size, y: self._monospace_base.y * geom.font_size };
let rect = Rect {
pos: vec2(
(pos.col as f32) * mono_size.x, geom.walk.y - mono_size.y * 1. - 0.5 * height_pad,
),
size: vec2(mono_size.x * 4. + self.line_number_width, mono_size.y * 4. + height_pad),
};
self.view.scroll_into_view(cx, rect);
}
}
}
fn scroll_last_cursor_top(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
if self.cursors.last_cursor >= self.cursors.set.len() {
panic!("LAST CURSOR INVALID");
}
let pos = self.cursors.get_last_cursor_text_pos(text_buffer);
let row = pos.row.min(self._line_geometry.len() - 1);
if row < self._line_geometry.len() {
let geom = &self._line_geometry[row];
let mono_size = Vec2 { x: self._monospace_base.x * geom.font_size, y: self._monospace_base.y * geom.font_size };
let rect = Rect {
pos: vec2(
0., geom.walk.y - mono_size.y * 1.,
),
size: vec2(mono_size.x * 4. + self.line_number_width, self._final_fill_height + mono_size.y * 1.),
};
self.view.scroll_into_view_no_smooth(cx, rect);
}
}
fn compute_grid_text_pos_from_abs(&mut self, cx: &Cx, abs: Vec2) -> TextPos {
let rel = abs - self.view.area().get_rect_for_first_instance(cx).pos;
let mut mono_size = Vec2::default();
for (row, geom) in self._line_geometry.iter().enumerate() {
mono_size = Vec2 { x: self._monospace_base.x * geom.font_size, y: self._monospace_base.y * geom.font_size };
if rel.y < geom.walk.y || rel.y >= geom.walk.y && rel.y <= geom.walk.y + mono_size.y {
let col = ((rel.x - self.line_number_width).max(0.) / mono_size.x) as usize;
return TextPos { row, col };
}
}
TextPos { row: self._line_geometry.len() - 1, col: (rel.x.max(0.) / mono_size.x) as usize }
}
fn compute_offset_from_ypos(&mut self, cx: &Cx, ypos_abs: f32, text_buffer: &TextBuffer, end: bool) -> usize {
let rel = ypos_abs - self.view.area().get_rect_for_first_instance(cx).pos.y;
let mut mono_size;
let end_col = if end { 1 << 31 } else { 0 };
for (row, geom) in self._line_geometry.iter().enumerate() {
mono_size = Vec2 { x: self._monospace_base.x * geom.font_size, y: self._monospace_base.y * geom.font_size };
if rel < geom.walk.y || rel >= geom.walk.y && rel <= geom.walk.y + mono_size.y {
return text_buffer.text_pos_to_offset(TextPos { row, col: end_col });
}
}
text_buffer.text_pos_to_offset(TextPos { row: self._line_geometry.len() - 1, col: end_col })
}
fn start_code_folding(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
let speed = 0.98;
self._anim_folding.state.do_folding(speed, 0.95);
self._anim_folding.focussed_line = self.compute_focussed_line_for_folding(cx, text_buffer);
cx.request_draw();
}
fn start_code_unfolding(&mut self, cx: &mut Cx, text_buffer: &TextBuffer) {
let speed = 0.96;
self._anim_folding.state.do_opening(speed, 0.97);
self._anim_folding.focussed_line = self.compute_focussed_line_for_folding(cx, text_buffer);
cx.request_draw();
}
fn check_select_scroll_dynamics(&mut self, fe: &FingerMoveEvent) -> bool {
let pow_scale = 0.1;
let pow_fac = 3.;
let max_speed = 40.;
let pad_scroll = 20.;
let rect = Rect { pos: fe.rect.pos + pad_scroll, size: fe.rect.size - 2. * pad_scroll };
let delta = Vec2 {
x: if fe.abs.x < rect.pos.x {
-((rect.pos.x - fe.abs.x) * pow_scale).powf(pow_fac).min(max_speed)
} else if fe.abs.x > rect.pos.x + rect.size.x {
((fe.abs.x - (rect.pos.x + rect.size.x)) * pow_scale).powf(pow_fac).min(max_speed)
} else {
0.
},
y: if fe.abs.y < rect.pos.y {
-((rect.pos.y - fe.abs.y) * pow_scale).powf(pow_fac).min(max_speed)
} else if fe.abs.y > rect.pos.y + rect.size.y {
((fe.abs.y - (rect.pos.y + rect.size.y)) * pow_scale).powf(pow_fac).min(max_speed)
} else {
0.
},
};
let last_scroll_none = self._select_scroll.is_none();
if delta.x != 0. || delta.y != 0. {
self._select_scroll = Some(SelectScroll { abs: fe.abs, delta, at_end: false });
} else {
self._select_scroll = None;
}
last_scroll_none
}
fn compute_next_unfolded_line_up(&self, text_buffer: &TextBuffer) -> usize {
let pos = self.cursors.get_last_cursor_text_pos(text_buffer);
let mut delta = 1;
if pos.row > 0 && pos.row < self._line_geometry.len() {
let mut scan = pos.row - 1;
while scan > 0 {
if !self._line_geometry[scan].was_folded {
delta = pos.row - scan;
break;
}
scan -= 1;
}
};
delta
}
fn compute_next_unfolded_line_down(&self, text_buffer: &TextBuffer) -> usize {
let pos = self.cursors.get_last_cursor_text_pos(text_buffer);
let mut delta = 1;
let mut scan = pos.row + 1;
while scan < self._line_geometry.len() {
if !self._line_geometry[scan].was_folded {
delta = scan - pos.row;
break;
}
scan += 1;
}
delta
}
fn compute_focussed_line_for_folding(&self, cx: &Cx, text_buffer: &TextBuffer) -> usize {
let scroll = self.view.get_scroll_pos(cx);
let rect = self.view.area().get_rect_for_first_instance(cx);
let pos = self.cursors.get_last_cursor_text_pos(text_buffer);
if pos.row < self._line_geometry.len() {
let geom = &self._line_geometry[pos.row];
if geom.walk.y - scroll.y > 0. && geom.walk.y - scroll.y < rect.size.y {
return pos.row;
}
}
let scroll = self.view.get_scroll_pos(cx);
let center_y = rect.size.y * 0.5 + scroll.y;
for (line, geom) in self._line_geometry.iter().enumerate() {
if geom.walk.y > center_y {
return line;
}
}
for (line, geom) in self._line_geometry.iter().enumerate() {
if geom.walk.y > scroll.y {
return line;
}
}
0
}
}
#[derive(Clone)]
pub enum AnimFoldingState {
Open,
Opening(f32, f32, f32),
Folded,
Folding(f32, f32, f32),
}
pub struct AnimFolding {
pub state: AnimFoldingState,
pub focussed_line: usize,
pub did_animate: bool,
}
#[derive(Clone)]
pub struct AnimSelect {
pub ypos: f32,
pub invert: bool,
pub time: f64,
}
#[derive(Clone, Default)]
pub struct LineGeom {
walk: Vec2,
was_folded: bool,
font_size: f32,
indent_id: f32,
}
#[derive(Clone, Default)]
pub struct SelectScroll {
pub delta: Vec2,
pub abs: Vec2,
pub at_end: bool,
}
#[derive(Clone)]
pub struct ParenItem {
pair_start: usize,
geom_open: Option<Rect>,
geom_close: Option<Rect>,
marked: bool,
exp_paren: char,
}
impl AnimFoldingState {
fn is_animating(&self) -> bool {
!matches!(self, AnimFoldingState::Open | AnimFoldingState::Folded)
}
fn is_folded(&self) -> bool {
matches!(self, AnimFoldingState::Folded | AnimFoldingState::Folding(_, _, _))
}
fn get_font_size(&self, open_size: f32, folded_size: f32) -> f32 {
match self {
AnimFoldingState::Open => open_size,
AnimFoldingState::Folded => folded_size,
AnimFoldingState::Opening(f, _, _) => f * folded_size + (1. - f) * open_size,
AnimFoldingState::Folding(f, _, _) => f * open_size + (1. - f) * folded_size,
}
}
fn do_folding(&mut self, speed: f32, speed2: f32) {
*self = match self {
AnimFoldingState::Open => AnimFoldingState::Folding(1.0, speed, speed2),
AnimFoldingState::Folded => AnimFoldingState::Folded,
AnimFoldingState::Opening(f, _, _) => AnimFoldingState::Folding(1.0 - *f, speed, speed2),
AnimFoldingState::Folding(f, _, _) => AnimFoldingState::Folding(*f, speed, speed2),
}
}
fn do_opening(&mut self, speed: f32, speed2: f32) {
*self = match self {
AnimFoldingState::Open => AnimFoldingState::Open,
AnimFoldingState::Folded => AnimFoldingState::Opening(1.0, speed, speed2),
AnimFoldingState::Opening(f, _, _) => AnimFoldingState::Opening(*f, speed, speed2),
AnimFoldingState::Folding(f, _, _) => AnimFoldingState::Opening(1.0 - *f, speed, speed2),
}
}
fn next_anim_step(&mut self) {
*self = match self {
AnimFoldingState::Open => AnimFoldingState::Open,
AnimFoldingState::Folded => AnimFoldingState::Folded,
AnimFoldingState::Opening(f, speed, speed2) => {
let new_f = *f * *speed;
if new_f < 0.001 {
AnimFoldingState::Open
} else {
AnimFoldingState::Opening(new_f, *speed * *speed2, *speed2)
}
}
AnimFoldingState::Folding(f, speed, speed2) => {
let new_f = *f * *speed;
if new_f < 0.001 {
AnimFoldingState::Folded
} else {
AnimFoldingState::Folding(new_f, *speed * *speed2, *speed2)
}
}
}
}
}