0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

Refactor flex layout namespace

This commit is contained in:
alonso.torres 2022-10-24 15:58:38 +02:00
parent 58fd20094a
commit b8c90fdcf3
7 changed files with 798 additions and 685 deletions

View file

@ -6,687 +6,13 @@
(ns app.common.geom.shapes.flex-layout
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.rect :as gsr]
[app.common.geom.shapes.transforms :as gst]
[app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :strech
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default)
;; :layout-wrap-type ;; :wrap, :no-wrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
;; ITEMS
;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
;; :layout-margin-type ;; :simple :multiple
;; :layout-h-behavior ;; :fill :fix :auto
;; :layout-v-behavior ;; :fill :fix :auto
;; :layout-max-h ;; num
;; :layout-min-h ;; num
;; :layout-max-w ;; num
;; :layout-min-w
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
(defn h-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :start))
(and (row? shape)
(= layout-justify-content :start))))
(defn h-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :center))
(and (row? shape)
(= layout-justify-content :center))))
(defn h-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :end))
(and (row? shape)
(= layout-justify-content :end))))
(defn v-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :start))
(and (col? shape)
(= layout-justify-content :start))))
(defn v-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :center))
(and (col? shape)
(= layout-justify-content :center))))
(defn v-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :end))
(and (col? shape)
(= layout-justify-content :end))))
(defn gaps
[{:keys [layout-gap layout-gap-type]}]
(let [layout-gap-row (or (-> layout-gap :row-gap) 0)
layout-gap-col (if (= layout-gap-type :simple)
layout-gap-row
(or (-> layout-gap :column-gap) 0))]
[layout-gap-row layout-gap-col]))
(defn calc-layout-lines
"Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation"
[{:keys [layout-wrap-type] :as parent} children layout-bounds]
(let [wrap? (= layout-wrap-type :wrap)
col? (col? parent)
row? (row? parent)
[layout-gap-row layout-gap-col] (gaps parent)
layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
reduce-fn
(fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child]
(let [child-bounds (gst/parent-coords-points child parent)
child-width (gpo/width-points child-bounds)
child-height (gpo/height-points child-bounds)
cur-child-fill?
(or (and row? (= :fill (:layout-h-behavior child)))
(and col? (= :fill (:layout-v-behavior child))))
cur-line-fill?
(or (and col? (= :fill (:layout-h-behavior child)))
(and row? (= :fill (:layout-v-behavior child))))
;; TODO LAYOUT: ADD MINWIDTH/HEIGHT
next-width (if (or (and row? cur-child-fill?)
(and col? cur-line-fill?))
0
child-width)
next-height (if (or (and col? cur-child-fill?)
(and row? cur-line-fill?))
0
child-height)
next-total-width (+ line-width next-width (* layout-gap-row (dec num-children)))
next-total-height (+ line-height next-height (* layout-gap-col (dec num-children)))]
(if (and (some? line-data)
(or (not wrap?)
(and row? (<= next-total-width layout-width))
(and col? (<= next-total-height layout-height))))
;; When :fill we add min width (0 by default)
[{:line-width (if row? (+ line-width next-width) (max line-width next-width))
:line-height (if col? (+ line-height next-height) (max line-height next-height))
:num-children (inc num-children)
:child-fill? (or cur-child-fill? child-fill?)
:line-fill? (or cur-line-fill? line-fill?)
:num-child-fill (cond-> num-child-fill cur-child-fill? inc)}
result]
[{:line-width next-width
:line-height next-height
:num-children 1
:child-fill? cur-child-fill?
:line-fill? cur-line-fill?
:num-child-fill (if cur-child-fill? 1 0)}
(cond-> result (some? line-data) (conj line-data))])))
[line-data layout-lines] (reduce reduce-fn [nil []] children)]
(cond-> layout-lines (some? line-data) (conj line-data))))
(defn calc-layout-lines-position
[{:keys [layout-justify-content] :as parent} layout-bounds layout-lines]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
[layout-gap-row layout-gap-col] (gaps parent)
row? (row? parent)
col? (col? parent)
space-between? (= layout-justify-content :space-between)
space-around? (= layout-justify-content :space-around)
h-center? (h-center? parent)
h-end? (h-end? parent)
v-center? (v-center? parent)
v-end? (v-end? parent)]
(letfn [;; short version to not repeat always with all arguments
(xv [val]
(gpo/start-hv layout-bounds val))
;; short version to not repeat always with all arguments
(yv [val]
(gpo/start-vv layout-bounds val))
(get-base-line
[total-width total-height]
(cond-> (gpo/origin layout-bounds)
(and col? h-center?)
(gpt/add (xv (/ (- layout-width total-width) 2)))
(and col? h-end?)
(gpt/add (xv (- layout-width total-width)))
(and row? v-center?)
(gpt/add (yv (/ (- layout-height total-height) 2)))
(and row? v-end?)
(gpt/add (yv (- layout-height total-height)))))
(get-start-line
[{:keys [line-width line-height num-children child-fill?]} base-p]
(let [children-gap-width (* layout-gap-row (dec num-children))
children-gap-height (* layout-gap-col (dec num-children))
line-width (if (and row? child-fill?)
(- layout-width (* layout-gap-row (dec num-children)))
line-width)
line-height (if (and col? child-fill?)
(- layout-height (* layout-gap-col (dec num-children)))
line-height)
start-p
(cond-> base-p
;; X AXIS
(and row? h-center? (not space-around?) (not space-between?))
(-> (gpt/add (xv (/ layout-width 2)))
(gpt/subtract (xv (/ (+ line-width children-gap-width) 2))))
(and row? h-end? (not space-around?) (not space-between?))
(-> (gpt/add (xv layout-width))
(gpt/subtract (xv (+ line-width children-gap-width))))
(and col? h-center?)
(gpt/add (xv (/ line-width 2)))
(and col? h-end?)
(gpt/add (xv line-width))
;; Y AXIS
(and col? v-center? (not space-around?) (not space-between?))
(-> (gpt/add (yv (/ layout-height 2)))
(gpt/subtract (yv (/ (+ line-height children-gap-height) 2))))
(and col? v-end? (not space-around?) (not space-between?))
(-> (gpt/add (yv layout-height))
(gpt/subtract (yv (+ line-height children-gap-height))))
(and row? v-center?)
(gpt/add (yv (/ line-height 2)))
(and row? v-end?)
(gpt/add (yv line-height)))]
start-p))
(get-next-line
[{:keys [line-width line-height]} base-p]
(cond-> base-p
col?
(gpt/add (xv (+ line-width layout-gap-row)))
row?
(gpt/add (yv (+ line-height layout-gap-col)))))
(add-lines [[total-width total-height] {:keys [line-width line-height]}]
[(+ total-width line-width)
(+ total-height line-height)])
(add-starts [[result base-p] layout-line]
(let [start-p (get-start-line layout-line base-p)
next-p (get-next-line layout-line base-p)]
[(conj result
(assoc layout-line :start-p start-p))
next-p]))]
(let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
total-width (+ total-width (* layout-gap-row (dec (count layout-lines))))
total-height (+ total-height (* layout-gap-col (dec (count layout-lines))))
vertical-fill-space (- layout-height total-height)
horizontal-fill-space (- layout-width total-width)
num-line-fill (count (->> layout-lines (filter :line-fill?)))
layout-lines
(->> layout-lines
(mapv #(cond-> %
(and row? (:line-fill? %))
(update :line-height + (/ vertical-fill-space num-line-fill))
(and col? (:line-fill? %))
(update :line-width + (/ horizontal-fill-space num-line-fill)))))
total-width (if (and col? (> num-line-fill 0)) layout-width total-width)
total-height (if (and row? (> num-line-fill 0)) layout-height total-height)
base-p (get-base-line total-width total-height)
[layout-lines _ _ _ _]
(reduce add-starts [[] base-p] layout-lines)]
layout-lines))))
(defn calc-layout-line-data
"Calculates the baseline for a flex layout"
[{:keys [layout-justify-content] :as shape}
layout-bounds
{:keys [num-children line-width line-height] :as line-data}]
(let [width (gpo/width-points layout-bounds)
height (gpo/height-points layout-bounds)
row? (row? shape)
col? (col? shape)
space-between? (= layout-justify-content :space-between)
space-around? (= layout-justify-content :space-around)
[layout-gap-row layout-gap-col] (gaps shape)
layout-gap-row
(cond (and row? space-around?)
0
(and row? space-between?)
(/ (- width line-width) (dec num-children))
:else
layout-gap-row)
layout-gap-col
(cond (and col? space-around?)
0
(and col? space-between?)
(/ (- height line-height) (dec num-children))
:else
layout-gap-col)
margin-x
(if (and row? space-around?)
(/ (- width line-width) (inc num-children))
0)
margin-y
(if (and col? space-around?)
(/ (- height line-height) (inc num-children))
0)]
(assoc line-data
:layout-bounds layout-bounds
:layout-gap-row layout-gap-row
:layout-gap-col layout-gap-col
:margin-x margin-x
:margin-y margin-y)))
(defn next-p
"Calculates the position for the current shape given the layout-data context"
[parent
child-width child-height
{:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}]
(let [row? (row? parent)
col? (col? parent)
h-center? (h-center? parent)
h-end? (h-end? parent)
v-center? (v-center? parent)
v-end? (v-end? parent)
points (:points parent)
xv (partial gpo/start-hv points)
yv (partial gpo/start-vv points)
corner-p
(cond-> start-p
(and col? h-center?)
(gpt/add (xv (- (/ child-width 2))))
(and col? h-end?)
(gpt/add (xv (- child-width)))
(and row? v-center?)
(gpt/add (yv (- (/ child-height 2))))
(and row? v-end?)
(gpt/add (yv (- child-height)))
(some? margin-x)
(gpt/add (xv margin-x))
(some? margin-y)
(gpt/add (yv margin-y)))
next-p
(cond-> start-p
row?
(gpt/add (xv (+ child-width layout-gap-row)))
col?
(gpt/add (yv (+ child-height layout-gap-col)))
(some? margin-x)
(gpt/add (xv margin-x))
(some? margin-y)
(gpt/add (yv margin-y)))
layout-data
(assoc layout-data :start-p next-p)]
[corner-p layout-data]))
(defn calc-fill-width-data
"Calculates the size and modifiers for the width of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-h-behavior] :as child}
child-origin child-width
{:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}]
(let [[layout-gap-row _] (gaps parent)]
(cond
(and (row? parent) (= :fill layout-h-behavior) child-fill?)
(let [layout-width (gpo/width-points layout-bounds)
fill-space (- layout-width line-width (* layout-gap-row (dec num-children)))
fill-width (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-width child-width)]
{:width fill-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})
(and (col? parent) (= :fill layout-h-behavior) line-fill?)
(let [fill-scale (/ line-width child-width)]
{:width line-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))))
(defn calc-fill-height-data
"Calculates the size and modifiers for the height of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-v-behavior] :as child}
child-origin child-height
{:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}]
(let [[_ layout-gap-col] (gaps parent)]
(cond
(and (col? parent) (= :fill layout-v-behavior) child-fill?)
(let [layout-height (gpo/height-points layout-bounds)
fill-space (- layout-height line-height (* layout-gap-col (dec num-children)))
fill-height (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-height child-height)]
{:height fill-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})
(and (row? parent) (= :fill layout-v-behavior) line-fill?)
(let [fill-scale (/ line-height child-height)]
{:height line-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))))
(defn normalize-child-modifiers
"Apply the modifiers and then normalized them against the parent coordinates"
[parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}]
(let [transformed-child (gst/transform-shape child modifiers)
child-bb-before (gst/parent-coords-rect child parent)
child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
scale-x (/ (:width child-bb-before) (:width child-bb-after))
scale-y (/ (:height child-bb-before) (:height child-bb-after))
resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n
resize-vector (gpt/point scale-x scale-y)]
(-> modifiers
(ctm/select-child-modifiers)
(ctm/set-resize resize-vector resize-origin transform transform-inverse))))
(defn calc-layout-data
"Digest the layout data to pass it to the constrains"
[{:keys [layout-flex-dir layout-padding layout-padding-type] :as parent} children]
(let [;; Add padding to the bounds
{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
[pad-top pad-right pad-bottom pad-left]
(if (= layout-padding-type :multiple)
[pad-top pad-right pad-bottom pad-left]
[pad-top pad-top pad-top pad-top])
;; Normalize the points to remove flips
points (gst/parent-coords-points parent parent)
layout-bounds (gpo/pad-points points pad-top pad-right pad-bottom pad-left)
;; Reverse
reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir))
children (cond->> children reverse? reverse)
;; Creates the layout lines information
layout-lines
(->> (calc-layout-lines parent children layout-bounds)
(calc-layout-lines-position parent layout-bounds)
(map (partial calc-layout-line-data parent layout-bounds)))]
{:layout-lines layout-lines
:reverse? reverse?}))
(defn calc-layout-modifiers
"Calculates the modifiers for the layout"
[parent child layout-line]
(let [child-bounds (gst/parent-coords-points child parent)
child-origin (gpo/origin child-bounds)
child-width (gpo/width-points child-bounds)
child-height (gpo/height-points child-bounds)
fill-width (calc-fill-width-data parent child child-origin child-width layout-line)
fill-height (calc-fill-height-data parent child child-origin child-height layout-line)
child-width (or (:width fill-width) child-width)
child-height (or (:height fill-height) child-height)
[corner-p layout-line] (next-p parent child-width child-height layout-line)
move-vec (gpt/to-vec child-origin corner-p)
modifiers
(-> (ctm/empty-modifiers)
(cond-> fill-width (ctm/add-modifiers (:modifiers fill-width)))
(cond-> fill-height (ctm/add-modifiers (:modifiers fill-height)))
(ctm/set-move move-vec))]
[modifiers layout-line]))
(defn layout-drop-areas
[{:keys [margin-x margin-y] :as frame} layout-data children]
(let [col? (col? frame)
row? (row? frame)
h-center? (and row? (h-center? frame))
h-end? (and row? (h-end? frame))
v-center? (and col? (v-center? frame))
v-end? (and row? (v-end? frame))
layout-gap-row (or (-> frame :layout-gap :row-gap) 0)
;;layout-gap-col (or (-> frame :layout-gap :column-gap) 0)
layout-gap layout-gap-row ;; TODO LAYOUT: FIXME
reverse? (:reverse? layout-data)
children (vec (cond->> (d/enumerate children)
reverse? reverse))
redfn-child
(fn [[result parent-rect prev-x prev-y] [[index child] next]]
(let [prev-x (or prev-x (:x parent-rect))
prev-y (or prev-y (:y parent-rect))
last? (nil? next)
start-p (gpt/point (:selrect child))
start-p (-> start-p
(gmt/transform-point-center (gco/center-shape child) (:transform frame))
(gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame)))
box-x (:x start-p)
box-y (:y start-p)
box-width (-> child :selrect :width)
box-height (-> child :selrect :height)
x (if col? (:x parent-rect) prev-x)
y (if row? (:y parent-rect) prev-y)
width (cond
(and row? last?)
(- (+ (:x parent-rect) (:width parent-rect)) x)
col?
(:width parent-rect)
:else
(+ box-width (- box-x prev-x) (/ layout-gap 2)))
height (cond
(and col? last?)
(- (+ (:y parent-rect) (:height parent-rect)) y)
row?
(:height parent-rect)
:else
(+ box-height (- box-y prev-y) (/ layout-gap 2)))
[line-area-1 line-area-2]
(if row?
(let [half-point-width (+ (- box-x x) (/ box-width 2))]
[(-> (gsr/make-rect x y half-point-width height)
(assoc :index (if reverse? (inc index) index)))
(-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height)
(assoc :index (if reverse? index (inc index))))])
(let [half-point-height (+ (- box-y y) (/ box-height 2))]
[(-> (gsr/make-rect x y width half-point-height)
(assoc :index (if reverse? (inc index) index)))
(-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height))
(assoc :index (if reverse? index (inc index))))]))
result (conj result line-area-1 line-area-2)
;;line-area
;;(-> (gsr/make-rect x y width height)
;; (assoc :index (if reverse? (inc index) index)))
;;result (conj result line-area)
;;result (conj result (gsr/make-rect box-x box-y box-width box-height))
]
[result parent-rect (+ x width) (+ y height)]))
redfn-lines
(fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]]
(let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame))
prev-x (or prev-x (:x frame))
prev-y (or prev-y (:y frame))
last? (nil? next)
line-width
(if row?
(:width frame)
(+ line-width margin-x
(if row? (* layout-gap (dec num-children)) 0)))
line-height
(if col?
(:height frame)
(+ line-height margin-y
(if col?
(* layout-gap (dec num-children))
0)))
box-x
(- (:x start-p)
(cond
h-center? (/ line-width 2)
h-end? line-width
:else 0))
box-y
(- (:y start-p)
(cond
v-center? (/ line-height 2)
v-end? line-height
:else 0))
x (if row? (:x frame) prev-x)
y (if col? (:y frame) prev-y)
width (cond
(and col? last?)
(- (+ (:x frame) (:width frame)) x)
row?
(:width frame)
:else
(+ line-width (- box-x prev-x) (/ layout-gap 2)))
height (cond
(and row? last?)
(- (+ (:y frame) (:height frame)) y)
col?
(:height frame)
:else
(+ line-height (- box-y prev-y) (/ layout-gap 2)))
line-area (gsr/make-rect x y width height)
children (subvec children from-idx (+ from-idx num-children))
;; To debug the lines
;;result (conj result line-area)
result (first (reduce redfn-child [result line-area] (d/with-next children)))]
[result (+ from-idx num-children) (+ x width) (+ y height)]))]
(first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data))))))
(defn get-drop-index
[frame-id objects position]
(let [frame (get objects frame-id)
position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))
children (cph/get-immediate-children objects frame-id)
layout-data (calc-layout-data frame children)
drop-areas (layout-drop-areas frame layout-data children)
area (d/seek #(gsr/contains-point? % position) drop-areas)]
(:index area)))
[app.common.data.macros :as dm]
[app.common.geom.shapes.flex-layout.drop-area :as fdr]
[app.common.geom.shapes.flex-layout.lines :as fli]
[app.common.geom.shapes.flex-layout.modifiers :as fmo]))
(dm/export fli/calc-layout-data)
(dm/export fmo/normalize-child-modifiers)
(dm/export fmo/calc-layout-modifiers)
(dm/export fdr/layout-drop-areas)
(dm/export fdr/get-drop-index)

