🎉 Cap stop amount in UI for wasm (#6438)

* 🎉 Cap in the colorpicker the amount of stops a gradient can have

* 🎉 Cap the stops amount in gradient handlers

* 🎉 Disable add stop in gradient handlers (viewport + colorpicker)

*  Add integration test for gradient limits

* 💄 Address PR suggestion
This commit is contained in:
Belén Albeza 2025-05-13 10:37:05 +02:00 committed by GitHub
parent 69cc4fb4c2
commit 91fbe8f8ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 438 additions and 102 deletions

View file

@ -13,8 +13,9 @@
[app.common.schema :as sm]
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.shape :refer [check-stroke]]
[app.common.types.shape :as shp]
[app.common.types.shape.shadow :refer [check-shadow]]
[app.config :as cfg]
[app.main.broadcast :as mbc]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh]
@ -24,6 +25,7 @@
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.util.storage :as storage]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@ -421,7 +423,7 @@
[ids stroke]
(assert
(check-stroke stroke)
(shp/check-stroke stroke)
"expected a valid stroke struct")
(assert
@ -821,39 +823,43 @@
(update [_ state]
(update state :colorpicker
(fn [{:keys [stops editing-stop] :as state}]
(if (cc/uniform-spread? stops)
;; Add to uniform
(let [stops (->> (cc/uniform-spread (first stops) (last stops) (inc (count stops)))
(mapv split-color-components))]
(-> state
(assoc :current-color (get stops editing-stop))
(assoc :stops stops)))
(let [cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))]
(if can-add-stop?
(if (cc/uniform-spread? stops)
;; Add to uniform
(let [stops (->> (cc/uniform-spread (first stops) (last stops) (inc (count stops)))
(mapv split-color-components))]
(-> state
(assoc :current-color (get stops editing-stop))
(assoc :stops stops)))
;; We add the stop to the middle point between the selected
;; and the next one.
;; If the last stop is selected then it's added between the
;; last two stops.
(let [index
(if (= editing-stop (dec (count stops)))
(dec editing-stop)
editing-stop)
;; We add the stop to the middle point between the selected
;; and the next one.
;; If the last stop is selected then it's added between the
;; last two stops.
(let [index
(if (= editing-stop (dec (count stops)))
(dec editing-stop)
editing-stop)
{from-offset :offset} (get stops index)
{to-offset :offset} (get stops (inc index))
{from-offset :offset} (get stops index)
{to-offset :offset} (get stops (inc index))
half-point-offset
(+ from-offset (/ (- to-offset from-offset) 2))
half-point-offset
(+ from-offset (/ (- to-offset from-offset) 2))
new-stop (-> (cc/interpolate-gradient stops half-point-offset)
(split-color-components))
new-stop (-> (cc/interpolate-gradient stops half-point-offset)
(split-color-components))
stops (conj stops new-stop)
stops (into [] (sort-by :offset stops))
editing-stop (d/index-of-pred stops #(= new-stop %))]
(-> state
(assoc :editing-stop editing-stop)
(assoc :current-color (get stops editing-stop))
(assoc :stops stops)))))))))
stops (conj stops new-stop)
stops (into [] (sort-by :offset stops))
editing-stop (d/index-of-pred stops #(= new-stop %))]
(-> state
(assoc :editing-stop editing-stop)
(assoc :current-color (get stops editing-stop))
(assoc :stops stops))))
state)))))))
(defn update-colorpicker-add-stop
[offset]
@ -863,15 +869,18 @@
(update state :colorpicker
(fn [state]
(let [stops (:stops state)
new-stop (-> (cc/interpolate-gradient stops offset)
(split-color-components))
stops (conj stops new-stop)
stops (into [] (sort-by :offset stops))
editing-stop (d/index-of-pred stops #(= new-stop %))]
(-> state
(assoc :editing-stop editing-stop)
(assoc :current-color (get stops editing-stop))
(assoc :stops stops))))))))
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
can-add-stop? (or (not cap-stops?) (< (count stops) shp/MAX-GRADIENT-STOPS))]
(if can-add-stop? (let [new-stop (-> (cc/interpolate-gradient stops offset)
(split-color-components))
stops (conj stops new-stop)
stops (into [] (sort-by :offset stops))
editing-stop (d/index-of-pred stops #(= new-stop %))]
(-> state
(assoc :editing-stop editing-stop)
(assoc :current-color (get stops editing-stop))
(assoc :stops stops)))
state)))))))
(defn update-colorpicker-stops
[stops]
@ -881,7 +890,8 @@
(update state :colorpicker
(fn [state]
(let [stop (or (:editing-stop state) 0)
stops (mapv split-color-components stops)]
cap-stops? (or (features/active-feature? state "render-wasm/v1") (contains? cfg/flags :binary-fills))
stops (mapv split-color-components (if cap-stops? (take shp/MAX-GRADIENT-STOPS stops) stops))]
(-> state
(assoc :current-color (get stops stop))
(assoc :stops stops))))))))

