diff --git a/README.md b/README.md
index 628ea13..9ad5605 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# keymap-viz: visualisation of keymaps
This is a Clojure program for generating visualisations of the keymaps of my keyboards, heavily inspired by [callum-oakley/keymap](https://github.com/callum-oakley/keymap) and the various spins on it.
+
+
diff --git a/env/dev/src/user.clj b/env/dev/src/user.clj
new file mode 100644
index 0000000..7a5c943
--- /dev/null
+++ b/env/dev/src/user.clj
@@ -0,0 +1,13 @@
+(ns user
+ "Userspace functions for local development."
+ (:require [mount.core :as m]))
+
+(defn start []
+ (m/start))
+
+(defn stop []
+ (m/stop))
+
+(defn restart []
+ (stop)
+ (start))
diff --git a/env/test/resources/keymaps/3x3.edn b/env/test/resources/keymaps/3x3.edn
new file mode 100644
index 0000000..7ee17b4
--- /dev/null
+++ b/env/test/resources/keymaps/3x3.edn
@@ -0,0 +1,7 @@
+{:layers [{:keymap {:all [1 2 3
+ 4 5 6
+ 7 8 9]}}
+
+ {:keymap {:all [1 2 3
+ 4 5 6
+ 7 8 9]}}]}
diff --git a/env/test/resources/layouts/3x3.edn b/env/test/resources/layouts/3x3.edn
new file mode 100644
index 0000000..5c71d13
--- /dev/null
+++ b/env/test/resources/layouts/3x3.edn
@@ -0,0 +1,10 @@
+{:blocks {:all {:pos [0 0]
+ :keys [{:pos [0 0] :size 1 :type :rect}
+ {:pos [0 1] :size 1 :type :rect}
+ {:pos [0 2] :size 1 :type :rect}
+ {:pos [1 0] :size 1 :type :rect}
+ {:pos [1 1] :size 1 :type :rect}
+ {:pos [1 2] :size 1 :type :rect}
+ {:pos [2 0] :size 1 :type :rect}
+ {:pos [2 1] :size 1 :type :rect}
+ {:pos [2 2] :size 1 :type :rect}]}}}
diff --git a/env/test/resources/layouts/split_2x2.edn b/env/test/resources/layouts/split_2x2.edn
new file mode 100644
index 0000000..3a21954
--- /dev/null
+++ b/env/test/resources/layouts/split_2x2.edn
@@ -0,0 +1,10 @@
+{:blocks {:left {:pos [0 0]
+ :keys [{:pos [0 0] :size 1 :type :rect}
+ {:pos [0 1] :size 1 :type :rect}
+ {:pos [1 0] :size 1 :type :rect}
+ {:pos [1 1] :size 1 :type :rect}]}
+ :right {:pos [3 0]
+ :keys [{:pos [0 0] :size 1 :type :rect}
+ {:pos [0 1] :size 1 :type :rect}
+ {:pos [1 0] :size 1 :type :rect}
+ {:pos [1 1] :size 1 :type :rect}]}}}
diff --git a/env/test/src/test_utils.clj b/env/test/src/test_utils.clj
new file mode 100644
index 0000000..07bbc04
--- /dev/null
+++ b/env/test/src/test_utils.clj
@@ -0,0 +1,10 @@
+(ns test-utils
+ (:require
+ [clojure.edn :as edn]
+ [clojure.java.io :as io]))
+
+(defn load-edn-file [filename]
+ (->> (io/resource filename)
+ (slurp)
+ (edn/read-string)
+ (eval)))
diff --git a/examples/keymaps/split_3x5_2.edn b/examples/keymaps/split_3x5_2.edn
index 5923253..da10709 100644
--- a/examples/keymaps/split_3x5_2.edn
+++ b/examples/keymaps/split_3x5_2.edn
@@ -7,14 +7,105 @@
:border-radius 6}
:layers [{:name "Colemak-DH base"
- :keymap {:left ["Q" "W" "F" "P" "B"
- "A" "R" "S" "T" "G"
- "Z" "X" "C" "D" "V"]
+ :keymap {:left [\Q \W \F \P \B
+ \A \R \S \T \G
+ \Z \X \C \D \V]
:left-thumbs ["Tab" "Space"]
- :right ["J" "L" "U" "Y" "Del"
- "M" "N" "E" "I" "O"
- "K" "H" "," "." "/"]
+ :right [\J \L \U \Y "Del"
+ \M \N \E \I \O
+ \K \H \, \. \/]
- :right-thumbs ["Bspc" "Esc"]}}]}
+ :right-thumbs ["Bspc" "Esc"]}}
+
+ {:name "Lower"
+ :keymap {:left [1 2 3 4 5
+ 6 7 8 9 0
+ "C-z" "C-x" "C-c" "C-v" "C-s"]
+
+ :left-thumbs [nil nil]
+
+ :right ["PgUp" "Home" "Up" "End" nil
+ "PgDn" "Left" "Down" "Right" nil
+ nil \Æ \Ø \Å nil]
+
+ :right-thumbs ["Enter" "Caps Word"]}}
+
+ {:name "Raise"
+ :keymap {:left [\! \@ \{ \} \%
+ \^ \$ \[ \] \#
+ \\ \| \` \~ \?]
+
+ :left-thumbs [nil nil]
+
+ :right [\& \< \> \" \=
+ \* \( \) \' \+
+ \/ \_ \; \: \-]
+
+ :right-thumbs [nil "RAlt"]}}
+
+ {:name "Adjust"
+ :keymap {:left ["Reset" nil "Bri -" "Bri +" "Toggle RBG"
+ nil "Mute" "Vol Down" "Vol Up" "Toggle Audio"
+ nil nil nil nil nil]
+
+ :left-thumbs [nil nil]
+
+ :right ["UC WinC" "Caps Lock" "Scroll Lock" "Num Lock" "Insert"
+ "UC Mac" "Back" "Forward" "WWW Home" "WWW Refresh"
+ "UC Linux" "Prev" "Play" "Stop" "Next"]
+
+ :right-thumbs [nil nil]}}
+
+ {:name "Fn"
+ :keymap {:left ["F1" "F2" "F3" "F4" "F5"
+ "F6" "F7" "F8" "F9" "F10"
+ "F11" "F12" "Menu" "Print Screen" "Caps Word"]
+
+ :left-thumbs [nil nil]
+
+ :right ["," "7" "8" "9" "."
+ "*" "4" "5" "6" "+"
+ "/" "1" "2" "3" "-"]
+
+ :right-thumbs ["Enter" "0"]}}
+
+ {:name "Accents"
+ :keymap {:left ["à" "ä" "â" "ã" "á"
+ "è" "ë" "ê" "ẽ" "é"
+ "ì" "ï" "î" "ĩ" "í"]
+
+ :left-thumbs ["Symbols" "Emoji"]
+
+ :right ["ò" "ö" "ô" "õ" "ó"
+ "ù" "ü" "û" "ũ" "ú"
+ "ỳ" "ÿ" "ŷ" "ỹ" "ý"]
+
+ :right-thumbs [nil nil]}}
+
+ {:name "Symbols"
+ :keymap {:left ["½" "²" "∫" "°" "•"
+ "∞" "®" "√" "™" "∇"
+ "█" "▒" "©" "∂" "∪"]
+
+ :left-thumbs ["∈" "Exit Layer"]
+
+ :right ["€" "£" "↑" "¥" "≠"
+ "×" "←" "↓" "→" "±"
+ "∩" "¬" "∅" "–" "—"]
+
+ :right-thumbs ["✓" "✗"]}}
+
+ {:name "Emoji"
+ :keymap {:left ["😏" "😳" "😩" "😉" "🙄"
+ "😅" "😍" "😭" "😭" "😘"
+ "😂" "🤪" "😔" "😜" "😒"]
+
+ :left-thumbs ["🅱" "Exit Layer"]
+
+ :right ["🤔" "🤫" "👌" "👀" "✨"
+ "🙃" "😬" "👏" "🧠" "💦"
+ "💩" "😤" "🙏" "🔥" "💕"]
+
+ :right-thumbs ["✅" "⛔"]}}]}
diff --git a/examples/svg/split_3x5_2.svg b/examples/svg/split_3x5_2.svg
index 67d0259..b370069 100644
--- a/examples/svg/split_3x5_2.svg
+++ b/examples/svg/split_3x5_2.svg
@@ -1,4 +1,4 @@
-
+QWFPBARSTGZXCDVTabSpaceJLUYDelMNEIOKH,./BspcEsc1234567890C-zC-xC-cC-vC-sPgUpHomeUpEndPgDnLeftDownRightÆØÅEnterCaps Word!@{}%^$[]#\|`~?&<>"=*()'+/_;:-RAltResetBri -Bri +Toggle RBGMuteVol DownVol UpToggle AudioUC WinCCaps LockScroll LockNum LockInsertUC MacBackForwardWWW HomeWWW RefreshUC LinuxPrevPlayStopNextF1F2F3F4F5F6F7F8F9F10F11F12MenuPrint ScreenCaps Word,789.*456+/123-Enter0àäâãáèëêẽéìïîĩíSymbolsEmojiòöôõóùüûũúỳÿŷỹý½²∫°•∞®√™∇█▒©∂∪∈Exit Layer€£↑¥≠×←↓→±∩¬∅–—✓✗😏😳😩😉🙄😅😍😭😭😘😂🤪😔😜😒🅱Exit Layer🤔🤫👌👀✨🙃😬👏🧠💦💩😤🙏🔥💕✅⛔
diff --git a/project.clj b/project.clj
index 1c56e7d..270977c 100644
--- a/project.clj
+++ b/project.clj
@@ -15,5 +15,17 @@
:test-paths ["test"]
:main ^:skip-aot keymap-viz.core
- :target-path "target/%s"
- :profiles {:uberjar {:aot :all}})
+ :target-path "target/%s/"
+
+ :profiles
+ {:uberjar {:aot :all
+ :omit-source true
+ :uberjar-name "keymap-viz.jar"}
+
+ :dev [:project/dev]
+
+ :project/dev {:source-paths ["env/dev/src"
+ "env/test/src"]
+ :resource-paths ["env/dev/resources"
+ "env/test/resources"]
+ :repl-options {:init-ns user}}})
diff --git a/src/keymap_viz/config.clj b/src/keymap_viz/config.clj
index ac1347b..7ca14a5 100644
--- a/src/keymap_viz/config.clj
+++ b/src/keymap_viz/config.clj
@@ -5,11 +5,12 @@
[mount.core :as m]))
(def ^:private default-config
- {:style :default
+ {:border-radius 6
:grid-size-horz 60
:grid-size-vert 50
:key-padding 3
- :border-radius 6})
+ :layer-margin 30
+ :style :default})
(m/defstate default-style
:start (->> (io/resource "styles/default.css")
@@ -29,4 +30,5 @@
(eval)))
(m/defstate config
- :start (get keymap :config default-config))
+ :start (merge default-config
+ (:config keymap)))
diff --git a/src/keymap_viz/core.clj b/src/keymap_viz/core.clj
index 25cfb8c..dc37bee 100644
--- a/src/keymap_viz/core.clj
+++ b/src/keymap_viz/core.clj
@@ -3,7 +3,8 @@
[hiccup.core :as h]
[keymap-viz.config :as conf]
[keymap-viz.rendering :as r]
- [mount.core :as m])
+ [mount.core :as m]
+ [keymap-viz.utils :as u])
(:gen-class))
(defn -main
@@ -13,6 +14,6 @@
(m/start)
(->> (r/render-keymap conf/keymap conf/layout)
- (apply r/render-svg)
+ (apply r/render-svg (u/calc-keymap-size conf/keymap conf/layout))
(h/html)
(println)))
diff --git a/src/keymap_viz/rendering.clj b/src/keymap_viz/rendering.clj
index f8d0bdc..a23c122 100644
--- a/src/keymap_viz/rendering.clj
+++ b/src/keymap_viz/rendering.clj
@@ -2,35 +2,39 @@
(:require
[hiccup.core :as h]
[keymap-viz.config :as conf]
- [keymap-viz.utils :refer [vector-sum]]))
+ [keymap-viz.utils :as u]))
;; HELPER FUNCTIONS ------------------------------------------------------------
-(defmulti keymap-key->keymap-opts class)
-(defmethod keymap-key->keymap-opts String [k] {:tap k})
-(defmethod keymap-key->keymap-opts clojure.lang.PersistentArrayMap [m] m)
+(defmulti key-labels->key-opts class)
+(defmethod key-labels->key-opts nil [_] {:tap ""})
+(defmethod key-labels->key-opts Number [n] {:tap n})
+(defmethod key-labels->key-opts Character [c] {:tap c})
+(defmethod key-labels->key-opts String [s] {:tap s})
+(defmethod key-labels->key-opts clojure.lang.PersistentArrayMap [m] m)
(defn make-key
"Construct a key map with all the data necessary for rendering"
[keymap-key layout-opts block-pos]
(let [{:keys [type pos size]} layout-opts
- keymap-opts (keymap-key->keymap-opts keymap-key)
+ keymap-opts (key-labels->key-opts keymap-key)
{:keys [tap hold]} keymap-opts
- key-pos (vector-sum pos block-pos)]
- {:type type :size size :pos key-pos :tap tap :hold hold}))
+ key-pos (u/vector-sum pos block-pos)]
+ {:type type :size size :pos key-pos :tap (str tap) :hold (str hold)}))
;; RENDERING FUNCTIONS ---------------------------------------------------------
(defmulti render-key
"Translate a key config map into Hiccup data."
- :type)
+ (fn [key _]
+ (:type key)))
-(defmethod render-key :rect render-key-rect [key]
+(defmethod render-key :rect render-key-rect [key yshift]
;; TODO: add support for multiword texts and Hold texts
- (let [{:keys [key-padding border-radius grid-size-horz grid-size-vert]} conf/config
+ (let [{:keys [key-padding border-radius grid-size-horz grid-size-vert layer-margin]} conf/config
{:keys [size pos tap hold]} key
- rect-x (+ key-padding (* grid-size-horz (first pos)))
- rect-y (+ key-padding (* grid-size-vert (second pos)))
- text-x (* grid-size-horz (+ (first pos) (* 0.5 size)))
- text-y (* grid-size-vert (+ (second pos) 0.5))]
+ rect-x (+ layer-margin key-padding (* grid-size-horz (first pos)))
+ rect-y (+ layer-margin key-padding (* grid-size-vert (second pos)) yshift)
+ text-x (+ layer-margin (* grid-size-horz (+ (first pos) (* 0.5 size))))
+ text-y (+ layer-margin (* grid-size-vert (+ (second pos) 0.5)) yshift)]
[[:rect {:x rect-x :y rect-y
:rx border-radius :ry border-radius
:width (- (* size grid-size-horz) (* 2 key-padding))
@@ -39,39 +43,42 @@
(defn render-block
"Translate a keymap block into a vector of Hiccup elements."
- [keymap-block layout-block]
+ [keymap-block layout-block yshift]
(let [block-pos (:pos layout-block)
- layout-keys (:keys layout-block)]
- (->> (map make-key keymap-block layout-keys (repeat block-pos))
- (map render-key)
+ layout-keys (:keys layout-block)
+ keys (map make-key keymap-block layout-keys (repeat block-pos))]
+ (->> (map render-key keys (repeat yshift))
(apply concat)
(into []))))
(defn render-layer
"Translate a single keymap layers into a vector of rendered blocks."
- [keymap-layer layout]
+ [keymap-layer layout yshift]
(let [keymap (:keymap keymap-layer)
block-layouts (:blocks layout)
block-keys (keys block-layouts)]
(->> (for [k block-keys]
- (render-block (k keymap) (k block-layouts)))
+ (render-block (k keymap) (k block-layouts) yshift))
(apply concat)
(into []))))
(defn render-keymap
"Translate a keymap definition into Hiccup data."
[keymap layout]
- (let [keymap-layers (:layers keymap)]
- (->> (map render-layer keymap-layers (repeat layout))
+ (let [keymap-layers (:layers keymap)
+ [_ yshift] (u/calc-board-size layout)]
+ (->> (iterate (partial + yshift) 0)
+ (map render-layer keymap-layers (repeat layout))
(apply concat)
(into []))))
(defn render-svg
"Create an SVG element as Hiccup data."
- [& elements]
+ [size & elements]
;; TODO: make hardcoded values configurable/inferred from config
- (let [w (* 11 (:grid-size-horz conf/config))
- h (* 4 (:grid-size-vert conf/config))
+ (let [{:keys [layer-margin grid-size-horz grid-size-vert]} conf/config
+ w (first size)
+ h (second size)
viewbox-str (str "0 0 " w " " h)
xmlns "http://www.w3.org/2000/svg"]
(-> [:svg {:width w :height h :viewBox viewbox-str :xmlns xmlns} [:style conf/default-style]]
diff --git a/src/keymap_viz/utils.clj b/src/keymap_viz/utils.clj
index 8b58fe1..208e7f5 100644
--- a/src/keymap_viz/utils.clj
+++ b/src/keymap_viz/utils.clj
@@ -1,4 +1,6 @@
-(ns keymap-viz.utils)
+(ns keymap-viz.utils
+ (:require
+ [keymap-viz.config :as conf]))
(defn vector-sum
"Returns the sum of one or more equal-sized numeric vectors."
@@ -8,3 +10,29 @@
(vec (map + x y)))
([x y & more]
(reduce vector-sum (concat [x y] more))))
+
+(defmulti calc-key-corner (fn [key _] (:type key)))
+
+(defmethod calc-key-corner :rect [key block-pos]
+ (let [{:keys [layer-margin grid-size-horz grid-size-vert]} conf/config
+ pos (vector-sum block-pos (:pos key))
+ size (:size key)
+ x (+ layer-margin (* grid-size-horz (+ size (first pos))))
+ y (+ layer-margin (* grid-size-vert (inc (second pos))))]
+ [x y]))
+
+(defn calc-board-size [layout]
+ (let [{:keys [layer-margin]} conf/config
+ key-corners (->> (:blocks layout)
+ vals
+ (map (fn [{:keys [keys pos]}]
+ (map calc-key-corner keys (repeat pos))))
+ (apply concat))
+ xmax (first (apply max-key first key-corners))
+ ymax (second (apply max-key second key-corners))]
+ (mapv #(+ layer-margin %) [xmax ymax])))
+
+(defn calc-keymap-size [keymap layout]
+ (let [n-layers (count (:layers keymap))
+ [x y] (calc-board-size layout)]
+ [x (* y n-layers)]))
diff --git a/test/keymap_viz/rendering_test.clj b/test/keymap_viz/rendering_test.clj
index fdaf7a6..bee0078 100644
--- a/test/keymap_viz/rendering_test.clj
+++ b/test/keymap_viz/rendering_test.clj
@@ -5,14 +5,15 @@
[keymap-viz.rendering :as subject]
[mount.core :as m]))
-(def ^:private mock-config
+(def ^:private test-config
{:key-padding 3.
:border-radius 5.
+ :layer-margin 0.
:grid-size-horz 60.
:grid-size-vert 55.})
(defn- start-states [f]
- (m/start-with {#'conf/config mock-config})
+ (m/start-with {#'conf/config test-config})
(f)
(m/stop))
@@ -21,7 +22,7 @@
(deftest make-key-test
(testing "should merge options from keymap and layout"
(testing "when key is given as a string"
- (is (= {:type :rect :size 1 :pos [6 2] :tap "H" :hold nil}
+ (is (= {:type :rect :size 1 :pos [6 2] :tap "H" :hold ""}
(subject/make-key "H" {:type :rect :size 1 :pos [1 2]} [5 0]))))
(testing "when key is given as a map"
(is (= {:type :rect :size 1 :pos [3 1] :tap "T" :hold "Shift"}
@@ -31,4 +32,4 @@
(testing "should generate a Hiccup-ready data structure"
(is (= [[:rect {:x 183. :y 58. :rx 5. :ry 5. :width 54. :height 49.}]
[:text {:x 210. :y 82.5} "T"]]
- (subject/render-key {:type :rect :size 1 :pos [3 1] :tap "T" :hold "Shift"})))))
+ (subject/render-key {:type :rect :size 1 :pos [3 1] :tap "T" :hold "Shift"} 0)))))
diff --git a/test/keymap_viz/utils_test.clj b/test/keymap_viz/utils_test.clj
new file mode 100644
index 0000000..268d2ca
--- /dev/null
+++ b/test/keymap_viz/utils_test.clj
@@ -0,0 +1,59 @@
+(ns keymap-viz.utils-test
+ (:require
+ [test-utils :as tu]
+ [clojure.test :refer :all]
+ [keymap-viz.config :as conf]
+ [keymap-viz.utils :as subject]
+ [mount.core :as m]))
+
+(def ^:private test-config
+ {:key-padding 3.
+ :border-radius 5.
+ :layer-margin 0.
+ :grid-size-horz 60.
+ :grid-size-vert 55.})
+
+(defn- start-states [f]
+ (m/start-with {#'conf/config test-config})
+ (f)
+ (m/stop))
+
+(use-fixtures :once start-states)
+
+(deftest vector-sum-test
+ (testing "Vector sum"
+ (testing "of a single vector"
+ (are [x] (= x (subject/vector-sum x))
+ [1 2]
+ [1 2 3]
+ [1 2 3 4]))
+ (testing "of two vectors"
+ (is (= [2 5] (subject/vector-sum [1 2] [1 3]))))))
+
+(deftest calc-key-corner-test
+ (testing "Calculating key corner"
+ (testing "of rect type key"
+ (let [alpha-key {:type :rect :size 1 :pos [1 2]}
+ lctl-key {:type :rect :size 1.25 :pos [0 4]}
+ space-key {:type :rect :size 6.25 :pos [3.75 4]}]
+ (is (= [120. 165.] (subject/calc-key-corner alpha-key [0 0])))
+ (is (= [75. 275.] (subject/calc-key-corner lctl-key [0 0])))
+ (is (= [600. 275.] (subject/calc-key-corner space-key [0 0])))
+ (is (= [420. 330.] (subject/calc-key-corner alpha-key [5 3])))))))
+
+(deftest calc-board-size-test
+ (testing "Calculating board size"
+ (testing "of 3x3 macropad"
+ (let [layout (tu/load-edn-file "layouts/3x3.edn")]
+ (is (= [180. 165.] (subject/calc-board-size layout)))))
+ (testing "of 2x2 split"
+ (let [layout (tu/load-edn-file "layouts/split_2x2.edn")]
+ (is (= [300. 110.] (subject/calc-board-size layout)))))))
+
+(deftest calc-keymap-size-test
+ (testing "Calculating total image size"
+ (testing "of 3x3 macropad"
+ (testing "with 2 layers"
+ (let [keymap (tu/load-edn-file "keymaps/3x3.edn")
+ layout (tu/load-edn-file "layouts/3x3.edn")]
+ (is (= [180. 330.] (subject/calc-keymap-size keymap layout))))))))