mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
🐛 Fix deselect and delete events for empty texts
This commit is contained in:
@@ -96,6 +96,16 @@
|
|||||||
(->> (rx/from ids)
|
(->> (rx/from ids)
|
||||||
(rx/map resize-wasm-text)))))
|
(rx/map resize-wasm-text)))))
|
||||||
|
|
||||||
|
;; -- Content helpers
|
||||||
|
|
||||||
|
(defn- v2-content-has-text?
|
||||||
|
[content]
|
||||||
|
(boolean
|
||||||
|
(when content
|
||||||
|
(some (fn [node]
|
||||||
|
(not (str/blank? (:text node ""))))
|
||||||
|
(txt/node-seq txt/is-text-node? content)))))
|
||||||
|
|
||||||
;; -- Editor
|
;; -- Editor
|
||||||
|
|
||||||
(defn update-editor
|
(defn update-editor
|
||||||
@@ -948,28 +958,34 @@
|
|||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
new-shape? (nil? (:content shape))]
|
new-shape? (nil? (:content shape))]
|
||||||
(rx/of
|
(rx/concat
|
||||||
(dwsh/update-shapes
|
(rx/of
|
||||||
[id]
|
(dwsh/update-shapes
|
||||||
(fn [shape]
|
[id]
|
||||||
(let [new-shape (-> shape
|
(fn [shape]
|
||||||
(assoc :content content)
|
(let [new-shape (-> shape
|
||||||
(cond-> (and update-name? (some? name))
|
(assoc :content content)
|
||||||
(assoc :name name)))]
|
(cond-> (and update-name? (some? name))
|
||||||
new-shape))
|
(assoc :name name)))]
|
||||||
{:undo-group (when new-shape? id)})
|
new-shape))
|
||||||
|
{:undo-group (when new-shape? id)})
|
||||||
|
|
||||||
(if (and (not= :fixed (:grow-type shape)) finalize?)
|
(if (and (not= :fixed (:grow-type shape)) finalize?)
|
||||||
(dwm/apply-wasm-modifiers
|
(dwm/apply-wasm-modifiers
|
||||||
(resize-wasm-text-modifiers shape content)
|
(resize-wasm-text-modifiers shape content)
|
||||||
{:undo-group (when new-shape? id)})
|
{:undo-group (when new-shape? id)})
|
||||||
|
|
||||||
(dwm/set-wasm-modifiers
|
(dwm/set-wasm-modifiers
|
||||||
(resize-wasm-text-modifiers shape content)
|
(resize-wasm-text-modifiers shape content)
|
||||||
{:undo-group (when new-shape? id)}))
|
{:undo-group (when new-shape? id)})))
|
||||||
|
|
||||||
(when finalize?
|
(when finalize?
|
||||||
(dwt/finish-transform))))
|
(rx/concat
|
||||||
|
(when (and (not (v2-content-has-text? content)) (some? id))
|
||||||
|
(rx/of
|
||||||
|
(dws/deselect-shape id)
|
||||||
|
(dwsh/delete-shapes #{id})))
|
||||||
|
(rx/of (dwt/finish-transform))))))
|
||||||
|
|
||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
[app.render-wasm.wasm :as wasm]
|
[app.render-wasm.wasm :as wasm]
|
||||||
[app.util.debug :as dbg]
|
[app.util.debug :as dbg]
|
||||||
[app.util.functions :as fns]
|
[app.util.functions :as fns]
|
||||||
|
[app.util.text.content :as tc]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
@@ -106,6 +107,14 @@
|
|||||||
(reset! pending-render false)
|
(reset! pending-render false)
|
||||||
(render ts)))))
|
(render ts)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- ensure-text-content
|
||||||
|
"Guarantee that the shape always sends a valid text tree to WASM. When the
|
||||||
|
content is nil (freshly created text) we fall back to
|
||||||
|
tc/default-text-content so the renderer receives typography information."
|
||||||
|
[content]
|
||||||
|
(or content (tc/v2-default-text-content)))
|
||||||
|
|
||||||
(defn use-shape
|
(defn use-shape
|
||||||
[id]
|
[id]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
@@ -850,7 +859,10 @@
|
|||||||
blend-mode (get shape :blend-mode)
|
blend-mode (get shape :blend-mode)
|
||||||
opacity (get shape :opacity)
|
opacity (get shape :opacity)
|
||||||
hidden (get shape :hidden)
|
hidden (get shape :hidden)
|
||||||
content (get shape :content)
|
content (let [content (get shape :content)]
|
||||||
|
(if (= type :text)
|
||||||
|
(ensure-text-content content)
|
||||||
|
content))
|
||||||
bool-type (get shape :bool-type)
|
bool-type (get shape :bool-type)
|
||||||
grow-type (get shape :grow-type)
|
grow-type (get shape :grow-type)
|
||||||
blur (get shape :blur)
|
blur (get shape :blur)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
(ns app.util.text.content
|
(ns app.util.text.content
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.types.text :as txt]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.util.text.content.from-dom :as fd]
|
[app.util.text.content.from-dom :as fd]
|
||||||
[app.util.text.content.to-dom :as td]))
|
[app.util.text.content.to-dom :as td]))
|
||||||
|
|
||||||
@@ -18,3 +20,22 @@
|
|||||||
"Sets the editor content from a CLJS structure"
|
"Sets the editor content from a CLJS structure"
|
||||||
[root]
|
[root]
|
||||||
(td/create-root root))
|
(td/create-root root))
|
||||||
|
|
||||||
|
(defn v2-default-text-content
|
||||||
|
"Build the base text tree (root -> paragraph-set -> paragraph -> span) with the
|
||||||
|
current default typography. Used by the V2 editor/WASM path when a shape is
|
||||||
|
created with no content yet."
|
||||||
|
[]
|
||||||
|
(let [default-font (deref refs/default-font)
|
||||||
|
text-defaults (merge (txt/get-default-text-attrs) default-font)
|
||||||
|
default-span (merge {:text ""}
|
||||||
|
(select-keys text-defaults txt/text-node-attrs))
|
||||||
|
default-paragraph (merge {:type "paragraph"
|
||||||
|
:children [default-span]}
|
||||||
|
(select-keys text-defaults txt/paragraph-attrs))
|
||||||
|
default-paragraph-set {:type "paragraph-set"
|
||||||
|
:children [default-paragraph]}]
|
||||||
|
(merge {:type "root"
|
||||||
|
:children [default-paragraph-set]}
|
||||||
|
txt/default-root-attrs
|
||||||
|
(select-keys text-defaults txt/root-attrs))))
|
||||||
|
|||||||
@@ -508,8 +508,80 @@ impl TextContent {
|
|||||||
self.set_layout_from_result(result, selrect.width(), selrect.height());
|
self.set_layout_from_result(result, selrect.width(), selrect.height());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.is_empty() {
|
||||||
|
let (placeholder_width, placeholder_height) = self.placeholder_dimensions(selrect);
|
||||||
|
self.size.width = placeholder_width;
|
||||||
|
self.size.height = placeholder_height;
|
||||||
|
self.size.max_width = placeholder_width;
|
||||||
|
}
|
||||||
|
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true when the content represents a freshly created empty text.
|
||||||
|
/// We consider it empty only if there is exactly one paragraph with a single
|
||||||
|
/// span whose text buffer is empty. Any additional paragraphs or characters
|
||||||
|
/// mean the user has already entered content.
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
if self.paragraphs.len() != 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paragraph = match self.paragraphs.first() {
|
||||||
|
Some(paragraph) => paragraph,
|
||||||
|
None => return true,
|
||||||
|
};
|
||||||
|
if paragraph.children().len() != 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let span = match paragraph.children().first() {
|
||||||
|
Some(span) => span,
|
||||||
|
None => return true,
|
||||||
|
};
|
||||||
|
|
||||||
|
span.text.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the placeholder size used while the text is still empty. We ask
|
||||||
|
/// Skia to measure a single glyph using the span's typography so the editor
|
||||||
|
/// shows a caret-sized box that reflects the selected font, size and spacing.
|
||||||
|
/// If that fails we fall back to the previous WASM size or the incoming
|
||||||
|
/// selrect dimensions.
|
||||||
|
fn placeholder_dimensions(&self, selrect: Rect) -> (f32, f32) {
|
||||||
|
if let Some(paragraph) = self.paragraphs.first() {
|
||||||
|
if let Some(span) = paragraph.children().first() {
|
||||||
|
let fonts = get_font_collection();
|
||||||
|
let fallback_fonts = get_fallback_fonts();
|
||||||
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
|
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
||||||
|
|
||||||
|
let text_style = span.to_style(
|
||||||
|
&self.bounds(),
|
||||||
|
fallback_fonts,
|
||||||
|
false,
|
||||||
|
paragraph.line_height(),
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.push_style(&text_style);
|
||||||
|
builder.add_text("0");
|
||||||
|
|
||||||
|
let mut paragraph_layout = builder.build();
|
||||||
|
paragraph_layout.layout(f32::MAX);
|
||||||
|
|
||||||
|
let width = paragraph_layout.max_intrinsic_width();
|
||||||
|
let height = paragraph_layout.height();
|
||||||
|
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fallback_width = selrect.width().max(self.size.width);
|
||||||
|
let fallback_height = selrect.height().max(self.size.height);
|
||||||
|
|
||||||
|
(fallback_width, fallback_height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextContent {
|
impl Default for TextContent {
|
||||||
|
|||||||
Reference in New Issue
Block a user