Skip to content

Commit

Permalink
Add a way to position items by arrow keys
Browse files Browse the repository at this point in the history
Also, fixed a possible bug - when you move/delete selected items, and
then make undo, we didn't recalculate the new selection
  • Loading branch information
astashov committed Aug 3, 2014
1 parent d540e80 commit 8a17a25
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 108 deletions.
11 changes: 3 additions & 8 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
* Add nice helpers showing the origin/dimensions of an item when you resize/move it around
* Find a way how to design UI for Undo Tree
* Add fills
* Position items by arrow keys
* Make sure we always show resize cursors (assign classes to .project)
* Make sure we clear cache when delete an item
* Add a way to align items relatively to each other
* Draw straight/square items with 'Shift'
* Make item/layer names consistent - name everything as 'items'
Expand All @@ -18,16 +16,13 @@
* Maybe custom colorize the items?
* Add comments here and there
* Add autoadjusting of items' size to the inner text size
* Add link to Github
* Fix bug - connectors keep 'connected' edges after deleting item with outlet
* Add "undo previews" - when you hover over the undo button, you will see a preview what gonna happen when you click undo
* Save user preferences (maybe in local storage?)
* Add export/import to/from a file
* Think how would it be possible to implement collaboration
- Could try Firebase, the problem - serializing. tixi.sync should know a lot,
and need to keep it updated when we change the data schema
- How to handle undo's? Keep a separate one for every user? Then need to move undo states out of :state
- How to distinguish who made what change? Prob generate uuid, and check if the change is made not by current
uuid
- How to handle undo's? Keep a separate one for every user? Also, need to add 'direction' of change, is this a new change or 'undo'?
- How to distinguish who made what change? Prob generate uuid, and check if the change is made not by current uuid
- Keep n 'currents' for every user, and n 'undo' stacks
- Would be nice to show typing text alive - how?
- If send updates to Firebase while drawing/resizing, we could spam it...
Expand Down
30 changes: 24 additions & 6 deletions src/tixi/controller.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
[tixi.mutators.delete :as md]
[tixi.mutators.render :as mr]
[tixi.mutators.selection :as ms]
[tixi.mutators.shared :as msh]
[tixi.mutators.text :as mt]
[tixi.mutators.undo :as mu]
[tixi.mutators.copy-paste :as mcp]
[tixi.mutators :as m]
[tixi.google-analytics :as ga]))

Expand Down Expand Up @@ -73,31 +75,47 @@
(m/z-show! (not (d/show-z-indexes?))))
:copy (do
(ga/event! "topbar" "copy")
(ms/copy!))
(mcp/copy!))
:cut (do
(ga/event! "topbar" "cut")
(ms/cut!))
(mcp/cut!))
:paste (do
(ga/event! "topbar" "paste")
(ms/paste!))
(mcp/paste!))
:grid (do
(ga/event! "topbar" "grid")
(m/toggle-grid! (not (d/show-grid?)))))))
(m/toggle-grid! (not (d/show-grid?))))
:move-up (do
(ga/event! "keypress" "move-up")
(msh/snapshot!)
(ms/move-selection! (g/build-point 0 -1)))
:move-down (do
(ga/event! "keypress" "move-down")
(msh/snapshot!)
(ms/move-selection! (g/build-point 0 1)))
:move-left (do
(ga/event! "keypress" "move-left")
(msh/snapshot!)
(ms/move-selection! (g/build-point -1 0)))
:move-right (do
(ga/event! "keypress" "move-right")
(msh/snapshot!)
(ms/move-selection! (g/build-point 1 0))))))

(defn mouse-down [client-point modifiers payload]
(render
(let [{:keys [action]} payload
point (p/position->coords client-point)]
(m/set-action! action)
(mu/snapshot!)
(msh/snapshot!)
(when (= action :draw)
(cond
(d/draw-tool?)
(mc/initiate-current-layer! point)

(d/select-tool?)
(let [id (p/item-id-at-point point)]
(when (= (d/selected-ids) [id])
(when (= (d/selected-ids) #{id})
(reset! select-second-clicked true))
(ms/select-layer! id point (:shift modifiers))))))))

Expand Down
3 changes: 1 addition & 2 deletions src/tixi/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
[tixi.sync :as s]
[tixi.compress :as c]
[tixi.mutators.shared :as ms]
[tixi.mutators.undo :as mu]
[tixi.utils :refer [p]]
[cljs.core.async :as async :refer [<!]]))

Expand All @@ -19,7 +18,7 @@

(s/load (fn [data]
(when (.val data)
(mu/snapshot!)
(ms/snapshot!)
(render
(ms/assign-state! (r/read-string (c/decompress (.val data))))))))

Expand Down
4 changes: 4 additions & 0 deletions src/tixi/dispatcher.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
73 (c/keypress :redo) ; u
78 (c/keypress :z-inc) ; n
77 (c/keypress :z-dec) ; m
37 (c/keypress :move-left) ; left
38 (c/keypress :move-up) ; up
39 (c/keypress :move-right) ; right
40 (c/keypress :move-down) ; down
nil)))