View file

@ -0,0 +1,179 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.geom.shapes.flex-layout.drop-area
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.flex-layout.lines :as fli]
[app.common.geom.shapes.rect :as gsr]
[app.common.pages.helpers :as cph]
[app.common.types.shape.layout :as ctl]))
(defn layout-drop-areas
[{:keys [margin-x margin-y] :as frame} layout-data children]
(let [col? (ctl/col? frame)
row? (ctl/row? frame)
h-center? (and row? (ctl/h-center? frame))
h-end? (and row? (ctl/h-end? frame))
v-center? (and col? (ctl/v-center? frame))
v-end? (and row? (ctl/v-end? frame))
reverse? (:reverse? layout-data)
[layout-gap-row layout-gap-col] (ctl/gaps frame)
children (vec (cond->> (d/enumerate children)
reverse? reverse))
redfn-child
(fn [[result parent-rect prev-x prev-y] [[index child] next]]
(let [prev-x (or prev-x (:x parent-rect))
prev-y (or prev-y (:y parent-rect))
last? (nil? next)
start-p (gpt/point (:selrect child))
start-p (-> start-p
(gmt/transform-point-center (gco/center-shape child) (:transform frame))
(gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame)))
box-x (:x start-p)
box-y (:y start-p)
box-width (-> child :selrect :width)
box-height (-> child :selrect :height)
x (if col? (:x parent-rect) prev-x)
y (if row? (:y parent-rect) prev-y)
width (cond
(and row? last?)
(- (+ (:x parent-rect) (:width parent-rect)) x)
col?
(:width parent-rect)
:else
(+ box-width (- box-x prev-x) (/ layout-gap-row 2)))
height (cond
(and col? last?)
(- (+ (:y parent-rect) (:height parent-rect)) y)
row?
(:height parent-rect)
:else
(+ box-height (- box-y prev-y) (/ layout-gap-col 2)))
[line-area-1 line-area-2]
(if row?
(let [half-point-width (+ (- box-x x) (/ box-width 2))]
[(-> (gsr/make-rect x y half-point-width height)
(assoc :index (if reverse? (inc index) index)))
(-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height)
(assoc :index (if reverse? index (inc index))))])
(let [half-point-height (+ (- box-y y) (/ box-height 2))]
[(-> (gsr/make-rect x y width half-point-height)
(assoc :index (if reverse? (inc index) index)))
(-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height))
(assoc :index (if reverse? index (inc index))))]))
result (conj result line-area-1 line-area-2)
;;line-area
;;(-> (gsr/make-rect x y width height)
;; (assoc :index (if reverse? (inc index) index)))
;;result (conj result line-area)
;;result (conj result (gsr/make-rect box-x box-y box-width box-height))
]
[result parent-rect (+ x width) (+ y height)]))
redfn-lines
(fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap-row layout-gap-col num-children line-width line-height]} next]]
(let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame))
prev-x (or prev-x (:x frame))
prev-y (or prev-y (:y frame))
last? (nil? next)
line-width
(if row?
(:width frame)
(+ line-width margin-x
(if row? (* layout-gap-row (dec num-children)) 0)))
line-height
(if col?
(:height frame)
(+ line-height margin-y
(if col?
(* layout-gap-col (dec num-children))
0)))
box-x
(- (:x start-p)
(cond
h-center? (/ line-width 2)
h-end? line-width
:else 0))
box-y
(- (:y start-p)
(cond
v-center? (/ line-height 2)
v-end? line-height
:else 0))
x (if row? (:x frame) prev-x)
y (if col? (:y frame) prev-y)
width (cond
(and col? last?)
(- (+ (:x frame) (:width frame)) x)
row?
(:width frame)
:else
(+ line-width (- box-x prev-x) (/ layout-gap-row 2)))
height (cond
(and row? last?)
(- (+ (:y frame) (:height frame)) y)
col?
(:height frame)
:else
(+ line-height (- box-y prev-y) (/ layout-gap-col 2)))
line-area (gsr/make-rect x y width height)
children (subvec children from-idx (+ from-idx num-children))
;; To debug the lines
;;result (conj result line-area)
result (first (reduce redfn-child [result line-area] (d/with-next children)))]
[result (+ from-idx num-children) (+ x width) (+ y height)]))]
(first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data))))))
(defn get-drop-index
[frame-id objects position]
(let [frame (get objects frame-id)
position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame))
children (cph/get-immediate-children objects frame-id)
layout-data (fli/calc-layout-data frame children)
drop-areas (layout-drop-areas frame layout-data children)
area (d/seek #(gsr/contains-point? % position) drop-areas)]
(:index area)))

