♻️ Refactor minor things

This commit is contained in:
Aitor Moreno
2026-02-02 09:34:37 +01:00
committed by Elena Torro
parent a14c36e996
commit 54f63c5dc5
5 changed files with 79 additions and 48 deletions

View File

@@ -1,12 +1,7 @@
use crate::shapes::{Shape, TextContent, Type, VerticalAlign};
use crate::state::{TextCursor, TextEditorState, TextSelection};
use crate::state::{TextEditorState, TextSelection};
use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
use skia_safe::{Canvas, Color, Matrix, Paint, Rect};
const CURSOR_WIDTH: f32 = 1.5;
/// FIXME: Use theme color, take into account background color for contrast
const SELECTION_COLOR: Color = Color::from_argb(80, 66, 133, 244);
const CURSOR_COLOR: Color = Color::BLACK;
use skia_safe::{BlendMode, Canvas, Matrix, Paint, Rect};
pub fn render_overlay(
canvas: &Canvas,
@@ -26,23 +21,28 @@ pub fn render_overlay(
canvas.concat(transform);
if editor_state.selection.is_selection() {
render_selection(canvas, &editor_state.selection, text_content, shape);
render_selection(canvas, editor_state, text_content, shape);
}
if editor_state.cursor_visible {
render_cursor(canvas, &editor_state.selection.focus, text_content, shape);
render_cursor(canvas, editor_state, text_content, shape);
}
canvas.restore();
}
fn render_cursor(canvas: &Canvas, cursor: &TextCursor, text_content: &TextContent, shape: &Shape) {
let Some(rect) = calculate_cursor_rect(cursor, text_content, shape) else {
fn render_cursor(
canvas: &Canvas,
editor_state: &TextEditorState,
text_content: &TextContent,
shape: &Shape,
) {
let Some(rect) = calculate_cursor_rect(editor_state, text_content, shape) else {
return;
};
let mut paint = Paint::default();
paint.set_color(CURSOR_COLOR);
paint.set_color(editor_state.theme.cursor_color);
paint.set_anti_alias(true);
canvas.draw_rect(rect, &paint);
@@ -50,10 +50,11 @@ fn render_cursor(canvas: &Canvas, cursor: &TextCursor, text_content: &TextConten
fn render_selection(
canvas: &Canvas,
selection: &TextSelection,
editor_state: &TextEditorState,
text_content: &TextContent,
shape: &Shape,
) {
let selection = &editor_state.selection;
let rects = calculate_selection_rects(selection, text_content, shape);
if rects.is_empty() {
@@ -61,9 +62,9 @@ fn render_selection(
}
let mut paint = Paint::default();
paint.set_color(SELECTION_COLOR);
paint.set_blend_mode(BlendMode::Multiply);
paint.set_color(editor_state.theme.selection_color);
paint.set_anti_alias(true);
for rect in rects {
canvas.draw_rect(rect, &paint);
}
@@ -82,10 +83,11 @@ fn vertical_align_offset(
}
fn calculate_cursor_rect(
cursor: &TextCursor,
editor_state: &TextEditorState,
text_content: &TextContent,
shape: &Shape,
) -> Option<Rect> {
let cursor = editor_state.selection.focus;
let paragraphs = text_content.paragraphs();
if cursor.paragraph >= paragraphs.len() {
return None;
@@ -120,7 +122,7 @@ fn calculate_cursor_rect(
} else if char_pos == 0 {
let rects = laid_out_para.get_rects_for_range(
0..1,
RectHeightStyle::Tight,
RectHeightStyle::Max,
RectWidthStyle::Tight,
);
if !rects.is_empty() {
@@ -131,7 +133,7 @@ fn calculate_cursor_rect(
} else if char_pos >= para_char_count {
let rects = laid_out_para.get_rects_for_range(
para_char_count.saturating_sub(1)..para_char_count,
RectHeightStyle::Tight,
RectHeightStyle::Max,
RectWidthStyle::Tight,
);
if !rects.is_empty() {
@@ -142,7 +144,7 @@ fn calculate_cursor_rect(
} else {
let rects = laid_out_para.get_rects_for_range(
char_pos..char_pos + 1,
RectHeightStyle::Tight,
RectHeightStyle::Max,
RectWidthStyle::Tight,
);
if !rects.is_empty() {
@@ -157,7 +159,7 @@ fn calculate_cursor_rect(
return Some(Rect::from_xywh(
selrect.x() + cursor_x,
selrect.y() + y_offset,
CURSOR_WIDTH,
editor_state.theme.cursor_width,
cursor_height,
));
}
@@ -216,7 +218,7 @@ fn calculate_selection_rects(
use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
let text_boxes = laid_out_para.get_rects_for_range(
range_start..range_end,
RectHeightStyle::Tight,
RectHeightStyle::Max,
RectWidthStyle::Tight,
);

View File

@@ -2,6 +2,7 @@
use crate::shapes::TextPositionWithAffinity;
use crate::uuid::Uuid;
use skia_safe::Color;
/// Cursor position within text content.
/// Uses character offsets for precise positioning.
@@ -105,27 +106,41 @@ pub enum EditorEvent {
NeedsLayout = 3,
}
/// FIXME: It should be better to get these constants from the frontend through the API.
const SELECTION_COLOR: Color = Color::from_argb(255, 0, 209, 184);
const CURSOR_WIDTH: f32 = 1.5;
const CURSOR_COLOR: Color = Color::BLACK;
const CURSOR_BLINK_INTERVAL_MS: f64 = 530.0;
pub struct TextEditorTheme {
pub selection_color: Color,
pub cursor_width: f32,
pub cursor_color: Color,
}
pub struct TextEditorState {
pub theme: TextEditorTheme,
pub selection: TextSelection,
pub is_active: bool,
pub active_shape_id: Option<Uuid>,
pub cursor_visible: bool,
pub last_blink_time: f64,
pub x_affinity: Option<f32>,
pending_events: Vec<EditorEvent>,
}
const CURSOR_BLINK_INTERVAL_MS: f64 = 530.0;
impl TextEditorState {
pub fn new() -> Self {
Self {
theme: TextEditorTheme {
selection_color: SELECTION_COLOR,
cursor_width: CURSOR_WIDTH,
cursor_color: CURSOR_COLOR,
},
selection: TextSelection::new(),
is_active: false,
active_shape_id: None,
cursor_visible: true,
last_blink_time: 0.0,
x_affinity: None,
pending_events: Vec::new(),
}
}
@@ -136,7 +151,6 @@ impl TextEditorState {
self.cursor_visible = true;
self.last_blink_time = 0.0;
self.selection = TextSelection::new();
self.x_affinity = None;
self.pending_events.clear();
}
@@ -144,7 +158,6 @@ impl TextEditorState {
self.is_active = false;
self.active_shape_id = None;
self.cursor_visible = false;
self.x_affinity = None;
self.pending_events.clear();
}
@@ -152,7 +165,6 @@ impl TextEditorState {
let cursor = TextCursor::new(position.paragraph as usize, position.offset as usize);
self.selection.set_caret(cursor);
self.reset_blink();
self.clear_x_affinity();
self.push_event(EditorEvent::SelectionChanged);
}
@@ -186,10 +198,6 @@ impl TextEditorState {
self.last_blink_time = 0.0;
}
pub fn clear_x_affinity(&mut self) {
self.x_affinity = None;
}
pub fn push_event(&mut self, event: EditorEvent) {
if self.pending_events.last() != Some(&event) {
self.pending_events.push(event);

View File

@@ -1,3 +1,5 @@
use macros::ToJs;
use crate::math::{Matrix, Point, Rect};
use crate::mem;
use crate::shapes::{Paragraph, Shape, TextContent, Type, VerticalAlign};
@@ -6,6 +8,18 @@ use crate::utils::uuid_from_u32_quartet;
use crate::utils::uuid_to_u32_quartet;
use crate::{with_state, with_state_mut, STATE};
#[derive(PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum CursorDirection {
Backward = 0,
Forward = 1,
LineBefore = 2,
LineAfter = 3,
LineStart = 4,
LineEnd = 5,
}
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
@@ -462,7 +476,7 @@ pub extern "C" fn text_editor_insert_paragraph() {
// ============================================================================
#[no_mangle]
pub extern "C" fn text_editor_move_cursor(direction: u8, extend_selection: bool) {
pub extern "C" fn text_editor_move_cursor(direction: CursorDirection, extend_selection: bool) {
with_state_mut!(state, {
if !state.text_editor_state.is_active {
return;
@@ -488,13 +502,16 @@ pub extern "C" fn text_editor_move_cursor(direction: u8, extend_selection: bool)
let current = state.text_editor_state.selection.focus;
let new_cursor = match direction {
0 => move_cursor_left(&current, paragraphs),
1 => move_cursor_right(&current, paragraphs),
2 => move_cursor_up(&current, paragraphs, text_content, shape),
3 => move_cursor_down(&current, paragraphs, text_content, shape),
4 => move_cursor_line_start(&current, paragraphs),
5 => move_cursor_line_end(&current, paragraphs),
_ => current,
CursorDirection::Backward => move_cursor_backward(&current, paragraphs),
CursorDirection::Forward => move_cursor_forward(&current, paragraphs),
CursorDirection::LineBefore => {
move_cursor_up(&current, paragraphs, text_content, shape)
}
CursorDirection::LineAfter => {
move_cursor_down(&current, paragraphs, text_content, shape)
}
CursorDirection::LineStart => move_cursor_line_start(&current, paragraphs),
CursorDirection::LineEnd => move_cursor_line_end(&current, paragraphs),
};
if extend_selection {
@@ -504,11 +521,6 @@ pub extern "C" fn text_editor_move_cursor(direction: u8, extend_selection: bool)
}
state.text_editor_state.reset_blink();
if direction == 0 || direction == 1 || direction == 4 || direction == 5 {
state.text_editor_state.clear_x_affinity();
}
state
.text_editor_state
.push_event(crate::state::EditorEvent::SelectionChanged);
@@ -944,7 +956,7 @@ fn clamp_cursor(cursor: TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
}
/// Move cursor left by one character.
fn move_cursor_left(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
fn move_cursor_backward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
if cursor.char_offset > 0 {
TextCursor::new(cursor.paragraph, cursor.char_offset - 1)
} else if cursor.paragraph > 0 {
@@ -957,7 +969,7 @@ fn move_cursor_left(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor
}
/// Move cursor right by one character.
fn move_cursor_right(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
fn move_cursor_forward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
let para = &paragraphs[cursor.paragraph];
let char_count = paragraph_char_count(para);