         (rx/from-coll
          (map #(move-shape % delta) selected))))))
 
-(defn update-shape-rotation
-  [sid rotation]
-  {:pre [(number? rotation)
-         (>= rotation 0)
-         (>= 360 rotation)]}
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (update-in state [:shapes-by-id sid]
-                 shapes/-rotate rotation))))
-
-;; TODO: implement locked resize
-
-(defn update-shape-size
-  [sid {:keys [width height lock] :as opts}]
-  (sc/validate! +shape-update-size-schema+ opts)
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (let [shape (get-in state [:shapes-by-id sid])
-            size (select-keys shape [:width :height])
-            size (merge size
-                        (when width {:width width})
-                        (when height {:height height}))]
-        (update-in state [:shapes-by-id sid]
-                   shapes/-resize size)))))
-
-(defn update-shape-position
-  [sid {:keys [x y] :as opts}]
-  (sc/validate! +shape-update-position-schema+ opts)
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (update-in state [:shapes-by-id sid]
-                 merge
-                 (when x {:x x})
-                 (when y {:y y})))))
-
-(defn update-shape-fill
-  [sid {:keys [fill opacity] :as opts}]
-  (sc/validate! +shape-update-fill-schema+ opts)
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (update-in state [:shapes-by-id sid]
-                 merge
-                 (when fill {:fill fill})
-                 (when opacity {:opacity opacity})))))
-
 (defn update-selected-shapes-fill
   "Update the fill related attributed on
   selected shapes."

      (let [selected (get-in state [:workspace :selected])]
        (rx/from-coll
         (into [(deselect-all)] (map #(delete-shape %) selected)))))))

(defn initialize
  "Initialize the workspace state."
  [projectid pageid]
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (let [s {:project projectid
               :toolboxes #{}
               :flags #{}
               :drawing nil
               :selected #{}
               :page pageid}]
        (assoc state :workspace s)))))

(defn move-shape
  "Mark a shape selected for drawing in the canvas."
  [sid [dx dy :as delta]]
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (let [shape (get-in state [:shapes-by-id sid])]
        (update-in state [:shapes-by-id sid] shapes/-move {:dx dx :dy dy})))))

(defn move-selected
  "Move a minimal position unit the selected shapes."

          (assoc-in $ [:pages-by-id pageid :shapes] shapes)
          (update-in $ [:shapes-by-id] dissoc sid))))))

(defn move-shape
  "Mark a shape selected for drawing in the canvas."
  [sid [dx dy :as delta]]
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (let [shape (get-in state [:shapes-by-id sid])]
        (update-in state [:shapes-by-id sid] shapes/-move {:dx dx :dy dy})))))

(defn update-shape-rotation
  [sid rotation]
  {:pre [(number? rotation)
         (>= rotation 0)
         (>= 360 rotation)]}
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (update-in state [:shapes-by-id sid]
                 shapes/-rotate rotation))))

;; TODO: implement locked resize

(defn update-shape-size
  [sid {:keys [width height lock] :as opts}]
  (sc/validate! +shape-update-size-schema+ opts)
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (let [shape (get-in state [:shapes-by-id sid])
            size (select-keys shape [:width :height])
            size (merge size
                        (when width {:width width})
                        (when height {:height height}))]
        (update-in state [:shapes-by-id sid]
                   shapes/-resize size)))))

(defn update-shape-position
  [sid {:keys [x y] :as opts}]
  (sc/validate! +shape-update-position-schema+ opts)
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (update-in state [:shapes-by-id sid]
                 merge
                 (when x {:x x})
                 (when y {:y y})))))

(defn update-shape-fill
  [sid {:keys [fill opacity] :as opts}]
  (sc/validate! +shape-update-fill-schema+ opts)
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (update-in state [:shapes-by-id sid]
                 merge
                 (when fill {:fill fill})
                 (when opacity {:opacity opacity})))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Events (for selected)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn deselect-all
  "Mark a shape selected for drawing in the canvas."
  []
  (reify
    rs/UpdateEvent
    (-apply-update [_ state]
      (assoc-in state [:workspace :selected] #{}))))

(defn remove-selected
  "Deselect all and remove all selected shapes."
  []
