This commit is contained in:
Elena Torro
2025-11-26 18:12:35 +01:00
parent db0cbbbc2e
commit 369979ffe6
11 changed files with 257 additions and 19 deletions

View File

@@ -216,6 +216,18 @@
on-frame-leave (actions/on-frame-leave frame-hover)
on-frame-select (actions/on-frame-select selected read-only?)
;; Text Editor Event Handlers
on-text-keydown (fn [event]
(when (and text-editing? (.-key event))
(.preventDefault event)
(wasm.api/handle-text-keydown (.-key event))))
on-text-mousedown (fn [event]
(when text-editing?
(let [rect (.getBoundingClientRect (.-currentTarget event))
x (- (.-clientX event) (.-left rect))
y (- (.-clientY event) (.-top rect))]
(wasm.api/handle-text-mousedown x y))))
disable-events? (contains? layout :comments)
show-comments? (= drawing-tool :comments)
show-cursor-tooltip? tooltip
@@ -231,8 +243,9 @@
show-pixel-grid? (and (contains? layout :show-pixel-grid)
(>= zoom 8))
show-text-editor? (and editing-shape (= :text (:type editing-shape)))
;; show-text-editor? (and editing-shape (= :text (:type editing-shape)))
show-text-editor? false
hover-grid? (and (some? @hover-top-frame-id)
(ctl/grid-layout? objects @hover-top-frame-id))
@@ -419,7 +432,9 @@
:on-pointer-enter on-pointer-enter
:on-pointer-leave on-pointer-leave
:on-pointer-move on-pointer-move
:on-pointer-up on-pointer-up}
:on-pointer-up on-pointer-up
:on-key-down on-text-keydown
:on-mouse-down on-text-mousedown}
[:defs
;; This clip is so the handlers are not over the rulers

View File

@@ -1212,6 +1212,7 @@
(defn init-canvas-context
[canvas]
(let [gl (unchecked-get wasm/internal-module "GL")
flags (debug-flags)
context-id (if (dbg/enabled? :wasm-gl-context-init-error) "fail" "webgl2")
@@ -1258,6 +1259,19 @@
(h/call wasm/internal-module "_hide_grid")
(request-render "clear-grid"))
;; Text Editor Functions
(defn handle-text-keydown
[key]
(when (and wasm/internal-module key)
(h/call wasm/internal-module "_handle_keydown" key)
(request-render "text-editor-keydown")))
(defn handle-text-mousedown
[x y]
(when (and wasm/internal-module x y)
(h/call wasm/internal-module "_handle_mousedown" x y)
(request-render "text-editor-mousedown")))
(defn get-grid-coords
[position]
(let [offset (h/call wasm/internal-module

View File

@@ -432,6 +432,7 @@ dependencies = [
"indexmap",
"macros",
"skia-safe",
"text_editor",
"uuid",
]
@@ -577,6 +578,14 @@ dependencies = [
"xattr",
]
[[package]]
name = "text_editor"
version = "0.1.0"
dependencies = [
"skia-safe",
"wasm-bindgen",
]
[[package]]
name = "toml"
version = "0.8.19"

View File

@@ -32,6 +32,7 @@ skia-safe = { version = "0.87.0", default-features = false, features = [
"binary-cache",
"webp",
] }
text_editor = { path = "src/text_editor" }
uuid = { version = "1.11.0", features = ["v4", "js"] }
[profile.release]

View File

@@ -650,6 +650,38 @@ pub extern "C" fn set_modifiers() {
});
}
#[no_mangle]
pub extern "C" fn handle_keydown(key_ptr: *const std::os::raw::c_char) {
if key_ptr.is_null() {
return;
}
let key = unsafe {
match std::ffi::CStr::from_ptr(key_ptr).to_str() {
Ok(s) => s,
Err(_) => return, // Invalid UTF-8, skip
}
};
with_state_mut!(state, {
state.render_state.text_editor.handle_keydown(key);
});
render_sync();
}
#[no_mangle]
pub extern "C" fn handle_mousedown(x: f32, y: f32) {
// Basic sanity checks
if !x.is_finite() || !y.is_finite() {
return;
}
with_state_mut!(state, {
state.render_state.text_editor.handle_mousedown(x, y);
});
render_sync();
}
fn main() {
#[cfg(target_arch = "wasm32")]
init_gl!();

View File

@@ -215,6 +215,7 @@ pub(crate) struct RenderState {
pub options: RenderOptions,
pub surfaces: Surfaces,
pub fonts: FontStore,
pub text_editor: ::text_editor::TextEditor,
pub viewbox: Viewbox,
pub cached_viewbox: Viewbox,
pub cached_target_snapshot: Option<skia::Image>,
@@ -288,12 +289,14 @@ impl RenderState {
let viewbox = Viewbox::new(width as f32, height as f32);
let tiles = tiles::TileHashMap::new();
let text_editor = ::text_editor::TextEditor::new(fonts.debug_font.clone());
RenderState {
gpu_state: gpu_state.clone(),
options: RenderOptions::default(),
surfaces,
fonts,
text_editor,
viewbox,
cached_viewbox: Viewbox::new(0., 0.),
cached_target_snapshot: None,
@@ -916,6 +919,8 @@ impl RenderState {
ui::render(self, shapes);
debug::render_wasm_label(self);
self.text_editor.render(self.surfaces.canvas(SurfaceId::Target));
self.flush_and_submit();
}
}
@@ -1791,6 +1796,8 @@ impl RenderState {
ui::render(self, tree);
debug::render_wasm_label(self);
self.text_editor.render(self.surfaces.canvas(SurfaceId::Target));
Ok(())
}

View File

@@ -21,7 +21,7 @@ pub struct FontStore {
font_mgr: FontMgr,
font_provider: textlayout::TypefaceFontProvider,
font_collection: textlayout::FontCollection,
debug_font: Font,
pub debug_font: Font,
fallback_fonts: HashSet<String>,
}

View File

@@ -1,10 +1,8 @@
use skia_safe::{self as skia, textlayout::FontCollection, Path, Point};
use skia_safe::{self as skia, Path, Point, textlayout::FontCollection};
use std::collections::HashMap;
mod shapes_pool;
mod text_editor;
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
pub use text_editor::*;
use crate::render::RenderState;
use crate::shapes::Shape;
@@ -20,7 +18,6 @@ use crate::shapes::modifiers::grid_layout::grid_cell_data;
/// must not be shared between different Web Workers.
pub(crate) struct State<'a> {
pub render_state: RenderState,
pub text_editor_state: TextEditorState,
pub current_id: Option<Uuid>,
pub current_browser: u8,
pub shapes: ShapesPool<'a>,
@@ -28,9 +25,9 @@ pub(crate) struct State<'a> {
impl<'a> State<'a> {
pub fn new(width: i32, height: i32) -> Self {
let render_state = RenderState::new(width, height);
State {
render_state: RenderState::new(width, height),
text_editor_state: TextEditorState::new(),
render_state,
current_id: None,
current_browser: 0,
shapes: ShapesPool::new(),
@@ -49,16 +46,6 @@ impl<'a> State<'a> {
&self.render_state
}
#[allow(dead_code)]
pub fn text_editor_state_mut(&mut self) -> &mut TextEditorState {
&mut self.text_editor_state
}
#[allow(dead_code)]
pub fn text_editor_state(&self) -> &TextEditorState {
&self.text_editor_state
}
pub fn render_from_cache(&mut self) {
self.render_state.render_from_cache(&self.shapes);
}

View File

@@ -0,0 +1,8 @@
[package]
name = "text_editor"
version = "0.1.0"
edition = "2021"
[dependencies]
skia-safe = { version = "0.87.0", default-features = false, features = ["gl"] }
wasm-bindgen = "0.2"

View File

@@ -0,0 +1,12 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn handle_keydown(key: String) {
// TODO: Handle keydown event
}
#[wasm_bindgen]
pub fn handle_mousedown(x: f32, y: f32) {
// TODO: Handle mousedown event
}

View File

@@ -0,0 +1,153 @@
pub mod events;
use skia_safe::{Canvas, Font, Paint, Point, Color};
pub struct TextEditor {
text: Vec<String>,
font: Font,
cursor_pos: Point,
}
impl TextEditor {
pub fn new(font: Font) -> Self {
TextEditor {
text: vec!["Hello, Skia!".to_string()],
font,
cursor_pos: Point::new(0.0, 0.0),
}
}
pub fn render(&self, canvas: &Canvas) {
let mut paint = Paint::default();
paint.set_color(Color::BLACK);
paint.set_anti_alias(true);
for (i, line) in self.text.iter().enumerate() {
canvas.draw_str(line, (20.0, 20.0 + (i as f32 * 18.0)), &self.font, &paint);
}
// Draw cursor - with bounds checking
let mut cursor_paint = Paint::default();
cursor_paint.set_color(Color::BLACK);
cursor_paint.set_anti_alias(true);
let y_idx = self.cursor_pos.y as usize;
let x_idx = self.cursor_pos.x as usize;
if y_idx < self.text.len() {
let line = &self.text[y_idx];
let safe_x_idx = x_idx.min(line.len());
let (x, _) = if safe_x_idx > 0 {
self.font.measure_str(&line[..safe_x_idx], None)
} else {
(0.0, skia_safe::Rect::new_empty())
};
let cursor_rect = skia_safe::Rect::from_xywh(
20.0 + x,
20.0 + (y_idx as f32 * 18.0) - 18.0,
1.0,
18.0
);
canvas.draw_rect(cursor_rect, &cursor_paint);
}
}
pub fn handle_keydown(&mut self, key: &str) {
if self.text.is_empty() {
self.text.push(String::new());
}
let y = self.cursor_pos.y as usize;
let x = self.cursor_pos.x as usize;
if y >= self.text.len() {
return;
}
match key {
"ArrowLeft" => {
if x > 0 {
self.cursor_pos.x -= 1.0;
} else if y > 0 {
self.cursor_pos.y -= 1.0;
self.cursor_pos.x = self.text[y - 1].len() as f32;
}
}
"ArrowRight" => {
if x < self.text[y].len() {
self.cursor_pos.x += 1.0;
} else if y < self.text.len() - 1 {
self.cursor_pos.y += 1.0;
self.cursor_pos.x = 0.0;
}
}
"ArrowUp" => {
if y > 0 {
self.cursor_pos.y -= 1.0;
let new_y = y - 1;
self.cursor_pos.x = self.cursor_pos.x.min(self.text[new_y].len() as f32);
}
}
"ArrowDown" => {
if y < self.text.len() - 1 {
self.cursor_pos.y += 1.0;
let new_y = y + 1;
self.cursor_pos.x = self.cursor_pos.x.min(self.text[new_y].len() as f32);
}
}
"Backspace" => {
if x > 0 {
self.text[y].remove(x - 1);
self.cursor_pos.x -= 1.0;
} else if y > 0 {
let line = self.text.remove(y);
self.cursor_pos.y -= 1.0;
self.cursor_pos.x = self.text[y - 1].len() as f32;
self.text[y - 1].push_str(&line);
}
}
"Enter" => {
let line = self.text[y].split_off(x);
self.text.insert(y + 1, line);
self.cursor_pos.y += 1.0;
self.cursor_pos.x = 0.0;
}
_ => {
if key.len() == 1 {
if let Some(ch) = key.chars().next() {
self.text[y].insert(x, ch);
self.cursor_pos.x += 1.0;
}
}
}
}
}
pub fn handle_mousedown(&mut self, x: f32, y: f32) {
println!("@@@ Mouse down at: ({}, {})", x, y);
if self.text.is_empty() {
self.text.push(String::new());
}
let line_height = 18.0;
let y_pos = ((y - 20.0) / line_height).floor().max(0.0) as usize;
let y_pos = y_pos.min(self.text.len() - 1);
self.cursor_pos.y = y_pos as f32;
let line = &self.text[y_pos];
let mut closest_pos = 0;
let mut min_dist = f32::MAX;
for i in 0..=line.len() {
let (width, _) = self.font.measure_str(&line[..i], None);
let dist = (x - (20.0 + width)).abs();
if dist < min_dist {
min_dist = dist;
closest_pos = i;
}
}
self.cursor_pos.x = closest_pos as f32;
}
}