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. + +![](examples/svg/split_3x5_2.svg) 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))))))))