├── .gitignore ├── resources ├── icon.icns ├── screenshot.png ├── icon.iconset │ ├── icon_16x16.png │ ├── icon_32x32.png │ ├── icon_128x128.png │ ├── icon_16x16@2x.png │ ├── icon_256x256.png │ ├── icon_32x32@2x.png │ ├── icon_512x512.png │ ├── icon_128x128@2x.png │ ├── icon_256x256@2x.png │ └── icon_512x512@2x.png ├── folder.svg └── file.svg ├── script └── nrepl.sh ├── README.md ├── deps.edn ├── .clj-kondo └── imports │ └── io.github.humbleui │ └── humbleui │ ├── hooks │ ├── deflazy.clj_kondo │ ├── loopr.clj_kondo │ └── condplus.clj_kondo │ └── config.edn ├── src └── file_picker │ └── core.clj ├── step-by-step ├── 00_hello_world.clj ├── 01_rect_padding.clj ├── 02_column_row.clj ├── 03_gap_align.clj ├── 04_comp_state.clj ├── 05_file_list.clj ├── 06_selectable.clj ├── 07_hoverable.clj ├── 08_current_dir.clj ├── 09_go_down.clj ├── 10_go_up.clj └── 11_icons.clj └── humble-file-picker.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .calva 2 | .clj-kondo/.cache 3 | .cpcache 4 | .idea 5 | .lsp 6 | .nrepl-port 7 | .repl-port -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /resources/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/screenshot.png -------------------------------------------------------------------------------- /script/nrepl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd `dirname $0`/.. 4 | 5 | clj -M:dev 6 | -------------------------------------------------------------------------------- /resources/icon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /resources/icon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumbleUI/humble-file-picker/HEAD/resources/icon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /resources/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo project for [Humble UI](https://github.com/HumbleUI/HumbleUI) 2 | 3 | ![](resources/screenshot.png) 4 | 5 | Start by running 6 | 7 | ``` 8 | clj -M:dev 9 | ``` 10 | 11 | Eval `(file-picker/-main)` to open main window. 12 | 13 | Replace `src/file_picker/core.clj` with any file from `step-by-step/` to see incremental progress. 14 | 15 | Eval `(io.github.humbleui.docs/open!)` to view Humble UI docs. 16 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {org.clojure/clojure {:mvn/version "1.12.0"} 3 | io.github.humbleui/humbleui 4 | {:git/sha "0a5c7584493d2ceb0f431a3e3165f92668209ff0"}} 5 | :aliases 6 | {:dev 7 | {:extra-paths ["dev"] 8 | :extra-deps {nrepl/nrepl {:mvn/version "1.3.0"} 9 | cider/cider-nrepl {:mvn/version "0.49.0"}} 10 | :jvm-opts ["-Djdk.attach.allowAttachSelf"] 11 | :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}}} 12 | -------------------------------------------------------------------------------- /.clj-kondo/imports/io.github.humbleui/humbleui/hooks/deflazy.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc hooks.deflazy 2 | "A clj-kondo hook to allow linting of humbleui `loopr` macro." 3 | (:require 4 | [clj-kondo.hooks-api :as api])) 5 | 6 | (defn deflazy-hook 7 | "(deflazy paragraph ([text] [opts text]) \"paragraph\")" 8 | [form] 9 | (let [[_ name arglists file] (:children (:node form))] 10 | {:node 11 | (api/list-node 12 | (list* 13 | (api/token-node 'defn) 14 | name 15 | (for [arglist arglists] 16 | (api/list-node 17 | (list arglist)))))})) 18 | -------------------------------------------------------------------------------- /src/file_picker/core.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp app [] 10 | [ui/center 11 | [ui/label "Hello, world!"]]) 12 | 13 | (defn -main [& args] 14 | (ui/start-app! 15 | (ui/window 16 | {:title "File picker" 17 | :mac-icon "resources/icon.icns"} 18 | #'app))) 19 | 20 | (comment 21 | ;; Start app 22 | (-main) 23 | 24 | ;; View docs 25 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 26 | -------------------------------------------------------------------------------- /step-by-step/00_hello_world.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp app [] 10 | [ui/center 11 | [ui/label "Hello, world!"]]) 12 | 13 | (defn -main [& args] 14 | (ui/start-app! 15 | (ui/window 16 | {:title "File picker" 17 | :mac-icon "resources/icon.icns"} 18 | #'app))) 19 | 20 | (comment 21 | ;; Start app 22 | (-main) 23 | 24 | ;; View docs 25 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 26 | -------------------------------------------------------------------------------- /.clj-kondo/imports/io.github.humbleui/humbleui/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {io.github.humbleui.ui/deflazy hooks.deflazy/deflazy-hook 4 | io.github.humbleui.util/loopr hooks.loopr/loopr-hook 5 | io.github.humbleui.util/condp hooks.condplus/condplus-hook}} 6 | :lint-as 7 | {io.github.humbleui.ui/defcomp clojure.core/defn 8 | io.github.humbleui.util/deftype+ clojure.core/deftype 9 | io.github.humbleui.util/for-map clojure.core/for 10 | io.github.humbleui.util/for-vec clojure.core/for 11 | io.github.humbleui.util/memo-fn clojure.core/fn 12 | io.github.humbleui.util/thread clojure.core/future 13 | io.github.humbleui.util/when-some+ clojure.core/when-some}} 14 | -------------------------------------------------------------------------------- /step-by-step/01_rect_padding.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp app [] 10 | [ui/rect {:paint {:fill "EEE"}} 11 | [ui/padding {:padding 10} 12 | [ui/rect {:paint {:fill "FFF"}} 13 | [ui/center 14 | [ui/label "Hello, world!"]]]]]) 15 | 16 | (defn -main [& args] 17 | (ui/start-app! 18 | (ui/window 19 | {:title "File picker" 20 | :mac-icon "resources/icon.icns"} 21 | #'app))) 22 | 23 | (comment 24 | ;; Start app 25 | (-main) 26 | 27 | ;; View docs 28 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 29 | -------------------------------------------------------------------------------- /step-by-step/02_column_row.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp app [] 10 | [ui/rect {:paint {:fill "EEE"}} 11 | [ui/padding {:padding 10} 12 | [ui/column 13 | ^:stretch 14 | [ui/rect {:paint {:fill "FFF"}} 15 | [ui/center 16 | [ui/label "Hello, world!"]]] 17 | [ui/row 18 | [ui/button {:style :default} "Open"] 19 | [ui/button "Cancel"]]]]]) 20 | 21 | (defn -main [& args] 22 | (ui/start-app! 23 | (ui/window 24 | {:title "File picker" 25 | :mac-icon "resources/icon.icns"} 26 | #'app))) 27 | 28 | (comment 29 | ;; Start app 30 | (-main) 31 | 32 | ;; View docs 33 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 34 | -------------------------------------------------------------------------------- /step-by-step/03_gap_align.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp app [] 10 | [ui/rect {:paint {:fill "EEE"}} 11 | [ui/padding {:padding 10} 12 | [ui/column {:gap 10} 13 | ^:stretch 14 | [ui/rect {:paint {:fill "FFF"}} 15 | [ui/center 16 | [ui/label "Hello, world!"]]] 17 | [ui/align {:x :right} 18 | [ui/row {:gap 10} 19 | [ui/button {:style :default} "Open"] 20 | [ui/button "Cancel"]]]]]]) 21 | 22 | (defn -main [& args] 23 | (ui/start-app! 24 | (ui/window 25 | {:title "File picker" 26 | :mac-icon "resources/icon.icns"} 27 | #'app))) 28 | 29 | (comment 30 | ;; Start app 31 | (-main) 32 | 33 | ;; View docs 34 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 35 | -------------------------------------------------------------------------------- /.clj-kondo/imports/io.github.humbleui/humbleui/hooks/loopr.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc hooks.loopr 2 | "A clj-kondo hook to allow linting of humbleui `loopr` macro." 3 | (:require 4 | [clj-kondo.hooks-api :as api])) 5 | 6 | (defn loopr-hook 7 | "`loopr` could be linted as 8 | 9 | (loop [acc1 [] 10 | acc2 []] 11 | (let [iter1 1 12 | iter2 2] 13 | (if iter1 14 | body 15 | final)))" 16 | [form] 17 | (let [[_ accs iters body & [final]] (:children (:node form))] 18 | {:node 19 | (api/list-node 20 | (list 21 | (api/token-node 'loop) 22 | accs 23 | (api/list-node 24 | (list 25 | (api/token-node 'let) 26 | iters 27 | (api/list-node 28 | (list 29 | (api/token-node 'if) 30 | (first (:children iters)) 31 | body 32 | final))))))})) 33 | -------------------------------------------------------------------------------- /step-by-step/04_comp_state.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp file-list [] 10 | (let [*dir (ui/signal 11 | (io/file (System/getProperty "user.home")))] 12 | (fn [] 13 | [ui/center 14 | [ui/label "Hello, world!"]]))) 15 | 16 | (ui/defcomp app [] 17 | [ui/rect {:paint {:fill "EEE"}} 18 | [ui/padding {:padding 10} 19 | [ui/column {:gap 10} 20 | ^:stretch 21 | [ui/rect {:paint {:fill "FFF"}} 22 | [file-list]] 23 | [ui/align {:x :right} 24 | [ui/row {:gap 10} 25 | [ui/button {:style :default} "Open"] 26 | [ui/button "Cancel"]]]]]]) 27 | 28 | (defn -main [& args] 29 | (ui/start-app! 30 | (ui/window 31 | {:title "File picker" 32 | :mac-icon "resources/icon.icns"} 33 | #'app))) 34 | 35 | (comment 36 | ;; Start app 37 | (-main) 38 | 39 | ;; View docs 40 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 41 | -------------------------------------------------------------------------------- /step-by-step/05_file_list.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp file-list [] 10 | (let [*dir (ui/signal 11 | (io/file (System/getProperty "user.home")))] 12 | (fn [] 13 | [ui/vscroll 14 | [ui/padding {:vertical 8} 15 | [ui/column 16 | (for [file (->> (.listFiles @*dir) 17 | (sort-by (comp str/lower-case File/.getName)))] 18 | [ui/padding {:padding 8} 19 | [ui/label (.getName file)]])]]]))) 20 | 21 | (ui/defcomp app [] 22 | [ui/rect {:paint {:fill "EEE"}} 23 | [ui/padding {:padding 10} 24 | [ui/column {:gap 10} 25 | ^:stretch 26 | [ui/rect {:paint {:fill "FFF"}} 27 | [file-list]] 28 | [ui/align {:x :right} 29 | [ui/row {:gap 10} 30 | [ui/button {:style :default} "Open"] 31 | [ui/button "Cancel"]]]]]]) 32 | 33 | (defn -main [& args] 34 | (ui/start-app! 35 | (ui/window 36 | {:title "File picker" 37 | :mac-icon "resources/icon.icns"} 38 | #'app))) 39 | 40 | (comment 41 | ;; Start app 42 | (-main) 43 | 44 | ;; View docs 45 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 46 | -------------------------------------------------------------------------------- /.clj-kondo/imports/io.github.humbleui/humbleui/hooks/condplus.clj_kondo: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc hooks.condplus 2 | "A clj-kondo hook to allow linting of humbleui `cond+` macro." 3 | (:require 4 | [clj-kondo.hooks-api :as api])) 5 | 6 | (defn condplus-hook [form] 7 | (let [[_ test expr & rest] (:children (:node form)) 8 | tail (when rest 9 | (api/list-node 10 | (list* 11 | (api/token-node 'io.github.humbleui.core/cond+) 12 | rest)))] 13 | {:node 14 | (cond 15 | (and (api/keyword-node? test) (= :do (:k test))) 16 | (api/list-node 17 | (list 18 | (api/token-node 'do) 19 | expr 20 | tail)) 21 | 22 | (and (api/keyword-node? test) (= :let (:k test))) 23 | (api/list-node 24 | (list 25 | (api/token-node 'let) 26 | expr 27 | tail)) 28 | 29 | (and (api/keyword-node? test) (= :some (:k test))) 30 | (api/list-node 31 | (list 32 | (api/token-node 'or) 33 | expr 34 | tail)) 35 | 36 | tail 37 | (api/list-node 38 | (list 39 | (api/token-node 'if) 40 | test 41 | expr 42 | tail)) 43 | 44 | :else 45 | (api/list-node 46 | (list 47 | (api/token-node 'when) 48 | test 49 | expr)))})) 50 | -------------------------------------------------------------------------------- /step-by-step/06_selectable.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp file-row [file *selected] 10 | [ui/clickable 11 | {:on-click 12 | (fn [_event] 13 | (reset! *selected file))} 14 | [ui/rect 15 | {:paint {:fill (if (= file @*selected) 16 | "BBDDFF" 17 | "FFFFFF")}} 18 | [ui/padding {:padding 8} 19 | [ui/label (.getName file)]]]]) 20 | 21 | (ui/defcomp file-list [] 22 | (let [*dir (ui/signal 23 | (io/file (System/getProperty "user.home"))) 24 | *selected (ui/signal nil)] 25 | (fn [] 26 | [ui/vscroll 27 | [ui/padding {:vertical 8} 28 | [ui/column 29 | (for [file (->> (.listFiles @*dir) 30 | (sort-by (comp str/lower-case File/.getName)))] 31 | [file-row file *selected])]]]))) 32 | 33 | (ui/defcomp app [] 34 | [ui/rect {:paint {:fill "EEE"}} 35 | [ui/padding {:padding 10} 36 | [ui/column {:gap 10} 37 | ^:stretch 38 | [ui/rect {:paint {:fill "FFF"}} 39 | [file-list]] 40 | [ui/align {:x :right} 41 | [ui/row {:gap 10} 42 | [ui/button {:style :default} "Open"] 43 | [ui/button "Cancel"]]]]]]) 44 | 45 | (defn -main [& args] 46 | (ui/start-app! 47 | (ui/window 48 | {:title "File picker" 49 | :mac-icon "resources/icon.icns"} 50 | #'app))) 51 | 52 | (comment 53 | ;; Start app 54 | (-main) 55 | 56 | ;; View docs 57 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 58 | -------------------------------------------------------------------------------- /step-by-step/07_hoverable.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp file-row [file *selected] 10 | [ui/clickable 11 | {:on-click 12 | (fn [_event] 13 | (reset! *selected file))} 14 | (fn [state] 15 | [ui/rect 16 | {:paint {:fill (cond 17 | (= file @*selected) "BBDDFF" 18 | (:hovered state) "E0F0FF" 19 | :else "FFFFFF")}} 20 | [ui/padding {:padding 8} 21 | [ui/label (.getName file)]]])]) 22 | 23 | (ui/defcomp file-list [] 24 | (let [*dir (ui/signal 25 | (io/file (System/getProperty "user.home"))) 26 | *selected (ui/signal nil)] 27 | (fn [] 28 | [ui/vscroll 29 | [ui/padding {:vertical 8} 30 | [ui/column 31 | (for [file (->> (.listFiles @*dir) 32 | (sort-by (comp str/lower-case File/.getName)))] 33 | [file-row file *selected])]]]))) 34 | 35 | (ui/defcomp app [] 36 | [ui/rect {:paint {:fill "EEE"}} 37 | [ui/padding {:padding 10} 38 | [ui/column {:gap 10} 39 | ^:stretch 40 | [ui/rect {:paint {:fill "FFF"}} 41 | [file-list]] 42 | [ui/align {:x :right} 43 | [ui/row {:gap 10} 44 | [ui/button {:style :default} "Open"] 45 | [ui/button "Cancel"]]]]]]) 46 | 47 | (defn -main [& args] 48 | (ui/start-app! 49 | (ui/window 50 | {:title "File picker" 51 | :mac-icon "resources/icon.icns"} 52 | #'app))) 53 | 54 | (comment 55 | ;; Start app 56 | (-main) 57 | 58 | ;; View docs 59 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 60 | -------------------------------------------------------------------------------- /step-by-step/08_current_dir.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (ui/defcomp file-row [file *selected] 10 | [ui/clickable 11 | {:on-click 12 | (fn [_event] 13 | (reset! *selected file))} 14 | (fn [state] 15 | [ui/rect 16 | {:paint {:fill (cond 17 | (= file @*selected) "BBDDFF" 18 | (:hovered state) "E0F0FF" 19 | :else "FFFFFF")}} 20 | [ui/padding {:padding 8} 21 | [ui/label (.getName file)]]])]) 22 | 23 | (ui/defcomp file-list [] 24 | (let [*dir (ui/signal 25 | (io/file (System/getProperty "user.home"))) 26 | *selected (ui/signal nil)] 27 | (fn [] 28 | [ui/vscroll 29 | [ui/padding {:vertical 8} 30 | [ui/column 31 | (for [file (->> (.listFiles @*dir) 32 | (sort-by (comp str/lower-case File/.getName)))] 33 | [file-row file *selected])]]]))) 34 | 35 | (ui/defcomp app [] 36 | [ui/rect {:paint {:fill "EEE"}} 37 | [ui/padding {:padding 10} 38 | [ui/column {:gap 10} 39 | ^:stretch 40 | [ui/rect {:paint {:fill "FFF"}} 41 | [file-list]] 42 | [ui/row {:gap 10} 43 | [ui/align {:y :center} 44 | [ui/label @*dir]] 45 | ^:stretch [ui/gap] 46 | [ui/button {:style :default} "Open"] 47 | [ui/button "Cancel"]]]]]) 48 | 49 | (defn -main [& args] 50 | (ui/start-app! 51 | (ui/window 52 | {:title "File picker" 53 | :mac-icon "resources/icon.icns"} 54 | #'app))) 55 | 56 | (comment 57 | ;; Start app 58 | (-main) 59 | 60 | ;; View docs 61 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 62 | -------------------------------------------------------------------------------- /humble-file-picker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /step-by-step/09_go_down.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (def *dir 10 | (ui/signal 11 | (io/file (System/getProperty "user.home")))) 12 | 13 | (def *selected 14 | (ui/signal nil)) 15 | 16 | (ui/defcomp file-row [file] 17 | [ui/clickable 18 | {:on-click 19 | (fn [event] 20 | (cond 21 | (and 22 | (>= (:clicks event) 2) 23 | (.isDirectory file)) 24 | (do 25 | (reset! *dir file) 26 | (reset! *selected nil)) 27 | 28 | :else 29 | (reset! *selected file)))} 30 | (fn [state] 31 | [ui/rect 32 | {:paint {:fill (cond 33 | (= file @*selected) "BBDDFF" 34 | (:hovered state) "E0F0FF" 35 | :else "FFFFFF")}} 36 | [ui/padding {:padding 8} 37 | [ui/label (.getName file)]]])]) 38 | 39 | (ui/defcomp file-list [] 40 | [ui/vscroll 41 | [ui/padding {:vertical 8} 42 | [ui/column 43 | (for [file (->> (.listFiles @*dir) 44 | (sort-by (comp str/lower-case File/.getName)))] 45 | [file-row file])]]]) 46 | 47 | (ui/defcomp app [] 48 | [ui/rect {:paint {:fill "EEE"}} 49 | [ui/padding {:padding 10} 50 | [ui/column {:gap 10} 51 | ^:stretch 52 | [ui/rect {:paint {:fill "FFF"}} 53 | [file-list]] 54 | [ui/row {:gap 10} 55 | [ui/align {:y :center} 56 | [ui/label @*dir]] 57 | ^:stretch [ui/gap] 58 | [ui/button {:style :default} "Open"] 59 | [ui/button "Cancel"]]]]]) 60 | 61 | (defn -main [& args] 62 | (ui/start-app! 63 | (ui/window 64 | {:title "File picker" 65 | :mac-icon "resources/icon.icns"} 66 | #'app))) 67 | 68 | (comment 69 | ;; Start app 70 | (-main) 71 | 72 | ;; View docs 73 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 74 | -------------------------------------------------------------------------------- /step-by-step/10_go_up.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (def *dir 10 | (ui/signal 11 | (io/file (System/getProperty "user.home")))) 12 | 13 | (def *selected 14 | (ui/signal nil)) 15 | 16 | (ui/defcomp file-row [file] 17 | [ui/clickable 18 | {:on-click 19 | (fn [event] 20 | (cond 21 | (and 22 | (>= (:clicks event) 2) 23 | (= ".." (.getName file))) 24 | (do 25 | (swap! *dir File/.getParentFile) 26 | (reset! *selected nil)) 27 | 28 | (and 29 | (>= (:clicks event) 2) 30 | (.isDirectory file)) 31 | (do 32 | (reset! *dir file) 33 | (reset! *selected nil)) 34 | 35 | :else 36 | (reset! *selected file)))} 37 | (fn [state] 38 | [ui/rect 39 | {:paint {:fill (cond 40 | (= file @*selected) "BBDDFF" 41 | (:hovered state) "E0F0FF" 42 | :else "FFFFFF")}} 43 | [ui/padding {:padding 8} 44 | [ui/label (.getName file)]]])]) 45 | 46 | (ui/defcomp file-list [] 47 | [ui/vscroll 48 | [ui/padding {:vertical 8} 49 | [ui/column 50 | (when (.getParentFile @*dir) 51 | [file-row (io/file "..")]) 52 | (for [file (->> (.listFiles @*dir) 53 | (sort-by (comp str/lower-case File/.getName)))] 54 | [file-row file])]]]) 55 | 56 | (ui/defcomp app [] 57 | [ui/rect {:paint {:fill "EEE"}} 58 | [ui/padding {:padding 10} 59 | [ui/column {:gap 10} 60 | ^:stretch 61 | [ui/rect {:paint {:fill "FFF"}} 62 | [file-list]] 63 | [ui/row {:gap 10} 64 | [ui/align {:y :center} 65 | [ui/label @*dir]] 66 | ^:stretch [ui/gap] 67 | [ui/button {:style :default} "Open"] 68 | [ui/button "Cancel"]]]]]) 69 | 70 | (defn -main [& args] 71 | (ui/start-app! 72 | (ui/window 73 | {:title "File picker" 74 | :mac-icon "resources/icon.icns"} 75 | #'app))) 76 | 77 | (comment 78 | ;; Start app 79 | (-main) 80 | 81 | ;; View docs 82 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 83 | -------------------------------------------------------------------------------- /step-by-step/11_icons.clj: -------------------------------------------------------------------------------- 1 | (ns file-picker.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [io.github.humbleui.ui :as ui]) 6 | (:import 7 | [java.io File])) 8 | 9 | (def *dir 10 | (ui/signal 11 | (io/file (System/getProperty "user.home")))) 12 | 13 | (def *selected 14 | (ui/signal nil)) 15 | 16 | (ui/defcomp file-row [file] 17 | [ui/clickable 18 | {:on-click 19 | (fn [event] 20 | (cond 21 | (and 22 | (>= (:clicks event) 2) 23 | (= ".." (.getName file))) 24 | (do 25 | (swap! *dir File/.getParentFile) 26 | (reset! *selected nil)) 27 | 28 | (and 29 | (>= (:clicks event) 2) 30 | (.isDirectory file)) 31 | (do 32 | (reset! *dir file) 33 | (reset! *selected nil)) 34 | 35 | :else 36 | (reset! *selected file)))} 37 | (fn [state] 38 | [ui/rect 39 | {:paint {:fill (cond 40 | (= file @*selected) "BBDDFF" 41 | (:hovered state) "E0F0FF" 42 | :else "FFFFFF")}} 43 | [ui/padding {:horizontal 8} 44 | [ui/row {:gap 4 45 | :align :center} 46 | [ui/size {:width 24 :height 24} 47 | (if (.isDirectory file) 48 | [ui/svg {:src "resources/folder.svg"}] 49 | [ui/svg {:src "resources/file.svg"}])] 50 | [ui/padding {:vertical 8} 51 | [ui/label (.getName file)]]]]])]) 52 | 53 | (ui/defcomp file-list [] 54 | [ui/vscroll 55 | [ui/padding {:vertical 8} 56 | [ui/column 57 | (when (.getParentFile @*dir) 58 | [file-row (io/file "..")]) 59 | (for [file (->> (.listFiles @*dir) 60 | (sort-by (comp str/lower-case File/.getName)))] 61 | [file-row file])]]]) 62 | 63 | (ui/defcomp app [] 64 | [ui/rect {:paint {:fill "EEE"}} 65 | [ui/padding {:padding 10} 66 | [ui/column {:gap 10} 67 | ^:stretch 68 | [ui/rect {:paint {:fill "FFF"}} 69 | [file-list]] 70 | [ui/row {:gap 10} 71 | [ui/align {:y :center} 72 | [ui/label @*dir]] 73 | ^:stretch [ui/gap] 74 | [ui/button {:style :default} "Open"] 75 | [ui/button "Cancel"]]]]]) 76 | 77 | (defn -main [& args] 78 | (ui/start-app! 79 | (ui/window 80 | {:title "File picker" 81 | :mac-icon "resources/icon.icns"} 82 | #'app))) 83 | 84 | (comment 85 | ;; Start app 86 | (-main) 87 | 88 | ;; View docs 89 | ((requiring-resolve 'io.github.humbleui.docs/open!))) 90 | --------------------------------------------------------------------------------