(defn handle-input-event [{:keys [type data]}]
Expand Down
37 changes: 37 additions & 0 deletions src/tixi/mutators/copy_paste.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
(ns tixi.mutators.copy-paste
(:require [tixi.data :as d]
[tixi.items :as i]
[tixi.geometry :as g]
[tixi.mutators :as m]
[tixi.mutators.shared :as msh]
[tixi.mutators.selection :as ms]
[tixi.mutators.render :as mr]
[tixi.mutators.locks :as ml]
[tixi.mutators.delete :as md]
[tixi.utils :refer [p]]))

(defn copy! []
(let [items (d/selected-items)]
(when (not-empty items)
(swap! d/data assoc :clipboard items))))

(defn cut! []
(let [ids (d/selected-ids)]
(copy!)
(ms/select-layer! nil)
(md/delete-items! ids)))

(defn paste! []
(msh/snapshot!)
(ms/select-layer! nil)
(doseq [item (d/clipboard)]
(let [id (d/autoincrement)]
(msh/autoincrement!)
(msh/update-state! assoc-in [:completed id] (update-in item [:input] g/move (g/build-size 1 1)))
(mr/render-items!)
(let [new-item (d/completed-item id)]
(when (i/connector? new-item)
(ml/try-to-lock! new-item id :start (-> new-item :input :start))
(ml/try-to-lock! new-item id :end (-> new-item :input :end)))
(ms/select-layer! id nil true)))))

3 changes: 1 addition & 2 deletions src/tixi/mutators/delete.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
(:require [tixi.data :as d]
[tixi.mutators.shared :as msh]
[tixi.mutators.locks :as ml]
[tixi.mutators.undo :as mu]
[tixi.items :as i]))

(defn delete-items! [ids]
(when (not-empty ids)
(mu/snapshot!)
(msh/snapshot!)
(doseq [id ids]
(swap! d/data update-in [:cache] dissoc id)
(ml/delete-from-locks! id (d/completed-item id))
Expand Down
37 changes: 9 additions & 28 deletions src/tixi/mutators/selection.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
[tixi.mutators.delete :as md]
[tixi.mutators.render :as mr]
[tixi.mutators.locks :as ml]
[tixi.mutators.undo :as mu]
[tixi.utils :refer [p]]
[tixi.position :as p]))

Expand Down Expand Up @@ -81,6 +80,13 @@
(when-let [rect (d/selection-rect)]
(update-selection-rect! (g/move rect diff))))

(defn refresh-selection! []
(doseq [id (d/selected-ids)]
(when-not (contains? (into #{} (keys (d/completed))) id)
(swap! d/data assoc-in [:selection :ids] (disj (d/selected-ids) id))))
(set-selection-rel-rects!)
(swap! d/data assoc-in [:selection :rect] (p/items-wrapping-rect (d/selected-ids))))

(defn resize-selection! [diff type]
(when-let [rect (d/selection-rect)]
(update-selection-rect! (g/resize rect diff type))))
Expand All @@ -93,35 +99,10 @@
(if add-more?
(swap! d/data update-in [:selection :ids] conj id)
(when-not (some #{id} (d/selected-ids))
(swap! d/data assoc-in [:selection :ids] [id])))
(swap! d/data assoc-in [:selection :ids] #{id})))
(do
(swap! d/data assoc-in [:selection :ids] [])
(swap! d/data assoc-in [:selection :ids] #{})
(when point
(start-selection! point))))
(set-selection-rel-rects!)
(swap! d/data assoc-in [:selection :rect] (p/items-wrapping-rect (d/selected-ids)))))

(defn copy! []
(let [items (d/selected-items)]
(when (not-empty items)
(swap! d/data assoc :clipboard items))))

(defn cut! []
(let [ids (d/selected-ids)]
(copy!)
(select-layer! nil)
(md/delete-items! ids)))

(defn paste! []
(mu/snapshot!)
(select-layer! nil)
(doseq [item (d/clipboard)]
(let [id (d/autoincrement)]
(ms/autoincrement!)
(ms/update-state! assoc-in [:completed id] (update-in item [:input] g/move (g/build-size 1 1)))
(mr/render-items!)
(let [new-item (d/completed-item id)]
(when (i/connector? new-item)
(ml/try-to-lock! new-item id :start (-> new-item :input :start))
(ml/try-to-lock! new-item id :end (-> new-item :input :end)))
(select-layer! id nil true)))))
11 changes: 8 additions & 3 deletions src/tixi/mutators/shared.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
[tixi.sync :as s]
[tixi.tree :as t]))

(defn autoincrement! []
(update-state! update-in [:autoincrement] inc))