View file

@ -12,6 +12,7 @@
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.types.shape :as shp]
[app.config :as cfg]
[app.main.data.event :as-alias ev]
[app.main.data.modal :as modal]
@ -20,6 +21,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.media :as dwm]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]]
@ -336,6 +338,8 @@
(fn [value]
(st/emit! (dc/update-colorpicker-gradient-opacity (/ value 100)))))
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
tabs
#js [#js {:aria-label (tr "workspace.libraries.colors.rgba")
:icon ic/rgba
@ -435,7 +439,7 @@
(when (= selected-mode :gradient)
[:> gradients*
{:type (:type state)
:stops (:stops state)
:stops (if cap-stops? (vec (take shp/MAX-GRADIENT-STOPS (:stops state))) (:stops state))
:editing-stop (:editing-stop state)
:on-stop-edit-start handle-stop-edit-start
:on-stop-edit-finish handle-stop-edit-finish

View file

@ -11,6 +11,9 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.types.shape :as shp]
[app.config :as cfg]
[app.main.features :as features]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
[app.main.ui.components.select :refer [select]]
@ -283,7 +286,9 @@
(mf/deps on-reverse-stops)
(fn []
(when on-reverse-stops
(on-reverse-stops))))]
(on-reverse-stops))))
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
add-stop-disabled? (when cap-stops? (>= (count stops) shp/MAX-GRADIENT-STOPS))]
[:div {:class (stl/css :gradient-panel)}
[:div {:class (stl/css :gradient-preview)}
@ -294,9 +299,10 @@
:on-pointer-leave handle-preview-leave
:on-pointer-move handle-preview-move
:on-pointer-down handle-preview-down}
[:div {:class (stl/css :gradient-preview-stop-preview)
:style {:display (if (:hover? @preview-state) "block" "none")
"--preview-position" (dm/str (* 100 (:offset @preview-state)) "%")}}]]
(when (not add-stop-disabled?)
[:div {:class (stl/css :gradient-preview-stop-preview)
:style {:display (if (:hover? @preview-state) "block" "none")
"--preview-position" (dm/str (* 100 (:offset @preview-state)) "%")}}])]
[:div {:class (stl/css :gradient-preview-stop-wrapper)}
(for [[index {:keys [color offset r g b alpha]}] (d/enumerate stops)]
@ -339,6 +345,7 @@
:icon "switch"}]
[:> icon-button* {:variant "ghost"
:aria-label "Add stop"
:disabled add-stop-disabled?
:on-click handle-add-stop
:icon "add"}]]]

View file