View file

@ -0,0 +1,309 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.geom.shapes.flex-layout.lines
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.transforms :as gst]
[app.common.types.shape.layout :as ctl]))
(defn layout-bounds
[{:keys [layout-padding layout-padding-type] :as shape}]
(let [;; Add padding to the bounds
{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
[pad-top pad-right pad-bottom pad-left]
(if (= layout-padding-type :multiple)
[pad-top pad-right pad-bottom pad-left]
[pad-top pad-top pad-top pad-top])
;; Normalize the points to remove flips
;; TODO LAYOUT: Need function to normalize the points
points (gst/parent-coords-points shape shape)]
(gpo/pad-points points pad-top pad-right pad-bottom pad-left)))
(defn init-layout-lines
"Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation"
[shape children layout-bounds]
(let [wrap? (ctl/wrap? shape)
col? (ctl/col? shape)
row? (ctl/row? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
reduce-fn
(fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child]
(let [child-bounds (gst/parent-coords-points child shape)
child-width (gpo/width-points child-bounds)
child-height (gpo/height-points child-bounds)
child-min-width (ctl/child-min-width child)
child-min-height (ctl/child-min-height child)
fill-width? (ctl/fill-width? child)
fill-height? (ctl/fill-height? child)
cur-child-fill? (or (and row? fill-width?) (and col? fill-height?))
cur-line-fill? (or (and col? fill-width?) (and row? fill-height?))
next-width (if fill-width? child-min-width child-width)
next-height (if fill-height? child-min-height child-height)
next-line-width (+ line-width next-width (* layout-gap-row (dec num-children)))
next-line-height (+ line-height next-height (* layout-gap-col (dec num-children)))]
(if (and (some? line-data)
(or (not wrap?)
(and row? (<= next-line-width layout-width))
(and col? (<= next-line-height layout-height))))
[{:line-width (if row? (+ line-width next-width) (max line-width next-width))
:line-height (if col? (+ line-height next-height) (max line-height next-height))
:num-children (inc num-children)
:child-fill? (or cur-child-fill? child-fill?)
:line-fill? (or cur-line-fill? line-fill?)
:num-child-fill (cond-> num-child-fill cur-child-fill? inc)}
result]
[{:line-width next-width
:line-height next-height
:num-children 1
:child-fill? cur-child-fill?
:line-fill? cur-line-fill?
:num-child-fill (if cur-child-fill? 1 0)}
(cond-> result (some? line-data) (conj line-data))])))
[line-data layout-lines] (reduce reduce-fn [nil []] children)]
(cond-> layout-lines (some? line-data) (conj line-data))))
(defn get-base-line
[parent layout-bounds total-width total-height]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
row? (ctl/row? parent)
col? (ctl/col? parent)
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
hv (partial gpo/start-hv layout-bounds)
vv (partial gpo/start-vv layout-bounds)]
(cond-> (gpo/origin layout-bounds)
(and col? h-center?)
(gpt/add (hv (/ (- layout-width total-width) 2)))
(and col? h-end?)
(gpt/add (hv (- layout-width total-width)))
(and row? v-center?)
(gpt/add (vv (/ (- layout-height total-height) 2)))
(and row? v-end?)
(gpt/add (vv (- layout-height total-height))))))
(defn get-next-line
[parent layout-bounds {:keys [line-width line-height]} base-p]
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
hv #(gpo/start-hv layout-bounds %)
vv #(gpo/start-vv layout-bounds %)]
(cond-> base-p
col?
(gpt/add (hv (+ line-width layout-gap-row)))
row?
(gpt/add (vv (+ line-height layout-gap-col))))))
(defn get-start-line
[parent layout-bounds {:keys [line-width line-height num-children child-fill? ]} base-p]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row? (ctl/row? parent)
col? (ctl/col? parent)
space-between? (ctl/space-between? parent)
space-around? (ctl/space-around? parent)
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
hv #(gpo/start-hv layout-bounds %)
vv #(gpo/start-vv layout-bounds %)
children-gap-width (* layout-gap-row (dec num-children))
children-gap-height (* layout-gap-col (dec num-children))
line-width (if (and row? child-fill?)
(- layout-width (* layout-gap-row (dec num-children)))
line-width)
line-height (if (and col? child-fill?)
(- layout-height (* layout-gap-col (dec num-children)))
line-height)
start-p
(cond-> base-p
;; X AXIS
(and row? h-center? (not space-around?) (not space-between?))
(-> (gpt/add (hv (/ layout-width 2)))
(gpt/subtract (hv (/ (+ line-width children-gap-width) 2))))
(and row? h-end? (not space-around?) (not space-between?))
(-> (gpt/add (hv layout-width))
(gpt/subtract (hv (+ line-width children-gap-width))))
(and col? h-center?)
(gpt/add (hv (/ line-width 2)))
(and col? h-end?)
(gpt/add (hv line-width))
;; Y AXIS
(and col? v-center? (not space-around?) (not space-between?))
(-> (gpt/add (vv (/ layout-height 2)))
(gpt/subtract (vv (/ (+ line-height children-gap-height) 2))))
(and col? v-end? (not space-around?) (not space-between?))
(-> (gpt/add (vv layout-height))
(gpt/subtract (vv (+ line-height children-gap-height))))
(and row? v-center?)
(gpt/add (vv (/ line-height 2)))
(and row? v-end?)
(gpt/add (vv line-height)))]
start-p))
(defn add-lines-positions
[parent layout-bounds layout-lines]
(let [layout-width (gpo/width-points layout-bounds)
layout-height (gpo/height-points layout-bounds)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row? (ctl/row? parent)
col? (ctl/col? parent)]
(letfn [(add-lines [[total-width total-height] {:keys [line-width line-height]}]
[(+ total-width line-width)
(+ total-height line-height)])
(add-starts [[result base-p] layout-line]
(let [start-p (get-start-line parent layout-bounds layout-line base-p)
next-p (get-next-line parent layout-bounds layout-line base-p)]
[(conj result
(assoc layout-line :start-p start-p))
next-p]))]
(let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0]))
total-width (+ total-width (* layout-gap-row (dec (count layout-lines))))
total-height (+ total-height (* layout-gap-col (dec (count layout-lines))))
vertical-fill-space (- layout-height total-height)
horizontal-fill-space (- layout-width total-width)
num-line-fill (count (->> layout-lines (filter :line-fill?)))
layout-lines
(->> layout-lines
(mapv #(cond-> %
(and row? (:line-fill? %))
(update :line-height + (/ vertical-fill-space num-line-fill))
(and col? (:line-fill? %))
(update :line-width + (/ horizontal-fill-space num-line-fill)))))
total-width (if (and col? (> num-line-fill 0)) layout-width total-width)
total-height (if (and row? (> num-line-fill 0)) layout-height total-height)
base-p (get-base-line parent layout-bounds total-width total-height)]
(first (reduce add-starts [[] base-p] layout-lines))))))
(defn add-line-spacing
"Calculates the baseline for a flex layout"
[shape layout-bounds {:keys [num-children line-width line-height] :as line-data}]
(let [width (gpo/width-points layout-bounds)
height (gpo/height-points layout-bounds)
row? (ctl/row? shape)
col? (ctl/col? shape)
space-between? (ctl/space-between? shape)
space-around? (ctl/space-around? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
layout-gap-row
(cond (and row? space-around?)
0
(and row? space-between?)
(/ (- width line-width) (dec num-children))
:else
layout-gap-row)
layout-gap-col
(cond (and col? space-around?)
0
(and col? space-between?)
(/ (- height line-height) (dec num-children))
:else
layout-gap-col)
margin-x
(if (and row? space-around?)
(/ (- width line-width) (inc num-children))
0)
margin-y
(if (and col? space-around?)
(/ (- height line-height) (inc num-children))
0)]
(assoc line-data
:layout-bounds layout-bounds
:layout-gap-row layout-gap-row
:layout-gap-col layout-gap-col
:margin-x margin-x
:margin-y margin-y)))
(defn calc-layout-data
"Digest the layout data to pass it to the constrains"
[shape children]
(let [layout-bounds (layout-bounds shape)
reverse? (ctl/reverse? shape)
children (cond->> children reverse? reverse)
;; Creates the layout lines information
layout-lines
(->> (init-layout-lines shape children layout-bounds)
(add-lines-positions shape layout-bounds)
(mapv (partial add-line-spacing shape layout-bounds)))]
{:layout-lines layout-lines
:reverse? reverse?}))

View file

@ -0,0 +1,103 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.geom.shapes.flex-layout.modifiers
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes.flex-layout.positions :as fpo]
[app.common.geom.shapes.points :as gpo]
[app.common.geom.shapes.transforms :as gst]
[app.common.types.modifiers :as ctm]
[app.common.types.shape.layout :as ctl]))
(defn normalize-child-modifiers
"Apply the modifiers and then normalized them against the parent coordinates"
[parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}]
(let [transformed-child (gst/transform-shape child modifiers)
child-bb-before (gst/parent-coords-rect child parent)
child-bb-after (gst/parent-coords-rect transformed-child transformed-parent)
scale-x (/ (:width child-bb-before) (:width child-bb-after))
scale-y (/ (:height child-bb-before) (:height child-bb-after))
resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n
resize-vector (gpt/point scale-x scale-y)]
(-> modifiers
(ctm/select-child-modifiers)
(ctm/set-resize resize-vector resize-origin transform transform-inverse))))
(defn calc-fill-width-data
"Calculates the size and modifiers for the width of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-h-behavior] :as child}
child-origin child-width
{:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}]
(let [[layout-gap-row _] (ctl/gaps parent)]
(cond
(and (ctl/row? parent) (= :fill layout-h-behavior) child-fill?)
(let [layout-width (gpo/width-points layout-bounds)
fill-space (- layout-width line-width (* layout-gap-row (dec num-children)))
fill-width (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-width child-width)]
{:width fill-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})
(and (ctl/col? parent) (= :fill layout-h-behavior) line-fill?)
(let [fill-scale (/ line-width child-width)]
{:width line-width
:modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))))
(defn calc-fill-height-data
"Calculates the size and modifiers for the height of an auto-fill child"
[{:keys [transform transform-inverse] :as parent}
{:keys [layout-v-behavior] :as child}
child-origin child-height
{:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}]
(let [[_ layout-gap-col] (ctl/gaps parent)]
(cond
(and (ctl/col? parent) (= :fill layout-v-behavior) child-fill?)
(let [layout-height (gpo/height-points layout-bounds)
fill-space (- layout-height line-height (* layout-gap-col (dec num-children)))
fill-height (/ fill-space (:num-child-fill layout-data))
fill-scale (/ fill-height child-height)]
{:height fill-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})
(and (ctl/row? parent) (= :fill layout-v-behavior) line-fill?)
(let [fill-scale (/ line-height child-height)]
{:height line-height
:modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))))
(defn calc-layout-modifiers
"Calculates the modifiers for the layout"
[parent child layout-line]
(let [child-bounds (gst/parent-coords-points child parent)
child-origin (gpo/origin child-bounds)
child-width (gpo/width-points child-bounds)
child-height (gpo/height-points child-bounds)
fill-width (calc-fill-width-data parent child child-origin child-width layout-line)
fill-height (calc-fill-height-data parent child child-origin child-height layout-line)
child-width (or (:width fill-width) child-width)
child-height (or (:height fill-height) child-height)
[corner-p layout-line] (fpo/get-child-position parent child-width child-height layout-line)
move-vec (gpt/to-vec child-origin corner-p)
modifiers
(-> (ctm/empty-modifiers)
(cond-> fill-width (ctm/add-modifiers (:modifiers fill-width)))
(cond-> fill-height (ctm/add-modifiers (:modifiers fill-height)))
(ctm/set-move move-vec))]
[modifiers layout-line]))

View file

@ -0,0 +1,68 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.geom.shapes.flex-layout.positions
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes.points :as gpo]
[app.common.types.shape.layout :as ctl]))
(defn get-child-position
"Calculates the position for the current shape given the layout-data context"
[parent
child-width child-height
{:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}]
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
points (:points parent)
hv (partial gpo/start-hv points)
vv (partial gpo/start-vv points)
corner-p
(cond-> start-p
(and col? h-center?)
(gpt/add (hv (- (/ child-width 2))))
(and col? h-end?)
(gpt/add (hv (- child-width)))
(and row? v-center?)
(gpt/add (vv (- (/ child-height 2))))
(and row? v-end?)
(gpt/add (vv (- child-height)))
(some? margin-x)
(gpt/add (hv margin-x))
(some? margin-y)
(gpt/add (vv margin-y)))
next-p
(cond-> start-p
row?
(gpt/add (hv (+ child-width layout-gap-row)))
col?
(gpt/add (vv (+ child-height layout-gap-col)))
(some? margin-x)
(gpt/add (hv margin-x))
(some? margin-y)
(gpt/add (vv margin-y)))
layout-data
(assoc layout-data :start-p next-p)]
[corner-p layout-data]))

