git.iko.soy/rowtype-yoga/psfp
on master help sign in
Magic!
c8302a6Mark Eibes
components/src/React/Basic/Hooks/UseGesture.purs+5−4
@@ -5,7 +5,6 @@ import Data.Array ((!!))
5import Data.Maybe (Maybe, fromMaybe')
5import Data.Maybe (Maybe, fromMaybe')
6import Data.Nullable (Nullable)
6import Data.Nullable (Nullable)
7import Data.Tuple.Nested ((/\), type (/\))
7import Data.Tuple.Nested ((/\), type (/\))
8−import Debug.Trace (spy)
9import Effect (Effect)
8import Effect (Effect)
10import Effect.Aff.Compat (runEffectFn2)
9import Effect.Aff.Compat (runEffectFn2)
11import Effect.Uncurried (EffectFn2)
10import Effect.Uncurried (EffectFn2)
@@ -22,10 +21,10 @@ import Yoga.Helpers ((?||))
22foreign import data UseDrag ∷ Type -> Type -> Type
21foreign import data UseDrag ∷ Type -> Type -> Type
23
22
24type DragHandler a
23type DragHandler a
25− = { arg ∷ a, down ∷ Boolean, movement ∷ Number /\ Number } -> Effect Unit
24+ = { arg ∷ a, down ∷ Boolean, movement ∷ Number /\ Number, xy ∷ Number /\ Number } -> Effect Unit
26
25
27type DragHandlerImpl a
26type DragHandlerImpl a
28− = { args ∷ Array a, down ∷ Boolean, movement ∷ Array Number } -> Unit
27+ = { args ∷ Array a, down ∷ Boolean, movement ∷ Array Number, xy ∷ Array Number } -> Unit
29
28
30type DragProps
29type DragProps
31 = { onMouseDown ∷ EventHandler, onTouchStart ∷ EventHandler }
30 = { onMouseDown ∷ EventHandler, onTouchStart ∷ EventHandler }
@@ -53,12 +52,14 @@ useDrag dragOptions dragHandler = unsafeHook (runEffectFn2 useDragImpl dragHandl
53 dragHandlerImpl ∷ DragHandlerImpl a
52 dragHandlerImpl ∷ DragHandlerImpl a
54 dragHandlerImpl x =
53 dragHandlerImpl x =
55 (unsafePerformEffect <<< dragHandler)
54 (unsafePerformEffect <<< dragHandler)
56− { arg, down: x.down, movement: mx /\ my
55+ { arg, down: x.down, movement: mx /\ my, xy: xyX /\ xyY
57 }
56 }
58 where
57 where
59 arg = x.args !! 0 # fromMaybe' (\_ -> unsafeCrashWith "Bollox")
58 arg = x.args !! 0 # fromMaybe' (\_ -> unsafeCrashWith "Bollox")
60 mx = x.movement !! 0 ?|| 0.0
59 mx = x.movement !! 0 ?|| 0.0
61 my = x.movement !! 1 ?|| 0.0
60 my = x.movement !! 1 ?|| 0.0
61+ xyX = x.xy !! 0 ?|| 0.0
62+ xyY = x.xy !! 1 ?|| 0.0
62
63
63withDragProps ∷
64withDragProps ∷
64 ∀ attrs.
65 ∀ attrs.
components/src/Yoga/Grimoire/Component.purs+89−12
@@ -1,27 +1,35 @@
1module Yoga.Grimoire.Component where
1module Yoga.Grimoire.Component where
2
2
3import Prelude
3import Prelude
4−import Data.Array (mapWithIndex, zip)
4+import Data.Array (mapWithIndex, zip, (!!))
5import Data.Array as Array
5import Data.Array as Array
6−import Data.Maybe (Maybe(..))
6+import Data.FoldableWithIndex (findWithIndex)
7+import Data.Lens (set)
8+import Data.Lens.Index (ix)
9+import Data.Maybe (Maybe(..), fromMaybe, isJust)
7import Data.Nullable (Nullable)
10import Data.Nullable (Nullable)
11+import Data.Nullable as Nullable
12+import Data.Traversable (for, sequence)
8−import Data.Tuple.Nested ((/\))
13+import Data.Tuple.Nested ((/\), type (/\))
9import Debug.Trace (spy)
14import Debug.Trace (spy)
10import Effect (Effect)
15import Effect (Effect)
11import Justifill (justifill)
16import Justifill (justifill)
12import React.Basic (ReactComponent)
17import React.Basic (ReactComponent)
13import React.Basic.DOM (css)
18import React.Basic.DOM (css)
14import React.Basic.Helpers (jsx)
19import React.Basic.Helpers (jsx)
15−import React.Basic.Hooks (Ref, component, element)
20+import React.Basic.Hooks (Ref, component, element, readRef, useLayoutEffect, useRef, writeRef)
16import React.Basic.Hooks as React
21import React.Basic.Hooks as React
17import React.Basic.Hooks.Spring (animatedDiv, useSprings)
22import React.Basic.Hooks.Spring (animatedDiv, useSprings)
18import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
23import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
19import Record.Extra (pick)
24import Record.Extra (pick)
20import Unsafe.Coerce (unsafeCoerce)
25import Unsafe.Coerce (unsafeCoerce)
21import Web.DOM (Node)
26import Web.DOM (Node)
27+import Web.HTML.HTMLElement (DOMRect, getBoundingClientRect)
28+import Web.HTML.HTMLElement as HTMLElement
22import Yoga.Grid.Component as Grid
29import Yoga.Grid.Component as Grid
23import Yoga.Grimoire.Spell.Component as GrimoireSpell
30import Yoga.Grimoire.Spell.Component as GrimoireSpell
24import Yoga.Grimoire.Styles (styles)
31import Yoga.Grimoire.Styles (styles)
32+import Yoga.Helpers ((?||))
25import Yoga.Spell.Types (Spell)
33import Yoga.Spell.Types (Spell)
26import Yoga.Theme.Styles (makeStylesJSS, useTheme)
34import Yoga.Theme.Styles (makeStylesJSS, useTheme)
27import Yoga.Theme.Types (CSSTheme)
35import Yoga.Theme.Types (CSSTheme)
@@ -30,6 +38,40 @@ type Props
30 = { spells ∷ Array Spell
38 = { spells ∷ Array Spell
31 }
39 }
32
40
41+getRects ref = do
42+ refNodes <- readRef ref
43+ for
44+ refNodes \nullinger ->
45+ sequence do
46+ Nullable.toMaybe nullinger >>= HTMLElement.fromNode <#> getBoundingClientRect
47+
48+center ∷ DOMRect -> (Number /\ Number)
49+center { left, top, width, height } = (left + width / 2.0) /\ (top + height / 2.0)
50+
51+overlaps ∷ DOMRect -> DOMRect -> Boolean
52+overlaps r1 { left, top, right, bottom } = between left right x && between top bottom y
53+ where
54+ x /\ y = center r1
55+
56+emptyDomRect ∷ DOMRect
57+emptyDomRect = { top: 0.0, right: 0.0, bottom: 0.0, left: 0.0, width: 0.0, height: 0.0 }
58+
59+translate ∷ (Number /\ Number) -> DOMRect -> DOMRect
60+translate (x /\ y) { top, right, bottom, left, width, height } =
61+ { top: top + y
62+ , right: right + x
63+ , bottom: bottom + y
64+ , left: left + x
65+ , width
66+ , height
67+ }
68+
69+swap ∷ ∀ a. Int -> Int -> Array a -> Maybe (Array a)
70+swap i j arr = do
71+ valueI <- arr !! i
72+ valueJ <- arr !! j
73+ pure $ (set (ix i) valueJ <<< set (ix j) valueI) arr
74+
33makeComponent ∷ Effect (ReactComponent Props)
75makeComponent ∷ Effect (ReactComponent Props)
34makeComponent = do
76makeComponent = do
35 grid <- Grid.makeComponent
77 grid <- Grid.makeComponent
@@ -38,21 +80,56 @@ makeComponent = do
38 component "Grimoire" \(props ∷ Props) -> React.do
80 component "Grimoire" \(props ∷ Props) -> React.do
39 {} <- useStyles (pick props)
81 {} <- useStyles (pick props)
40 springs <- useSprings (Array.length props.spells) \_ -> { x: 0.0, y: 0.0, zIndex: 0, immediate: false, transform: "scale3d(1.0,1.0,1.0)" }
82 springs <- useSprings (Array.length props.spells) \_ -> { x: 0.0, y: 0.0, zIndex: 0, immediate: false, transform: "scale3d(1.0,1.0,1.0)" }
41− let
83+ nodeRefs <- useRef (props.spells $> (Nullable.null ∷ (_ Node)))
42− _ = spy "springs" springs
84+ positionsRef <- useRef ([] ∷ _ DOMRect)
85+ originalPositionsRef <- useRef ([] ∷ _ DOMRect)
86+ useLayoutEffect unit do
87+ rects <- getRects nodeRefs
88+ writeRef positionsRef (rects <#> fromMaybe emptyDomRect)
89+ writeRef originalPositionsRef (rects <#> fromMaybe emptyDomRect)
90+ mempty
43 theme ∷ CSSTheme <- useTheme
91 theme ∷ CSSTheme <- useTheme
44 bindDragProps <-
92 bindDragProps <-
45− useDrag (justifill {}) \{ arg, down, movement: mx /\ my } -> do
93+ useDrag (justifill {}) \{ arg, down, movement: mx /\ my, xy: x /\ y } -> do
94+ rects <- getRects nodeRefs
95+ originalPositions <- readRef originalPositionsRef
96+ positionsBefore <- readRef positionsRef
97+ let
98+ originalPosDraggedBefore = positionsBefore !! arg ?|| emptyDomRect
99+
100+ currentPosDraggedBefore = originalPosDraggedBefore # translate (mx /\ my)
101+
102+ overlapsOtherBefore = positionsBefore # findWithIndex \i' pos -> i' /= arg && overlaps currentPosDraggedBefore pos
103+ case down, overlapsOtherBefore of
104+ false, Just { index, value } -> writeRef positionsRef (swap arg index positionsBefore ?|| positionsBefore)
105+ _, _ -> mempty
106+ positions <- readRef positionsRef
107+ let
108+ originalPosDragged = positions !! arg ?|| emptyDomRect
109+
110+ currentPosDragged = originalPosDragged # translate (mx /\ my)
111+
112+ overlapsOther = positions # findWithIndex \i' pos -> i' /= arg && overlaps currentPosDragged pos
46− springs.set \i ->
113+ springs.set \i -> do
47− if i == arg && down then
114+ let
115+ iPos = positions !! i ?|| emptyDomRect
116+
117+ origIPos = originalPositions !! i ?|| emptyDomRect
118+
119+ leftOffset = if iPos == origIPos then 0.0 else iPos.left - origIPos.left
120+
121+ topOffset = if iPos == origIPos then 0.0 else iPos.top - origIPos.top
122+ case i == arg, down, overlapsOther of
123+ false, true, Just { index, value }
124+ | index == i -> { x: originalPosDragged.left - value.left + leftOffset, y: originalPosDragged.top - value.top + topOffset, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
48− { x: mx, y: my, zIndex: 1, transform: "scale3d(1.1, 1.1, 1.1)", immediate: \n -> n == "x" || n == "y" || n == "zIndex" }
125+ true, true, _ -> { x: mx + leftOffset, y: my + topOffset, zIndex: 1, transform: "scale3d(1.1, 1.1, 1.1)", immediate: \n -> n == "x" || n == "y" || n == "zIndex" }
49− else
50− { x: 0.0, y: 0.0, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
126+ _, _, _ -> { x: leftOffset, y: topOffset, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
51 let
127 let
52 renderSpells =
128 renderSpells =
53 mapWithIndex \i (spell /\ style) ->
129 mapWithIndex \i (spell /\ style) ->
54 animatedDiv
130 animatedDiv
55 $ { style: css style
131 $ { style: css style
132+ , ref: unsafeCoerce (unsafeUpdateRefs nodeRefs i)
56 , children:
133 , children:
57 [ element
134 [ element
58 spellComponent
135 spellComponent
components/src/Yoga/Grimoire/Stories.purs+13
@@ -21,4 +21,17 @@ spells =
21 [ { name: "cast", signature: "String -> Effect Unit", description: "Casts an incantation" }
21 [ { name: "cast", signature: "String -> Effect Unit", description: "Casts an incantation" }
22 , { name: "take", signature: "Int -> String -> String", description: "Takes the first characters of a string" }
22 , { name: "take", signature: "Int -> String -> String", description: "Takes the first characters of a string" }
23 , { name: "append", signature: "a -> a -> a", description: "Takes two values and produces one" }
23 , { name: "append", signature: "a -> a -> a", description: "Takes two values and produces one" }
24+ , { name: "drop", signature: "Int -> String -> String", description: "Removes the first characters of a string" }
25+ , { name: "cast", signature: "String -> Effect Unit", description: "Casts an incantation" }
26+ , { name: "take", signature: "Int -> String -> String", description: "Takes the first characters of a string" }
27+ , { name: "append", signature: "a -> a -> a", description: "Takes two values and produces one" }
28+ , { name: "drop", signature: "Int -> String -> String", description: "Removes the first characters of a string" }
29+ , { name: "cast", signature: "String -> Effect Unit", description: "Casts an incantation" }
30+ , { name: "take", signature: "Int -> String -> String", description: "Takes the first characters of a string" }
31+ , { name: "append", signature: "a -> a -> a", description: "Takes two values and produces one" }
32+ , { name: "drop", signature: "Int -> String -> String", description: "Removes the first characters of a string" }
33+ , { name: "cast", signature: "String -> Effect Unit", description: "Casts an incantation" }
34+ , { name: "take", signature: "Int -> String -> String", description: "Takes the first characters of a string" }
35+ , { name: "append", signature: "a -> a -> a", description: "Takes two values and produces one" }
36+ , { name: "drop", signature: "Int -> String -> String", description: "Removes the first characters of a string" }
24 ]
37 ]