@ -15,7 +15,10 @@
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.points :as gsp]
[app.common.math :as mth]
[app.common.types.shape :as shp]
[app.config :as cfg]
[app.main.data.workspace.colors :as dc]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
@ -131,6 +134,9 @@
handler-state (mf/use-state {:display? false :offset 0 :hover nil})
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
can-add-stop? (if cap-stops? (< (count stops) shp/MAX-GRADIENT-STOPS) true)
endpoint-on-pointer-down
(fn [position event]
(dom/stop-propagation event)
@ -164,7 +170,8 @@
points-on-pointer-enter
(mf/use-fn
(fn []
(swap! handler-state assoc :display? true)))
(when can-add-stop?
(swap! handler-state assoc :display? true))))
points-on-pointer-leave
(mf/use-fn
@ -177,17 +184,17 @@
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(let [raw-pt (dom/get-client-position e)
position (uwvv/point->viewport raw-pt)
lv (-> (gpt/to-vec from-p to-p) (gpt/unit))
nv (gpt/normal-left lv)
offset (-> (gsp/project-t position [from-p to-p] nv)
(mth/precision 2))
new-stop (cc/interpolate-gradient stops offset)
stops (conj stops new-stop)
stops (->> stops (sort-by :offset) (into []))]
(st/emit! (dc/update-colorpicker-stops stops)))))
(when can-add-stop?
(let [raw-pt (dom/get-client-position e)
position (uwvv/point->viewport raw-pt)
lv (-> (gpt/to-vec from-p to-p) (gpt/unit))
nv (gpt/normal-left lv)
offset (-> (gsp/project-t position [from-p to-p] nv)
(mth/precision 2))
new-stop (cc/interpolate-gradient stops offset)
stops (conj stops new-stop)
stops (->> stops (sort-by :offset) (into []))]
(st/emit! (dc/update-colorpicker-stops stops))))))
points-on-pointer-move
(mf/use-fn
@ -354,7 +361,7 @@
:cx (:x width-p)
:cy (:y width-p)
:r (/ gradient-width-handler-radius-handler zoom)
:fill "transpgarent"
:fill "transparent"
:on-pointer-down (partial endpoint-on-pointer-down :width-p)
:on-pointer-enter (partial endpoint-on-pointer-enter :width-p)
:on-pointer-leave (partial endpoint-on-pointer-leave :width-p)
@ -518,7 +525,10 @@
shape (mf/deref shape-ref)
state (mf/deref refs/colorpicker)
gradient (:gradient state)
stops (:stops state)
cap-stops? (or (features/use-feature "render-wasm/v1") (contains? cfg/flags :binary-fills))
stops (if cap-stops?
(vec (take shp/MAX-GRADIENT-STOPS (:stops state)))
(:stops state))
editing-stop (:editing-stop state)]
(when (and (some? gradient) (= id (:shape-id gradient)))

View file

@ -1,16 +1,13 @@
(ns app.render-wasm.serializers.fills
(:require
[app.common.types.shape :as shp]
[app.common.uuid :as uuid]
[app.render-wasm.serializers.color :as clr]))
(def ^:private GRADIENT-STOP-SIZE 8)
(def ^:private GRADIENT-BASE-SIZE 28)
;; TODO: Define in shape model
(def ^:private MAX-GRADIENT-STOPS 16)
(def GRADIENT-BYTE-SIZE
(+ GRADIENT-BASE-SIZE (* MAX-GRADIENT-STOPS GRADIENT-STOP-SIZE)))
(def GRADIENT-BYTE-SIZE 156)
(def SOLID-BYTE-SIZE 4)
(def IMAGE-BYTE-SIZE 28)
@ -48,7 +45,7 @@
end-x (:end-x gradient)
end-y (:end-y gradient)
width (or (:width gradient) 0)
stops (take MAX-GRADIENT-STOPS (:stops gradient))
stops (take shp/MAX-GRADIENT-STOPS (:stops gradient))
type (if (= (:type gradient) :linear) 0x01 0x02)]
(.setUint8 dview offset type true)
(.setFloat32 dview (+ offset 4) start-x true)