From e9d60913d083c6bc2cdd6d38cd093bc5e6b4eba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 9 Apr 2020 09:57:24 +0200 Subject: [PATCH] :tada: Add space distribution of shapes --- .../images/icons/shape-hdistribute.svg | 1 + .../images/icons/shape-vdistribute.svg | 1 + frontend/resources/locales.json | 104 +++++++++++++----- .../main/partials/sidebar-align-options.scss | 16 ++- frontend/src/uxbox/builtins/icons.cljs | 2 + frontend/src/uxbox/main/data/workspace.cljs | 19 +++- frontend/src/uxbox/main/geom.cljs | 82 +++++++++++++- .../main/ui/workspace/sidebar/align.cljs | 80 ++++++++++---- 8 files changed, 250 insertions(+), 55 deletions(-) create mode 100644 frontend/resources/images/icons/shape-hdistribute.svg create mode 100644 frontend/resources/images/icons/shape-vdistribute.svg diff --git a/frontend/resources/images/icons/shape-hdistribute.svg b/frontend/resources/images/icons/shape-hdistribute.svg new file mode 100644 index 000000000..8e250a0a6 --- /dev/null +++ b/frontend/resources/images/icons/shape-hdistribute.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/images/icons/shape-vdistribute.svg b/frontend/resources/images/icons/shape-vdistribute.svg new file mode 100644 index 000000000..f7271ede1 --- /dev/null +++ b/frontend/resources/images/icons/shape-vdistribute.svg @@ -0,0 +1 @@ + diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index b0c7a5658..95c981219 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -145,19 +145,19 @@ } }, "dashboard.sidebar.drafts" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:113" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:112" ], "translations" : { "en" : "Drafts" } }, "dashboard.sidebar.libraries" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:120" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:119" ], "translations" : { "en" : "Libraries" } }, "dashboard.sidebar.recent" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:106" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:105" ], "translations" : { "en" : "Recent" } @@ -188,11 +188,11 @@ } }, "ds.cancel" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:115" ], "translations" : { "en" : null, "fr" : null - }, - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:115" ] + } }, "ds.confirm-cancel" : { "used-in" : [ "src/uxbox/main/ui/confirm.cljs:19" ], @@ -223,18 +223,18 @@ "unused" : true }, "ds.history.pinned" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:96" ], "translations" : { "en" : null, "fr" : null - }, - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:96" ] + } }, "ds.history.versions" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:93" ], "translations" : { "en" : null, "fr" : null - }, - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:93" ] + } }, "ds.icons-collection.new" : { "translations" : { @@ -258,18 +258,18 @@ } }, "ds.search.placeholder" : { - "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:176" ], + "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:175" ], "translations" : { "en" : "Search...", "fr" : "Rechercher..." } }, "ds.settings.document-history" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:87" ], "translations" : { "en" : null, "fr" : null - }, - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/history.cljs:87" ] + } }, "ds.store-colors-title" : { "translations" : { @@ -628,49 +628,49 @@ } }, "settings.profile.lang" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:92" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:88" ], "translations" : { "en" : "Default language", "fr" : "Langue par défaut" } }, "settings.profile.profile-saved" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:51" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:47" ], "translations" : { "en" : "Profile saved successfully!", "fr" : "Profil enregistré avec succès !" } }, "settings.profile.section-basic-data" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:66" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:62" ], "translations" : { "en" : "Name, username and email", "fr" : "Nom, nom d'utilisateur et adresse email" } }, "settings.profile.your-avatar" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:138" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:134" ], "translations" : { "en" : "Your avatar", "fr" : "Votre avatar" } }, "settings.profile.your-email" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:87" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:83" ], "translations" : { "en" : null, "fr" : null } }, "settings.profile.your-name" : { - "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:75" ], + "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:71" ], "translations" : { "en" : "Your name", "fr" : "Votre nom complet" } }, "settings.update-settings" : { - "used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:105" ], + "used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:101" ], "translations" : { "en" : "Update settings", "fr" : "Mettre à jour les paramètres" @@ -730,6 +730,54 @@ "en" : "Sitemap" } }, + "workspace.align.hcenter" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:51" ], + "translations" : { + "en" : "Align horizontal center" + } + }, + "workspace.align.hdistribute" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:63" ], + "translations" : { + "en" : "Distribute horizontal spacing" + } + }, + "workspace.align.hleft" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:45" ], + "translations" : { + "en" : "Align left" + } + }, + "workspace.align.hright" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:57" ], + "translations" : { + "en" : "Align right" + } + }, + "workspace.align.vbottom" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:82" ], + "translations" : { + "en" : "Align bottom" + } + }, + "workspace.align.vcenter" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:76" ], + "translations" : { + "en" : "Align vertical center" + } + }, + "workspace.align.vdistribute" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:88" ], + "translations" : { + "en" : "Distribute vertical spacing" + } + }, + "workspace.align.vtop" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/align.cljs:70" ], + "translations" : { + "en" : "Align top" + } + }, "workspace.header.menu.hide-grid" : { "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:120" ], "translations" : { @@ -897,21 +945,21 @@ "unused" : true }, "workspace.options.position" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:97", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:126", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:99", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:94" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:97", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:99", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:95", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:94", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:126" ], "translations" : { "en" : "Position", "fr" : "Position" } }, "workspace.options.radius" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:132", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:141", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:137", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:136" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:141", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:137", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:132", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:136" ], "translations" : { "en" : "Radius", "fr" : "TODO" } }, "workspace.options.rotation" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:110", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:118", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:114", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:113" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:118", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:114", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:110", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:113" ], "translations" : { "en" : "Rotation", "fr" : "TODO" @@ -925,7 +973,7 @@ } }, "workspace.options.size" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:70", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:72", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:101", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:72", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:115", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:67" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:115", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:70", "src/uxbox/main/ui/workspace/sidebar/options/icon.cljs:72", "src/uxbox/main/ui/workspace/sidebar/options/image.cljs:68", "src/uxbox/main/ui/workspace/sidebar/options/circle.cljs:72", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:67", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:101" ], "translations" : { "en" : "Size", "fr" : "Taille" @@ -938,35 +986,35 @@ } }, "workspace.options.stroke" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:73", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:127" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:74", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:129" ], "translations" : { "en" : "Stroke", "fr" : null } }, "workspace.options.stroke.dashed" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:114" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:115" ], "translations" : { "en" : "Dashed", "fr" : "Tiré" } }, "workspace.options.stroke.dotted" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:113" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:114" ], "translations" : { "en" : "Dotted", "fr" : "Pointillé" } }, "workspace.options.stroke.mixed" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:115" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:116" ], "translations" : { "en" : "Mixed", "fr" : "Mixte" } }, "workspace.options.stroke.solid" : { - "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:112" ], + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:113" ], "translations" : { "en" : "Solid", "fr" : "Solide" diff --git a/frontend/resources/styles/main/partials/sidebar-align-options.scss b/frontend/resources/styles/main/partials/sidebar-align-options.scss index 132b09781..706afcb6e 100644 --- a/frontend/resources/styles/main/partials/sidebar-align-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-align-options.scss @@ -7,13 +7,23 @@ .align-options { display: flex; - width: 100%; - justify-content: space-evenly; border-bottom: solid 1px $color-gray-60; + height: 37px; + padding: 0 $x-small; + + .align-group { + display: flex; + justify-content: space-evenly; + width: 100%; + + &:not(:last-child) { + border-right: solid 1px $color-gray-60; + } + } .align-button { cursor: pointer; - padding: $small; + padding: $small $x-small; svg { height: 16px; width: 16px; diff --git a/frontend/src/uxbox/builtins/icons.cljs b/frontend/src/uxbox/builtins/icons.cljs index e2683b90c..9e12c7a00 100644 --- a/frontend/src/uxbox/builtins/icons.cljs +++ b/frontend/src/uxbox/builtins/icons.cljs @@ -72,9 +72,11 @@ (def shape-halign-left (icon-xref :shape-halign-left)) (def shape-halign-center (icon-xref :shape-halign-center)) (def shape-halign-right (icon-xref :shape-halign-right)) +(def shape-hdistribute (icon-xref :shape-hdistribute)) (def shape-valign-top (icon-xref :shape-valign-top)) (def shape-valign-center (icon-xref :shape-valign-center)) (def shape-valign-bottom (icon-xref :shape-valign-bottom)) +(def shape-vdistribute (icon-xref :shape-vdistribute)) (def size-horiz (icon-xref :size-horiz)) (def size-vert (icon-xref :size-vert)) (def stroke (icon-xref :stroke)) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index d27f78807..7deb6acd9 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1445,14 +1445,14 @@ (rx/of (commit-changes [rchange] [uchange])))))) -;; --- Shape / Selection Alignment +;; --- Shape / Selection Alignment and Distribution (declare align-object-to-frame) (declare align-objects-list) (defn align-objects [axis] - (us/verify ::geom/axis axis) + (us/verify ::geom/align-axis axis) (ptk/reify :align-objects IBatchedChange ptk/UpdateEvent @@ -1478,6 +1478,21 @@ rect (geom/selection-rect selected-objs)] (map #(geom/align-to-rect % rect axis) selected-objs))) +(defn distribute-objects + [axis] + (us/verify ::geom/dist-axis axis) + (ptk/reify :align-objects + IBatchedChange + ptk/UpdateEvent + (update [_ state] + (let [page-id (::page-id state) + objects (get-in state [:workspace-data page-id :objects]) + selected (get-in state [:workspace-local :selected]) + selected-objs (map #(get objects %) selected) + moved-objs (geom/distribute-space selected-objs axis) + updated-objs (merge objects (d/index-by :id moved-objs))] + (assoc-in state [:workspace-data page-id :objects] updated-objs))))) + ;; --- Temportal displacement for Shape / Selection diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index d73a83376..6a6ca56ad 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -107,7 +107,6 @@ ;; --- Size -(declare size-rect) (declare size-circle) (declare size-path) @@ -141,6 +140,39 @@ (merge shape {:width (* rx 2) :height (* ry 2)})) +;; --- Center + +(declare center-rect) +(declare center-circle) +(declare center-path) + +(defn center + "Calculate the center of the shape." + [shape] + (case (:type shape) + :circle (center-circle shape) + :curve (center-path shape) + :path (center-path shape) + (center-rect shape))) + +(defn- center-rect + [{:keys [x y width height] :as shape}] + (gpt/point (+ x (/ width 2)) (+ y (/ height 2)))) + +(defn- center-circle + [{:keys [cx cy] :as shape}] + (gpt/point cx cy)) + +(defn- center-path + [{:keys [segments x1 y1 x2 y2] :as shape}] + (if (and x1 y1 x2 y2) + (gpt/point (/ (+ x1 x2) 2) (/ (+ y1 y2) 2)) + (let [minx (apply min (map :x segments)) + miny (apply min (map :y segments)) + maxx (apply max (map :x segments)) + maxy (apply max (map :y segments))] + (gpt/point (/ (+ minx maxx) 2) (/ (+ miny maxy) 2))))) + ;; --- Proportions (declare assign-proportions-path) @@ -566,10 +598,9 @@ [shape {:keys [x y] :as frame}] (move shape (gpt/point (+ x) (+ y)))) - ;; --- Alignment -(s/def ::axis #{:hleft :hcenter :hright :vtop :vcenter :vbottom}) +(s/def ::align-axis #{:hleft :hcenter :hright :vtop :vcenter :vbottom}) (declare calc-align-pos) @@ -612,6 +643,51 @@ {:x (:x wrapper-rect) :y (- bottom (:height wrapper-rect))}))) +;; --- Distribute + +(s/def ::dist-axis #{:horizontal :vertical}) + +(defn distribute-space + "Distribute equally the space between shapes in the given axis. If + there is no space enough, it does nothing. It takes into account + the form of the shape and the rotation, what is distributed is + the wrapping recangles of the shapes." + [shapes axis] + (let [coord (if (= axis :horizontal) :x :y) + other-coord (if (= axis :horizontal) :y :x) + size (if (= axis :horizontal) :width :height) + ; The rectangle that wraps the whole selection + wrapper-rect (selection-rect shapes) + ; Sort shapes by the center point in the given axis + sorted-shapes (sort-by #(coord (center %)) shapes) + ; Each shape wrapped in its own rectangle + wrapped-shapes (map #(selection-rect [%]) sorted-shapes) + ; The total space between shapes + space (reduce - (size wrapper-rect) (map size wrapped-shapes))] + + (if (<= space 0) + shapes + (let [unit-space (/ space (- (count wrapped-shapes) 1)) + ; Calculate the distance we need to move each shape. + ; The new position of each one is the position of the + ; previous one plus its size plus the unit space. + deltas (loop [shapes' wrapped-shapes + start-pos (coord wrapper-rect) + deltas []] + + (let [first-shape (first shapes') + delta (- start-pos (coord first-shape)) + new-pos (+ start-pos (size first-shape) unit-space)] + + (if (= (count shapes') 1) + (conj deltas delta) + (recur (rest shapes') + new-pos + (conj deltas delta)))))] + + (map #(move %1 {coord %2 other-coord 0}) sorted-shapes deltas))))) + + ;; --- Helpers (defn contained-in? diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs index f6e82b61f..fae75dc99 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/align.cljs @@ -12,6 +12,7 @@ [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.data.workspace :as dw] + [uxbox.util.i18n :as i18n :refer [t]] [uxbox.util.uuid :as uuid])) (mf/defc align-options @@ -25,26 +26,67 @@ :else (= uuid/zero (:frame-id (get objects (first selected))))) + disabled-distribute (cond + (empty? selected) true + (< (count selected) 2) true + :else false) + + locale (i18n/use-locale) + on-align-button-clicked - (fn [axis] (when-not disabled (st/emit! (dw/align-objects axis))))] + (fn [axis] (when-not disabled (st/emit! (dw/align-objects axis)))) + + on-distribute-button-clicked + (fn [axis] (when-not disabled-distribute (st/emit! (dw/distribute-objects axis))))] [:div.align-options - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :hleft)} - i/shape-halign-left] - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :hcenter)} - i/shape-halign-center] - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :hright)} - i/shape-halign-right] - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :vtop)} - i/shape-valign-top] - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :vcenter)} - i/shape-valign-center] - [:div.align-button {:class (when disabled "disabled") - :on-click #(on-align-button-clicked :vbottom)} - i/shape-valign-bottom]])) + [:div.align-group + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.hleft") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hleft)} + i/shape-halign-left] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.hcenter") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hcenter)} + i/shape-halign-center] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.hright") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hright)} + i/shape-halign-right] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.hdistribute") + :class (when disabled-distribute "disabled") + :on-click #(on-distribute-button-clicked :horizontal)} + i/shape-hdistribute]] + + [:div.align-group + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.vtop") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vtop)} + i/shape-valign-top] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.vcenter") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vcenter)} + i/shape-valign-center] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.vbottom") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vbottom)} + i/shape-valign-bottom] + + [:div.align-button.tooltip.tooltip-bottom + {:alt (t locale "workspace.align.vdistribute") + :class (when disabled-distribute "disabled") + :on-click #(on-distribute-button-clicked :vertical)} + i/shape-vdistribute]]]))