Advertisement
Guest User

Untitled

a guest
Mar 22nd, 2019
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.42 KB | None | 0 0
  1. module Elmish.Boxes
  2.  
  3. open System
  4. open Fable.Helpers.React
  5. open Fable.Helpers.React.Props
  6.  
  7.  
  8. [<AutoOpen>]
  9. module Domain =
  10.  
  11.  
  12. [<NoComparison>]
  13. type Point =
  14. { x: float
  15. y: float }
  16. static member (+) (p1, p2) = { x = p1.x + p2.x; y = p1.y + p2.y }
  17. static member (+) (p, scalar) = { x = p.x + scalar; y = p.y + scalar }
  18. static member (-) (p1, p2) = { x = p1.x - p2.x; y = p1.y - p2.y }
  19. static member (-) (p, scalar) = { x = p.x - scalar; y = p.y - scalar }
  20. static member (*) (p, scalar) = { x = p.x * scalar; y = p.y * scalar }
  21. static member (*) (p1, p2) = { x = p1.x * p2.x; y = p1.y * p2.y }
  22. static member (/) (p1, p2) = { x = p1.x / p2.x; y = p1.y / p2.y }
  23. static member (/) (p, scalar) = { x = p.x / scalar; y = p.y / scalar }
  24.  
  25. type BoxId = Guid
  26.  
  27. /// Anchor relative to box upper left
  28. type BoxRelativeAnchor = Point
  29.  
  30.  
  31. type Box =
  32. { id: BoxId
  33. upperLeft: Point
  34. size: float
  35. color: string }
  36.  
  37.  
  38. module Box =
  39.  
  40. let create (center: Point) size color =
  41. { id = Guid.NewGuid()
  42. upperLeft = center - (size / 2.)
  43. size = size
  44. color = color }
  45.  
  46. let moveBy dPos box =
  47. { box with upperLeft = box.upperLeft + dPos }
  48.  
  49. let resizeBy factor (anchor: Point) box =
  50. let relAnchor = (anchor - box.upperLeft) / box.size
  51. let resizedBox = { box with size = box.size * factor }
  52. let relAnchorResized = (anchor - box.upperLeft) / resizedBox.size
  53. let dPos = (relAnchorResized - relAnchor) * resizedBox.size
  54. { resizedBox with upperLeft = box.upperLeft + dPos }
  55.  
  56.  
  57. type BoxHierarchy =
  58. { boxes: Map<BoxId, Box>
  59. childToParent: Map<BoxId, BoxId> }
  60.  
  61.  
  62. module BoxHierarchy =
  63.  
  64. let childIdsOf parentId hierarchy =
  65. hierarchy.childToParent
  66. |> Map.filter (fun _ pid -> pid = parentId)
  67. |> Map.toList
  68. |> List.map fst
  69.  
  70. let childrenOf parentId hierarchy =
  71. hierarchy
  72. |> childIdsOf parentId
  73. |> List.choose hierarchy.boxes.TryFind
  74.  
  75. let rec withDescendantIds hierarchy parentId =
  76. parentId
  77. :: ( hierarchy
  78. |> childIdsOf parentId
  79. |> List.collect (withDescendantIds hierarchy) )
  80.  
  81. let topLevelBoxes hierarchy =
  82. hierarchy.boxes
  83. |> Map.filter (fun boxId _ -> not <| hierarchy.childToParent.ContainsKey boxId)
  84. |> Map.toList
  85. |> List.map snd
  86.  
  87. let addBox box hierarchy =
  88. { hierarchy with boxes = hierarchy.boxes.Add(box.id, box) }
  89.  
  90. let removeWithDescendants boxId hierarchy =
  91. let toRemove = boxId |> withDescendantIds hierarchy |> Set.ofList
  92. { hierarchy with
  93. boxes =
  94. hierarchy.boxes
  95. |> Map.filter (fun bid _ -> not <| toRemove.Contains bid)
  96. childToParent =
  97. hierarchy.childToParent
  98. |> Map.filter (fun childId parentId ->
  99. not <| toRemove.Contains childId
  100. && not <| toRemove.Contains parentId
  101. )
  102. }
  103.  
  104. let setTopLevel boxId hierarchy =
  105. { hierarchy with childToParent = hierarchy.childToParent |> Map.remove boxId }
  106.  
  107. let move boxId newPos relativeAnchor hierarchy =
  108. match hierarchy.boxes.TryFind boxId with
  109. | None -> hierarchy
  110. | Some box ->
  111. let idsToMove = box.id |> withDescendantIds hierarchy |> Set.ofList
  112. let dPos = newPos - box.upperLeft - relativeAnchor
  113. let updatedBoxes =
  114. hierarchy.boxes
  115. |> Map.map (fun _ box ->
  116. if idsToMove.Contains box.id then
  117. box |> Box.moveBy dPos
  118. else box
  119. )
  120. { hierarchy with boxes = updatedBoxes }
  121.  
  122. let setRelationship childId parentId hierarchy =
  123. { hierarchy with childToParent = hierarchy.childToParent.Add(childId, parentId) }
  124.  
  125. let resize boxId factor absoluteAnchor hierarchy =
  126. let idsToResize = boxId |> withDescendantIds hierarchy |> Set.ofList
  127. { hierarchy with
  128. boxes =
  129. hierarchy.boxes
  130. |> Map.map (fun _ box ->
  131. if idsToResize.Contains box.id then
  132. box |> Box.resizeBy factor absoluteAnchor
  133. else box
  134. )
  135. }
  136.  
  137.  
  138. [<AutoOpen>]
  139. module MvuModel =
  140.  
  141. type Model =
  142. { hierarchy: BoxHierarchy
  143. mousePos: Point
  144. dragging: (BoxId * BoxRelativeAnchor) option
  145. undo: Model list
  146. redo: Model list }
  147.  
  148. let init () =
  149. let id1 = Guid.NewGuid()
  150. let id2 = Guid.NewGuid()
  151. let id3 = Guid.NewGuid()
  152. let id4 = Guid.NewGuid()
  153. { hierarchy =
  154. { boxes =
  155. [
  156. {id = id1; upperLeft = { x = 20.; y = 20. }; size = 200.; color = "red"}
  157. {id = id2; upperLeft = { x = 100.; y = 100. }; size = 50.; color = "green"}
  158. {id = id3; upperLeft = { x = 400.; y = 400. }; size = 200.; color = "blue"}
  159. {id = id4; upperLeft = { x = 500.; y = 500. }; size = 80.; color = "yellow"}
  160. ]
  161. |> List.map (fun b -> b.id, b)
  162. |> Map.ofList
  163. childToParent = Map.empty.Add(id2, id1).Add(id4, id3) }
  164. mousePos = { x = 0.; y = 0. }
  165. dragging = None
  166. undo = []
  167. redo = [] }
  168.  
  169. let isDragging boxId model =
  170. model.dragging
  171. |> Option.map (fun (bid, _) -> bid = boxId)
  172. |> Option.defaultValue false
  173.  
  174. let drop childId parentId model =
  175. { model with
  176. dragging = None
  177. hierarchy = model.hierarchy |> BoxHierarchy.setRelationship childId parentId }
  178.  
  179. let rand = Random()
  180.  
  181. let addRandomBox center model =
  182. let size = rand.Next(40, 200) |> float
  183. let color = String.Format("#{0:X6}", rand.Next(0x1000000))
  184. let box = Box.create center size color
  185. { model with hierarchy = model.hierarchy |> BoxHierarchy.addBox box }
  186.  
  187.  
  188. [<AutoOpen>]
  189. module MvuUpdate =
  190.  
  191. type Msg =
  192. | PickUp of BoxId * BoxRelativeAnchor
  193. | DropOnBox of BoxId
  194. | DropOnEmpty
  195. | CreateNew of Point
  196. | Delete of BoxId
  197. | Grow of BoxId
  198. | Shrink of BoxId
  199. | MouseMove of Point
  200. | Undo
  201. | Redo
  202.  
  203.  
  204. let update msg model =
  205. match msg with
  206.  
  207. | PickUp (boxId, relativeAnchor) ->
  208. { model with
  209. dragging = Some (boxId, relativeAnchor)
  210. hierarchy = model.hierarchy |> BoxHierarchy.setTopLevel boxId
  211. undo = model :: model.undo
  212. redo = [] }
  213.  
  214. | DropOnBox parentId ->
  215. model.dragging
  216. |> Option.map (fun (childId, _) -> drop childId parentId model)
  217. |> Option.defaultValue model
  218.  
  219. | DropOnEmpty -> { model with dragging = None }
  220.  
  221. | CreateNew center ->
  222. { model with
  223. undo = model :: model.undo
  224. redo = [] }
  225. |> addRandomBox center
  226.  
  227. | Delete boxId ->
  228. { model with
  229. hierarchy = model.hierarchy |> BoxHierarchy.removeWithDescendants boxId
  230. undo = model :: model.undo
  231. redo = [] }
  232.  
  233. | Grow boxId ->
  234. { model with
  235. hierarchy = model.hierarchy |> BoxHierarchy.resize boxId 1.1 model.mousePos
  236. undo = model :: model.undo
  237. redo = [] }
  238.  
  239. | Shrink boxId ->
  240. { model with
  241. hierarchy = model.hierarchy |> BoxHierarchy.resize boxId (1./1.1) model.mousePos
  242. undo = model :: model.undo
  243. redo = [] }
  244.  
  245. | MouseMove newPos ->
  246. { model with
  247. mousePos = newPos
  248. hierarchy =
  249. match model.dragging with
  250. | None -> model.hierarchy
  251. | Some (boxId, anchor) -> model.hierarchy |> BoxHierarchy.move boxId newPos anchor
  252. }
  253.  
  254. | Undo ->
  255. match model.undo with
  256. | [] -> model
  257. | head :: tail -> { head with undo = tail; redo = model :: model.redo }
  258.  
  259. | Redo ->
  260. match model.redo with
  261. | [] -> model
  262. | head :: tail -> { head with redo = tail; undo = model :: model.undo }
  263.  
  264.  
  265. [<AutoOpen>]
  266. module MvuView =
  267.  
  268. let rec boxWithChildren model dispatch origin box =
  269. let dragging = isDragging box.id model
  270. div [
  271. Key (string box.id)
  272. Class "box"
  273. OnMouseDown (fun ev ->
  274. ev.preventDefault()
  275. ev.stopPropagation()
  276. match ev.button with
  277. | 0. -> (box.id, model.mousePos - box.upperLeft) |> PickUp |> dispatch
  278. | 1. -> Delete box.id |> dispatch
  279. | _ -> ())
  280. OnMouseUp (fun ev ->
  281. ev.preventDefault()
  282. ev.stopPropagation()
  283. DropOnBox box.id |> dispatch)
  284. OnWheel (fun ev ->
  285. ev.preventDefault()
  286. ev.stopPropagation()
  287. if ev.deltaY < 0. then Grow box.id |> dispatch
  288. elif ev.deltaY > 0. then Shrink box.id |> dispatch)
  289. Style [
  290. Width box.size
  291. Height box.size
  292. Top (box.upperLeft.y - origin.y)
  293. Left (box.upperLeft.x - origin.x)
  294. BackgroundColor box.color
  295. ZIndex (if dragging then 2 else 1)
  296. Opacity (if dragging then 0.7 else 1.)
  297. // Never trigger pointer event (e.g. drop) on currently dragging box;
  298. // let next layer trigger the event
  299. PointerEvents (if dragging then "none" else "auto")
  300. ]
  301. ] [
  302. model.hierarchy
  303. |> BoxHierarchy.childrenOf box.id
  304. |> List.map (boxWithChildren model dispatch box.upperLeft)
  305. |> ofList
  306. ]
  307.  
  308. let view model dispatch =
  309. div [
  310. Class "main-container"
  311. OnMouseDown (fun ev ->
  312. ev.preventDefault()
  313. match ev.button with
  314. | 0. -> CreateNew { x = ev.pageX; y = ev.pageY } |> dispatch
  315. | _ -> ())
  316. OnMouseUp (fun ev -> ev.preventDefault(); dispatch DropOnEmpty)
  317. OnMouseMove (fun ev ->
  318. MouseMove { x = ev.pageX; y = ev.pageY } |> dispatch)
  319. ] [
  320. div [
  321. Class "instructions"
  322. OnMouseMove (fun ev -> ev.stopPropagation())
  323. OnMouseDown (fun ev -> ev.stopPropagation())
  324. OnMouseUp (fun ev -> ev.stopPropagation())
  325. ] [
  326. p [] [ str "Drag box to move" ]
  327. p [] [ str "Drag box inside other box to set as child" ]
  328. p [] [ str "Left-click empty area to create new box" ]
  329. p [] [ str "Middle-click box to remove" ]
  330. p [] [ str "Scroll box to resize" ]
  331. button [
  332. OnClick (fun _ -> dispatch Undo)
  333. Disabled model.undo.IsEmpty
  334. ] [ str "Undo ("; ofInt model.undo.Length; str ")" ]
  335. button [
  336. OnClick (fun _ -> dispatch Redo)
  337. Disabled model.redo.IsEmpty
  338. ] [ str "Redo ("; ofInt model.redo.Length; str ")" ]
  339. ]
  340. model.hierarchy
  341. |> BoxHierarchy.topLevelBoxes
  342. |> List.map (boxWithChildren model dispatch { x=0.; y=0. })
  343. |> ofList
  344. ]
  345.  
  346.  
  347. open Elmish
  348. open Elmish.React
  349.  
  350. Program.mkSimple init update view
  351. |> Program.withReactSynchronous "app"
  352. |> Program.run
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement