mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
WIP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
9
render-wasm/Cargo.lock
generated
9
render-wasm/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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!();
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
8
render-wasm/src/text_editor/Cargo.toml
Normal file
8
render-wasm/src/text_editor/Cargo.toml
Normal 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"
|
||||
12
render-wasm/src/text_editor/src/events.rs
Normal file
12
render-wasm/src/text_editor/src/events.rs
Normal 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
|
||||
}
|
||||
153
render-wasm/src/text_editor/src/lib.rs
Normal file
153
render-wasm/src/text_editor/src/lib.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user