View file

@ -9,6 +9,27 @@
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :strech
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default)
;; :layout-wrap-type ;; :wrap, :no-wrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
;; ITEMS
;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
;; :layout-margin-type ;; :simple :multiple
;; :layout-h-behavior ;; :fill :fix :auto
;; :layout-v-behavior ;; :fill :fix :auto
;; :layout-max-h ;; num
;; :layout-min-h ;; num
;; :layout-max-w ;; num
;; :layout-min-w
(s/def ::layout #{:flex :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-gap-type #{:simple :multiple})
@ -75,3 +96,111 @@
::layout-max-w
::layout-min-w
::layout-align-self]))
(defn wrap? [{:keys [layout-wrap-type]}]
(= layout-wrap-type :wrap))
(defn fill-width? [child]
(= :fill (:layout-h-behavior child)))
(defn fill-height? [child]
(= :fill (:layout-v-behavior child)))
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
(defn gaps
[{:keys [layout-gap layout-gap-type]}]
(let [layout-gap-row (or (-> layout-gap :row-gap) 0)
layout-gap-col (if (= layout-gap-type :simple)
layout-gap-row
(or (-> layout-gap :column-gap) 0))]
[layout-gap-row layout-gap-col]))
(defn child-min-width
[child]
(if (and (fill-width? child)
(some? (:layout-min-h child)))
(max 0 (:layout-min-h child))
0))
(defn child-max-width
[child]
(if (and (fill-width? child)
(some? (:layout-min-h child)))
(max 0 (:layout-min-h child))
0))
(defn child-min-height
[child]
(if (and (fill-width? child)
(some? (:layout-min-v child)))
(max 0 (:layout-min-v child))
0))
(defn child-max-height
[child]
(if (and (fill-width? child)
(some? (:layout-min-v child)))
(max 0 (:layout-min-v child))
0))
(defn h-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :start))
(and (row? shape)
(= layout-justify-content :start))))
(defn h-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :center))
(and (row? shape)
(= layout-justify-content :center))))
(defn h-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (col? shape)
(= layout-align-items :end))
(and (row? shape)
(= layout-justify-content :end))))
(defn v-start?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :start))
(and (col? shape)
(= layout-justify-content :start))))
(defn v-center?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :center))
(and (col? shape)
(= layout-justify-content :center))))
(defn v-end?
[{:keys [layout-align-items layout-justify-content] :as shape}]
(or (and (row? shape)
(= layout-align-items :end))
(and (col? shape)
(= layout-justify-content :end))))
(defn reverse?
[{:keys [layout-flex-dir]}]
(or (= :reverse-row layout-flex-dir)
(= :reverse-column layout-flex-dir)))
(defn space-between?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-between))
(defn space-around?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-around))

View file

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.sidebar.options
(:require
[app.common.data :as d]
[app.common.types.modifiers :as ctm]
[app.main.data.workspace :as udw]
[app.main.refs :as refs]
[app.main.store :as st]