(defn assign-state! [new-value]
(swap! d/data assoc-in [:state] new-value)
(swap! d/data assoc-in [:stack] (z/replace (d/stack-loc)
Expand All @@ -16,3 +13,11 @@

(defn update-state! [f ks & args]
(assign-state! (apply f (d/state) ks args)))

(defn snapshot! []
(swap! d/data assoc-in [:stack] (-> (d/stack-loc)
(z/insert-child (t/node (d/stack)))
z/down)))
(defn autoincrement! []
(update-state! update-in [:autoincrement] inc))

11 changes: 5 additions & 6 deletions src/tixi/mutators/undo.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
(:require [tixi.data :as d]
[clojure.zip :as z]
[tixi.mutators.render :as mr]
[tixi.mutators.selection :as ms]
[tixi.tree :as t]
[tixi.utils :refer [p]]))

(defn snapshot! []
(swap! d/data assoc-in [:stack] (-> (d/stack-loc)
(z/insert-child (t/node (d/stack)))
z/down)))
(defn undo! []
(when (d/can-undo?)
(swap! d/data assoc-in [:stack] (z/up (d/stack-loc)))
(swap! d/data assoc-in [:state] (d/stack))
(doseq [[id _] (d/completed)]
(mr/touch-item! id))))
(mr/touch-item! id))
(ms/refresh-selection!)))

(defn redo! []
(when (d/can-redo?)
(swap! d/data assoc-in [:stack] (z/down (d/stack-loc)))
(swap! d/data assoc-in [:state] (d/stack))
(doseq [[id _] (d/completed)]
(mr/touch-item! id))))
(mr/touch-item! id))
(ms/refresh-selection!)))

(defn undo-if-unchanged! []
(when (and (z/up (d/stack-loc))
Expand Down
4 changes: 2 additions & 2 deletions test/test/tixi/dispatcher.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
(m/set-tool! :select)
(di/handle-input-event {:type :down, :data {:action :draw, :point (p/coords->position (g/build-point 2 2))}})
(di/handle-input-event {:type :down, :data {:action :draw, :point (p/coords->position (g/build-point 5 5))}})
(is (= (d/selected-ids) [1])))
(is (= (d/selected-ids) #{1})))

(deftest handle-input-event-select-down-more
(create-layer! (g/build-rect 2 2 4 4))
Expand All @@ -98,7 +98,7 @@
(di/handle-input-event {:type :down, :data {:action :draw,
:point (p/coords->position (g/build-point 5 5)),
:modifiers {:shift true}}})
(is (= (d/selected-ids) [0 1])))
(is (= (d/selected-ids) #{0 1})))

(deftest handle-input-event-select-down-edit-text
(let [id (create-layer! (g/build-rect 2 2 4 4))]
Expand Down
64 changes: 64 additions & 0 deletions test/test/tixi/mutators/copy_paste.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
(ns test.tixi.mutators.copy-paste
(:require-macros [cemerick.cljs.test :refer (is deftest use-fixtures)]
[tixi.utils :refer (b)])
(:require [cemerick.cljs.test :as test]
[tixi.geometry :as g]
[tixi.mutators :as m]
[tixi.mutators.copy-paste :as mcp]
[tixi.mutators.selection :as ms]
[tixi.mutators.undo :as mu]
[test.tixi.utils :refer [create-layer! create-sample-layer! create-locked-layers! item-with-input]]
[tixi.utils :refer [p]]
[tixi.data :as d]))

(defn- setup [f]
(m/reset-data!)
(f))

(use-fixtures :each setup)

(deftest copy-paste
(let [id1 (create-layer! (g/build-rect 0 0 4 4))
id2 (create-layer! (g/build-rect 5 5 8 8))
id3 (create-layer! (g/build-rect 10 10 15 15))]
(ms/select-layer! id2)
(ms/select-layer! id3 nil true)
(mcp/copy!)
(mcp/paste!)
(is (= (count (d/completed)) 5))
(is (not= (item-with-input (g/build-rect 6 6 9 9)) nil))
(is (not= (item-with-input (g/build-rect 11 11 16 16)) nil))
(let [id4 (first (item-with-input (g/build-rect 6 6 9 9)))
id5 (first (item-with-input (g/build-rect 11 11 16 16)))]
(is (= (d/selected-ids) #{id4 id5})))))

(deftest cut-paste
(let [id1 (create-layer! (g/build-rect 0 0 4 4))]
(ms/select-layer! id1)
(mcp/cut!)
(mcp/paste!)
(is (= (count (d/completed)) 1))
(is (= (item-with-input (g/build-rect 0 0 4 4)) nil))
(is (not= (item-with-input (g/build-rect 1 1 5 5)) nil))))

(deftest copy-paste-undo
(let [id1 (create-layer! (g/build-rect 0 0 4 4))]
(ms/select-layer! id1)
(mcp/copy!)
(mcp/paste!)
(mu/undo!)
(is (= (keys (d/completed)) [id1]))))

(deftest copy-paste-locked-items
(let [[id1 id2 id3] (create-locked-layers!)]
(ms/select-layer! id1)
(ms/select-layer! id2 nil true)
(mcp/copy!)
(mcp/paste!)
(let [new-rect-id (first (item-with-input (g/build-rect 6 6 16 16)))
new-connector-id (first (item-with-input (g/build-rect 3 3 6 6)))]
(ms/select-layer! nil)
(ms/select-layer! new-rect-id)
(ms/move-selection! (g/build-size 4 4))
(is (= (:input (d/completed-item new-connector-id)) (g/build-rect 3 3 10 10))))))

Loading

0 comments on commit 8a17a25

Please sign in to comment.