diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 43ff2a71af..ea48693eb2 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -788,14 +788,15 @@ (when (features/active-feature? state "render-wasm/v1") (rx/concat ;; Apply style to selected spans and sync content - (when (wasm.api/text-editor-is-active?) - (let [span-attrs (select-keys attrs txt/text-node-attrs)] - (when (not (empty? span-attrs)) - (let [result (wasm.api/apply-style-to-selection span-attrs)] - (when result - (rx/of (v2-update-text-shape-content - (:shape-id result) (:content result) - :update-name? true))))))) + (let [has-selection? (wasm.api/text-editor-has-selection?)] + (when has-selection? + (let [span-attrs (select-keys attrs txt/text-node-attrs)] + (when (not (empty? span-attrs)) + (let [result (wasm.api/apply-style-to-selection span-attrs)] + (when result + (rx/of (v2-update-text-shape-content + (:shape-id result) (:content result) + :update-name? true)))))))) ;; Resize (with delay for font-id changes) (cond->> (rx/of (dwwt/resize-wasm-text id)) (contains? attrs :font-id) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs index bfc2e07947..1b77ee8660 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs @@ -120,7 +120,7 @@ on-copy (mf/use-fn (fn [^js event] - (when (text-editor/text-editor-is-active?) + (when (text-editor/text-editor-has-focus?) (dom/prevent-default event) (when (text-editor/text-editor-get-selection) (let [text (text-editor/text-editor-export-selection)] @@ -129,7 +129,7 @@ on-cut (mf/use-fn (fn [^js event] - (when (text-editor/text-editor-is-active?) + (when (text-editor/text-editor-has-focus?) (dom/prevent-default event) (when (text-editor/text-editor-get-selection) (let [text (text-editor/text-editor-export-selection)] @@ -144,7 +144,7 @@ on-key-down (mf/use-fn (fn [^js event] - (when (and (text-editor/text-editor-is-active?) + (when (and (text-editor/text-editor-has-focus?) (not @composing?)) (let [key (.-key event) ctrl? (or (.-ctrlKey event) (.-metaKey event)) @@ -283,13 +283,13 @@ on-focus (mf/use-fn (fn [^js _event] - (wasm.api/text-editor-start shape-id))) + (wasm.api/text-editor-focus shape-id))) on-blur (mf/use-fn (fn [^js _event] (sync-wasm-text-editor-content! {:finalize? true}) - (wasm.api/text-editor-stop))) + (wasm.api/text-editor-blur))) style #js {:pointerEvents "all" "--editor-container-width" (dm/str width "px") @@ -312,11 +312,15 @@ (fn [] (let [timeout-id (atom nil) schedule-blink (fn schedule-blink [] - (when (text-editor/text-editor-is-active?) + (when (text-editor/text-editor-has-focus?) (wasm.api/request-render "cursor-blink")) (reset! timeout-id (js/setTimeout schedule-blink caret-blink-interval-ms)))] (schedule-blink) (fn [] + ;; ESTO ES JUSTO LO QUE NO QUIERO, NO QUIERO QUE SE HAGA + ;; DISPOSE CUANDO SE DESMONTA EL COMPONENTE. + #_(when (text-editor/text-editor-dispose) + (wasm.api/request-render "text-editor-dispose")) (when @timeout-id (js/clearTimeout @timeout-id)))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index f9f9319b06..e9b07b77b6 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -292,7 +292,7 @@ (when left-click? (st/emit! (mse/->MouseEvent :up ctrl? shift? alt? meta?)) - (when (wasm.api/text-editor-is-active?) + (when (wasm.api/text-editor-has-focus?) (wasm.api/text-editor-pointer-up (.-x off-pt) (.-y off-pt)))) (when middle-click? diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 27dc63e5c6..27164d94f0 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -92,14 +92,15 @@ (def clear-canvas-pixels webgl/clear-canvas-pixels) ;; Re-export public text editor functions -(def text-editor-start text-editor/text-editor-start) -(def text-editor-stop text-editor/text-editor-stop) +(def text-editor-focus text-editor/text-editor-focus) +(def text-editor-blur text-editor/text-editor-blur) (def text-editor-set-cursor-from-offset text-editor/text-editor-set-cursor-from-offset) (def text-editor-set-cursor-from-point text-editor/text-editor-set-cursor-from-point) (def text-editor-pointer-down text-editor/text-editor-pointer-down) (def text-editor-pointer-move text-editor/text-editor-pointer-move) (def text-editor-pointer-up text-editor/text-editor-pointer-up) -(def text-editor-is-active? text-editor/text-editor-is-active?) +(def text-editor-has-focus? text-editor/text-editor-has-focus?) +(def text-editor-has-selection? text-editor/text-editor-has-selection?) (def text-editor-select-all text-editor/text-editor-select-all) (def text-editor-select-word-boundary text-editor/text-editor-select-word-boundary) (def text-editor-sync-content text-editor/text-editor-sync-content) @@ -136,6 +137,13 @@ :fill "none"} [:& shape-wrapper {:shape shape}]])) +(defn is-text-editor-wasm-enabled + [state] + (let [runtime-features (get state :features-runtime) + enabled-features (get state :features)] + (or (contains? runtime-features "text-editor-wasm/v1") + (contains? enabled-features "text-editor-wasm/v1")))) + (defn get-static-markup [shape] (-> @@ -155,17 +163,17 @@ ;; Update text editor blink (so cursor toggles) using the same timestamp (try (when wasm/context-initialized? - (text-editor/text-editor-update-blink timestamp) ;; Render text editor overlay on top of main canvas (only if feature enabled) ;; Determine if text-editor-wasm feature is active without requiring ;; app.main.features to avoid circular dependency: check runtime and ;; persisted feature sets in the store state. - (when (text-editor-wasm?) - (text-editor/text-editor-render-overlay)) - ;; Poll for editor events; if any event occurs, trigger a re-render - (let [ev (text-editor/text-editor-poll-event)] - (when (and ev (not= ev 0)) - (request-render "text-editor-event")))) + (when (is-text-editor-wasm-enabled @st/state) + (text-editor/text-editor-update-blink timestamp) + (text-editor/text-editor-render-overlay) + ;; Poll for editor events; if any event occurs, trigger a re-render + (let [ev (text-editor/text-editor-poll-event)] + (when (and ev (not= ev 0)) + (request-render "text-editor-event"))))) (catch :default e (js/console.error "text-editor overlay/update failed:" e))) diff --git a/frontend/src/app/render_wasm/text_editor.cljs b/frontend/src/app/render_wasm/text_editor.cljs index 70c225ced2..75c8f4cdf8 100644 --- a/frontend/src/app/render_wasm/text_editor.cljs +++ b/frontend/src/app/render_wasm/text_editor.cljs @@ -12,16 +12,16 @@ [app.render-wasm.mem :as mem] [app.render-wasm.wasm :as wasm])) -(defn text-editor-start +(defn text-editor-focus [id] (when wasm/context-initialized? (let [buffer (uuid/get-u32 id)] - (when-not (h/call wasm/internal-module "_text_editor_start" + (when-not (h/call wasm/internal-module "_text_editor_focus" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3)) - (throw (js/Error. "TextEditor initialization failed")))))) + (throw (js/Error. "TextEditor focus failed")))))) (defn text-editor-set-cursor-from-offset "Sets caret position from shape relative coordinates" @@ -142,19 +142,29 @@ (when wasm/context-initialized? (h/call wasm/internal-module "_text_editor_select_word_boundary" x y))) -(defn text-editor-stop +(defn text-editor-blur [] (when wasm/context-initialized? - (when-not (h/call wasm/internal-module "_text_editor_stop") - (throw (js/Error. "TextEditor finalization failed"))))) + (when-not (h/call wasm/internal-module "_text_editor_blur") + (throw (js/Error. "TextEditor blur failed"))))) -(defn text-editor-is-active? +(defn text-editor-dispose + [] + (when wasm/context-initialized? + (h/call wasm/internal-module "_text_editor_dispose"))) + +(defn text-editor-has-focus? ([id] (when wasm/context-initialized? - (not (zero? (h/call wasm/internal-module "_text_editor_is_active_with_id" id))))) + (not (zero? (h/call wasm/internal-module "_text_editor_has_focus_with_id" id))))) ([] (when wasm/context-initialized? - (not (zero? (h/call wasm/internal-module "_text_editor_is_active")))))) + (not (zero? (h/call wasm/internal-module "_text_editor_has_focus")))))) + +(defn text-editor-has-selection? + ([] + (when wasm/context-initialized? + (not (zero? (h/call wasm/internal-module "_text_editor_has_selection")))))) (defn text-editor-export-content [] @@ -198,18 +208,20 @@ (defn text-editor-get-selection [] (when wasm/context-initialized? - (let [byte-offset (mem/alloc 16) - u32-offset (mem/->offset-32 byte-offset) - heap (mem/get-heap-u32) - active? (h/call wasm/internal-module "_text_editor_get_selection" byte-offset)] - (try - (when (= active? 1) - {:anchor-para (aget heap u32-offset) - :anchor-offset (aget heap (+ u32-offset 1)) - :focus-para (aget heap (+ u32-offset 2)) - :focus-offset (aget heap (+ u32-offset 3))}) - (finally - (mem/free)))))) + (let [byte-offset (mem/alloc 16) + u32-offset (mem/->offset-32 byte-offset) + heap (mem/get-heap-u32) + has-selection? (h/call wasm/internal-module "_text_editor_get_selection" byte-offset)] + (if has-selection? + (let [result {:anchor-para (aget heap u32-offset) + :anchor-offset (aget heap (+ u32-offset 1)) + :focus-para (aget heap (+ u32-offset 2)) + :focus-offset (aget heap (+ u32-offset 3))}] + (mem/free) + result) + (do + (mem/free) + nil))))) ;; This is used as a intermediate cache between Clojure global state and WASM state. (def ^:private shape-text-contents (atom {})) @@ -260,7 +272,7 @@ shape-id and the fully merged content map ready for v2-update-text-shape-content." [] - (when (and wasm/context-initialized? (text-editor-is-active?)) + (when (and wasm/context-initialized? (text-editor-has-focus?)) (let [shape-id (text-editor-get-active-shape-id) new-texts (text-editor-export-content)] (when (and shape-id new-texts) @@ -332,7 +344,7 @@ (defn apply-style-to-selection [attrs use-shape-fn set-shape-text-content-fn] - (when (and wasm/context-initialized? (text-editor-is-active?)) + (when wasm/context-initialized? (let [shape-id (text-editor-get-active-shape-id) sel (text-editor-get-selection)] (when (and shape-id sel) diff --git a/render-wasm/src/render/text_editor.rs b/render-wasm/src/render/text_editor.rs index 75178d146b..a9acc7bd57 100644 --- a/render-wasm/src/render/text_editor.rs +++ b/render-wasm/src/render/text_editor.rs @@ -4,7 +4,7 @@ use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; use skia_safe::{BlendMode, Canvas, Paint, Rect}; pub fn render_overlay(canvas: &Canvas, editor_state: &TextEditorState, shape: &Shape) { - if !editor_state.is_active { + if !editor_state.has_focus { return; } diff --git a/render-wasm/src/state/text_editor.rs b/render-wasm/src/state/text_editor.rs index 1c40c25053..cd65e92068 100644 --- a/render-wasm/src/state/text_editor.rs +++ b/render-wasm/src/state/text_editor.rs @@ -165,7 +165,7 @@ pub struct TextEditorState { pub theme: TextEditorTheme, pub selection: TextSelection, pub composition: TextComposition, - pub is_active: bool, + pub has_focus: bool, // This property indicates that we've started // selecting something with the pointer. pub is_pointer_selection_active: bool, @@ -185,7 +185,7 @@ impl TextEditorState { }, selection: TextSelection::new(), composition: TextComposition::new(), - is_active: false, + has_focus: false, is_pointer_selection_active: false, active_shape_id: None, cursor_visible: true, @@ -194,8 +194,8 @@ impl TextEditorState { } } - pub fn start(&mut self, shape_id: Uuid) { - self.is_active = true; + pub fn focus(&mut self, shape_id: Uuid) { + self.has_focus = true; self.active_shape_id = Some(shape_id); self.cursor_visible = true; self.last_blink_time = 0.0; @@ -204,8 +204,18 @@ impl TextEditorState { self.pending_events.clear(); } - pub fn stop(&mut self) { - self.is_active = false; + pub fn blur(&mut self) { + self.has_focus = false; + // self.active_shape_id = None; + self.cursor_visible = false; + self.last_blink_time = 0.0; + // self.selection.reset(); + self.is_pointer_selection_active = false; + self.pending_events.clear(); + } + + pub fn dispose(&mut self) { + self.has_focus = false; self.active_shape_id = None; self.cursor_visible = false; self.last_blink_time = 0.0; @@ -344,7 +354,7 @@ impl TextEditorState { } pub fn update_blink(&mut self, timestamp_ms: f64) { - if !self.is_active { + if !self.has_focus { return; } diff --git a/render-wasm/src/wasm/text_editor.rs b/render-wasm/src/wasm/text_editor.rs index 1c6de52fe8..3312d62131 100644 --- a/render-wasm/src/wasm/text_editor.rs +++ b/render-wasm/src/wasm/text_editor.rs @@ -44,7 +44,7 @@ pub extern "C" fn text_editor_apply_theme( } #[no_mangle] -pub extern "C" fn text_editor_start(a: u32, b: u32, c: u32, d: u32) -> bool { +pub extern "C" fn text_editor_focus(a: u32, b: u32, c: u32, d: u32) -> bool { with_state_mut!(state, { let shape_id = uuid_from_u32_quartet(a, b, c, d); @@ -56,35 +56,48 @@ pub extern "C" fn text_editor_start(a: u32, b: u32, c: u32, d: u32) -> bool { return false; } - state.text_editor_state.start(shape_id); + state.text_editor_state.focus(shape_id); true }) } #[no_mangle] -pub extern "C" fn text_editor_stop() -> bool { +pub extern "C" fn text_editor_blur() -> bool { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return false; } - state.text_editor_state.stop(); + state.text_editor_state.blur(); true }) } #[no_mangle] -pub extern "C" fn text_editor_is_active() -> bool { - with_state!(state, { state.text_editor_state.is_active }) +pub extern "C" fn text_editor_dispose() -> bool { + with_state_mut!(state, { + state.text_editor_state.dispose(); + true + }) } #[no_mangle] -pub extern "C" fn text_editor_is_active_with_id(a: u32, b: u32, c: u32, d: u32) -> bool { +pub extern "C" fn text_editor_has_selection() -> bool { + with_state!(state, { state.text_editor_state.selection.is_selection() }) +} + +#[no_mangle] +pub extern "C" fn text_editor_has_focus() -> bool { + with_state!(state, { state.text_editor_state.has_focus }) +} + +#[no_mangle] +pub extern "C" fn text_editor_has_focus_with_id(a: u32, b: u32, c: u32, d: u32) -> bool { with_state!(state, { let shape_id = uuid_from_u32_quartet(a, b, c, d); let Some(active_shape_id) = state.text_editor_state.active_shape_id else { return false; }; - state.text_editor_state.is_active && active_shape_id == shape_id + state.text_editor_state.has_focus && active_shape_id == shape_id }) } @@ -106,7 +119,7 @@ pub extern "C" fn text_editor_get_active_shape_id(buffer_ptr: *mut u32) { #[no_mangle] pub extern "C" fn text_editor_select_all() -> bool { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return false; } @@ -128,7 +141,7 @@ pub extern "C" fn text_editor_select_all() -> bool { #[no_mangle] pub extern "C" fn text_editor_select_word_boundary(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -165,7 +178,7 @@ pub extern "C" fn text_editor_poll_event() -> u8 { #[no_mangle] pub extern "C" fn text_editor_pointer_down(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } let Some(shape_id) = state.text_editor_state.active_shape_id else { @@ -188,7 +201,7 @@ pub extern "C" fn text_editor_pointer_down(x: f32, y: f32) { #[no_mangle] pub extern "C" fn text_editor_pointer_move(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } let point = Point::new(x, y); @@ -216,7 +229,7 @@ pub extern "C" fn text_editor_pointer_move(x: f32, y: f32) { #[no_mangle] pub extern "C" fn text_editor_pointer_up(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } let point = Point::new(x, y); @@ -244,7 +257,7 @@ pub extern "C" fn text_editor_pointer_up(x: f32, y: f32) { #[no_mangle] pub extern "C" fn text_editor_set_cursor_from_offset(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -267,7 +280,7 @@ pub extern "C" fn text_editor_set_cursor_from_offset(x: f32, y: f32) { #[no_mangle] pub extern "C" fn text_editor_set_cursor_from_point(x: f32, y: f32) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -435,7 +448,7 @@ pub extern "C" fn text_editor_insert_text() -> Result<()> { }; with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return Ok(()); } @@ -488,7 +501,7 @@ pub extern "C" fn text_editor_insert_text() -> Result<()> { #[no_mangle] pub extern "C" fn text_editor_delete_backward(word_boundary: bool) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -541,7 +554,7 @@ pub extern "C" fn text_editor_delete_backward(word_boundary: bool) { #[no_mangle] pub extern "C" fn text_editor_delete_forward(word_boundary: bool) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -594,7 +607,7 @@ pub extern "C" fn text_editor_delete_forward(word_boundary: bool) { #[no_mangle] pub extern "C" fn text_editor_insert_paragraph() { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -652,7 +665,7 @@ pub extern "C" fn text_editor_move_cursor( extend_selection: bool, ) { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return; } @@ -735,7 +748,7 @@ pub extern "C" fn text_editor_move_cursor( #[no_mangle] pub extern "C" fn text_editor_get_cursor_rect() -> *mut u8 { with_state_mut!(state, { - if !state.text_editor_state.is_active || !state.text_editor_state.cursor_visible { + if !state.text_editor_state.has_focus || !state.text_editor_state.cursor_visible { return std::ptr::null_mut(); } @@ -769,7 +782,7 @@ pub extern "C" fn text_editor_get_cursor_rect() -> *mut u8 { #[no_mangle] pub extern "C" fn text_editor_get_selection_rects() -> *mut u8 { with_state_mut!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return std::ptr::null_mut(); } @@ -818,10 +831,6 @@ pub extern "C" fn text_editor_update_blink(timestamp_ms: f64) { #[no_mangle] pub extern "C" fn text_editor_render_overlay() { with_state_mut!(state, { - if !state.text_editor_state.is_active { - return; - } - let Some(shape_id) = state.text_editor_state.active_shape_id else { return; }; @@ -858,7 +867,7 @@ pub extern "C" fn text_editor_render_overlay() { #[no_mangle] pub extern "C" fn text_editor_export_content() -> *mut u8 { with_state!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return std::ptr::null_mut(); } @@ -901,7 +910,7 @@ pub extern "C" fn text_editor_export_content() -> *mut u8 { pub extern "C" fn text_editor_export_selection() -> *mut u8 { use std::ptr; with_state!(state, { - if !state.text_editor_state.is_active { + if !state.text_editor_state.has_focus { return ptr::null_mut(); } let Some(shape_id) = state.text_editor_state.active_shape_id else { @@ -976,10 +985,10 @@ pub extern "C" fn text_editor_export_selection() -> *mut u8 { } #[no_mangle] -pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> u32 { +pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> bool { with_state!(state, { - if !state.text_editor_state.is_active { - return 0; + if !state.text_editor_state.selection.is_selection() { + return false; } let sel = &state.text_editor_state.selection; unsafe { @@ -988,7 +997,7 @@ pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> u32 { *buffer_ptr.add(2) = sel.focus.paragraph as u32; *buffer_ptr.add(3) = sel.focus.offset as u32; } - 1 + true }) }