mirror of
https://github.com/penpot/penpot.git
synced 2025-03-13 08:11:30 -05:00
🎉 Add space distribution of shapes
This commit is contained in:
parent
a0c5f32a42
commit
e9d60913d0
8 changed files with 250 additions and 55 deletions
1
frontend/resources/images/icons/shape-hdistribute.svg
Normal file
1
frontend/resources/images/icons/shape-hdistribute.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500.00001 500.00001" width="500" height="500"><path d="M356.25 500c-4.487 0-8.975-2.066-12.712-6.2-2.194-2.431-4.466-6.131-5.044-8.228-.578-2.094-.994-107.944-.994-107.944V122.373s.416-105.848.997-107.942c.575-2.094 2.847-5.797 5.04-8.228 7.476-8.27 17.95-8.27 25.426 0 2.197 2.43 4.465 6.132 5.044 8.228.578 2.094.993 107.943.993 107.943v255.257s-.415 105.847-.997 107.94c-.575 2.095-2.846 5.798-5.04 8.229-3.738 4.134-8.225 6.2-12.713 6.2zm-212.5 0c-4.488 0-8.975-2.066-12.712-6.2-2.195-2.431-4.467-6.131-5.045-8.228-.579-2.094-.993-107.944-.993-107.944V122.373s.416-105.848.995-107.942c.577-2.094 2.849-5.797 5.043-8.228 7.474-8.27 17.95-8.27 25.426 0 2.194 2.43 4.465 6.132 5.042 8.228.58 2.094.994 107.943.994 107.943v255.257s-.417 105.847-.996 107.94c-.577 2.095-2.847 5.798-5.042 8.229-3.737 4.134-8.224 6.2-12.712 6.2zM287.5 392.425c0 4.197-4.346 7.575-9.745 7.575h-55.51c-5.4 0-9.745-3.378-9.745-7.575V107.577c0-4.198 4.346-7.577 9.745-7.577h55.51c5.399 0 9.745 3.38 9.745 7.577z" clip-rule="evenodd" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
frontend/resources/images/icons/shape-vdistribute.svg
Normal file
1
frontend/resources/images/icons/shape-vdistribute.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500.00001 500.00001" width="500" height="500"><path d="M500 143.75c0 4.488-2.066 8.975-6.2 12.712-2.431 2.194-6.131 4.467-8.228 5.045-2.094.578-107.944.993-107.944.993H122.373s-105.848-.415-107.942-.993c-2.095-.579-5.797-2.85-8.229-5.043-8.27-7.474-8.27-17.951 0-25.426 2.43-2.194 6.133-4.465 8.229-5.042 2.095-.581 107.943-.996 107.943-.996h255.257s105.847.416 107.94.997c2.095.578 5.798 2.847 8.229 5.042 4.134 3.736 6.2 8.223 6.2 12.711zm0 212.5c0 4.487-2.066 8.975-6.2 12.713-2.431 2.193-6.131 4.465-8.228 5.043-2.094.582-107.944.994-107.944.994H122.373s-105.848-.416-107.942-.994c-2.095-.578-5.797-2.85-8.229-5.043-8.27-7.475-8.27-17.95 0-25.425 2.43-2.197 6.133-4.466 8.229-5.044 2.095-.578 107.943-.994 107.943-.994h255.257s105.847.416 107.94.997c2.095.575 5.798 2.847 8.229 5.04 4.134 3.738 6.2 8.226 6.2 12.713zM392.425 212.5c4.197 0 7.575 4.347 7.575 9.746v55.509c0 5.4-3.378 9.745-7.575 9.745H107.576c-4.197 0-7.576-4.346-7.576-9.745v-55.51c0-5.398 3.38-9.745 7.576-9.745z" clip-rule="evenodd" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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]]]))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue