├── .github └── workflows │ ├── build.yml │ └── deploy-clojars.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.clj ├── csource ├── .gitignore ├── check_release.sh ├── compile_ios.sh ├── compile_linux.sh ├── compile_local.sh ├── compile_macosx.sh ├── deploy_clojars.py ├── download_skia.sh ├── download_skia_local.sh ├── glfw-linux-x86-64 │ ├── .gitignore │ ├── build.clj │ ├── deps.edn │ └── resources │ │ └── linux-x86-64 │ │ └── libglfw.so ├── glfw-macosx-aarch64 │ ├── .gitignore │ ├── build.clj │ ├── deps.edn │ └── resources │ │ └── darwin-aarch64 │ │ └── libglfw.dylib ├── glfw-macosx-x86-64 │ ├── .gitignore │ ├── build.clj │ ├── deps.edn │ └── resources │ │ └── darwin-x86-64 │ │ └── libglfw.dylib ├── libs │ ├── .gitignore │ └── Readme.md ├── linux-x86-64 │ ├── .gitignore │ ├── Readme.md │ ├── deps.edn │ └── resources │ │ └── linux-x86-64 │ │ └── .gitkeep ├── macos-aarch64 │ ├── .gitignore │ ├── Readme.md │ ├── deps.edn │ └── resources │ │ └── darwin-aarch64 │ │ └── .gitkeep ├── macos-x86-64 │ ├── .gitignore │ ├── Readme.md │ ├── deps.edn │ └── resources │ │ └── darwin-x86-64 │ │ └── .gitkeep ├── prepare_linux.sh ├── release.sh ├── skia.cpp ├── skia.h └── testglfw.cpp ├── deps.edn ├── docs ├── api │ ├── css │ │ └── default.css │ ├── highlight │ │ ├── highlight.min.js │ │ └── solarized-light.css │ ├── index.html │ ├── js │ │ ├── jquery.min.js │ │ └── page_effects.js │ ├── membrane.basic-components.html │ ├── membrane.component.html │ ├── membrane.java2d.html │ ├── membrane.lanterna.html │ ├── membrane.skia.html │ ├── membrane.skija.html │ ├── membrane.toolkit.html │ └── membrane.ui.html ├── distribution.md ├── images │ ├── button-elem.png │ ├── center.png │ ├── center2.png │ ├── checkbox-elem.png │ ├── checkbox-elen.png │ ├── checkbox.gif │ ├── checkbox1.png │ ├── coordinates.png │ ├── counter1.gif │ ├── counter1.png │ ├── counter2.gif │ ├── counter2.png │ ├── counter3.gif │ ├── group.png │ ├── horizontal-layout-spacing.png │ ├── horizontal-layout.png │ ├── item-row.png │ ├── item-selector-filtered.png │ ├── item-selector.gif │ ├── item-selector.png │ ├── label-color.png │ ├── label-default.png │ ├── label-font.png │ ├── label-size.png │ ├── membrane.png │ ├── overview.gif │ ├── rectangle.png │ ├── rounded-rectangle.png │ ├── sawtooth.png │ ├── scale.png │ ├── star.png │ ├── todo.gif │ ├── translate.png │ └── vertical-layout.png ├── membrane-topics.html ├── membrane-topics │ ├── blog.css │ ├── bootstrap.min.css │ ├── coordinates.png │ ├── favicon.ico │ ├── overview-01.png │ └── overview.png ├── overview-01.png ├── styled-text │ └── index.html ├── terminal-uis.md ├── topics.md ├── tutorial.md └── webgl.md ├── examples ├── gol │ ├── .gitignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── build.clj │ ├── deps.edn │ ├── doc │ │ └── intro.md │ ├── resources │ │ └── .keep │ ├── src │ │ └── membrane │ │ │ └── gol.clj │ └── test │ │ └── membrane │ │ └── gol_test.clj ├── readme │ ├── README.md │ ├── deps.edn │ └── src │ │ ├── counter.clj │ │ ├── fun_features.clj │ │ └── readme.clj ├── rss │ ├── deps.edn │ └── src │ │ ├── rss.clj │ │ └── rss │ │ └── feed.clj └── tutorial │ ├── deps.edn │ └── src │ └── tutorial.clj ├── native-image ├── .gitignore ├── README.md ├── compile.sh ├── config.sh └── src │ └── com │ └── phronemophobic │ └── native_image │ └── main.clj ├── notebooks └── paragraph.clj ├── pom-template.xml ├── project.clj ├── resources ├── lines.png └── public │ ├── autouitest.html │ ├── buildertest.html │ ├── css │ ├── addon │ │ └── hint │ │ │ └── show-hint.css │ ├── codemirror.css │ ├── datepicker.css │ └── style.css │ ├── index.html │ ├── js │ ├── opentype.min.js │ ├── opentype.min.js.map │ └── subpar │ │ ├── subpar.core.js │ │ └── subpar.js │ ├── vdomtest.html │ └── webgltest.html ├── shadow-cljs.edn ├── src-java └── com │ └── phronemophobic │ └── membrane │ └── Skia.java ├── src ├── deps.cljs └── membrane │ ├── alpha │ └── component │ │ ├── drag_and_drop.cljc │ │ └── impl │ │ └── scroll.cljc │ ├── analyze.cljc │ ├── audio.cljs │ ├── autoui.cljc │ ├── basic_components.cljc │ ├── cljfx.clj │ ├── component.cljc │ ├── components │ └── code_editor │ │ └── code_editor.clj │ ├── customappmain.clj │ ├── eval.cljs │ ├── example │ ├── README.md │ ├── counter.cljc │ ├── file_selector.clj │ ├── kitchen_sink.clj │ ├── terminal_todo.clj │ └── todo.cljc │ ├── fulcro.clj │ ├── ios.clj │ ├── java2d.clj │ ├── lanterna.clj │ ├── macroexpand.cljs │ ├── re_frame.cljc │ ├── skia.clj │ ├── skia │ ├── impl │ │ └── paint.clj │ ├── paragraph.clj │ └── paragraph │ │ ├── paragraph_test.clj │ │ └── spec.clj │ ├── skija.clj │ ├── terminal.clj │ ├── toolkit.cljc │ ├── ui.cljc │ ├── vdom.cljs │ ├── webgl.cljs │ ├── webgl_macros.clj │ └── webgltest.cljs └── test └── membrane ├── component_test.clj └── defui_test.clj /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy skialib 2 | on: 3 | workflow_dispatch: 4 | # push: 5 | # branches: 6 | # - master 7 | # paths: 8 | # - .github/workflows/** 9 | # - "*.sh" 10 | 11 | # env: 12 | # release: ${{ GITHUB_SHA }} 13 | 14 | jobs: 15 | macos: 16 | runs-on: macos-15 17 | environment: Actions 18 | strategy: 19 | matrix: 20 | build_type: [Release] 21 | arch: ["x86_64", "arm64"] 22 | fail-fast: false 23 | env: 24 | platform: macos 25 | build_type: ${{ matrix.build_type }} 26 | arch: ${{ matrix.arch }} 27 | java_arch: ${{ matrix.arch == 'x86_64' && 'x64' || 'aarch64' }} 28 | shared_suffix: "dylib" 29 | artifact_name: libmembraneskia-${{matrix.arch}}.dylib 30 | steps: 31 | - uses: actions/checkout@v2 32 | - run: ./csource/check_release.sh 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | - run: ./csource/download_skia.sh 36 | - run: ./csource/compile_macosx.sh 37 | - name: Setup Java 38 | uses: actions/setup-java@v2 39 | with: 40 | distribution: 'temurin' # See 'Supported distributions' for available options 41 | java-version: '17' 42 | architecture: ${{ env.java_arch }} 43 | - name: Install clojure tools 44 | uses: DeLaGuardo/setup-clojure@12.5 45 | with: 46 | # Install just one or all simultaneously 47 | cli: 1.11.2.1446 # Clojure CLI based on tools.deps 48 | - run: clojure -T:skialib-deploy :platform '"${{env.platform}}"' :arch '"${{env.arch}}"' 49 | env: 50 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 51 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 52 | 53 | linux: 54 | runs-on: ubuntu-22.04 55 | environment: Actions 56 | strategy: 57 | matrix: 58 | build_type: [Release] 59 | arch: ["x86_64"] 60 | fail-fast: false 61 | env: 62 | platform: linux 63 | build_type: ${{ matrix.build_type }} 64 | arch: ${{ matrix.arch }} 65 | shared_suffix: "so" 66 | artifact_name: libmembraneskia-${{matrix.arch}}.so 67 | steps: 68 | - uses: actions/checkout@v2 69 | - run: ./csource/check_release.sh 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | - run: sudo ./csource/prepare_linux.sh 73 | - run: ./csource/download_skia.sh 74 | - run: ./csource/compile_linux.sh 75 | - name: Setup Java 76 | uses: actions/setup-java@v2 77 | with: 78 | distribution: 'temurin' # See 'Supported distributions' for available options 79 | java-version: '11' 80 | - name: Install clojure tools 81 | uses: DeLaGuardo/setup-clojure@12.5 82 | with: 83 | # Install just one or all simultaneously 84 | cli: 1.11.2.1446 # Clojure CLI based on tools.deps 85 | - run: clojure -T:skialib-deploy :platform '"${{env.platform}}"' :arch '"${{env.arch}}"' 86 | env: 87 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 88 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 89 | -------------------------------------------------------------------------------- /.github/workflows/deploy-clojars.yml: -------------------------------------------------------------------------------- 1 | name: Clojars deploy 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Prepare java 11 | uses: actions/setup-java@v2 12 | with: 13 | distribution: 'zulu' 14 | java-version: '11' 15 | 16 | - uses: actions/checkout@v2 17 | - name: Setup Clojure 18 | # You may pin to the exact commit or the version. 19 | # uses: DeLaGuardo/setup-clojure@5042876523f30f5efcf1d6feaa48bd1498d7814f 20 | uses: DeLaGuardo/setup-clojure@3.5 21 | with: 22 | # Clojure CLI version to make available on the path. 23 | cli: 1.11.0.1100 24 | 25 | - name: Install dependencies 26 | run: clojure -P 27 | 28 | - name: Deploy 29 | env: 30 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 31 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 32 | run: clojure -T:build deploy 33 | 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /resources/public/js/compiled/** 2 | figwheel_server.log 3 | pom.xml 4 | *jar 5 | /classes/ 6 | /out/ 7 | /target/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .repl 12 | .nrepl-port 13 | saves 14 | *.log 15 | build 16 | dist 17 | projects 18 | media/ 19 | pom.xml.asc 20 | node_modules 21 | package-lock.json 22 | package.json 23 | .shadow-cljs 24 | .cpcache 25 | /tmp/ 26 | .clerk 27 | *.gif 28 | *.mov 29 | *.png 30 | docs/ 31 | /target-skialib -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.data.json :as json] 4 | [clojure.java.io :as io] 5 | [clojure.string :as str])) 6 | 7 | (def lib 'com.phronemophobic/membrane) 8 | (def version "0.17.0-beta-SNAPSHOT") 9 | (def class-dir "target/classes") 10 | (def basis (b/create-basis {:project "deps.edn"})) 11 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 12 | (def src-pom "./pom-template.xml") 13 | 14 | (defn clean [_] 15 | (b/delete {:path "target"})) 16 | 17 | (defn compile [_] 18 | (b/javac {:src-dirs ["src-java"] 19 | :class-dir class-dir 20 | :basis basis 21 | :javac-opts ["-source" "8" "-target" "8"]})) 22 | 23 | (defn compile-native-image [_] 24 | (let [basis (b/create-basis {:project "deps.edn" 25 | :aliases [:native-image]})] 26 | (b/javac {:src-dirs ["src-java"] 27 | :class-dir class-dir 28 | :basis basis}) 29 | (b/compile-clj {:class-dir class-dir 30 | :basis basis 31 | :java-opts ["-Dtech.v3.datatype.graal-native=true" 32 | "-Dclojure.compiler.direct-linking=true" 33 | "-Dclojure.spec.skip-macros=true"] 34 | :ns-compile '[com.phronemophobic.native-image.main]}))) 35 | 36 | (defn fix-reflect-config [f] 37 | (let [config (with-open [rdr (io/reader f)] 38 | (json/read rdr)) 39 | new-config (->> config 40 | (remove (fn [{:strs [name]}] 41 | (str/ends-with? name "__init"))))] 42 | (with-open [w (io/writer f)] 43 | (json/write new-config w)))) 44 | 45 | (defn fix-config [_] 46 | (fix-reflect-config (io/file "native-image" "config" "reflect-config.json"))) 47 | 48 | (defn jar [opts] 49 | (compile opts) 50 | (b/write-pom {:class-dir class-dir 51 | :src-pom src-pom 52 | :lib lib 53 | :version version 54 | :basis basis 55 | :src-dirs ["src"]}) 56 | (b/copy-dir {:src-dirs ["src" "resources"] 57 | :target-dir class-dir}) 58 | (b/jar {:class-dir class-dir 59 | :jar-file jar-file})) 60 | 61 | (defn deploy [opts] 62 | (jar opts) 63 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 64 | (merge {:installer :remote 65 | :artifact jar-file 66 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 67 | opts)) 68 | (catch Exception e 69 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 70 | (throw e) 71 | (println "This release was already deployed.")))) 72 | opts) 73 | 74 | 75 | ;; Skialib 76 | (def skialib-class-dir "target-skialib/classes") 77 | (def skialib-jar-file (.getCanonicalPath 78 | (io/file "target-skialib" "skialib.jar"))) 79 | 80 | (defn deploy-skialib [{:keys [platform 81 | arch]}] 82 | (let [resource-prefix (case platform 83 | "macos" "darwin" 84 | ;; else 85 | platform) 86 | resource-suffix (case arch 87 | "arm64" "aarch64" 88 | "x86_64" "x86-64") 89 | 90 | shared-suffix (case platform 91 | "macos" "dylib" 92 | "linux" "so") 93 | shared-lib-path (.getCanonicalPath 94 | (io/file "csource" 95 | (str "libmembraneskia-" arch "." shared-suffix))) 96 | 97 | coord-platform (case platform 98 | "macos" "macosx" 99 | platform) 100 | coord (symbol "com.phronemophobic.membrane" 101 | (str "skialib-" coord-platform "-" resource-suffix)) 102 | 103 | 104 | glfw-dep (symbol "com.phronemophobic" 105 | (str "glfw-" coord-platform "-" resource-suffix)) 106 | skia-basis (b/create-basis {:project 107 | {:deps 108 | {glfw-dep {:mvn/version "3.3.8"}}}})] 109 | (b/write-pom {:class-dir skialib-class-dir 110 | :src-pom "bogus-src-pom" 111 | :pom-data 112 | [[:licenses 113 | [:license 114 | [:name "BSD 3-Clause \"New\" or \"Revised\" License"] 115 | [:url "https://github.com/google/skia/blob/c61843470d89de81c571d87ed2c810911edeb1a3/LICENSE"]] 116 | [:license 117 | [:name "Apache License, Version 2.0"] 118 | [:url "http://www.apache.org/licenses/LICENSE-2.0"]]]] 119 | :lib coord 120 | :version "0.17-beta" 121 | :basis skia-basis}) 122 | (b/copy-file {:src shared-lib-path 123 | :target (.getCanonicalPath 124 | (io/file skialib-class-dir 125 | (str resource-prefix "-" resource-suffix) 126 | (str "libmembraneskia." shared-suffix)))}) 127 | (b/jar {:class-dir skialib-class-dir 128 | :jar-file skialib-jar-file}) 129 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 130 | {:installer :remote 131 | :artifact skialib-jar-file 132 | :pom-file (b/pom-path {:lib coord :class-dir skialib-class-dir})}) 133 | (catch Exception e 134 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 135 | (throw e) 136 | (println "This release was already deployed.")))))) 137 | 138 | 139 | -------------------------------------------------------------------------------- /csource/.gitignore: -------------------------------------------------------------------------------- 1 | Archive.zip 2 | libfreetype.a 3 | libglfw.3.dylib 4 | libglfw.dylib 5 | libmysoil.dylib 6 | libwebsheet.dylib 7 | libmembrane.dylib 8 | *.dylib 9 | *.so 10 | testglfw 11 | testtext -------------------------------------------------------------------------------- /csource/check_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "`dirname $0`" 4 | 5 | auth="Authorization: token ${GITHUB_TOKEN}" 6 | accept="Accept: application/vnd.github.v3+json" 7 | 8 | release="release-${GITHUB_RUN_ID}" 9 | 10 | if ! curl --fail --location --silent --show-error --header "${auth}" --header "${accept}" "https://api.github.com/repos/phronmophobic/membrane/releases/tags/${release}" > release.json ; then 11 | exit 0 12 | fi 13 | 14 | if grep -q "$artifact_name" release.json; then 15 | echo "> Artifact exists: $artifact_name, stopping" 16 | rm release.json 17 | exit 1 18 | fi 19 | 20 | rm release.json 21 | -------------------------------------------------------------------------------- /csource/compile_ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | cd "$DIR" 8 | 9 | export SDKROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk" 10 | 11 | clang++ \ 12 | -I ./libs/skia.bak \ 13 | -I ./libs/skia.bak/include/gpu \ 14 | -I ./libs/skia.bak/include/gpu/gl \ 15 | -I ./libs/skia.bak/include/core \ 16 | -I ./libs/skia.bak/include/utils \ 17 | -I ./libs/skia.bak/include/private \ 18 | -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk \ 19 | -DTARGET_OS_IOS=1 \ 20 | -DSK_METAL=1 \ 21 | -DSK_SUPPORT_GPU=1 \ 22 | -target arm64-apple-ios14.1 \ 23 | -c \ 24 | -std=c++17 \ 25 | -arch arm64 \ 26 | -o libmembraneiosskia.o \ 27 | skia.cpp 28 | 29 | 30 | # install_name_tool -change @rpath/libskia.so @loader_path/libskia.so libmembraneiosskia.so 31 | 32 | echo 'done' 33 | 34 | -------------------------------------------------------------------------------- /csource/compile_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | cd "$DIR" 8 | 9 | skia_root="./libs/skia" 10 | 11 | c++ \ 12 | -fPIC \ 13 | -I "$skia_root" \ 14 | -I "$skia_root"/include/gpu \ 15 | -I "$skia_root"/include/gpu/gl \ 16 | -I "$skia_root"/include/core \ 17 | -I "$skia_root"/include/utils \ 18 | -I "$skia_root"/include/private \ 19 | -I "$skia_root"/include/codec \ 20 | -L "$skia_root"/out/Release-${arch} \ 21 | -Wl,--whole-archive \ 22 | "$skia_root"/out/Release-${arch}/libskia.a \ 23 | "$skia_root"/out/Release-${arch}/libskparagraph.a \ 24 | "$skia_root"/out/Release-${arch}/libsvg.a \ 25 | -Wl,--no-whole-archive \ 26 | -shared \ 27 | -std=c++17 \ 28 | -o libmembraneskia-${arch}.so \ 29 | -DSK_GL=1 \ 30 | -lGL \ 31 | -lfontconfig \ 32 | -lskunicode_core \ 33 | -lskunicode_icu \ 34 | -lskshaper \ 35 | -lsvg \ 36 | skia.cpp 37 | 38 | cp libmembraneskia-x86_64.so ./linux-x86-64/resources/linux-x86-64/libmembraneskia.so 39 | 40 | echo 'done' 41 | -------------------------------------------------------------------------------- /csource/compile_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | cd "$DIR" 8 | 9 | export arch=arm64 10 | 11 | ## deleting the file seems to help workaround apple quarantining? 12 | rm -f libmembraneskia-${arch}.dylib 13 | ./compile_macosx.sh 14 | 15 | ## deleting the file seems to help workaround apple quarantining? 16 | rm ./macos-aarch64/resources/darwin-aarch64/libmembraneskia.dylib 17 | cp libmembraneskia-${arch}.dylib ./macos-aarch64/resources/darwin-aarch64/libmembraneskia.dylib 18 | 19 | 20 | -------------------------------------------------------------------------------- /csource/compile_macosx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | cd "$DIR" 8 | 9 | export SDKROOT="/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" 10 | 11 | skia_root="./libs/skia" 12 | 13 | clang++ \ 14 | -I "$skia_root" \ 15 | -I "$skia_root"/include/gpu \ 16 | -I "$skia_root"/include/gpu/gl \ 17 | -I "$skia_root"/include/core \ 18 | -I "$skia_root"/include/utils \ 19 | -I "$skia_root"/include/private \ 20 | -I "$skia_root"/include/codec \ 21 | -framework OpenGL \ 22 | -framework Cocoa \ 23 | -framework IOKit \ 24 | -framework CoreFoundation \ 25 | -framework CoreVideo \ 26 | -framework AppKit \ 27 | -framework CoreGraphics \ 28 | -framework CoreServices \ 29 | -framework Foundation \ 30 | -framework Metal \ 31 | -mmacosx-version-min=10.14 \ 32 | "$skia_root"/out/Release-${arch}/*.a \ 33 | -DSK_GL=1 \ 34 | -arch ${arch} \ 35 | -dynamiclib \ 36 | -std=c++17 \ 37 | -x objective-c++ \ 38 | -o libmembraneskia-${arch}.dylib \ 39 | skia.cpp 40 | 41 | # cd ./libs/libtmt && ./compile.sh 42 | # cd - 43 | 44 | # test 45 | # clang++ -I ./libs/glfw-3.3.bin.MACOS/include \ 46 | # -I ./libs/skia \ 47 | # -I ./libs/skia/include/gpu \ 48 | # -I ./libs/skia/include/gpu/gl \ 49 | # -I ./libs/skia/include/core \ 50 | # -I ./libs/skia/include/utils \ 51 | # -I ./libs/skia/include/private \ 52 | # -I ./libs/libtmt \ 53 | # -framework OpenGL \ 54 | # -framework Cocoa \ 55 | # -framework IOKit \ 56 | # -framework CoreFoundation \ 57 | # -framework CoreVideo \ 58 | # -framework AppKit \ 59 | # -framework CoreGraphics \ 60 | # -framework CoreServices \ 61 | # -framework Foundation \ 62 | # -framework Metal \ 63 | # -mmacosx-version-min=10.13 \ 64 | # -DTESTING \ 65 | # ./libs/glfw-3.3.bin.MACOS/lib-macos/libglfw3.a \ 66 | # /Users/adrian/Downloads/Skia-m92-f46c37ba85-2-macos-Release-x64/out/Release-x64/libskia.a \ 67 | # ./libs/libtmt/tmt.o \ 68 | # -DSK_GL=1 \ 69 | # -std=c++17 \ 70 | # -arch ${arch} \ 71 | # -o testglfw \ 72 | # testglfw.cpp skia.cpp 73 | 74 | 75 | 76 | echo 'done' 77 | -------------------------------------------------------------------------------- /csource/deploy_clojars.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import sys 4 | import re 5 | 6 | os.chdir(os.path.dirname(sys.argv[0])) 7 | 8 | env = os.environ 9 | 10 | resource_prefix = { 11 | 'macos': 'darwin', 12 | }.get(env['platform'], env['platform']) 13 | 14 | resource_suffix = { 15 | 'arm64': 'aarch64', 16 | 'x86_64' : 'x86-64', 17 | }[env['arch']] 18 | 19 | def run(args): 20 | print(args) 21 | subprocess.run(args, check=True) 22 | 23 | run(['cp', 24 | 'libmembraneskia-{arch}.{shared_suffix}'.format(arch=env['arch'],shared_suffix=env['shared_suffix']), 25 | '{platform}-{resource_suffix}/resources/{resource_prefix}-{resource_suffix}/libmembraneskia.{shared_suffix}'.format( 26 | platform=env['platform'], 27 | resource_suffix=resource_suffix, 28 | resource_prefix=resource_prefix, 29 | shared_suffix=env['shared_suffix'], 30 | )]) 31 | 32 | # cp libmembraneskia-${arch}.${shared_suffix} ${platform}-${resource_suffix}/resources/${resource_prefix}-${resource_suffix}/libmembraneskia.${shared_suffix} 33 | 34 | version="0.15-beta-SNAPSHOT" 35 | 36 | print('using version: "{version}"'.format(version=version)) 37 | 38 | os.chdir('{platform}-{resource_suffix}'.format(platform=env['platform'], resource_suffix=resource_suffix) ) 39 | 40 | run(['clojure', '-X:jar', ':sync-pom', 'true', ':version', '"{version}"'.format(version=version)]) 41 | 42 | run(['clojure', '-M:deploy']) 43 | 44 | -------------------------------------------------------------------------------- /csource/download_skia.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | # platform=windows 7 | # arch=x86_64 8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 9 | cd "$DIR" 10 | 11 | mkdir -p libs 12 | 13 | pushd libs 14 | 15 | 16 | RELEASE="dbfd72770bdd2e7b82d493df8cdda6338fdf7f7c-3" 17 | URL="https://github.com/phronmophobic/skia-build/releases/download/${RELEASE}/Skia-${RELEASE}-${platform}-Release-${arch}.zip" 18 | 19 | curl -L -o skia.zip "${URL}" 20 | 21 | unzip -d skia skia.zip 22 | 23 | popd 24 | -------------------------------------------------------------------------------- /csource/download_skia_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | cd "$DIR" 8 | 9 | export arch=arm64 10 | export platform="macosx" 11 | 12 | ./download_skia.sh 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /csource/glfw-linux-x86-64/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /csource/glfw-linux-x86-64/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.string :as str])) 4 | 5 | (def lib 'com.phronemophobic/glfw-linux-x86-64) 6 | (def version "3.3.8") 7 | (def class-dir "target/classes") 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 10 | 11 | (defn clean [_] 12 | (b/delete {:path "target"})) 13 | 14 | (defn jar [opts] 15 | (b/write-pom {:class-dir class-dir 16 | :lib lib 17 | :version version 18 | :basis basis 19 | :src-dirs ["src"]}) 20 | (b/copy-dir {:src-dirs ["src" "resources"] 21 | :target-dir class-dir}) 22 | (b/jar {:class-dir class-dir 23 | :jar-file jar-file})) 24 | 25 | (defn deploy [opts] 26 | (jar opts) 27 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 28 | (merge {:installer :remote 29 | :artifact jar-file 30 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 31 | opts)) 32 | (catch Exception e 33 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 34 | (throw e) 35 | (println "This release was already deployed.")))) 36 | opts) 37 | -------------------------------------------------------------------------------- /csource/glfw-linux-x86-64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :aliases 3 | {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.3" :git/sha "0d20256"} 4 | slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :ns-default build}}} 6 | -------------------------------------------------------------------------------- /csource/glfw-linux-x86-64/resources/linux-x86-64/libglfw.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/glfw-linux-x86-64/resources/linux-x86-64/libglfw.so -------------------------------------------------------------------------------- /csource/glfw-macosx-aarch64/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /csource/glfw-macosx-aarch64/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.string :as str])) 4 | 5 | (def lib 'com.phronemophobic/glfw-macosx-aarch64) 6 | (def version "3.3.8") 7 | (def class-dir "target/classes") 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 10 | 11 | (defn clean [_] 12 | (b/delete {:path "target"})) 13 | 14 | (defn jar [opts] 15 | (b/write-pom {:class-dir class-dir 16 | :lib lib 17 | :version version 18 | :basis basis 19 | :src-dirs ["src"]}) 20 | (b/copy-dir {:src-dirs ["src" "resources"] 21 | :target-dir class-dir}) 22 | (b/jar {:class-dir class-dir 23 | :jar-file jar-file})) 24 | 25 | (defn deploy [opts] 26 | (jar opts) 27 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 28 | (merge {:installer :remote 29 | :artifact jar-file 30 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 31 | opts)) 32 | (catch Exception e 33 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 34 | (throw e) 35 | (println "This release was already deployed.")))) 36 | opts) 37 | -------------------------------------------------------------------------------- /csource/glfw-macosx-aarch64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :aliases 3 | {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.3" :git/sha "0d20256"} 4 | slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :ns-default build}}} 6 | -------------------------------------------------------------------------------- /csource/glfw-macosx-aarch64/resources/darwin-aarch64/libglfw.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/glfw-macosx-aarch64/resources/darwin-aarch64/libglfw.dylib -------------------------------------------------------------------------------- /csource/glfw-macosx-x86-64/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /csource/glfw-macosx-x86-64/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [clojure.string :as str])) 4 | 5 | (def lib 'com.phronemophobic/glfw-macosx-x86-64) 6 | (def version "3.3.8") 7 | (def class-dir "target/classes") 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 10 | 11 | (defn clean [_] 12 | (b/delete {:path "target"})) 13 | 14 | (defn jar [opts] 15 | (b/write-pom {:class-dir class-dir 16 | :lib lib 17 | :version version 18 | :basis basis 19 | :src-dirs ["src"]}) 20 | (b/copy-dir {:src-dirs ["src" "resources"] 21 | :target-dir class-dir}) 22 | (b/jar {:class-dir class-dir 23 | :jar-file jar-file})) 24 | 25 | (defn deploy [opts] 26 | (jar opts) 27 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 28 | (merge {:installer :remote 29 | :artifact jar-file 30 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 31 | opts)) 32 | (catch Exception e 33 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 34 | (throw e) 35 | (println "This release was already deployed.")))) 36 | opts) 37 | -------------------------------------------------------------------------------- /csource/glfw-macosx-x86-64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :aliases 3 | {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.3" :git/sha "0d20256"} 4 | slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :ns-default build}}} 6 | -------------------------------------------------------------------------------- /csource/glfw-macosx-x86-64/resources/darwin-x86-64/libglfw.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/glfw-macosx-x86-64/resources/darwin-x86-64/libglfw.dylib -------------------------------------------------------------------------------- /csource/libs/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /csource/libs/Readme.md: -------------------------------------------------------------------------------- 1 | Freetype 2 | 3 | 4 | ``` 5 | wget https://download.savannah.gnu.org/releases/freetype/freetype-2.10.1.tar.gz 6 | tar xvf freetype-2.10.1.tar.gz 7 | export MACOSX_DEPLOYMENT_TARGET=10.10 8 | ./configure --without-harfbuzz 9 | make 10 | ``` 11 | 12 | Glfw 13 | 14 | 15 | 16 | ``` 17 | 18 | wget https://github.com/glfw/glfw/releases/download/3.3/glfw-3.3.bin.MACOS.zip 19 | unzip glfw-3.3.bin.MACOS.zip 20 | ``` 21 | 22 | 23 | zlib 24 | 25 | ``` 26 | 27 | wget https://www.zlib.net/zlib1211.zip 28 | cd zlib-1.2.11/ 29 | 30 | 31 | export MACOSX_DEPLOYMENT_TARGET=10.10 32 | ./configure 33 | make 34 | ``` 35 | 36 | 37 | png 38 | 39 | ``` 40 | 41 | 42 | wget 'http://prdownloads.sourceforge.net/libpng/libpng-1.6.37.tar.xz?download' 43 | tar xvf libpng-1.6.37.tar.xz\?download 44 | export MACOSX_DEPLOYMENT_TARGET=10.10 45 | ./configure 46 | make 47 | 48 | 49 | 50 | ``` 51 | 52 | 53 | bz2 54 | 55 | ``` 56 | 57 | wget 'https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz' 58 | tar xvf bzip2-1.0.8.tar.gz 59 | export MACOSX_DEPLOYMENT_TARGET=10.10 60 | make 61 | 62 | ``` 63 | 64 | Soil 65 | 66 | ``` 67 | 68 | git clone git@github.com:phronmophobic/libSOIL.git 69 | export MACOSX_DEPLOYMENT_TARGET=10.10 70 | make 71 | 72 | ``` 73 | 74 | Skia 75 | 76 | ```sh 77 | 78 | # install depot tools 79 | 80 | export MACOSX_DEPLOYMENT_TARGET=10.10 81 | 82 | export IOS_MIN_TARGET=14.1 83 | git clone 'https://chromium.googlesource.com/chromium/tools/depot_tools.git' 84 | export PATH="${PWD}/depot_tools:${PATH}" 85 | 86 | fetch skia 87 | cd skia 88 | python2 tools/git-sync-deps 89 | 90 | # on linux 91 | # tools/install_dependencies.sh 92 | 93 | # mac/linux 94 | bin/gn gen out/Static --args='target_cpu="x86_64" is_official_build=true skia_use_system_libwebp=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_harfbuzz=false skia_use_system_icu=false skia_use_system_expat=false skia_use_system_zlib=false cc="clang" cxx="clang++"' 95 | 96 | #ios sim 97 | # bin/gn gen out/ios64 --args='target_os="ios" is_official_build=true skia_use_system_expat=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false' 98 | 99 | # ios device 100 | 101 | # add is_component_build=true for shared 102 | # shared library doesn't export all symbols needed, but ios app needs shared library. need to build both 103 | # may need to comment out ios_min app in BUILD.gn 104 | bin/gn gen out/ios64Static --args='target_os="ios" is_official_build=true skia_use_system_expat=false skia_use_system_harfbuzz=false skia_use_system_icu=false skia_use_system_libjpeg_turbo=false skia_use_system_libpng=false skia_use_system_libwebp=false skia_use_system_zlib=false ios_min_target="14.1" skia_enable_skottie=false skia_use_metal=true skia_enable_skshaper=true skia_enable_skparagraph=true skia_enable_gpu=true' 105 | 106 | # for ios 107 | ninja -C out/ios64Static skia modules 108 | 109 | # in general 110 | ninja -C out/Static/ 111 | 112 | ``` 113 | 114 | 115 | Linux Glad 116 | 117 | 118 | Generated from http://glad.dav1d.de/#profile=compatibility&specification=gl&api=gl%3D3.3&api=gles1%3Dnone&api=gles2%3Dnone&api=glsc2%3Dnone&language=c&loader=on 119 | Included in libs/glad 120 | 121 | 122 | Linux glfw 123 | 124 | `` 125 | 126 | 127 | wget https://github.com/glfw/glfw/releases/download/3.3.2/glfw-3.3.2.zip 128 | 129 | cmake -DBUILD_SHARED_LIBS=ON . 130 | make 131 | 132 | 133 | ``` 134 | 135 | -------------------------------------------------------------------------------- /csource/linux-x86-64/.gitignore: -------------------------------------------------------------------------------- 1 | my.jar -------------------------------------------------------------------------------- /csource/linux-x86-64/Readme.md: -------------------------------------------------------------------------------- 1 | Steps to Deploy: 2 | 3 | 1. Make sure libglfw.so is already in the resources/darwin folder 4 | 2. Download the release version of libmembraneskia.so from github to resources/linux-x86-64 folder 5 | 3. Updated the pom with the matching version. 6 | 4. `clojure -X:jar` 7 | 5. `clojure -M:deploy` 8 | -------------------------------------------------------------------------------- /csource/linux-x86-64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :deps {com.phronemophobic/glfw-linux-x86-64 {:mvn/version "3.3.8"}} 3 | :aliases 4 | {:install {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :main-opts ["-m" "deps-deploy.deps-deploy" "install" "my.jar"]} 6 | :deploy {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 7 | :main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "my.jar"]} 8 | :jar {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}} 9 | :exec-fn hf.depstar/jar 10 | :exec-args {:jar "my.jar"}}}} 11 | 12 | -------------------------------------------------------------------------------- /csource/linux-x86-64/resources/linux-x86-64/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/linux-x86-64/resources/linux-x86-64/.gitkeep -------------------------------------------------------------------------------- /csource/macos-aarch64/.gitignore: -------------------------------------------------------------------------------- 1 | my.jar -------------------------------------------------------------------------------- /csource/macos-aarch64/Readme.md: -------------------------------------------------------------------------------- 1 | Steps to Deploy: 2 | 3 | 1. Make sure libglfw.dylib is already in the resources/linux-x86-64 folder 4 | 2. Download the release version of libmembraneskia.dylib from github to resources/darwin folder 5 | 3. Updated the pom with the matching version. 6 | 4. `clojure -X:jar` 7 | 5. `clojure -M:deploy` 8 | -------------------------------------------------------------------------------- /csource/macos-aarch64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :deps {com.phronemophobic/glfw-macosx-aarch64 {:mvn/version "3.3.8"}} 3 | :aliases 4 | {:install {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :main-opts ["-m" "deps-deploy.deps-deploy" "install" "my.jar"]} 6 | :deploy {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 7 | :main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "my.jar"]} 8 | :jar {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}} 9 | :exec-fn hf.depstar/jar 10 | :exec-args {:jar "my.jar"}}}} 11 | 12 | -------------------------------------------------------------------------------- /csource/macos-aarch64/resources/darwin-aarch64/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/macos-aarch64/resources/darwin-aarch64/.gitkeep -------------------------------------------------------------------------------- /csource/macos-x86-64/.gitignore: -------------------------------------------------------------------------------- 1 | my.jar -------------------------------------------------------------------------------- /csource/macos-x86-64/Readme.md: -------------------------------------------------------------------------------- 1 | Steps to Deploy: 2 | 3 | 1. Make sure libglfw.dylib is already in the resources/linux-x86-64 folder 4 | 2. Download the release version of libmembraneskia.dylib from github to resources/darwin folder 5 | 3. Updated the pom with the matching version. 6 | 4. `clojure -X:jar` 7 | 5. `clojure -M:deploy` 8 | -------------------------------------------------------------------------------- /csource/macos-x86-64/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources"] 2 | :deps {com.phronemophobic/glfw-macosx-x86-64 {:mvn/version "3.3.8"}} 3 | :aliases 4 | {:install {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 5 | :main-opts ["-m" "deps-deploy.deps-deploy" "install" "my.jar"]} 6 | :deploy {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}} 7 | :main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "my.jar"]} 8 | :jar {:extra-deps {seancorfield/depstar {:mvn/version "RELEASE"}} 9 | :exec-fn hf.depstar/jar 10 | :exec-args {:jar "my.jar"}}}} 11 | 12 | -------------------------------------------------------------------------------- /csource/macos-x86-64/resources/darwin-x86-64/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/csource/macos-x86-64/resources/darwin-x86-64/.gitkeep -------------------------------------------------------------------------------- /csource/prepare_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | 4 | apt-get update -y 5 | apt-get install build-essential software-properties-common -y 6 | add-apt-repository ppa:ubuntu-toolchain-r/test -y 7 | apt-get update -y 8 | apt-get install build-essential software-properties-common -y 9 | apt-get update 10 | 11 | apt-get install gcc-9 g++-9 -y 12 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 60 --slave /usr/bin/g++ g++ /usr/bin/g++-9 13 | update-alternatives --config gcc 14 | 15 | apt-get install fontconfig libfontconfig1-dev libglu1-mesa-dev curl zip -y 16 | -------------------------------------------------------------------------------- /csource/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "`dirname $0`" 4 | 5 | set -x 6 | 7 | auth="Authorization: token ${GITHUB_TOKEN}" 8 | accept="Accept: application/vnd.github.v3+json" 9 | 10 | release="release-${GITHUB_RUN_ID}" 11 | 12 | if ! curl --fail --location --silent --show-error --header "${auth}" --header "${accept}" "https://api.github.com/repos/phronmophobic/membrane/releases/tags/${release}" > release.json ; then 13 | echo "> Creating release ${release}" 14 | curl --fail --location --silent --show-error --header "${auth}" --header "${accept}" --request POST \ 15 | --data "{\"tag_name\":\"${release}\",\"name\":\"${release}\"}" \ 16 | https://api.github.com/repos/phronmophobic/membrane/releases > release.json 17 | else 18 | echo "> Release ${release} exists" 19 | cat release.json 20 | fi 21 | 22 | [[ $(cat release.json | grep '"upload_url"') =~ https://.*/assets ]] 23 | upload_url="${BASH_REMATCH[0]}?name=${artifact_name}" 24 | rm release.json 25 | 26 | echo "Uploading ${artifact_name} to ${upload_url}" 27 | curl --fail --location --silent --show-error --header "${auth}" --header "${accept}" --header "Content-Type: application/octet-stream" --request POST --data-binary "@${artifact_name}" ${upload_url} 28 | -------------------------------------------------------------------------------- /csource/skia.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ganesh/GrDirectContext.h" 3 | #include "SkData.h" 4 | #include "SkImage.h" 5 | #include "SkStream.h" 6 | #include "SkSurface.h" 7 | 8 | #include "include/core/SkCanvas.h" 9 | #include "include/core/SkFont.h" 10 | #include "SkTextBlob.h" 11 | 12 | 13 | class SkiaResource { 14 | 15 | 16 | 17 | public: 18 | 19 | sk_sp grContext; 20 | sk_sp surface; 21 | std::stack paints; 22 | 23 | ~SkiaResource(){ 24 | grContext.reset(); 25 | surface.reset(); 26 | } 27 | 28 | SkiaResource(sk_sp _grContext, sk_sp _surface):grContext(_grContext), surface(_surface){ 29 | paints.emplace(SkPaint()); 30 | SkPaint& paint = paints.top(); 31 | paint.setAntiAlias(true); 32 | paint.setStrokeWidth(1); 33 | paint.setColor(SK_ColorBLACK); 34 | } 35 | 36 | SkPaint& getPaint(){ 37 | return paints.top(); 38 | } 39 | 40 | void pushPaint(){ 41 | paints.emplace(SkPaint(paints.top())); 42 | } 43 | 44 | void popPaint(){ 45 | paints.pop(); 46 | } 47 | }; 48 | 49 | 50 | extern "C"{ 51 | SkiaResource* skia_init(); 52 | SkiaResource* skia_init_cpu(int width, int height); 53 | 54 | void skia_reshape(SkiaResource* resource, int frameBufferWidth, int frameBufferHeight, float xscale, float yscale); 55 | void skia_clear(SkiaResource* resources); 56 | void skia_flush(SkiaResource* resources); 57 | void skia_cleanup(SkiaResource* resources); 58 | void skia_set_scale (SkiaResource* resource, float sx, float sy); 59 | void skia_render_line(SkiaResource* resource, SkFont* font, const char* text, int text_length, float x, float y); 60 | void skia_next_line(SkiaResource* resource, SkFont* font); 61 | float skia_line_height(SkFont* font); 62 | 63 | void skia_font_metrics(SkFont* font, 64 | uint32_t *fFlags, 65 | SkScalar *fTop, 66 | SkScalar *fAscent, 67 | SkScalar *fDescent, 68 | SkScalar *fBottom, 69 | SkScalar *fLeading, 70 | SkScalar *fAvgCharWidth, 71 | SkScalar *fMaxCharWidth, 72 | SkScalar *fXMin, 73 | SkScalar *fXMax, 74 | SkScalar *fXHeight, 75 | SkScalar *fCapHeight, 76 | SkScalar *fUnderlineThickness, 77 | SkScalar *fUnderlinePosition, 78 | SkScalar *fStrikeoutThickness, 79 | SkScalar *fStrikeoutPosition); 80 | 81 | float skia_advance_x(SkFont* font, const char* text, int text_length); 82 | void skia_render_cursor(SkiaResource* resource, SkFont * font, const char* text, int text_length , int cursor); 83 | void skia_render_selection(SkiaResource* resource, SkFont * font, const char* text, int text_length , int selection_start, int selection_end); 84 | 85 | int skia_index_for_position(SkFont* font, const char* text, int text_length, float px); 86 | void skia_text_bounds(SkFont* font, const char* text, int text_length, float* ox, float* oy, float* width, float* height); 87 | 88 | void skia_save(SkiaResource* resource); 89 | void skia_restore(SkiaResource* resource); 90 | void skia_translate(SkiaResource* resource, float tx, float ty); 91 | void skia_clip_rect(SkiaResource* resource, float ox, float oy, float width, float height); 92 | 93 | SkImage* skia_load_image(const char* path); 94 | SkImage* skia_load_image_from_memory(const unsigned char *const buffer,int buffer_length); 95 | void skia_draw_image(SkiaResource* resource, SkImage* image); 96 | void skia_draw_image_rect(SkiaResource* resource, SkImage* image, float w, float h); 97 | 98 | void skia_draw_path(SkiaResource* resource, float* points, int count); 99 | void skia_draw_polygon(SkiaResource* resource, float* points, int count); 100 | 101 | void skia_draw_rounded_rect(SkiaResource* resource, float width, float height, float radius); 102 | 103 | SkFont* skia_load_font2(const char* name, float size, int weight, int width, int slant); 104 | 105 | 106 | // Paint related calls 107 | void skia_push_paint(SkiaResource* resource); 108 | void skia_pop_paint(SkiaResource* resource); 109 | void skia_set_color(SkiaResource* resource, float r, float g, float b, float a); 110 | void skia_set_style(SkiaResource* resource, SkPaint::Style style); 111 | void skia_set_stroke_width(SkiaResource* resource, float stroke_width); 112 | void skia_set_alpha(SkiaResource* resource, unsigned char a); 113 | 114 | // offscreen buffer stuff 115 | SkiaResource* skia_offscreen_buffer(SkiaResource* resource, int width, int height); 116 | SkImage* skia_offscreen_image(SkiaResource* resource); 117 | 118 | int skia_save_image(SkiaResource* image, int format, int quality, const char* path); 119 | 120 | int skia_fork_pty(unsigned short rows, unsigned short columns); 121 | #if defined(__APPLE__) 122 | void skia_osx_run_on_main_thread_sync(void(*callback)(void)); 123 | #endif 124 | } 125 | -------------------------------------------------------------------------------- /docs/api/highlight/solarized-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #fdf6e3; 12 | color: #657b83; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #93a1a1; 18 | } 19 | 20 | /* Solarized Green */ 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-addition { 24 | color: #859900; 25 | } 26 | 27 | /* Solarized Cyan */ 28 | .hljs-number, 29 | .hljs-string, 30 | .hljs-meta .hljs-meta-string, 31 | .hljs-literal, 32 | .hljs-doctag, 33 | .hljs-regexp { 34 | color: #2aa198; 35 | } 36 | 37 | /* Solarized Blue */ 38 | .hljs-title, 39 | .hljs-section, 40 | .hljs-name, 41 | .hljs-selector-id, 42 | .hljs-selector-class { 43 | color: #268bd2; 44 | } 45 | 46 | /* Solarized Yellow */ 47 | .hljs-attribute, 48 | .hljs-attr, 49 | .hljs-variable, 50 | .hljs-template-variable, 51 | .hljs-class .hljs-title, 52 | .hljs-type { 53 | color: #b58900; 54 | } 55 | 56 | /* Solarized Orange */ 57 | .hljs-symbol, 58 | .hljs-bullet, 59 | .hljs-subst, 60 | .hljs-meta, 61 | .hljs-meta .hljs-keyword, 62 | .hljs-selector-attr, 63 | .hljs-selector-pseudo, 64 | .hljs-link { 65 | color: #cb4b16; 66 | } 67 | 68 | /* Solarized Red */ 69 | .hljs-built_in, 70 | .hljs-deletion { 71 | color: #dc322f; 72 | } 73 | 74 | .hljs-formula { 75 | background: #eee8d5; 76 | } 77 | 78 | .hljs-emphasis { 79 | font-style: italic; 80 | } 81 | 82 | .hljs-strong { 83 | font-weight: bold; 84 | } 85 | -------------------------------------------------------------------------------- /docs/api/js/page_effects.js: -------------------------------------------------------------------------------- 1 | function visibleInParent(element) { 2 | var position = $(element).position().top 3 | return position > -50 && position < ($(element).offsetParent().height() - 50) 4 | } 5 | 6 | function hasFragment(link, fragment) { 7 | return $(link).attr("href").indexOf("#" + fragment) != -1 8 | } 9 | 10 | function findLinkByFragment(elements, fragment) { 11 | return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first() 12 | } 13 | 14 | function scrollToCurrentVarLink(elements) { 15 | var elements = $(elements); 16 | var parent = elements.offsetParent(); 17 | 18 | if (elements.length == 0) return; 19 | 20 | var top = elements.first().position().top; 21 | var bottom = elements.last().position().top + elements.last().height(); 22 | 23 | if (top >= 0 && bottom <= parent.height()) return; 24 | 25 | if (top < 0) { 26 | parent.scrollTop(parent.scrollTop() + top); 27 | } 28 | else if (bottom > parent.height()) { 29 | parent.scrollTop(parent.scrollTop() + bottom - parent.height()); 30 | } 31 | } 32 | 33 | function setCurrentVarLink() { 34 | $('.secondary a').parent().removeClass('current') 35 | $('.anchor'). 36 | filter(function(index) { return visibleInParent(this) }). 37 | each(function(index, element) { 38 | findLinkByFragment(".secondary a", element.id). 39 | parent(). 40 | addClass('current') 41 | }); 42 | scrollToCurrentVarLink('.secondary .current'); 43 | } 44 | 45 | var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }()) 46 | 47 | function scrollPositionId(element) { 48 | var directory = window.location.href.replace(/[^\/]+\.html$/, '') 49 | return 'scroll::' + $(element).attr('id') + '::' + directory 50 | } 51 | 52 | function storeScrollPosition(element) { 53 | if (!hasStorage) return; 54 | localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft()) 55 | localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop()) 56 | } 57 | 58 | function recallScrollPosition(element) { 59 | if (!hasStorage) return; 60 | $(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x")) 61 | $(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y")) 62 | } 63 | 64 | function persistScrollPosition(element) { 65 | recallScrollPosition(element) 66 | $(element).scroll(function() { storeScrollPosition(element) }) 67 | } 68 | 69 | function sidebarContentWidth(element) { 70 | var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() }) 71 | return Math.max.apply(Math, widths) 72 | } 73 | 74 | function calculateSize(width, snap, margin, minimum) { 75 | if (width == 0) { 76 | return 0 77 | } 78 | else { 79 | return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2)) 80 | } 81 | } 82 | 83 | function resizeSidebars() { 84 | var primaryWidth = sidebarContentWidth('.primary') 85 | var secondaryWidth = 0 86 | 87 | if ($('.secondary').length != 0) { 88 | secondaryWidth = sidebarContentWidth('.secondary') 89 | } 90 | 91 | // snap to grid 92 | primaryWidth = calculateSize(primaryWidth, 32, 13, 160) 93 | secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160) 94 | 95 | $('.primary').css('width', primaryWidth) 96 | $('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1) 97 | 98 | if (secondaryWidth > 0) { 99 | $('#content').css('left', primaryWidth + secondaryWidth + 2) 100 | } 101 | else { 102 | $('#content').css('left', primaryWidth + 1) 103 | } 104 | } 105 | 106 | $(window).ready(resizeSidebars) 107 | $(window).ready(setCurrentVarLink) 108 | $(window).ready(function() { persistScrollPosition('.primary')}) 109 | $(window).ready(function() { 110 | $('#content').scroll(setCurrentVarLink) 111 | $(window).resize(setCurrentVarLink) 112 | $(window).resize(resizeSidebars) 113 | }) 114 | -------------------------------------------------------------------------------- /docs/distribution.md: -------------------------------------------------------------------------------- 1 | # Distributing your app 2 | 3 | Regardless of how you distribute your app, you'll probably want to use `run-ui-sync` to prevent the jvm from shutting down. Below is an example of `-main`. 4 | 5 | ```clojure 6 | (defn -main [ & args] 7 | (let [initial-state {:foo "bar"}] 8 | (run-ui-sync #'app-root initial-state))) 9 | ``` 10 | 11 | 12 | ## Uberjar 13 | 14 | You can always distribute your app as an uberjar. It will require java 1.8+ to be installed on the target platform, but it will work regardless of which platform the app was developed on. 15 | 16 | 17 | ## Mac OS X .app 18 | 19 | For distrbuting to Mac OS X, appbundler does a great job of turning your uberjar into a .app. 20 | 21 | https://github.com/TheInfiniteKind/appbundler/ 22 | -------------------------------------------------------------------------------- /docs/images/button-elem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/button-elem.png -------------------------------------------------------------------------------- /docs/images/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/center.png -------------------------------------------------------------------------------- /docs/images/center2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/center2.png -------------------------------------------------------------------------------- /docs/images/checkbox-elem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/checkbox-elem.png -------------------------------------------------------------------------------- /docs/images/checkbox-elen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/checkbox-elen.png -------------------------------------------------------------------------------- /docs/images/checkbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/checkbox.gif -------------------------------------------------------------------------------- /docs/images/checkbox1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/checkbox1.png -------------------------------------------------------------------------------- /docs/images/coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/coordinates.png -------------------------------------------------------------------------------- /docs/images/counter1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/counter1.gif -------------------------------------------------------------------------------- /docs/images/counter1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/counter1.png -------------------------------------------------------------------------------- /docs/images/counter2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/counter2.gif -------------------------------------------------------------------------------- /docs/images/counter2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/counter2.png -------------------------------------------------------------------------------- /docs/images/counter3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/counter3.gif -------------------------------------------------------------------------------- /docs/images/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/group.png -------------------------------------------------------------------------------- /docs/images/horizontal-layout-spacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/horizontal-layout-spacing.png -------------------------------------------------------------------------------- /docs/images/horizontal-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/horizontal-layout.png -------------------------------------------------------------------------------- /docs/images/item-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/item-row.png -------------------------------------------------------------------------------- /docs/images/item-selector-filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/item-selector-filtered.png -------------------------------------------------------------------------------- /docs/images/item-selector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/item-selector.gif -------------------------------------------------------------------------------- /docs/images/item-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/item-selector.png -------------------------------------------------------------------------------- /docs/images/label-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/label-color.png -------------------------------------------------------------------------------- /docs/images/label-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/label-default.png -------------------------------------------------------------------------------- /docs/images/label-font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/label-font.png -------------------------------------------------------------------------------- /docs/images/label-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/label-size.png -------------------------------------------------------------------------------- /docs/images/membrane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/membrane.png -------------------------------------------------------------------------------- /docs/images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/overview.gif -------------------------------------------------------------------------------- /docs/images/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/rectangle.png -------------------------------------------------------------------------------- /docs/images/rounded-rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/rounded-rectangle.png -------------------------------------------------------------------------------- /docs/images/sawtooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/sawtooth.png -------------------------------------------------------------------------------- /docs/images/scale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/scale.png -------------------------------------------------------------------------------- /docs/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/star.png -------------------------------------------------------------------------------- /docs/images/todo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/todo.gif -------------------------------------------------------------------------------- /docs/images/translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/translate.png -------------------------------------------------------------------------------- /docs/images/vertical-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/images/vertical-layout.png -------------------------------------------------------------------------------- /docs/membrane-topics/blog.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | @media (min-width: 48em) { 6 | html { 7 | font-size: 18px; 8 | } 9 | } 10 | 11 | /* body { */ 12 | /* font-family: Georgia, "Times New Roman", Times, serif; */ 13 | /* color: #555; */ 14 | /* } */ 15 | 16 | /* h1, .h1, */ 17 | /* h2, .h2, */ 18 | /* h3, .h3, */ 19 | /* h4, .h4, */ 20 | /* h5, .h5, */ 21 | /* h6, .h6 { */ 22 | /* font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; */ 23 | /* font-weight: normal; */ 24 | /* color: #333; */ 25 | /* } */ 26 | 27 | 28 | /* 29 | * Override Bootstrap's default container. 30 | */ 31 | 32 | .container { 33 | max-width: 60rem; 34 | } 35 | 36 | 37 | /* 38 | * Masthead for nav 39 | */ 40 | 41 | .blog-masthead { 42 | margin-bottom: 3rem; 43 | background-color: #428bca; 44 | -webkit-box-shadow: inset 0 -.1rem .25rem rgba(0,0,0,.1); 45 | box-shadow: inset 0 -.1rem .25rem rgba(0,0,0,.1); 46 | } 47 | 48 | /* Nav links */ 49 | .nav-link { 50 | position: relative; 51 | padding: 1rem; 52 | font-weight: 500; 53 | color: #cdddeb; 54 | } 55 | .nav-link:hover, 56 | .nav-link:focus { 57 | color: #fff; 58 | background-color: transparent; 59 | } 60 | 61 | /* Active state gets a caret at the bottom */ 62 | .nav-link.active { 63 | color: #fff; 64 | } 65 | .nav-link.active:after { 66 | position: absolute; 67 | bottom: 0; 68 | left: 50%; 69 | width: 0; 70 | height: 0; 71 | margin-left: -.3rem; 72 | vertical-align: middle; 73 | content: ""; 74 | border-right: .3rem solid transparent; 75 | border-bottom: .3rem solid; 76 | border-left: .3rem solid transparent; 77 | } 78 | 79 | 80 | /* 81 | * Blog name and description 82 | */ 83 | 84 | .blog-header { 85 | padding-bottom: 1.25rem; 86 | margin-bottom: 2rem; 87 | border-bottom: .05rem solid #eee; 88 | } 89 | .blog-title { 90 | margin-bottom: 0; 91 | font-size: 2rem; 92 | font-weight: normal; 93 | } 94 | .blog-description { 95 | font-size: 1.1rem; 96 | color: #999; 97 | } 98 | 99 | @media (min-width: 40em) { 100 | .blog-title { 101 | font-size: 3.5rem; 102 | } 103 | } 104 | 105 | 106 | /* 107 | * Main column and sidebar layout 108 | */ 109 | 110 | /* Sidebar modules for boxing content */ 111 | .sidebar-module { 112 | padding: 1rem; 113 | /*margin: 0 -1rem 1rem;*/ 114 | } 115 | .sidebar-module-inset { 116 | padding: 1rem; 117 | background-color: #f5f5f5; 118 | border-radius: .25rem; 119 | } 120 | .sidebar-module-inset p:last-child, 121 | .sidebar-module-inset ul:last-child, 122 | .sidebar-module-inset ol:last-child { 123 | margin-bottom: 0; 124 | } 125 | 126 | 127 | /* Pagination */ 128 | .blog-pagination { 129 | margin-bottom: 4rem; 130 | } 131 | .blog-pagination > .btn { 132 | border-radius: 2rem; 133 | } 134 | 135 | 136 | /* 137 | * Blog posts 138 | */ 139 | 140 | .blog-post { 141 | margin-bottom: 4rem; 142 | } 143 | .blog-post-title { 144 | margin-bottom: .25rem; 145 | font-size: 2.5rem; 146 | } 147 | .blog-post-meta { 148 | margin-bottom: 1.25rem; 149 | color: #999; 150 | } 151 | 152 | 153 | /* 154 | * Footer 155 | */ 156 | 157 | .blog-footer { 158 | padding: 2.5rem 0; 159 | color: #999; 160 | text-align: center; 161 | background-color: #f9f9f9; 162 | border-top: .05rem solid #e5e5e5; 163 | } 164 | .blog-footer p:last-child { 165 | margin-bottom: 0; 166 | } 167 | 168 | 169 | blockquote { 170 | /* background: #f9f9f9; */ 171 | border-left: 10px solid #ccc; 172 | margin: 1.5em 10px; 173 | padding: 0.5em 10px; 174 | /* quotes: "\201C""\201D""\2018""\2019"; */ 175 | } 176 | /* blockquote:before { */ 177 | /* color: #ccc; */ 178 | /* content: open-quote; */ 179 | /* font-size: 4em; */ 180 | /* line-height: 0.1em; */ 181 | /* margin-right: 0.25em; */ 182 | /* vertical-align: -0.4em; */ 183 | /* } */ 184 | blockquote p { 185 | display: inline; 186 | } 187 | 188 | /* a { */ 189 | /* word-break: break-word; */ 190 | /* } */ 191 | 192 | .footnotes a{ 193 | word-break: break-all; 194 | } 195 | -------------------------------------------------------------------------------- /docs/membrane-topics/coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/membrane-topics/coordinates.png -------------------------------------------------------------------------------- /docs/membrane-topics/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/membrane-topics/favicon.ico -------------------------------------------------------------------------------- /docs/membrane-topics/overview-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/membrane-topics/overview-01.png -------------------------------------------------------------------------------- /docs/membrane-topics/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/membrane-topics/overview.png -------------------------------------------------------------------------------- /docs/overview-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/docs/overview-01.png -------------------------------------------------------------------------------- /docs/terminal-uis.md: -------------------------------------------------------------------------------- 1 | ## Terminal UIs 2 | 3 | Terminal UIs can be created using the `membrane.lanterna` backend. For a full example, check out the example projects: 4 | - https://github.com/phronmophobic/terminal-todo-mvc 5 | - Using re-frame https://github.com/phronmophobic/membrane-re-frame-example 6 | - Using Fulcro https://github.com/phronmophobic/membrane-fulcro 7 | 8 | ### Differences 9 | 10 | Compared to most of the other backends, there are a few major differences: 11 | - Terminal UIs have an integer coordinate system. Most other backends have a floating point coordinate system 12 | - Terminals don't really have a way to draw multiple elements on top of each other. There is a foreground and a background, but the background is just a solid color 13 | - Terminals often have a limited color palette 14 | - Text is always monospaced and 1 unit high 15 | 16 | Many of the utilities found in `membrane.ui` can be used as-is for building terminal UIs. For example: `bounds`, `origin`, `vertical-layout`, `on`, `translate`, `with-color`, and others all work as you would expect. However, some of the `membrane.ui` primitives don't really make sense for terminals UIs. For example, `label`, `checkbox`, `rectangle`, and some others will not work for terminal user interfaces because of the differences from most other backends. For primitives in `membrane.ui` that don't make sense for terminal UIs, you can find counterparts in the `membrane.lanterna` namespace. 17 | 18 | Supporting user interfaces that simultaneously work in terminals and with other backends is a non-goal. While it might be tempting to try to make the exact same primitives work for these two different mediums, the differences would make the development experience unfun in the general case. However, the primitives in `membrane.ui` and `membrane.lanterna` are just regular clojure data structures. Building a reduced set of elements for a constrained use case that work in terminal and for desktop is possible, but is left as an exercise for the reader. 19 | 20 | ### Repl Driven Development 21 | 22 | Repl driven development requires a config step since the default repl will consume `System/in` and `System/out`. 23 | 24 | Below is the setup for cider/nrepl. If you need support for a different REPL, please file an issue or drop a request in [#membrane](https://clojurians.slack.com/archives/CVB8K7V50) on the clojurians slack. 25 | 26 | #### Cider/nREPL 27 | 28 | ##### 1. Start your repl 29 | 30 | To setup a repl driven workflow, start up the nrepl server in the terminal that will be displaying your UI. Probably something like: 31 | ```sh 32 | clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "0.8.3"} cider/cider-nrepl {:mvn/version "0.25.6"}}}' -M:dev:nrepl --middleware '["cider.nrepl/cider-middleware" "membrane.lanterna/preserve-system-io"]' --port 7888 33 | ``` 34 | This command will start an nrepl server on port 7888. The default nrepl middleware will replace `System/in` and `System/out`. Add the `membrane.lanterna/preserve-system-io` to store `System/in` and `System/out` in the respective vars: `membrane.lanterna/in` and `membrane.lanterna/out`. 35 | 36 | ##### 2. Connect to your repl 37 | To connect to nrepl server in emacs, use `M-x cider-connect` and pass `localhost` as the host and `7888` for the port. 38 | 39 | ##### 3. Run your UI 40 | 41 | To run a UI that uses the correct I/O streams, pass the `:in` and `:out` as options like so: 42 | ```clojure 43 | (lanterna/run #'app-root 44 | {:in membrane.lanterna/in 45 | :out membrane.lanterna/out}) 46 | ``` 47 | 48 | ##### Stopping/Restarting your UI 49 | 50 | In some cases, you may want to restart the UI or display a different UI. You can stop a UI by passing a channel as the `:close-ch` option that will stop rendering when it receives a value. 51 | 52 | ```clojure 53 | (require '[clojure.core.async :as async]) 54 | 55 | ;; Rich comment block 56 | (comment 57 | ;; start the ui 58 | (do 59 | (def close-ch (async/chan)) 60 | (lanterna/run #'app-root 61 | {:in membrane.lanterna/in 62 | :out membrane.lanterna/out 63 | :close-ch close-ch})) 64 | 65 | ;; Some time later, stop the UI. You can call `lanterna/run` again to start a new UI. 66 | (async/close! close-ch) 67 | 68 | ,) 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/webgl.md: -------------------------------------------------------------------------------- 1 | # Membrane with WebGL target 2 | 3 | The basic idea is that everything in `membrane.component` and `membrane.ui` is platform agnostic. As long as you build your GUI using the tools in those namespaces, your app should work on other platforms. 4 | 5 | Below are a few tips for setting up your project when targeting opengl. 6 | 7 | ## Hello World 8 | 9 | ```clojure 10 | (ns helloworld 11 | ;; typical requires 12 | (:require-macros [membrane.webgl-macros 13 | :refer [add-image!]]) 14 | (:require [membrane.component :refer [defui]] 15 | [membrane.webgl :as webgl] 16 | membrane.basic-components 17 | [membrane.ui :as ui 18 | :refer [horizontal-layout 19 | vertical-layout]])) 20 | 21 | ;; Must be an element 22 | (def canvas (.getElementById js/document "canvas")) 23 | (defonce start-app (membrane.webgl/run #(ui/label "Hello World") {:container canvas})) 24 | ``` 25 | 26 | To receive key events, your canvas needs to have a "tabindex" attribute set to zero or greater (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex). If you don't know what value to set it to, then set it to zero. 27 | 28 | You can also create your canvas in clojurescript and add it to the page. Creating a canvas element using `membrane.webgl/create-canvas` will set the tabindex to "0" for you. 29 | 30 | ```clojure 31 | (let [canvas (webgl/create-canvas 300 400)] 32 | (.appendChild (.-body js/document) canvas) 33 | (defonce start-app (membrane.webgl/run #(ui/label "Hello World") {:container canvas}))) 34 | ``` 35 | 36 | ## Images 37 | 38 | Images must be loaded using `(add-image! url-path image-path-or-bounds)` where `url-path` is the url the image should be loaded from and `image-path-or-bounds` is either a 2 element vector of `[width height]` or the path to an image on the local filesystem that is read to figure out the image's size. 39 | 40 | The images can then be used like 41 | 42 | ```clojure 43 | (ui/image url-path) 44 | ``` 45 | 46 | ## Fonts 47 | 48 | Font layout information is needed to correctly layout fonts. By default, the Ubuntu font is loaded and used. Better font support coming soon! 49 | 50 | ## Components 51 | 52 | Components can still be run using `membrane.component/run-ui`. 53 | 54 | For example: 55 | ```clojure 56 | (defonce start-todo-app (membrane.component/run-ui #'todo/todo-app todo/todo-state nil {:container canvas})) 57 | ``` 58 | -------------------------------------------------------------------------------- /examples/gol/.gitignore: -------------------------------------------------------------------------------- 1 | .calva/output-window/ 2 | .classpath 3 | .clj-kondo/.cache 4 | .cpcache 5 | .eastwood 6 | .factorypath 7 | .hg/ 8 | .hgignore 9 | .java-version 10 | .lein-* 11 | .lsp/.cache 12 | .lsp/sqlite.db 13 | .nrepl-history 14 | .nrepl-port 15 | .project 16 | .rebel_readline_history 17 | .settings 18 | .socket-repl-port 19 | .sw* 20 | .vscode 21 | *.class 22 | *.jar 23 | *.swp 24 | *~ 25 | /checkouts 26 | /classes 27 | /target 28 | -------------------------------------------------------------------------------- /examples/gol/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2022-10-13 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2022-10-13 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/membrane/gol/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/membrane/gol/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /examples/gol/README.md: -------------------------------------------------------------------------------- 1 | # membrane/gol 2 | 3 | An implementation of Conway's Game of Life. 4 | 5 | ## Usage 6 | 7 | Run from the command line 8 | 9 | ``` 10 | clojure -M -m membrane.gol 11 | ``` 12 | 13 | Run from the Repl: 14 | 15 | Load the `membrane.gol` namespace and call `(show!)`. 16 | 17 | ## License 18 | 19 | Copyright © 2022 Adrian 20 | 21 | Distributed under the Eclipse Public License version 1.0. 22 | -------------------------------------------------------------------------------- /examples/gol/build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:refer-clojure :exclude [test]) 3 | (:require [clojure.tools.build.api :as b] 4 | [clojure.string :as str])) 5 | 6 | (def lib 'com.phronemophobic.membrane/gol) 7 | (def version "1.0") 8 | (def class-dir "target/classes") 9 | (def basis (b/create-basis {:project "deps.edn"})) 10 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 11 | 12 | (defn clean [_] 13 | (b/delete {:path "target"})) 14 | 15 | (defn jar [opts] 16 | (b/write-pom {:class-dir class-dir 17 | :lib lib 18 | :version version 19 | :basis basis 20 | :src-dirs ["src"]}) 21 | (b/copy-dir {:src-dirs ["src" "resources"] 22 | :target-dir class-dir}) 23 | (b/jar {:class-dir class-dir 24 | :jar-file jar-file})) 25 | 26 | 27 | (defn deploy [opts] 28 | (jar opts) 29 | (try ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 30 | (merge {:installer :remote 31 | :artifact jar-file 32 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 33 | opts)) 34 | (catch Exception e 35 | (if-not (str/includes? (ex-message e) "redeploying non-snapshots is not allowed") 36 | (throw e) 37 | (println "This release was already deployed.")))) 38 | opts) 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/gol/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.1"} 3 | com.phronemophobic/membrane {:mvn/version "0.10.3-beta"}} 4 | :aliases 5 | {:test 6 | {:extra-paths ["test"] 7 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 8 | io.github.cognitect-labs/test-runner 9 | {:git/tag "v0.5.1" :git/sha "dfb30dd"}}} 10 | :build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.3" :git/sha "0d20256"} 11 | slipset/deps-deploy {:mvn/version "RELEASE"}} 12 | :ns-default build}}} 13 | -------------------------------------------------------------------------------- /examples/gol/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to membrane/gol 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /examples/gol/resources/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/examples/gol/resources/.keep -------------------------------------------------------------------------------- /examples/gol/src/membrane/gol.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.gol 2 | (:require [membrane.ui :as ui] 3 | [membrane.java2d :as backend]) 4 | (:import java.awt.event.WindowEvent)) 5 | 6 | 7 | 8 | (def state-atm 9 | (atom 10 | {:board #{[1 0] [1 1] [1 2]}})) 11 | 12 | (defn neighbours [[x y]] 13 | (for [dx [-1 0 1] dy (if (zero? dx) [-1 1] [-1 0 1])] 14 | [(+ dx x) (+ dy y)])) 15 | 16 | (defn step [cells] 17 | (set (for [[loc n] (frequencies (mapcat neighbours cells)) 18 | :when (or (= n 3) (and (= n 2) (cells loc)))] 19 | loc))) 20 | 21 | (def grid-size 20) 22 | 23 | 24 | (defn button [text] 25 | (let [[text-width text-height] (ui/bounds (ui/label text)) 26 | padding 12 27 | rect-width (+ text-width padding) 28 | rect-height (+ text-height padding) 29 | border-radius 3] 30 | [ 31 | (ui/with-style ::ui/style-fill 32 | (ui/with-color [1 1 1] 33 | (ui/rounded-rectangle rect-width rect-height border-radius))) 34 | (ui/with-style ::ui/style-stroke 35 | [ 36 | (ui/with-color [0.76 0.76 0.76 1] 37 | (ui/rounded-rectangle (+ 0.5 rect-width) (+ 0.5 rect-height) border-radius)) 38 | (ui/with-color [0.85 0.85 0.85] 39 | (ui/rounded-rectangle rect-width rect-height border-radius))]) 40 | 41 | (ui/translate (/ padding 2) 42 | (- (/ padding 2) 2) 43 | (ui/label text))])) 44 | 45 | (defn view [{:keys [board running?] :as state}] 46 | (ui/translate 47 | 30 30 48 | (ui/on 49 | :mouse-move 50 | (fn [[x y]] 51 | (swap! state-atm update :board 52 | conj [(int (/ x grid-size)) 53 | (int (/ y grid-size))]) 54 | nil) 55 | (ui/wrap-on 56 | :mouse-down 57 | (fn [handler [x y]] 58 | (swap! state-atm update :board 59 | conj [(int (/ x grid-size)) 60 | (int (/ y grid-size))]) 61 | (handler [x y])) 62 | [(ui/spacer 600 600) 63 | 64 | (into [] 65 | (map (fn [[x y]] 66 | (ui/translate (* x grid-size) (* y grid-size) 67 | (ui/rectangle grid-size grid-size)))) 68 | board) 69 | (ui/translate 0 400 70 | (ui/on 71 | :mouse-down 72 | (fn [_] 73 | (swap! state-atm 74 | update :running? not) 75 | nil) 76 | (button (if running? 77 | "Stop" 78 | "Start"))))]))) 79 | ) 80 | 81 | (defn add-random [] 82 | (swap! state-atm 83 | update :board 84 | (fn [board] 85 | (into board 86 | (repeatedly 30 (fn [] 87 | [(rand-int 20) 88 | (rand-int 30)]))))) 89 | ) 90 | 91 | (add-watch state-atm ::run-gol (fn [k ref old updated] 92 | (when (and (:running? updated) 93 | (not (:running? old))) 94 | (future 95 | (while (:running? @state-atm) 96 | (swap! state-atm 97 | update :board step) 98 | (Thread/sleep 30)))))) 99 | 100 | (add-random) 101 | 102 | 103 | (defn run-gol [nsteps] 104 | (dotimes [i nsteps] 105 | (swap! state-atm 106 | update :board step) 107 | (Thread/sleep 30))) 108 | 109 | (defn show! [] 110 | (let [window-info (backend/run #(view @state-atm))] 111 | (when-let [repaint (::backend/repaint window-info)] 112 | (add-watch state-atm 113 | ::repaint 114 | (fn [r k old new] 115 | (when (not= old new) 116 | (repaint))))))) 117 | 118 | (defn -main [& args] 119 | (let [window-info (backend/run #(view @state-atm))] 120 | (when-let [repaint (::backend/repaint window-info)] 121 | (add-watch state-atm 122 | ::repaint 123 | (fn [r k old new] 124 | (when (not= old new) 125 | (repaint))))) 126 | 127 | (let [p (promise) 128 | ^javax.swing.JFrame frame (::backend/frame window-info)] 129 | (.addWindowListener frame 130 | (reify java.awt.event.WindowListener 131 | (^void windowActivated [this ^WindowEvent e]) 132 | (^void windowClosed [this ^WindowEvent e]) 133 | (^void windowClosing [this ^WindowEvent e] 134 | (deliver p window-info)) 135 | (^void windowDeactivated [this ^WindowEvent e]) 136 | (^void windowDeiconified [this ^WindowEvent e]) 137 | (^void windowIconified [this ^WindowEvent e]) 138 | (^void windowOpened [this ^WindowEvent e]))) 139 | @p 140 | (.dispose frame) 141 | (swap! state-atm assoc :running? false) 142 | (shutdown-agents)))) 143 | -------------------------------------------------------------------------------- /examples/gol/test/membrane/gol_test.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.gol-test 2 | (:require [clojure.test :refer :all] 3 | [membrane.gol :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /examples/readme/README.md: -------------------------------------------------------------------------------- 1 | Project to test Readme examples. 2 | -------------------------------------------------------------------------------- /examples/readme/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"} 3 | com.phronemophobic/membrane {:local/root "../../"} 4 | 5 | org.clojure/test.check {:mvn/version "0.9.0"}} 6 | :aliases 7 | {:test {:extra-paths ["test"] 8 | :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"} 9 | } 10 | 11 | 12 | 13 | } 14 | :runner 15 | {:extra-deps {com.cognitect/test-runner 16 | {:git/url "https://github.com/cognitect-labs/test-runner" 17 | :sha "b6b3193fcc42659d7e46ecd1884a228993441182"}} 18 | :main-opts ["-m" "cognitect.test-runner" 19 | "-d" "test"]} 20 | :uberjar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.128"}} 21 | :main-opts ["-m" "hf.depstar.uberjar" "membrane-skija-example.jar" 22 | "-C" "-m" "com.phronemophobic.membrane-skija-example"]}}} 23 | -------------------------------------------------------------------------------- /examples/readme/src/counter.clj: -------------------------------------------------------------------------------- 1 | (ns counter 2 | (:require [membrane.java2d :as java2d] 3 | [membrane.ui :as ui 4 | :refer [horizontal-layout 5 | button 6 | label 7 | spacer 8 | on]])) 9 | 10 | 11 | (defonce counter-state (atom 0)) 12 | 13 | ;; Display a "more!" button next to label showing num 14 | ;; clicking on "more!" will increment the counter 15 | (defn counter [num] 16 | (horizontal-layout 17 | (on :mouse-down (fn [[mouse-x mouse-y]] 18 | (swap! counter-state inc) 19 | nil) 20 | (button "more!")) 21 | (spacer 5 0) 22 | (label num (ui/font nil 19)))) 23 | 24 | (comment 25 | ;; pop up a window that shows our counter 26 | (java2d/run #(counter @counter-state)) 27 | ,) 28 | 29 | 30 | 31 | (ns counter 32 | (:require [membrane.java2d :as java2d] 33 | [membrane.ui :as ui 34 | :refer [horizontal-layout 35 | vertical-layout 36 | button 37 | label 38 | on]] 39 | [membrane.component :as component 40 | :refer [defui make-app defeffect]]) 41 | (:gen-class)) 42 | 43 | 44 | ;; Display a "more!" button next to label showing num 45 | ;; clicking on "more!" will dispatch a ::counter-increment effect 46 | (defui counter [{:keys [num]}] 47 | (horizontal-layout 48 | (on :mouse-down (fn [[mouse-x mouse-y]] 49 | [[::counter-increment $num]]) 50 | (ui/button "more!")) 51 | (ui/label num))) 52 | 53 | (defeffect ::counter-increment [$num] 54 | (dispatch! :update $num inc)) 55 | 56 | (comment 57 | ;; pop up a window showing our counter with 58 | ;; num initially set to 10 59 | (java2d/run (make-app #'counter {:num 10})) 60 | ,) 61 | 62 | 63 | (defui counter-counter [{:keys [nums]}] 64 | (apply 65 | vertical-layout 66 | (on :mouse-down (fn [[mx my]] 67 | [[::add-counter $nums]]) 68 | (ui/button "Add Counter")) 69 | (for [num nums] 70 | (counter {:num num})))) 71 | 72 | (defeffect ::add-counter [$nums] 73 | (dispatch! :update $nums conj 0)) 74 | 75 | (comment 76 | ;; pop up a window showing our counter-counter 77 | ;; with nums initially set to [0 1 2] 78 | (java2d/run (make-app #'counter-counter {:nums [0 1 2]})) 79 | ,) 80 | -------------------------------------------------------------------------------- /examples/readme/src/fun_features.clj: -------------------------------------------------------------------------------- 1 | (ns fun-features 2 | (:require [membrane.java2d :as java2d] 3 | [membrane.example.todo :refer [todo-app]] 4 | [clojure.spec.alpha :as s] 5 | [clojure.spec.gen.alpha :as gen] 6 | [membrane.ui :as ui 7 | :refer [horizontal-layout 8 | vertical-layout 9 | button 10 | label 11 | spacer 12 | on]])) 13 | 14 | 15 | ;; graphical elements are values 16 | ;; no need to attach elements to the dom to get layout info 17 | (ui/bounds (vertical-layout 18 | (ui/label "hello") 19 | (ui/checkbox true))) 20 | ;; [33.181640625 28.48828125] 21 | 22 | 23 | ;; events are pure functions that return effects which are also values 24 | (let [mpos [15 15]] 25 | (ui/mouse-down 26 | (ui/translate 10 10 27 | (on :mouse-down (fn [[mx my]] 28 | ;;return a sequence of effects 29 | [[:say-hello]]) 30 | (ui/label "Hello"))) 31 | mpos)) 32 | ;; ([:say-hello]) 33 | 34 | 35 | ;; horizontal and vertical centering! 36 | (java2d/run #(let [rect (ui/with-style :membrane.ui/style-stroke 37 | (ui/rectangle 200 200))] 38 | [rect 39 | (ui/center (ui/label "hello") (ui/bounds rect))]) ) 40 | 41 | 42 | ;; save graphical elem as an image 43 | (let [todos [{:complete? false 44 | :description "first"} 45 | {:complete? false 46 | :description "second"} 47 | {:complete? true 48 | :description "third"}]] 49 | (java2d/save-image "todoapp.png" 50 | (todo-app {:todos todos :selected-filter :all}))) 51 | 52 | 53 | (s/def :todo/complete? boolean?) 54 | (s/def :todo/description (s/and string? 55 | #(< (count %) 20))) 56 | (s/def :todo/todo (s/keys :req-un [:todo/complete? 57 | :todo/description])) 58 | (s/def ::todos (s/and 59 | (s/coll-of :todo/todo 60 | :into []) 61 | #(< (count %) 10))) 62 | 63 | ;; use spec to generate images of variations of your app 64 | (doseq [[i todo-list] (map-indexed vector (gen/sample (s/gen ::todos)))] 65 | (java2d/save-image (str "todo" i ".png") 66 | (ui/vertical-layout 67 | (ui/label (with-out-str 68 | (clojure.pprint/pprint todo-list))) 69 | (ui/with-style :membrane.ui/style-stroke 70 | (ui/path [0 0] [400 0])) 71 | (todo-app {:todos todo-list :selected-filter :all})))) 72 | -------------------------------------------------------------------------------- /examples/readme/src/readme.clj: -------------------------------------------------------------------------------- 1 | (ns readme) 2 | -------------------------------------------------------------------------------- /examples/rss/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"} 3 | com.phronemophobic/membrane {:local/root "../../"}}} 4 | -------------------------------------------------------------------------------- /examples/rss/src/rss.clj: -------------------------------------------------------------------------------- 1 | (ns rss 2 | (:require 3 | [rss.feed :as rss] 4 | [membrane.java2d :as backend] 5 | [membrane.ui :as ui 6 | :refer [vertical-layout 7 | translate 8 | horizontal-layout 9 | button 10 | label 11 | with-color 12 | bounds 13 | spacer 14 | on]] 15 | [membrane.component :as component 16 | :refer [defui defeffect]] 17 | [membrane.basic-components :as basic])) 18 | 19 | ;; make it easier to inspect current state 20 | ;; for debugging 21 | (def state (atom nil)) 22 | (defn rui [v s] 23 | (reset! state s) 24 | (backend/run (component/make-app v state))) 25 | 26 | (defui feed-item [{:keys [title uri]}] 27 | (basic/button 28 | {:text title 29 | :on-click (fn [] [[::display-feed uri]])})) 30 | 31 | (comment 32 | (rui #'feed-item {:title "Foo"})) 33 | 34 | (def title-font 35 | (-> :sans-serif 36 | backend/logical-font->font-family 37 | (ui/font 14) 38 | (assoc :weight :bold))) 39 | 40 | (defui feed-view [{items :item name :title}] 41 | (vertical-layout 42 | (label name) 43 | (basic/scrollview 44 | {:scroll-bounds [800 800] 45 | :body 46 | (apply 47 | vertical-layout 48 | (for [{:keys [title description]} items] 49 | (vertical-layout 50 | (label title title-font) 51 | (label description))))}))) 52 | 53 | (defui op-view [{:keys [feed-uri feeds current-feed]}] 54 | (on 55 | ::display-feed (fn [f] [[:set $current-feed f]]) 56 | (horizontal-layout 57 | (vertical-layout 58 | (vertical-layout 59 | (ui/wrap-on 60 | :key-press 61 | (fn [default-handler s] 62 | (let [effects (default-handler s)] 63 | (if (and (seq effects) 64 | (= s :enter)) 65 | [[::add-feed $feeds feed-uri] 66 | [:set $feed-uri ""]] 67 | effects))) 68 | (basic/textarea {:text feed-uri})) 69 | (basic/button {:text "Add Feed" 70 | :on-click (fn [] 71 | [[::add-feed $feeds feed-uri] 72 | [:set $feed-uri ""]])})) 73 | (apply vertical-layout (for [feed feeds] (feed-item feed)))) 74 | (when-let [feed (->> feeds 75 | (filter (fn [{:keys [uri]}] (= current-feed uri))) 76 | first)] 77 | 78 | (feed-view feed))))) 79 | 80 | (comment 81 | (rui #'op-view {:feeds [] 82 | :current-feed "" 83 | :feed-uri "https://www.cognitect.com/feed.xml"}) 84 | (swap! state assoc :feed-uri "https://hnrss.org/frontpage" ) 85 | , 86 | ) 87 | 88 | (defeffect ::add-feed [$feeds feed-uri] 89 | (future 90 | (dispatch! 91 | :update 92 | $feeds 93 | #(conj % (assoc (rss/parse feed-uri) :uri feed-uri))))) 94 | -------------------------------------------------------------------------------- /examples/rss/src/rss/feed.clj: -------------------------------------------------------------------------------- 1 | (ns rss.feed 2 | (:require 3 | [clojure.xml :as xml])) 4 | 5 | (defmulti -parse :tag) 6 | (defmethod -parse :rss [{:keys [content]}] (into {} (map -parse) content)) 7 | (defmethod -parse :default [m] 8 | (let [k (:tag m)] 9 | (when-let [v (-> m 10 | :content 11 | first)] 12 | {k v}))) 13 | 14 | (defmethod -parse :channel [{:keys [content]}] 15 | (apply merge-with (fn [a b] (if (vector? a) (conj a b) [a b])) (map -parse content))) 16 | 17 | (defmethod -parse :title [{:keys [content]}] {:title (first content)}) 18 | (defmethod -parse :description [{:keys [content]}] {:description (first content)}) 19 | (defmethod -parse :link [{:keys [content]}] {:link (first content)}) 20 | (defmethod -parse :atom:link [_] {}) 21 | (defmethod -parse :item [{:keys [content]}] {:item (apply merge-with vector (map -parse content))}) 22 | (defmethod -parse :pubDate [{:keys [content]}] {:pubDate (first content)}) 23 | (defmethod -parse :guid [{:keys [content]}] {:guid (first content)}) 24 | 25 | (defn parse 26 | [feed-uri] 27 | (-parse (xml/parse feed-uri))) 28 | -------------------------------------------------------------------------------- /examples/tutorial/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"} 3 | com.phronemophobic/membrane {:local/root "../../"} 4 | 5 | org.clojure/test.check {:mvn/version "0.9.0"}} 6 | :aliases 7 | {:test {:extra-paths ["test"] 8 | :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"} 9 | } 10 | 11 | 12 | 13 | } 14 | :runner 15 | {:extra-deps {com.cognitect/test-runner 16 | {:git/url "https://github.com/cognitect-labs/test-runner" 17 | :sha "b6b3193fcc42659d7e46ecd1884a228993441182"}} 18 | :main-opts ["-m" "cognitect.test-runner" 19 | "-d" "test"]} 20 | :uberjar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.128"}} 21 | :main-opts ["-m" "hf.depstar.uberjar" "membrane-skija-example.jar" 22 | "-C" "-m" "com.phronemophobic.membrane-skija-example"]}}} 23 | -------------------------------------------------------------------------------- /native-image/.gitignore: -------------------------------------------------------------------------------- 1 | config -------------------------------------------------------------------------------- /native-image/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```bash 4 | # in root folder 5 | rm -rf target native-image/config 6 | 7 | # This will generate config for native image 8 | # A window will pop up. Exercise the window and the close 9 | # config saved to native-image/config 10 | ./native-image/config.sh 11 | 12 | # build the native image executable 13 | ./native/image/compile.sh 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /native-image/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | which java 7 | java -version 8 | 9 | clojure -T:build compile-native-image 10 | 11 | native-image \ 12 | -cp "$(clojure -A:native-image -Spath):target/classes" \ 13 | -H:Name=todo \ 14 | -Djava.awt.headless=false \ 15 | -H:ConfigurationFileDirectories=native-image/config \ 16 | -H:+ReportExceptionStackTraces \ 17 | -H:+AddAllCharsets \ 18 | -J-Dclojure.spec.skip-macros=true \ 19 | -J-Dclojure.compiler.direct-linking=true \ 20 | -J-Dtech.v3.datatype.graal-native=true \ 21 | --features=clj_easy.graal_build_time.InitClojureClasses \ 22 | --verbose \ 23 | --no-fallback \ 24 | com.phronemophobic.native_image.main 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /native-image/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | clojure -T:build compile-native-image 7 | 8 | # not currently needed 9 | java -XstartOnFirstThread -agentlib:native-image-agent=config-output-dir=native-image/config -cp "$(clojure -A:native-image -Spath):target/classes" com.phronemophobic.main 10 | 11 | clojure -T:build fix-config 12 | -------------------------------------------------------------------------------- /native-image/src/com/phronemophobic/native_image/main.clj: -------------------------------------------------------------------------------- 1 | (ns com.phronemophobic.native-image.main 2 | (:require [membrane.example.todo :as todo] 3 | [membrane.component :as component] 4 | [membrane.skia :as skia]) 5 | (:gen-class)) 6 | 7 | (def app (component/make-app #'todo/todo-app 8 | {:todos 9 | [{:complete? false 10 | :description "first"} 11 | {:complete? false 12 | :description "second"} 13 | {:complete? true 14 | :description "third"}] 15 | :next-todo-text ""})) 16 | 17 | 18 | (defn -main [& args] 19 | (skia/run-sync app)) 20 | -------------------------------------------------------------------------------- /pom-template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.phronemophobic 5 | membrane 6 | com.phronemophobic/membrane 7 | A Simple UI Library That Runs Anywhere. 8 | https://github.com/phronmophobic/membrane 9 | 10 | 11 | Apache License, Version 2.0 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | 15 | 16 | 17 | Adrian 18 | 19 | 20 | 21 | https://github.com/phronmophobic/membrane 22 | scm:git:git://github.com/phronmophobic/membrane.git 23 | scm:git:ssh://git@github.com/phronmophobic/membrane.git 24 | v0.1.0-SNAPSHOT 25 | 26 | 27 | 28 | org.clojure 29 | clojure 30 | 1.10.3 31 | 32 | 33 | 34 | src 35 | 36 | 37 | 38 | clojars 39 | https://repo.clojars.org/ 40 | 41 | 42 | sonatype 43 | https://oss.sonatype.org/content/repositories/snapshots/ 44 | 45 | 46 | 47 | 48 | clojars 49 | Clojars repository 50 | https://clojars.org/repo 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.phronemophobic/membrane "0.11.1-beta" 2 | :description "A platform agnostic library for creating user interfaces" 3 | :url "https://github.com/phronmophobic/membrane" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "http://www.apache.org/licenses/LICENSE-2.0"} 6 | 7 | :dependencies [[org.clojure/clojure "1.10.3"] 8 | 9 | [net.n01se/clojure-jna "1.0.0" 10 | :exclusions [net.java.dev.jna/jna]] 11 | [net.java.dev.jna/jna "5.10.0"] 12 | [org.clojure/core.async "1.4.627"] 13 | 14 | [com.rpl/specter "1.1.3"] 15 | [org.apache.commons/commons-text "1.9"] 16 | 17 | [org.clojure/core.cache "1.0.225"] 18 | [com.phronemophobic/cljs-cache "0.1.8"] 19 | ;; these two go together 20 | ;; built and installed locally! 21 | ;; [com.oracle/appbundler "1.0ea-local"] 22 | ;; [org.apache.ant/ant "1.10.5"] 23 | 24 | ] 25 | 26 | :aot [ 27 | ] 28 | 29 | :plugins [[lein-cljsbuild "1.1.7"] 30 | [lein-figwheel "0.5.18"] 31 | [lein-codox "0.10.7"] 32 | ] 33 | 34 | :source-paths ["src"] 35 | :java-source-paths ["src-java"] 36 | :javac-options ["-target" "1.8" "-source" "1.8"] 37 | ;; :java-cmd "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java" 38 | ;; :java-cmd "/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/bin/java" 39 | :jvm-opts [;;"-Dapple.awt.UIElement=false" 40 | ;; "-Djna.debug_load=true" 41 | ;; "-Djna.debug_load.jna=true" 42 | ;; "-Xmx14g" 43 | 44 | ;; for testing graalvm 45 | ;; "-Dclojure.compiler.direct-linking=true" 46 | ;; "-XstartOnFirstThread" 47 | ;; useful for clj-async-profiler 48 | ;; "-Djdk.attach.allowAttachSelf" 49 | ;; "-XX:+UnlockDiagnosticVMOptions" 50 | ;; "-XX:+DebugNonSafepoints" 51 | ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions 52 | "-XX:-OmitStackTraceInFastThrow" 53 | ] 54 | 55 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"] 56 | :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/" 57 | "space-maven" "https://packages.jetbrains.team/maven/p/skija/maven"} 58 | :profiles 59 | {:dev {:dependencies 60 | [ 61 | [cider/piggieback "0.4.0"] 62 | 63 | [org.clojure/test.check "0.9.0"] 64 | [criterium "0.4.5"] 65 | [com.clojure-goes-fast/clj-async-profiler "0.4.1"] 66 | [figwheel-sidecar "0.5.18" :exclusions [org.clojure/tools.nrepl]] 67 | [org.clojure/data.json "1.0.0"] 68 | ] 69 | :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]} 70 | ;;:source-paths ["cljs_src"] 71 | } 72 | :provided {:dependencies [[org.clojure/clojurescript "1.10.764"] 73 | [com.phronemophobic/vdom "0.2.2"] 74 | [com.googlecode.lanterna/lanterna "3.1.1"] 75 | [spec-provider "0.4.14"] 76 | [mogenslund/liquid "2.0.3"] 77 | [re-frame "1.2.0"] 78 | 79 | [cljfx "1.7.16"] 80 | 81 | [com.fulcrologic/fulcro "3.5.8"] 82 | 83 | ;; skia 84 | [com.phronemophobic.membrane/skialib-macosx-x86-64 "0.9.31.0-beta"] 85 | [com.phronemophobic.membrane/skialib-linux-x86-64 "0.9.31.0-beta"] 86 | [com.phronemophobic.membrane/skialib-macosx-aarch64 "0.9.31.0-beta"] 87 | 88 | ;; skija 89 | [org.jetbrains.skija/skija-macos-arm64 "0.93.4"] 90 | [org.jetbrains.skija/skija-macos-x64 "0.93.4"] 91 | [org.jetbrains.skija/skija-linux "0.93.1"] 92 | [org.lwjgl/lwjgl "3.3.0"] 93 | [org.lwjgl/lwjgl "3.3.0" :classifier "natives-macos"] 94 | [org.lwjgl/lwjgl "3.3.0" :classifier "natives-macos-arm64"] 95 | [org.lwjgl/lwjgl "3.3.0" :classifier "natives-linux"] 96 | [org.lwjgl/lwjgl-glfw "3.3.0"] 97 | [org.lwjgl/lwjgl-glfw "3.3.0" :classifier "natives-macos"] 98 | [org.lwjgl/lwjgl-glfw "3.3.0" :classifier "natives-macos-arm64"] 99 | [org.lwjgl/lwjgl-glfw "3.3.0" :classifier "natives-linux"] 100 | [org.lwjgl/lwjgl-opengl "3.3.0"] 101 | [org.lwjgl/lwjgl-opengl "3.3.0" :classifier "natives-macos"] 102 | [org.lwjgl/lwjgl-opengl "3.3.0" :classifier "natives-macos-arm64"] 103 | [org.lwjgl/lwjgl-opengl "3.3.0" :classifier "natives-linux"] 104 | ]}} 105 | 106 | :deploy-repositories [["releases" :clojars] 107 | ["snapshots" :clojars]] 108 | :cljsbuild {:builds 109 | [{:id "webgltest" 110 | :source-paths ["src"] 111 | :figwheel {:on-jsload "membrane.webgltest/on-js-reload"} 112 | :compiler {:main membrane.webgltest 113 | :asset-path "js/compiled/out.webgltest" 114 | :output-to "resources/public/js/compiled/webgltest.js" 115 | :output-dir "resources/public/js/compiled/out.webgltest" 116 | :source-map-timestamp true 117 | ;;:optimizations :whitespace 118 | }} 119 | {:id "vdomtest" 120 | :source-paths ["src"] 121 | :figwheel {:on-jsload "membrane.vdomtest/on-js-reload"} 122 | :compiler {:main membrane.vdom 123 | :asset-path "js/compiled/out.vdomtest" 124 | :output-to "resources/public/js/compiled/vdomtest.js" 125 | :output-dir "resources/public/js/compiled/out.vdomtest" 126 | :source-map-timestamp true 127 | :aot-cache true 128 | :optimizations :simple 129 | :infer-externs true 130 | }} 131 | {:id "buildertest" 132 | :source-paths ["src"] 133 | :figwheel {:on-jsload "membrane.buildertest/on-js-reload"} 134 | :compiler {:main membrane.builder 135 | :asset-path "js/compiled/out.buildertest" 136 | :output-to "resources/public/js/compiled/buildertest.js" 137 | :output-dir "resources/public/js/compiled/out.buildertest" 138 | :aot-cache true 139 | :source-map-timestamp true 140 | ;; :optimizations :simple 141 | }} 142 | {:id "autouitest" 143 | :source-paths ["src"] 144 | :figwheel {:on-jsload "membrane.autouitest/on-js-reload"} 145 | :compiler {:main membrane.autoui 146 | :asset-path "js/compiled/out.autouitest" 147 | :output-to "resources/public/js/compiled/autouitest.js" 148 | :output-dir "resources/public/js/compiled/out.autouitest" 149 | :aot-cache true 150 | :source-map-timestamp true 151 | ;; :optimizations :simple 152 | }}]} 153 | 154 | :figwheel {;; :http-server-root "public" ;; default and assumes "resources" 155 | ;; :server-port 3449 ;; default 156 | ;; :server-ip "127.0.0.1" 157 | 158 | :css-dirs ["resources/public/css"] ;; watch and update CSS 159 | 160 | ;; Start an nREPL server into the running figwheel process 161 | ;; :nrepl-port 7888 162 | 163 | ;; Server Ring Handler (optional) 164 | ;; if you want to embed a ring handler into the figwheel http-kit 165 | ;; server, this is for simple ring servers, if this 166 | ;; doesn't work for you just run your own server :) 167 | ;; :ring-handler hello_world.server/handler 168 | 169 | ;; To be able to open files in your editor from the heads up display 170 | ;; you will need to put a script on your path. 171 | ;; that script will have to take a file path and a line number 172 | ;; ie. in ~/bin/myfile-opener 173 | ;; #! /bin/sh 174 | ;; emacsclient -n +$2 $1 175 | ;; 176 | ;; :open-file-command "myfile-opener" 177 | 178 | ;; if you want to disable the REPL 179 | ;; :repl false 180 | 181 | ;; to configure a different figwheel logfile path 182 | ;; :server-logfile "tmp/logs/figwheel-logfile.log" 183 | }) 184 | -------------------------------------------------------------------------------- /resources/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phronmophobic/membrane/8dff0a1c6cbc79f14668bebfc8577a3e29138225/resources/lines.png -------------------------------------------------------------------------------- /resources/public/autouitest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/public/buildertest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/public/css/addon/hint/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 11 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 12 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 13 | border-radius: 3px; 14 | border: 1px solid silver; 15 | 16 | background: white; 17 | font-size: 90%; 18 | font-family: monospace; 19 | 20 | max-height: 20em; 21 | overflow-y: auto; 22 | } 23 | 24 | .CodeMirror-hint { 25 | margin: 0; 26 | padding: 0 4px; 27 | border-radius: 2px; 28 | max-width: 19em; 29 | overflow: hidden; 30 | white-space: pre; 31 | color: black; 32 | cursor: pointer; 33 | } 34 | 35 | li.CodeMirror-hint-active { 36 | background: #08f; 37 | color: white; 38 | } 39 | -------------------------------------------------------------------------------- /resources/public/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-ruler { 92 | border-left: 1px solid #ccc; 93 | position: absolute; 94 | } 95 | 96 | /* DEFAULT THEME */ 97 | 98 | .cm-s-default .cm-header {color: blue;} 99 | .cm-s-default .cm-quote {color: #090;} 100 | .cm-negative {color: #d44;} 101 | .cm-positive {color: #292;} 102 | .cm-header, .cm-strong {font-weight: bold;} 103 | .cm-em {font-style: italic;} 104 | .cm-link {text-decoration: underline;} 105 | .cm-strikethrough {text-decoration: line-through;} 106 | 107 | .cm-s-default .cm-keyword {color: #708;} 108 | .cm-s-default .cm-atom {color: #219;} 109 | .cm-s-default .cm-number {color: #164;} 110 | .cm-s-default .cm-def {color: #00f;} 111 | .cm-s-default .cm-variable, 112 | .cm-s-default .cm-punctuation, 113 | .cm-s-default .cm-property, 114 | .cm-s-default .cm-operator {} 115 | .cm-s-default .cm-variable-2 {color: #05a;} 116 | .cm-s-default .cm-variable-3 {color: #085;} 117 | .cm-s-default .cm-comment {color: #a50;} 118 | .cm-s-default .cm-string {color: #a11;} 119 | .cm-s-default .cm-string-2 {color: #f50;} 120 | .cm-s-default .cm-meta {color: #555;} 121 | .cm-s-default .cm-qualifier {color: #555;} 122 | .cm-s-default .cm-builtin {color: #30a;} 123 | .cm-s-default .cm-bracket {color: #997;} 124 | .cm-s-default .cm-tag {color: #170;} 125 | .cm-s-default .cm-attribute {color: #00c;} 126 | .cm-s-default .cm-hr {color: #999;} 127 | .cm-s-default .cm-link {color: #00c;} 128 | 129 | .cm-s-default .cm-error {color: #f00;} 130 | .cm-invalidchar {color: #f00;} 131 | 132 | .CodeMirror-composing { border-bottom: 2px solid; } 133 | 134 | /* Default styles for common addons */ 135 | 136 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 137 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 138 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 139 | .CodeMirror-activeline-background {background: #e8f2ff;} 140 | 141 | /* STOP */ 142 | 143 | /* The rest of this file contains styles related to the mechanics of 144 | the editor. You probably shouldn't touch them. */ 145 | 146 | .CodeMirror { 147 | position: relative; 148 | overflow: hidden; 149 | background: white; 150 | } 151 | 152 | .CodeMirror-scroll { 153 | overflow: scroll !important; /* Things will break if this is overridden */ 154 | /* 30px is the magic margin used to hide the element's real scrollbars */ 155 | /* See overflow: hidden in .CodeMirror */ 156 | margin-bottom: -30px; margin-right: -30px; 157 | padding-bottom: 30px; 158 | height: 100%; 159 | outline: none; /* Prevent dragging from highlighting the element */ 160 | position: relative; 161 | } 162 | .CodeMirror-sizer { 163 | position: relative; 164 | border-right: 30px solid transparent; 165 | } 166 | 167 | /* The fake, visible scrollbars. Used to force redraw during scrolling 168 | before actuall scrolling happens, thus preventing shaking and 169 | flickering artifacts. */ 170 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 171 | position: absolute; 172 | z-index: 6; 173 | display: none; 174 | } 175 | .CodeMirror-vscrollbar { 176 | right: 0; top: 0; 177 | overflow-x: hidden; 178 | overflow-y: scroll; 179 | } 180 | .CodeMirror-hscrollbar { 181 | bottom: 0; left: 0; 182 | overflow-y: hidden; 183 | overflow-x: scroll; 184 | } 185 | .CodeMirror-scrollbar-filler { 186 | right: 0; bottom: 0; 187 | } 188 | .CodeMirror-gutter-filler { 189 | left: 0; bottom: 0; 190 | } 191 | 192 | .CodeMirror-gutters { 193 | position: absolute; left: 0; top: 0; 194 | z-index: 3; 195 | } 196 | .CodeMirror-gutter { 197 | white-space: normal; 198 | height: 100%; 199 | display: inline-block; 200 | margin-bottom: -30px; 201 | /* Hack to make IE7 behave */ 202 | *zoom:1; 203 | *display:inline; 204 | } 205 | .CodeMirror-gutter-wrapper { 206 | position: absolute; 207 | z-index: 4; 208 | background: none !important; 209 | border: none !important; 210 | } 211 | .CodeMirror-gutter-background { 212 | position: absolute; 213 | top: 0; bottom: 0; 214 | z-index: 4; 215 | } 216 | .CodeMirror-gutter-elt { 217 | position: absolute; 218 | cursor: default; 219 | z-index: 4; 220 | } 221 | .CodeMirror-gutter-wrapper { 222 | -webkit-user-select: none; 223 | -moz-user-select: none; 224 | user-select: none; 225 | } 226 | 227 | .CodeMirror-lines { 228 | cursor: text; 229 | min-height: 1px; /* prevents collapsing before first draw */ 230 | } 231 | .CodeMirror pre { 232 | /* Reset some styles that the rest of the page might have set */ 233 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 234 | border-width: 0; 235 | background: transparent; 236 | font-family: inherit; 237 | font-size: inherit; 238 | margin: 0; 239 | white-space: pre; 240 | word-wrap: normal; 241 | line-height: inherit; 242 | color: inherit; 243 | z-index: 2; 244 | position: relative; 245 | overflow: visible; 246 | -webkit-tap-highlight-color: transparent; 247 | } 248 | .CodeMirror-wrap pre { 249 | word-wrap: break-word; 250 | white-space: pre-wrap; 251 | word-break: normal; 252 | } 253 | 254 | .CodeMirror-linebackground { 255 | position: absolute; 256 | left: 0; right: 0; top: 0; bottom: 0; 257 | z-index: 0; 258 | } 259 | 260 | .CodeMirror-linewidget { 261 | position: relative; 262 | z-index: 2; 263 | overflow: auto; 264 | } 265 | 266 | .CodeMirror-widget {} 267 | 268 | .CodeMirror-code { 269 | outline: none; 270 | } 271 | 272 | /* Force content-box sizing for the elements where we expect it */ 273 | .CodeMirror-scroll, 274 | .CodeMirror-sizer, 275 | .CodeMirror-gutter, 276 | .CodeMirror-gutters, 277 | .CodeMirror-linenumber { 278 | -moz-box-sizing: content-box; 279 | box-sizing: content-box; 280 | } 281 | 282 | .CodeMirror-measure { 283 | position: absolute; 284 | width: 100%; 285 | height: 0; 286 | overflow: hidden; 287 | visibility: hidden; 288 | } 289 | 290 | .CodeMirror-cursor { position: absolute; } 291 | .CodeMirror-measure pre { position: static; } 292 | 293 | div.CodeMirror-cursors { 294 | visibility: hidden; 295 | position: relative; 296 | z-index: 3; 297 | } 298 | div.CodeMirror-dragcursors { 299 | visibility: visible; 300 | } 301 | 302 | .CodeMirror-focused div.CodeMirror-cursors { 303 | visibility: visible; 304 | } 305 | 306 | .CodeMirror-selected { background: #d9d9d9; } 307 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 308 | .CodeMirror-crosshair { cursor: crosshair; } 309 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 310 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 311 | 312 | .cm-searching { 313 | background: #ffa; 314 | background: rgba(255, 255, 0, .4); 315 | } 316 | 317 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 318 | .CodeMirror span { *vertical-align: text-bottom; } 319 | 320 | /* Used to force a border model for a node */ 321 | .cm-force-border { padding-right: .1px; } 322 | 323 | @media print { 324 | /* Hide the cursor when printing */ 325 | .CodeMirror div.CodeMirror-cursors { 326 | visibility: hidden; 327 | } 328 | } 329 | 330 | /* See issue #2901 */ 331 | .cm-tab-wrap-hack:after { content: ''; } 332 | 333 | /* Help users use markselection to safely style text background */ 334 | span.CodeMirror-selectedtext { background: none; } 335 | -------------------------------------------------------------------------------- /resources/public/css/datepicker.css: -------------------------------------------------------------------------------- 1 | /* 2 | Use of this source code is governed by an Apache 2.0 License. 3 | See the COPYING file for details. 4 | */ 5 | 6 | /* Copyright 2008 Google Inc. All Rights Reserved. */ 7 | /**/ 8 | 9 | /* goog.ui.DatePicker */ 10 | 11 | .goog-date-picker, 12 | .goog-date-picker th, 13 | .goog-date-picker td { 14 | font: 13px Arial, sans-serif; 15 | } 16 | 17 | .goog-date-picker { 18 | -moz-user-focus: normal; 19 | -moz-user-select: none; 20 | position: relative; 21 | border: 1px solid #000; 22 | float: left; 23 | padding: 2px; 24 | color: #000; 25 | background: #c3d9ff; 26 | cursor: default; 27 | } 28 | 29 | .goog-date-picker th { 30 | text-align: center; 31 | } 32 | 33 | .goog-date-picker td { 34 | text-align: center; 35 | vertical-align: middle; 36 | padding: 1px 3px; 37 | } 38 | 39 | 40 | .goog-date-picker-menu { 41 | position: absolute; 42 | background: threedface; 43 | border: 1px solid gray; 44 | -moz-user-focus: normal; 45 | z-index: 1; 46 | outline: none; 47 | } 48 | 49 | .goog-date-picker-menu ul { 50 | list-style: none; 51 | margin: 0px; 52 | padding: 0px; 53 | } 54 | 55 | .goog-date-picker-menu ul li { 56 | cursor: default; 57 | } 58 | 59 | .goog-date-picker-menu-selected { 60 | background: #ccf; 61 | } 62 | 63 | .goog-date-picker th { 64 | font-size: .9em; 65 | } 66 | 67 | .goog-date-picker td div { 68 | float: left; 69 | } 70 | 71 | .goog-date-picker button { 72 | padding: 0px; 73 | margin: 1px 0; 74 | border: 0; 75 | color: #20c; 76 | font-weight: bold; 77 | background: transparent; 78 | } 79 | 80 | .goog-date-picker-date { 81 | background: #fff; 82 | } 83 | 84 | .goog-date-picker-week, 85 | .goog-date-picker-wday { 86 | padding: 1px 3px; 87 | border: 0; 88 | border-color: #a2bbdd; 89 | border-style: solid; 90 | } 91 | 92 | .goog-date-picker-week { 93 | border-right-width: 1px; 94 | } 95 | 96 | .goog-date-picker-wday { 97 | border-bottom-width: 1px; 98 | } 99 | 100 | .goog-date-picker-head td { 101 | text-align: center; 102 | } 103 | 104 | /** Use td.className instead of !important */ 105 | td.goog-date-picker-today-cont { 106 | text-align: center; 107 | } 108 | 109 | /** Use td.className instead of !important */ 110 | td.goog-date-picker-none-cont { 111 | text-align: center; 112 | } 113 | 114 | .goog-date-picker-month { 115 | width: 12ex; 116 | } 117 | 118 | .goog-date-picker-year { 119 | width: 6ex; 120 | } 121 | 122 | .goog-date-picker table { 123 | border-collapse: collapse; 124 | } 125 | 126 | .goog-date-picker-other-month { 127 | color: #888; 128 | } 129 | 130 | .goog-date-picker-wkend-start, 131 | .goog-date-picker-wkend-end { 132 | background: #eee; 133 | } 134 | 135 | /** Use td.className instead of !important */ 136 | td.goog-date-picker-selected { 137 | background: #c3d9ff; 138 | } 139 | 140 | .goog-date-picker-today { 141 | background: #9ab; 142 | font-weight: bold !important; 143 | border-color: #246 #9bd #9bd #246; 144 | color: #fff; 145 | } 146 | -------------------------------------------------------------------------------- /resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* some style */ 2 | 3 | .CodeMirror { 4 | border: 1px solid #eee; 5 | height: auto; 6 | } 7 | .CodeMirror { 8 | border-bottom: none; 9 | } 10 | 11 | .widget-row:last-child .CodeMirror{ 12 | border-bottom: 1px solid #eee; 13 | } 14 | 15 | .widget-row input.name-field{ 16 | border: solid 1px #eee; 17 | } 18 | .widget-row input.name-field{ 19 | border-bottom: none; 20 | border-right: none; 21 | } 22 | 23 | .widget-row:last-child input.name-field{ 24 | border-bottom: 1px solid #eee;; 25 | } 26 | 27 | 28 | .CodeMirror-scroll { 29 | height: auto; 30 | overflow-y: hidden; 31 | overflow-x: auto; 32 | } 33 | 34 | .CodeMirror-hint { 35 | max-width: 700px; 36 | } 37 | 38 | .clearfix:after { 39 | content: ""; 40 | display: table; 41 | clear: both; 42 | } 43 | .CodeMirror-lines { 44 | padding: 0 0; /* Vertical padding around content */ 45 | } 46 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Figwheel template

14 |

Checkout your developer console.

15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/public/js/subpar/subpar.js: -------------------------------------------------------------------------------- 1 | var loadsubparkeymap = (function() { 2 | //Modifiers in this order: Shift-, Cmd-, Ctrl-, and Alt- 3 | CodeMirror.keyMap.subpar = { 4 | "Backspace" : function(cm) {subpar.core.backward_delete(cm)}, 5 | "Delete" : function(cm) {subpar.core.forward_delete(cm)}, 6 | "Ctrl-D" : function(cm) {subpar.core.forward_delete(cm)}, 7 | 8 | "Shift-9" : function(cm) {subpar.core.open_expression(cm,"()")}, 9 | "[" : function(cm) {subpar.core.open_expression(cm,"[]")}, 10 | "Shift-[" : function(cm) {subpar.core.open_expression(cm,"{}")}, 11 | 12 | "Shift-0" : function(cm) {subpar.core.close_expression(cm,")")}, 13 | "]" : function(cm) {subpar.core.close_expression(cm,"]")}, 14 | "Shift-]" : function(cm) {subpar.core.close_expression(cm,"}")}, 15 | 16 | "Shift-'" : function(cm) {subpar.core.double_quote(cm)}, 17 | 18 | "Ctrl-Alt-F" : function(cm) {subpar.core.forward(cm)}, 19 | "Ctrl-Alt-B" : function(cm) {subpar.core.backward(cm)}, 20 | "Ctrl-Alt-U" : function(cm) {subpar.core.backward_up(cm)}, 21 | "Ctrl-Alt-D" : function(cm) {subpar.core.forward_down(cm)}, 22 | "Ctrl-Alt-P" : function(cm) {subpar.core.backward_down(cm)}, 23 | "Ctrl-Alt-N" : function(cm) {subpar.core.forward_up(cm)},// doesn't work for chrome on windows 24 | 25 | "Shift-Ctrl-[" : function(cm) {subpar.core.backward_barf(cm)}, 26 | "Ctrl-Alt-Right" : function(cm) {subpar.core.backward_barf(cm)}, 27 | "Ctrl-]" : function(cm) {subpar.core.backward_barf(cm)}, 28 | 29 | "Shift-Ctrl-]" : function(cm) {subpar.core.forward_barf(cm)}, 30 | "Ctrl-Left" : function(cm) {subpar.core.forward_barf(cm)}, 31 | 32 | "Shift-Ctrl-9" : function(cm) {subpar.core.backward_slurp(cm)}, 33 | "Ctrl-Alt-Left" : function(cm) {subpar.core.backward_slurp(cm)}, 34 | "Ctrl-[" : function(cm) {subpar.core.backward_slurp(cm)}, 35 | 36 | "Shift-Ctrl-0" : function(cm) {subpar.core.forward_slurp(cm)},// todo key combination didn't work in chrome on windows 37 | "Ctrl-Right" : function(cm) {subpar.core.forward_slurp(cm)}, 38 | 39 | //todo add padding space if necessary for all splices 40 | "Alt-Up" : function(cm) {subpar.core.splice_delete_backward(cm)}, 41 | "Alt-Down" : function(cm) {subpar.core.splice_delete_forward(cm)}, 42 | "Alt-S" : function(cm) {subpar.core.splice(cm)}, 43 | //todo wrap expression in round, square, curly. 44 | "Ctrl-Alt-\\" : function(cm) {subpar.core.indent_selection(cm)}, 45 | fallthrough: ["basic", "emacs"] // not sure if this is right 46 | }; 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /resources/public/vdomtest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/public/webgltest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:source-paths ["src"] 2 | :dependencies [[com.rpl/specter "1.1.3"] 3 | [com.phronemophobic/cljs-cache "0.1.8"]] 4 | :builds {:webgltest { ;;:figwheel {:on-jsload "membrane.webgltest/on-js-reload"} 5 | :modules {:main {:init-fn membrane.webgltest/-main}} 6 | :asset-path "js/compiled/out.webgltest" 7 | ;; :output-to "resources/public/js/compiled/webgltest.js" 8 | :output-dir "resources/public/js/compiled/out.webgltest" 9 | :target :browser 10 | ;; :compiler-options 11 | ;; { 12 | ;; :source-map-timestamp true 13 | ;; ;;:optimizations :whitespace 14 | ;; } 15 | }}} 16 | 17 | -------------------------------------------------------------------------------- /src-java/com/phronemophobic/membrane/Skia.java: -------------------------------------------------------------------------------- 1 | package com.phronemophobic.membrane; 2 | import com.sun.jna.*; 3 | 4 | public class Skia { 5 | 6 | public static native Pointer skia_init(); 7 | public static native Pointer skia_init_cpu(int width, int height); 8 | 9 | public static native void skia_reshape(Pointer resource, int frameBufferWidth, int frameBufferHeight, float xscale, float yscale); 10 | public static native void skia_clear(Pointer resources); 11 | public static native void skia_flush_and_submit(Pointer resources); 12 | public static native void skia_cleanup(Pointer resources); 13 | public static native void skia_set_scale (Pointer resource, float sx, float sy); 14 | public static native void skia_render_line(Pointer resource, Pointer font, Pointer text, int text_length, float x, float y); 15 | public static native void skia_next_line(Pointer resource, Pointer font); 16 | public static native float skia_line_height(Pointer font); 17 | public static native float skia_advance_x(Pointer font, Pointer text, int text_length); 18 | public static native void skia_render_cursor(Pointer resource, Pointer font, Pointer text, int text_length , int cursor); 19 | public static native void skia_render_selection(Pointer resource, Pointer font, Pointer text, int text_length , int selection_start, int selection_end); 20 | 21 | public static native int skia_index_for_position(Pointer font, Pointer text, int text_length, float px); 22 | public static native void skia_text_bounds(Pointer font, Pointer text, int text_length, Pointer ox, Pointer oy, Pointer width, Pointer height); 23 | 24 | public static native void skia_save(Pointer resource); 25 | public static native void skia_restore(Pointer resource); 26 | public static native void skia_translate(Pointer resource, float tx, float ty); 27 | public static native void skia_rotate(Pointer resource, float degrees); 28 | public static native void skia_transform(Pointer resource, float scaleX, float skewX, float transX, float skewY, float scaleY, float transY); 29 | 30 | public static native void skia_clip_rect(Pointer resource, float ox, float oy, float width, float height); 31 | 32 | public static native Pointer skia_load_image(String path); 33 | public static native Pointer skia_load_image_from_memory(byte[] buf,int buffer_length); 34 | public static native void skia_draw_image(Pointer resource, Pointer image); 35 | public static native void skia_draw_image_rect(Pointer resource, Pointer image, float w, float h); 36 | 37 | public static native void skia_draw_path(Pointer resource, Pointer points, int count); 38 | public static native void skia_draw_polygon(Pointer resource, Pointer points, int count); 39 | 40 | public static native void skia_draw_rounded_rect(Pointer resource, float width, float height, float radius); 41 | 42 | public static native Pointer skia_load_font2(String name, float size, int weight, int width, int slant); 43 | 44 | 45 | // Paint related calls 46 | public static native void skia_push_paint(Pointer resource); 47 | public static native void skia_pop_paint(Pointer resource); 48 | public static native void skia_set_color(Pointer resource, float r, float g, float b, float a); 49 | public static native void skia_set_style(Pointer resource, byte style); 50 | public static native void skia_set_stroke_width(Pointer resource, float stroke_width); 51 | public static native void skia_set_alpha(Pointer resource, byte a); 52 | 53 | // offscreen buffer stuff 54 | public static native Pointer skia_offscreen_buffer(Pointer resource, int width, int height); 55 | public static native Pointer skia_offscreen_image(Pointer resource); 56 | 57 | public static native int skia_save_image(Pointer image, int format, int quality, String path); 58 | 59 | public static native int skia_fork_pty(short rows, short columns); 60 | 61 | static { 62 | Native.register("membraneskia"); 63 | } 64 | 65 | public static void main(String[] args) { 66 | System.out.println("init: " + skia_init()); 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:npm-deps {"opentype.js" "1.3.4" 2 | "buffer" "4.9.2"}} 3 | -------------------------------------------------------------------------------- /src/membrane/alpha/component/drag_and_drop.cljc: -------------------------------------------------------------------------------- 1 | (ns membrane.alpha.component.drag-and-drop 2 | (:refer-clojure :exclude [drop abs]) 3 | (:require [membrane.ui :as ui] 4 | [membrane.component 5 | :refer [defui defeffect]])) 6 | 7 | 8 | (defprotocol IDrop 9 | :extend-via-metadata true 10 | (-drop [elem pos obj])) 11 | 12 | (defprotocol IDropMove 13 | :extend-via-metadata true 14 | (-drop-move [elem pos obj])) 15 | 16 | (defn drop 17 | "Returns the effects of a drop event on elem. 18 | 19 | Requires that the a drag-and-drop component is wrapping the necessary elements in the stack." 20 | ([elem pos obj] 21 | (-drop elem pos obj))) 22 | 23 | (defn drop-move 24 | "Returns the effects of a drop event on elem. 25 | 26 | Requires that the a drag-and-drop component is wrapping the necessary elements in the stack." 27 | ([elem pos obj] 28 | (-drop-move elem pos obj))) 29 | 30 | (defrecord OnDrop [on-drop elem] 31 | ui/IOrigin 32 | (-origin [_] 33 | [0 0]) 34 | 35 | ui/IBounds 36 | (-bounds [this] 37 | (ui/child-bounds elem)) 38 | 39 | ui/IChildren 40 | (-children [this] 41 | [elem]) 42 | 43 | IDrop 44 | (-drop [this pos obj] 45 | (when-let [local-pos (ui/within-bounds? elem pos)] 46 | (when on-drop 47 | (on-drop local-pos obj))))) 48 | 49 | (defn on-drop [handler body] 50 | (OnDrop. handler body)) 51 | 52 | 53 | (defrecord OnDropMove [on-drop-move elem] 54 | ui/IOrigin 55 | (-origin [_] 56 | [0 0]) 57 | 58 | ui/IBounds 59 | (-bounds [this] 60 | (ui/child-bounds elem)) 61 | 62 | ui/IChildren 63 | (-children [this] 64 | [elem]) 65 | 66 | IDropMove 67 | (-drop-move [this pos obj] 68 | (when-let [local-pos (ui/within-bounds? elem pos)] 69 | (when on-drop-move 70 | (on-drop-move local-pos obj))))) 71 | 72 | (defn on-drop-move [handler body] 73 | (OnDropMove. handler body)) 74 | 75 | (defprotocol IDragMouseOffset 76 | (-drag-mouse-offset [this mpos])) 77 | 78 | (extend-protocol IDragMouseOffset 79 | 80 | #?(:clj Object 81 | :cljs default) 82 | (-drag-mouse-offset [this mpos] 83 | mpos) 84 | 85 | membrane.ui.Padding 86 | (-drag-mouse-offset [this [mx my]] 87 | [(- mx (:left this)) 88 | (- my (:top this))]) 89 | 90 | membrane.ui.Scale 91 | (-drag-mouse-offset [this mpos] 92 | (let [scalars (:scalars this)] 93 | [(/ (nth mpos 0) 94 | (nth scalars 0)) 95 | (/ (nth mpos 1) 96 | (nth scalars 1))])) 97 | 98 | membrane.ui.ScrollView 99 | (-drag-mouse-offset [this [mx my]] 100 | (let [offset (:offset this)] 101 | [(- mx (nth offset 0)) 102 | (- my (nth offset 1))]))) 103 | 104 | 105 | (extend-type #?(:clj Object 106 | :cljs default) 107 | 108 | IDrop 109 | (-drop [elem local-pos obj] 110 | (let [intents 111 | ;; use seq to make sure we don't stop for empty sequences 112 | (some #(when-let [local-pos (ui/within-bounds? % local-pos)] 113 | (seq (-drop % (-drag-mouse-offset elem local-pos) obj))) 114 | (reverse (ui/children elem)))] 115 | (ui/-bubble elem intents))) 116 | 117 | IDropMove 118 | (-drop-move [elem local-pos obj] 119 | (let [intents 120 | ;; use seq to make sure we don't stop for empty sequences 121 | (some #(when-let [local-pos (ui/within-bounds? % local-pos)] 122 | (seq (-drop-move % (-drag-mouse-offset elem local-pos) obj))) 123 | (reverse (ui/children elem)))] 124 | (ui/-bubble elem intents)))) 125 | 126 | (comment 127 | (require '[membrane.skia :as skia]) 128 | ,) 129 | 130 | 131 | (defn ^:private abs [n] 132 | (Math/abs n)) 133 | 134 | (defeffect ::drag-start [drop-object] 135 | ;; do nothing 136 | ) 137 | 138 | (defn drag-start? [intent] 139 | (when (= ::drag-start (first intent)) 140 | intent)) 141 | 142 | ;; drag start event 143 | ;; :drop-object 144 | ;; 145 | ;; :pending 146 | 147 | (defui drag-and-drop [{:keys [body 148 | pending-intents 149 | pending-drop-object 150 | pending-init-intents 151 | drag-start 152 | ^:membrane.component/contextual 153 | drop-object]}] 154 | (let [body (cond 155 | drag-start 156 | (ui/wrap-on 157 | :mouse-up 158 | (fn [handler mpos] 159 | (concat 160 | (eduction 161 | (remove drag-start?) 162 | pending-intents) 163 | [[:set $drag-start nil] 164 | [:set $pending-intents nil] 165 | [:set $pending-init-intents nil] 166 | [:set $pending-drop-object nil]] 167 | (handler mpos))) 168 | :mouse-move 169 | (fn [handler [mx my]] 170 | (when (> (+ (abs (- mx (first drag-start))) 171 | (abs (- my (second drag-start)))) 172 | 4) 173 | (into [[:set $drag-start nil] 174 | [:set $pending-intents nil] 175 | [:set $pending-drop-object nil] 176 | [:set $pending-init-intents nil] 177 | [:set $drop-object pending-drop-object]] 178 | pending-init-intents))) 179 | body) 180 | 181 | drop-object 182 | (ui/on 183 | :mouse-move 184 | (fn [mpos] 185 | (drop-move body mpos drop-object)) 186 | :mouse-up 187 | (fn [mpos] 188 | (cons [:set $drop-object nil] 189 | (drop body mpos drop-object))) 190 | body) 191 | 192 | :else 193 | (ui/wrap-on 194 | :mouse-down 195 | (fn [handler mpos] 196 | (let [intents (handler mpos) 197 | drag-start-intent (some drag-start? 198 | intents)] 199 | (if drag-start-intent 200 | (let [m (nth drag-start-intent 1)] 201 | [[:set $drag-start mpos] 202 | [:set $pending-intents intents] 203 | [:set $pending-init-intents (::init m)] 204 | [:set $pending-drop-object (::obj m)]]) 205 | ;; else 206 | intents))) 207 | body))] 208 | body)) 209 | 210 | (defui on-drag-hover 211 | "Component for adding a hover? state." 212 | [{:keys [hover? body]}] 213 | (if hover? 214 | (ui/wrap-on 215 | :mouse-move-global 216 | (fn [handler [x y :as pos]] 217 | (let [[w h] (ui/bounds body) 218 | child-intents (handler pos)] 219 | (if (or (neg? x) 220 | (> x w) 221 | (neg? y) 222 | (> y h)) 223 | (conj child-intents 224 | [:set $hover? false]) 225 | child-intents))) 226 | body) 227 | (on-drop-move 228 | (fn [_ _] 229 | [[:set $hover? true]]) 230 | body))) 231 | -------------------------------------------------------------------------------- /src/membrane/alpha/component/impl/scroll.cljc: -------------------------------------------------------------------------------- 1 | (ns membrane.alpha.component.scroll 2 | (:require [membrane.ui :as ui])) 3 | 4 | 5 | (defprotocol IDrop 6 | :extend-via-metadata true 7 | (-drop [elem pos obj])) 8 | 9 | (defprotocol IDropMove 10 | :extend-via-metadata true 11 | (-drop-move [elem pos obj])) 12 | 13 | (defn drop 14 | "Returns the effects of a mouse move event on elem. Will only call -mouse-move on mouse events within an elements bounds." 15 | ([elem pos obj] 16 | (when-let [local-pos (ui/within-bounds? elem pos)] 17 | (-drop elem local-pos obj)))) 18 | 19 | 20 | (defn drop-move 21 | "Returns the effects of a mouse move event on elem. Will only call -mouse-move on mouse events within an elements bounds." 22 | ([elem pos obj] 23 | (when-let [local-pos (ui/within-bounds? elem pos)] 24 | (-drop-move elem local-pos obj)))) 25 | 26 | (defrecord OnDrop [on-drop elem] 27 | ui/IOrigin 28 | (-origin [_] 29 | [0 0]) 30 | 31 | ui/IBounds 32 | (-bounds [this] 33 | (ui/child-bounds elem)) 34 | 35 | ui/IChildren 36 | (-children [this] 37 | [elem]) 38 | 39 | IDrop 40 | (-drop [this pos obj] 41 | (when on-drop 42 | (on-drop pos obj)))) 43 | 44 | (defn on-drop [handler body] 45 | (OnDrop. handler body)) 46 | 47 | 48 | (defrecord OnDropMove [on-drop-move elem] 49 | ui/IOrigin 50 | (-origin [_] 51 | [0 0]) 52 | 53 | ui/IBounds 54 | (-bounds [this] 55 | (ui/child-bounds elem)) 56 | 57 | ui/IChildren 58 | (-children [this] 59 | [elem]) 60 | 61 | IDropMove 62 | (-drop-move [this pos obj] 63 | (when on-drop-move 64 | (on-drop-move pos obj)))) 65 | 66 | (defn on-drop-move [handler body] 67 | (OnDropMove. handler body)) 68 | 69 | (extend-type #?(:clj Object 70 | :cljs default) 71 | 72 | IDrop 73 | (-drop [elem local-pos obj] 74 | (let [intents 75 | ;; use seq to make sure we don't stop for empty sequences 76 | (some #(when-let [local-pos (ui/within-bounds? % local-pos)] 77 | (seq (-drop % local-pos obj))) 78 | (reverse (ui/children elem)))] 79 | (ui/-bubble elem intents))) 80 | 81 | IDropMove 82 | (-drop-move [elem local-pos obj] 83 | (let [intents 84 | ;; use seq to make sure we don't stop for empty sequences 85 | (some #(when-let [local-pos (ui/within-bounds? % local-pos)] 86 | (seq (-drop-move % local-pos obj))) 87 | (reverse (ui/children elem)))] 88 | (ui/-bubble elem intents)))) 89 | -------------------------------------------------------------------------------- /src/membrane/analyze.cljc: -------------------------------------------------------------------------------- 1 | (ns membrane.analyze 2 | #?(:clj 3 | (:require clojure.walk)) 4 | #?@ 5 | (:cljs 6 | [(:require-macros [cljs.core.async.macros :refer [go]]) 7 | 8 | (:require clojure.walk 9 | [membrane.macroexpand :refer [macroexpand-all]] 10 | [cljs.core.async :refer [put! chan > bindings (drop 1) (take-nth 2)) ~@body)) 26 | form)) 27 | form)) 28 | 29 | (defn ignore-new-bindings [form] 30 | (clojure.walk/postwalk 31 | (fn [form] 32 | (if (and (seq? form) 33 | (= 'new (first form))) 34 | (let [[newsym constructor-sym body] form] 35 | body) 36 | form)) 37 | form)) 38 | 39 | (defn ignore-fns [form] 40 | (clojure.walk/prewalk 41 | (fn [form] 42 | (if (and (seq? form) 43 | (= (first form) 'fn*)) 44 | nil 45 | form)) 46 | form)) 47 | 48 | (defn unbound-syms [form] 49 | (cond 50 | (= '() form) nil 51 | 52 | (seq? form) 53 | (case (first form) 54 | ;; broken for 55 | ;; (let [b a a a ]1) 56 | (let* loop* clojure.core/let) 57 | (let [[letsym bindings & body] form 58 | newbindings (apply hash-set (take-nth 2 bindings)) 59 | unbound (concat 60 | (unbound-syms (->> bindings (drop 1) (take-nth 2))) 61 | (unbound-syms body)) 62 | unbound (remove newbindings unbound)] 63 | unbound) 64 | 65 | ;; this doesn't cover all of the binding forms 66 | fn* 67 | (let [sigs (rest form) 68 | fn-name (if (symbol? (first sigs)) (first sigs) nil) 69 | sigs (if fn-name (next sigs) sigs) 70 | sigs (if (vector? (first sigs)) 71 | (list sigs) 72 | (if (seq? (first sigs)) 73 | sigs 74 | ;; Assume single arity syntax 75 | (throw (if (seq sigs) 76 | (str "Parameter declaration " 77 | (first sigs) 78 | " should be a vector") 79 | (str "Parameter declaration missing"))))) 80 | fnsym (first form) 81 | unbound-body-syms (apply concat 82 | (for [[bindings & body] sigs 83 | :let [bindings (into #{} bindings)]] 84 | (remove bindings (unbound-syms body)))) 85 | unbound (remove #{fn-name} unbound-body-syms) 86 | ] 87 | unbound) 88 | 89 | new 90 | (let [[newsym classname & args] form] 91 | (unbound-syms args)) 92 | 93 | . (concat 94 | (unbound-syms (second form)) 95 | (unbound-syms (if (list? (nth form 2)) 96 | (rest (nth form 2)) 97 | (drop 3 form)))) 98 | 99 | 100 | quote nil 101 | 102 | var nil 103 | 104 | catch (let [[catch-sym catch-class new-binding & body] form] 105 | (remove #{new-binding} (unbound-syms body))) 106 | 107 | reify* 108 | (let [[_ interfaces & methods] form] 109 | (apply 110 | concat 111 | (for [[method-name bindings & body] methods] 112 | (remove (set bindings) 113 | (unbound-syms body))))) 114 | 115 | (mapcat unbound-syms form)) 116 | 117 | (symbol? form) (list form) 118 | 119 | (seqable? form) (mapcat unbound-syms form) 120 | 121 | :default nil)) 122 | 123 | 124 | 125 | (defn cell-deps 126 | ([expr] 127 | (#?(:clj identity :cljs go) 128 | (->> #?(:cljs (> #?(:cljs (Cached 260 | (Buffer. nil focused? buf))))))) 261 | 262 | 263 | (buffer/text (buffer/buffer "adsf\nadsfa")) 264 | 265 | (defui buf-ui [{:keys [buf]}] 266 | (ui/translate 10 10 267 | [(let [gray 0.7] 268 | (ui/with-color [gray gray gray] 269 | (ui/with-style ::ui/style-stroke 270 | (ui/rectangle 200 200)))) 271 | (text-editor {:buf buf})]) 272 | ) 273 | 274 | (defonce buf-state (atom nil)) 275 | (defn initial-buf-state [] 276 | {:buf (buffer/buffer "" {:rows 40 :cols 5 277 | :mode :insert})}) 278 | 279 | #_(defn test-buf [] 280 | (reset! buf-state (initial-buf-state)) 281 | (skia/run (component/make-app #'buf-ui 282 | buf-state)) 283 | ) 284 | 285 | -------------------------------------------------------------------------------- /src/membrane/customappmain.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.customappmain 2 | (:require ;; membrane.uibuilder 3 | clojure.main) 4 | (:gen-class)) 5 | 6 | 7 | (def main-class-loader @clojure.lang.Compiler/LOADER) 8 | (defn -main [& args] 9 | #_(clojure.main/with-bindings 10 | (let [project-path (java.lang.System/getProperty "project.path")] 11 | (membrane.uibuilder/run-project (read-string (slurp project-path)))))) 12 | -------------------------------------------------------------------------------- /src/membrane/eval.cljs: -------------------------------------------------------------------------------- 1 | (ns membrane.eval 2 | (:require [cljs.js :as cljs] 3 | [cljs.core.async :refer [put! chan > (clojure.java.io/file ".") 60 | (.listFiles) 61 | (map #(.getName %)))}))) 62 | 63 | (defn file-selector [path] 64 | (let [state (atom {:item-names 65 | (->> (clojure.java.io/file path) 66 | (.listFiles) 67 | (map #(.getName %)) 68 | sort)})] 69 | (skia/run-sync (component/make-app #'item-selector state)) 70 | (:selected @state))) 71 | 72 | (defn -main 73 | ([] 74 | (-main ".")) 75 | ([path] 76 | (doseq [fname (file-selector path)] 77 | (println fname)))) 78 | -------------------------------------------------------------------------------- /src/membrane/example/terminal_todo.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.example.terminal-todo 2 | (:require [membrane.ui :as ui 3 | :refer 4 | [horizontal-layout 5 | vertical-layout 6 | on]] 7 | ;; need effects 8 | [membrane.lanterna 9 | :refer [textarea checkbox label button] 10 | :as lanterna] 11 | [membrane.basic-components :as basic] 12 | [membrane.component :as component 13 | :refer [defui defeffect]])) 14 | 15 | ;;; todo app 16 | (defui todo-item [{:keys [todo]}] 17 | (horizontal-layout 18 | (on 19 | :mouse-down 20 | (fn [[mx my]] 21 | [[:delete $todo]]) 22 | (ui/with-color [1 0 0] 23 | (label "X"))) 24 | (checkbox {:checked? (:complete? todo)}) 25 | (ui/wrap-on 26 | :key-press 27 | (fn [default-handler s] 28 | (when (not= s :enter) 29 | (default-handler s))) 30 | (textarea {:text (:description todo)})))) 31 | 32 | (defui todo-list [{:keys [todos]}] 33 | (apply 34 | vertical-layout 35 | (for [todo todos] 36 | (todo-item {:todo todo})))) 37 | 38 | 39 | (def filter-fns 40 | {:all (constantly true) 41 | :active (comp not :complete?) 42 | :complete? :complete?}) 43 | 44 | ;; Create a toggle that allows the user 45 | ;; to toggle between options 46 | (defui toggle [{:keys [options selected]}] 47 | (apply 48 | horizontal-layout 49 | (for [option options] 50 | (if (= option selected) 51 | (label (name option)) 52 | (on 53 | :mouse-down 54 | (fn [[mx my]] 55 | [[:set $selected option]]) 56 | (ui/with-color [0.8 0.8 0.8] 57 | (label (name option)))))))) 58 | 59 | 60 | (defui todo-app [{:keys [todos next-todo-text selected-filter] 61 | :or {selected-filter :all}}] 62 | (vertical-layout 63 | (horizontal-layout 64 | (button "Add Todo" 65 | (fn [] 66 | [[::add-todo $todos next-todo-text] 67 | [:set $next-todo-text ""]])) 68 | (ui/wrap-on 69 | :key-press 70 | (fn [default-handler s] 71 | (let [effects (default-handler s)] 72 | (if (and (seq effects) 73 | (= s :enter)) 74 | [[::add-todo $todos next-todo-text] 75 | [:set $next-todo-text ""]] 76 | effects))) 77 | (textarea {:text next-todo-text}))) 78 | (toggle {:selected selected-filter :options [:all :active :complete?]}) 79 | (let [filter-fn (get filter-fns selected-filter :all) 80 | visible-todos (filter filter-fn todos)] 81 | (todo-list {:todos visible-todos})))) 82 | 83 | 84 | (def todo-state (atom {:todos 85 | [{:complete? false 86 | :description "first"} 87 | {:complete? false 88 | :description "second"} 89 | {:complete? true 90 | :description "third"}] 91 | :next-todo-text ""})) 92 | 93 | (defeffect ::add-todo [$todos next-todo-text] 94 | (dispatch! :update $todos #(conj % {:description next-todo-text 95 | :complete? false}))) 96 | 97 | 98 | 99 | 100 | (def todo-state (atom {:todos 101 | [{:complete? false 102 | :description "first"} 103 | {:complete? false 104 | :description "second"} 105 | {:complete? true 106 | :description "third"}] 107 | :next-todo-text ""})) 108 | 109 | (defn -main [& args] 110 | ;; (component/run-ui-sync #'term-test {:num 0 :s "hh"}) 111 | (lanterna/run-sync (component/make-app #'todo-app 112 | todo-state)) 113 | ;; (.close System/in) 114 | ;; (shutdown-agents) 115 | ) 116 | -------------------------------------------------------------------------------- /src/membrane/example/todo.cljc: -------------------------------------------------------------------------------- 1 | (ns membrane.example.todo 2 | #?(:cljs 3 | (:require-macros [membrane.component 4 | :refer [defui defeffect]])) 5 | (:require [membrane.ui :as ui 6 | :refer [vertical-layout 7 | translate 8 | horizontal-layout 9 | label 10 | with-color 11 | bounds 12 | spacer 13 | on]] 14 | [membrane.component :as component 15 | :refer [#?(:clj defui) 16 | #?(:clj defeffect)]] 17 | [membrane.basic-components :as basic]) 18 | #?(:clj (:gen-class))) 19 | 20 | ;; Draw a red X that we'll use to display a delete button 21 | ;; No interactivity, so `defui` not needed 22 | (defn delete-X [] 23 | (ui/with-style :membrane.ui/style-stroke 24 | (ui/with-color 25 | [1 0 0] 26 | (ui/with-stroke-width 27 | 3 28 | [(ui/path [0 0] 29 | [10 10]) 30 | (ui/path [10 0] 31 | [0 10])])))) 32 | 33 | (comment 34 | (defn run-ui [ui-var initial-state] 35 | (skia/run (component/make-app ui-var initial-state))) 36 | 37 | (require '[membrane.skia :as skia]) 38 | (skia/run #(delete-X)) 39 | ,) 40 | 41 | ;; Display a single todo item 42 | (defui todo-item [{:keys [todo]}] 43 | (horizontal-layout 44 | (translate 5 5 45 | (on 46 | :mouse-down 47 | (fn [[mx my]] 48 | [[:delete $todo]]) 49 | (delete-X))) 50 | (translate 10 4 51 | (basic/checkbox {:checked? (:complete? todo)})) 52 | (spacer 10 0) 53 | (basic/textarea {:text (:description todo)}))) 54 | 55 | (comment 56 | (run-ui #'todo-item {:todo 57 | {:complete? false 58 | :description "fix me"}})) 59 | 60 | 61 | ;; Display a list of `todo-item`s stacked vertically 62 | ;; Add 5px of spacing between `todo-item`s 63 | (defui todo-list [{:keys [todos]}] 64 | (apply 65 | vertical-layout 66 | (interpose 67 | (spacer 0 5) 68 | (for [todo todos] 69 | (todo-item {:todo todo}))))) 70 | 71 | (comment 72 | (run-ui #'todo-list {:todos 73 | [{:complete? false 74 | :description "first"} 75 | {:complete? false 76 | :description "second"} 77 | {:complete? true 78 | :description "third"}]})) 79 | 80 | 81 | 82 | (def filter-fns 83 | {:all (constantly true) 84 | :active (comp not :complete?) 85 | :complete? :complete?}) 86 | 87 | ;; Create a toggle that allows the user 88 | ;; to toggle between options 89 | (defui toggle [{:keys [options selected]}] 90 | (apply 91 | horizontal-layout 92 | (interpose 93 | (spacer 5 0) 94 | (for [option options] 95 | (if (= option selected) 96 | (ui/label (name option)) 97 | (on 98 | :mouse-down 99 | (fn [[mx my]] 100 | [[:set $selected option]]) 101 | (ui/with-color [0.8 0.8 0.8] 102 | (ui/label (name option))))))))) 103 | 104 | (comment 105 | (run-ui #'toggle 106 | {:options [:all :active :complete?] 107 | :selected nil})) 108 | 109 | (defui todo-app [{:keys [todos next-todo-text selected-filter] 110 | :or {selected-filter :all}}] 111 | (vertical-layout 112 | (horizontal-layout 113 | (basic/button {:text "Add Todo" 114 | :on-click (fn [] 115 | [[::add-todo $todos next-todo-text] 116 | [:set $next-todo-text ""]])}) 117 | (translate 10 10 118 | (ui/wrap-on 119 | :key-press 120 | (fn [default-handler s] 121 | (let [effects (default-handler s)] 122 | (if (and (seq effects) 123 | (= s :enter)) 124 | [[::add-todo $todos next-todo-text] 125 | [:set $next-todo-text ""]] 126 | effects))) 127 | (basic/textarea {:text next-todo-text})))) 128 | (spacer 0 10) 129 | (toggle {:selected selected-filter :options [:all :active :complete?]}) 130 | (spacer 0 10) 131 | (let [filter-fn (get filter-fns selected-filter :all) 132 | visible-todos (filter filter-fn todos)] 133 | (todo-list {:todos visible-todos})))) 134 | 135 | 136 | (def todo-state (atom {:todos 137 | [{:complete? false 138 | :description "first"} 139 | {:complete? false 140 | :description "second"} 141 | {:complete? true 142 | :description "third"}] 143 | :next-todo-text ""})) 144 | 145 | (defeffect ::add-todo [$todos next-todo-text] 146 | (dispatch! :update $todos #(conj % {:description next-todo-text 147 | :complete? false}))) 148 | 149 | (comment 150 | (skia/run (component/make-app #'todo-app todo-state))) 151 | 152 | 153 | (comment 154 | (def todo-state 155 | (skia/run (component/make-app #'todo-app 156 | {:todos 157 | [{:complete? false 158 | :description "first"} 159 | {:complete? false 160 | :description "second"} 161 | {:complete? true 162 | :description "third"}] 163 | :next-todo-text ""})))) 164 | 165 | 166 | #? 167 | (:clj 168 | (defn -main [& args] 169 | ((requiring-resolve 'membrane.skia/run-sync) 170 | (component/make-app #'todo-app 171 | {:todos 172 | [{:complete? false 173 | :description "first"} 174 | {:complete? false 175 | :description "second"} 176 | {:complete? true 177 | :description "third"}] 178 | :next-todo-text ""})) 179 | )) 180 | 181 | 182 | 183 | #? 184 | (:clj 185 | (defn save-image [{:keys [path] 186 | :or {path "todo.png"}}] 187 | ((requiring-resolve 'membrane.skia/save-image) 188 | path 189 | (todo-app 190 | {:selected-filter :all 191 | :todos 192 | [{:complete? false 193 | :description "first"} 194 | {:complete? false 195 | :description "second"} 196 | {:complete? true 197 | :description "third"}] 198 | :next-todo-text ""})) 199 | )) 200 | -------------------------------------------------------------------------------- /src/membrane/macroexpand.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (C) Adrian Smith - All Rights Reserved 2 | ;; Unauthorized copying of this file, via any medium is strictly prohibited 3 | ;; Proprietary and confidential 4 | ;; Written by Adrian Smith adrian@phronemophobic.com, June 2019 5 | (ns membrane.macroexpand 6 | (:require-macros [cljs.core.async.macros :refer [go]]) 7 | (:require clojure.walk 8 | [cljs.core.async :refer [put! chan (resolve component) 73 | meta 74 | :arglists 75 | first) 76 | m (first arglist) 77 | defaults (:or m) 78 | 79 | get-sid-sym (gensym "getsid-") 80 | args (into {} 81 | cat 82 | [(for [[sym v] defaults] 83 | [(keyword sym) v]) 84 | (for [sym (:keys m) 85 | :when (-> sym meta ::component/contextual)] 86 | [(keyword sym) 87 | `(get @re-frame-state 88 | ~(keyword sym) 89 | ~(when (contains? defaults sym) 90 | (get defaults sym)))]) 91 | (for [sym (:keys m) 92 | :let [$k (keyword (str "$" (name sym))) 93 | $v (if (-> sym meta ::component/contextual) 94 | [(keyword sym)] 95 | [get-sid-sym (keyword sym)]) 96 | $v (if (contains? defaults sym) 97 | (conj $v `(quote ~(list 'nil->val (get defaults sym)) )) 98 | $v)]] 99 | [$k $v])]) 100 | 101 | m-sym (get m-args :as (gensym "m-")) 102 | params [id-sym (assoc m-args :as m-sym)]] 103 | `(defn ~ui-name ~params 104 | (let [~get-sid-sym ~(list 'list (quote 'get) id-sym)] 105 | (ui/on-bubble 106 | (fn [effects#] 107 | ~@(for [k (:keys m-args)] 108 | `(re-frame-dispatch :set ~(get args (keyword (str "$" (name k)))) ~k)) 109 | (run! #(apply re-frame-dispatch %) effects#) 110 | (remove nil? 111 | [~@(for [k (:keys m-args)] 112 | `(let [new-val# (re-frame-dispatch :get ~(get args (keyword (str "$" (name k)))))] 113 | (when (not= new-val# ~k) 114 | [:change ~(keyword k) new-val#])))])) 115 | (~component (merge ~args (get @re-frame-state ~id-sym) ~m-sym))))))) 116 | 117 | (defrf text-box basic/textarea [tid {:keys [text font]}]) 118 | (defrf scrollview basic/scrollview [sid {:keys [scroll-bounds]}]) 119 | 120 | (defn fix-scroll [elem] 121 | (ui/on-scroll (fn [[sx sy] pos] 122 | (ui/scroll elem [(- sx) (- sy)] pos)) 123 | elem)) 124 | 125 | (comment 126 | (def lorem-ipsum (clojure.string/join 127 | "\n" 128 | (repeatedly 800 129 | (fn [] 130 | (clojure.string/join 131 | (repeatedly (rand-int 50) 132 | #(rand-nth "abcdefghijklmnopqrstuvwxyz "))))))) 133 | (defn test-scrollview [] 134 | [(ui/translate 10 10 135 | (fix-scroll 136 | (scrollview :test-scroll {:scroll-bounds [300 300] 137 | :body (ui/label lorem-ipsum)})))]) 138 | 139 | (require '[membrane.skia :as skia]) 140 | (skia/run #(re-frame-app (test-scrollview))) 141 | , 142 | 143 | ) 144 | -------------------------------------------------------------------------------- /src/membrane/skia/paragraph/paragraph_test.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.skia.paragraph.paragraph-test 2 | (:require 3 | [membrane.skia.paragraph :as para] 4 | [membrane.skia.paragraph.spec :as ps] 5 | [membrane.ui :as ui] 6 | [clojure.edn :as edn] 7 | [clojure.spec.alpha :as s] 8 | [clojure.spec.gen.alpha :as gen] 9 | [membrane.component :refer [make-app defui defeffect]] 10 | [membrane.skia :as skia] 11 | [membrane.basic-components :as basic])) 12 | 13 | (defn write-edn [w obj] 14 | (binding [*print-length* nil 15 | *print-level* nil 16 | *print-dup* false 17 | *print-meta* false 18 | *print-readably* true 19 | 20 | ;; namespaced maps not part of edn spec 21 | *print-namespace-maps* false 22 | 23 | *out* w] 24 | (pr obj))) 25 | 26 | (def test-paragraph-view-gen 27 | (gen/such-that 28 | (fn [view] 29 | #_(with-open [w ((requiring-resolve 'clojure.java.io/writer) "paragraph.edn")] 30 | (write-edn w view)) 31 | (let [[w h] (ui/bounds view)] 32 | (and (<= w 400) 33 | (<= h 400)))) 34 | (gen/fmap (fn [[paragraph width paragraph-style]] 35 | (para/paragraph paragraph width paragraph-style)) 36 | (s/gen (s/cat :paragraph ::ps/paragraph 37 | :width ::ps/paragraph-width 38 | :paragraph-style (s/nilable ::ps/paragraph-style)))) 39 | 100)) 40 | 41 | 42 | (declare window-info) 43 | (defeffect ::reroll [$paragraph $running?] 44 | (if (dispatch! :get $running?) 45 | (dispatch! :set $running? false) 46 | (do 47 | (dispatch! :set $running? true) 48 | (future 49 | (while (dispatch! :get $running?) 50 | (let [paragraph (gen/generate test-paragraph-view-gen)] 51 | (dispatch! :set $paragraph paragraph) 52 | ((::skia/repaint window-info)) 53 | (Thread/sleep 100))) 54 | (dispatch! :set $running? false)))) 55 | (dispatch! :set $paragraph (gen/generate test-paragraph-view-gen))) 56 | 57 | (def my-fonts (para/available-font-families)) 58 | (defui rand-paragraph-viewer [{:keys [paragraph font-index running?]}] 59 | (let [font-index (or font-index 0)] 60 | (ui/vertical-layout 61 | (basic/button {:text "Reroll" 62 | :on-click 63 | (fn [] 64 | [[::reroll $paragraph $running?]])}) 65 | (basic/number-slider {:num font-index 66 | :min 0 67 | :max (dec (count my-fonts)) 68 | :integer? true}) 69 | (ui/label (nth my-fonts font-index)) 70 | (ui/translate 71 | 100 100 72 | [(ui/filled-rectangle 73 | [0 0 0 0.05] 74 | 450 450) 75 | (ui/scissor-view [0 0] 76 | [450 450] 77 | paragraph)] 78 | #_(para/paragraph [{:text "The quick brown fox jumped over the lazy dog." 79 | :style {:text-style/font-families [(nth my-fonts font-index)] 80 | ;; :text-style/color [0 0 0] 81 | } 82 | }]))))) 83 | 84 | (comment 85 | (def state (atom {})) 86 | (def app (make-app #'rand-paragraph-viewer state)) 87 | (def window-info (skia/run app)) 88 | 89 | (require 'dev 90 | '[clojure.java.io :as io]) 91 | 92 | 93 | ,) 94 | 95 | 96 | 97 | (defn last-paragraph [] 98 | (edn/read-string {:readers {'membrane.skia.paragraph.Paragraph 99 | (fn [m] 100 | (para/map->Paragraph m))}} 101 | (slurp "paragraph.edn") 102 | )) 103 | 104 | (defn -main [& args] 105 | (skia/run-sync (constantly 106 | (last-paragraph)))) 107 | 108 | 109 | 110 | 111 | 112 | (defn run-random [& args] 113 | (while true 114 | (let [paragraph (gen/generate test-paragraph-view-gen)] 115 | (with-open [w ((requiring-resolve 'clojure.java.io/writer) "paragraph.edn")] 116 | (write-edn w paragraph)) 117 | (skia/save-image "paragraph.png" 118 | paragraph 119 | [450 450])))) 120 | -------------------------------------------------------------------------------- /src/membrane/skia/paragraph/spec.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.skia.paragraph.spec 2 | (:require 3 | [membrane.skia.paragraph :as para] 4 | [clojure.spec.alpha :as s] 5 | [clojure.spec.gen.alpha :as gen])) 6 | 7 | 8 | (s/def ::zero-to-one (s/and 9 | number? 10 | #(>= % 0) 11 | #(<= % 1))) 12 | 13 | (s/def ::color 14 | (s/coll-of ::zero-to-one 15 | :min-count 3 16 | :max-count 4)) 17 | 18 | (defn within-normal-range? [n] 19 | (<= (Math/abs n) 20 | 1000)) 21 | 22 | (defn with-normal-range-gen [spec] 23 | (s/with-gen spec 24 | #(gen/such-that within-normal-range? 25 | (s/gen spec) 26 | 500))) 27 | 28 | (s/def ::positive-int 29 | (with-normal-range-gen 30 | (s/and number? 31 | #(>= % 0) 32 | #(try 33 | (int %) 34 | (catch ArithmeticException e 35 | false) 36 | (catch IllegalArgumentException e 37 | false))))) 38 | 39 | (s/def ::integer 40 | (with-normal-range-gen 41 | (s/and number? 42 | #(try 43 | (int %) 44 | (catch ArithmeticException e 45 | false) 46 | (catch IllegalArgumentException e 47 | false))))) 48 | 49 | (s/def ::float 50 | (with-normal-range-gen 51 | (s/and number? 52 | #(try 53 | (float %) 54 | (catch IllegalArgumentException e 55 | false))))) 56 | 57 | (s/def ::positive-float 58 | (with-normal-range-gen 59 | (s/and number? 60 | #(>= % 0) 61 | #(try 62 | (float %) 63 | (catch IllegalArgumentException e 64 | false))))) 65 | 66 | (s/def :font-style/weight 67 | (s/or :number ::positive-int 68 | :named #{:invisible 69 | :thin 70 | :extra-light 71 | :light 72 | :normal 73 | :medium 74 | :semi-bold 75 | :bold 76 | :extra-bold 77 | :black 78 | :extra-black})) 79 | 80 | (s/def :font-style/width 81 | (s/or :number #{1 2 3 4 5 6 7 8 9} 82 | :named #{:ultracondensed 83 | :extracondensed 84 | :condensed 85 | :semicondensed 86 | :normal 87 | :semiexpanded 88 | :expanded 89 | :extraexpanded 90 | :ultraexpanded})) 91 | 92 | (s/def :font-style/slant 93 | (s/or :number #{1 2 3} 94 | :named #{:upright 95 | :italic 96 | :oblique})) 97 | 98 | (s/def ::font-style 99 | (s/keys :opt 100 | [:font-style/weight 101 | :font-style/width 102 | :font-style/slant])) 103 | 104 | (s/def :text-style/shadows 105 | (s/coll-of :text-style/shadow)) 106 | (s/def :text-style/background-color ::color) 107 | (s/def :text-style/baseline-shift ::positive-float) 108 | (s/def :text-style/color ::color) 109 | (s/def :text-style/decoration 110 | (s/coll-of #{:text-decoration/no-decoration 111 | :text-decoration/underline 112 | :text-decoration/overline 113 | :text-decoration/line-through} 114 | :into #{})) 115 | (s/def :text-style/decoration-style 116 | #{:text-decoration-style/solid 117 | :text-decoration-style/double 118 | :text-decoration-style/dotted 119 | :text-decoration-style/dashed 120 | :text-decoration-style/wavy}) 121 | (s/def :text-style/decoration-mode 122 | #{:text-decoration-mode/gaps 123 | :text-decoration-mode/through}) 124 | (s/def :text-style/decoration-color ::color) 125 | (s/def :text-style/decoration-thickness-multiplier 126 | (s/and ::positive-float 127 | ;; skia hard crashed with numbers near zero 128 | #(>= % 0.1))) 129 | (s/def :text-style/font-families 130 | (s/with-gen (s/coll-of string?) 131 | (fn [] 132 | (s/gen (s/coll-of (set (para/available-font-families))))))) 133 | (s/def :text-style/font-size 134 | (s/and ::positive-float 135 | #(>= % 1))) 136 | (s/def :text-style/font-style ::font-style) 137 | 138 | 139 | (s/def :text-style/half-leading ::integer) 140 | (s/def :text-style/height ::float) 141 | (s/def :text-style/height-override boolean?) 142 | (s/def :text-style/letter-spacing ::float) 143 | (s/def :text-style/locale string?) 144 | (s/def :text-style/placeholder? boolean?) 145 | (s/def :text-style/text-baseline ::integer) 146 | (s/def :text-style/word-spacing ::float) 147 | 148 | (s/def ::paint #{}) 149 | (s/def ::typeface #{}) 150 | (s/def :text-style/typeface ::typeface) 151 | (s/def :text-style/foreground ::paint) 152 | 153 | (s/def ::text-style 154 | (s/keys 155 | :opt [:text-style/font-families 156 | :text-style/baseline-shift 157 | :text-style/color 158 | :text-style/decoration 159 | :text-style/decoration-style 160 | :text-style/decoration-mode 161 | :text-style/decoration-color 162 | :text-style/decoration-thickness-multiplier 163 | 164 | :text-style/font-size 165 | :text-style/font-style 166 | 167 | :text-style/half-leading 168 | :text-style/height 169 | :text-style/height-override 170 | :text-style/letter-spacing 171 | :text-style/locale 172 | :text-style/placeholder? 173 | :text-style/text-baseline 174 | :text-style/word-spacing 175 | ;; unimplemented 176 | ;; :text-style/typeface 177 | ;; :text-style/foreground 178 | ;; :text-style/shadows 179 | ;; :text-style/background-color 180 | ])) 181 | 182 | 183 | ;; not sure what strut style is 184 | ;; (s/def :paragraph-style/strut-style) 185 | (s/def :paragraph-style/hinting? boolean?) 186 | (s/def :paragraph-style/text-style ::text-style) 187 | (s/def :paragraph-style/text-direction 188 | #{:text-direction/right-to-left 189 | :text-direction/left-to-right}) 190 | (s/def :paragraph-style/text-align 191 | #{:text-align/left 192 | :text-align/right 193 | :text-align/center 194 | :text-align/justify 195 | :text-align/start 196 | :text-align/end}) 197 | 198 | (s/def :paragraph-style/max-lines ::positive-int) 199 | (s/def :paragraph-style/ellipsis string?) 200 | (s/def :paragraph-style/height ::positive-float) 201 | (s/def :paragraph-style/text-height-behavior 202 | #{:text-height-behavior/all 203 | :text-height-behavior/disable-first-ascent 204 | :text-height-behavior/disable-last-ascent 205 | :text-height-behavior/disable-all}) 206 | (s/def :paragraph-style/replace-tab-characters? boolean?) 207 | 208 | (s/def ::paragraph-style* 209 | (s/keys :opt [:paragraph-style/hinting? 210 | :paragraph-style/text-style 211 | :paragraph-style/text-direction 212 | :paragraph-style/text-align 213 | :paragraph-style/max-lines 214 | :paragraph-style/ellipsis 215 | :paragraph-style/height 216 | :paragraph-style/text-height-behavior 217 | :paragraph-style/replace-tab-characters?])) 218 | (s/def ::paragraph-style 219 | (s/with-gen ::paragraph-style* 220 | (fn [] 221 | (gen/such-that 222 | (fn [paragraph-style] 223 | ;; try to filter known crashes and hangs 224 | ;; 1. crash: text-align:justify + max lines + ellipsis 225 | ;; 2. hang: text-align:justify + ellipsis 226 | (not (and (= :text-align/justify 227 | (:paragraph-style/text-align paragraph-style)) 228 | (:paragraph-style/ellipsis paragraph-style)))) 229 | (s/gen ::paragraph-style*))))) 230 | 231 | 232 | (s/def :styled-text/text 233 | (s/with-gen string? 234 | #(gen/string))) 235 | (s/def :styled-text/style ::text-style) 236 | 237 | (s/def ::styled-text 238 | (s/or :string string? 239 | :styled (s/keys :opt-un [:styled-text/style] 240 | :req-un [:styled-text/text]))) 241 | 242 | (s/def ::paragraph 243 | (s/coll-of ::styled-text)) 244 | 245 | 246 | (s/def ::paragraph-width (s/or :nil nil? 247 | :number ::positive-float)) 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /src/membrane/toolkit.cljc: -------------------------------------------------------------------------------- 1 | (ns membrane.toolkit) 2 | 3 | (defprotocol IToolkit 4 | "Empty protocol to mark toolkits") 5 | 6 | (defn toolkit? [o] 7 | (satisfies? IToolkit o)) 8 | 9 | (defprotocol IToolkitRun 10 | :extend-via-metadata true 11 | (run 12 | [toolkit view-fn] 13 | [toolkit view-fn options] 14 | "Run a user interface with `view-fn` to draw. 15 | 16 | `view-fn` should be a 0 argument function that returns an object satisfying `IDraw`. 17 | `view-fn` will be called for every repaint. 18 | 19 | `options` is a map with extra options. available options will depend on the specific toolkit.")) 20 | 21 | (defprotocol IToolkitRunSync 22 | :extend-via-metadata true 23 | (run-sync 24 | [toolkit view-fn] 25 | [toolkit view-fn options] 26 | "Run a user interface synchronously with `view-fn` to draw. 27 | 28 | `view-fn` should be a 0 argument function that returns an object satisfying `IDraw`. 29 | `view-fn` will be called for every repaint. 30 | 31 | `options` is a map with extra options. available options will depend on the specific toolkit.")) 32 | 33 | (defprotocol IToolkitFontExists 34 | :extend-via-metadata true 35 | (font-exists? [toolkit font] 36 | "Returns true if the font can be found by the toolkit.")) 37 | (defprotocol IToolkitFontMetrics 38 | :extend-via-metadata true 39 | (font-metrics [toolkit font] 40 | "Returns the font metrics for font.")) 41 | (defprotocol IToolkitFontAdvanceX 42 | :extend-via-metadata true 43 | (font-advance-x [toolkit font s] 44 | "Returns the advance-x for font.")) 45 | (defprotocol IToolkitFontLineHeight 46 | :extend-via-metadata true 47 | (font-line-height [toolkit font] 48 | "Returns the line height for font.")) 49 | (defprotocol IToolkitLogicalFontFontFamily 50 | :extend-via-metadata true 51 | (logical-font->font-family [toolkit logical-font] 52 | "Returns the font family for the given `logical-font`. 53 | 54 | `logical-font`: should be one of :monospace :serif :sans-serif")) 55 | 56 | (defprotocol IToolkitSaveImage 57 | :extend-via-metadata true 58 | (save-image 59 | [toolkit dest elem] 60 | [toolkit dest elem [w h :as size]] 61 | "Saves an image of elem to file with name `dest`. 62 | 63 | `dest`: the filename to write the image to. 64 | `elem`: the graphical element to draw 65 | `size`: the width and height of the image. If size is nil, the bounds and origin of elem will be used.")) 66 | 67 | 68 | #?(:clj 69 | (extend-type clojure.lang.Namespace 70 | IToolkit 71 | 72 | IToolkitRun 73 | (run 74 | ([toolkit view-fn] 75 | ((ns-resolve toolkit 'run) view-fn)) 76 | ([toolkit view-fn options] 77 | ((ns-resolve toolkit 'run) view-fn options))) 78 | 79 | IToolkitRunSync 80 | (run-sync [toolkit view-fn] 81 | ((ns-resolve toolkit 'run-sync) view-fn)) 82 | (run-sync [toolkit view-fn options] 83 | ((ns-resolve toolkit 'run-sync) view-fn options)) 84 | 85 | IToolkitFontExists 86 | (font-exists? [toolkit font] 87 | ((ns-resolve toolkit 'font-exists?) font)) 88 | 89 | IToolkitFontMetrics 90 | (font-metrics [toolkit font] 91 | ((ns-resolve toolkit 'font-metrics) font)) 92 | 93 | IToolkitFontAdvanceX 94 | (font-advance-x [toolkit font s] 95 | ((ns-resolve toolkit 'font-advance-x) font s)) 96 | 97 | IToolkitFontLineHeight 98 | (font-line-height [toolkit font] 99 | ((ns-resolve toolkit 'font-line-height) font)) 100 | 101 | IToolkitLogicalFontFontFamily 102 | (logical-font->font-family [toolkit font-class] 103 | ((ns-resolve toolkit 'logical-font->font-family) font-class)) 104 | 105 | IToolkitSaveImage 106 | (save-image 107 | ([toolkit dest elem] 108 | ((ns-resolve toolkit 'save-image) elem)) 109 | ([toolkit dest elem [w h :as size]] 110 | ((ns-resolve toolkit 'save-image) h :as size))))) 111 | 112 | 113 | (comment 114 | (require 'membrane.ui 115 | 'membrane.java2d) 116 | 117 | (run (the-ns 'membrane.java2d) 118 | (constantly (membrane.ui/label "woohoo!" ))) 119 | 120 | (require 'membrane.ui 121 | 'membrane.skia) 122 | (run (the-ns 'membrane.skia) 123 | (constantly (membrane.ui/label "woohoo!" ))) 124 | 125 | ,) 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/membrane/webgl_macros.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.webgl-macros 2 | (:import javax.imageio.ImageIO)) 3 | 4 | (defmacro push-state [ctx & body] 5 | `(let [ctx# ~ctx] 6 | (try 7 | (.save ctx#) 8 | ~@body 9 | (finally 10 | (.restore ctx#))))) 11 | 12 | 13 | (defn image-size-raw [image-path] 14 | (let [image-file (clojure.java.io/file image-path)] 15 | (when (.exists image-file) 16 | (try 17 | (let [buffered-image (ImageIO/read image-file)] 18 | [(.getWidth buffered-image) 19 | (.getHeight buffered-image)]) 20 | (catch Exception e 21 | [0 0]))))) 22 | 23 | 24 | 25 | (defmacro add-image! [url-path image-path-or-bounds] 26 | (let [size (cond 27 | (and (vector? image-path-or-bounds) 28 | (= (count image-path-or-bounds) 2)) 29 | image-path-or-bounds 30 | 31 | (string? image-path-or-bounds) 32 | (image-size-raw image-path-or-bounds) 33 | 34 | :else 35 | (throw (Exception. "image-path-or size should either be a vector with [width height] or a path to an image on the local filesystem.")))] 36 | `(swap! ~'membrane.webgl/images assoc ~url-path 37 | {:image-obj (let [img# (js/Image.)] 38 | (set! (.-src img#) ~url-path) 39 | img#) 40 | :url ~url-path 41 | :size ~size} 42 | ))) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/membrane/webgltest.cljs: -------------------------------------------------------------------------------- 1 | (ns membrane.webgltest 2 | (:require-macros [membrane.webgl-macros 3 | :refer [add-image!]]) 4 | (:require 5 | [membrane.component :refer [defui] 6 | :as component] 7 | membrane.audio 8 | [membrane.webgl :as webgl] 9 | [com.rpl.specter :as spec 10 | :refer [ATOM ALL FIRST LAST MAP-VALS META]] 11 | membrane.basic-components 12 | [membrane.ui :as ui 13 | :refer [vertical-layout 14 | label 15 | width 16 | height 17 | translate 18 | origin-x 19 | origin-y]] 20 | [membrane.example.todo :as todo])) 21 | 22 | (def canvas (.getElementById js/document "canvas")) 23 | 24 | (defui test-ui [{:keys [a b]}] 25 | (let [l (label a) 26 | ] 27 | [(translate 20 20 28 | (membrane.basic-components/textarea {:text a}))] 29 | #_(translate 20 20 30 | ) 31 | #_(ui/with-color [255 0 0] 32 | (ui/rectangle 100 100)) 33 | #_(ui/rectangle 10 10) 34 | #_(vertical-layout 35 | (label "hi there") 36 | (ui/on 37 | :mouse-move 38 | (fn [[x y]] 39 | [[:set $b (str [x y] 40 | (ui/index-for-position ui/default-font a x y))]] 41 | ) 42 | (label a)) 43 | (label b)) 44 | 45 | 46 | ) 47 | ) 48 | 49 | (def enlarge-bottom-button (.getElementById js/document "enlarge-canvas-bottom")) 50 | 51 | (defonce enlargeBottomEventHandler 52 | (doto enlarge-bottom-button 53 | (.addEventListener "mousedown" 54 | (fn [] 55 | (let [style (.-style canvas) 56 | ch (.-clientHeight canvas)] 57 | (set! (.-height style) (str (int (+ ch 200)) "px"))))))) 58 | 59 | (def enlarge-right-button (.getElementById js/document "enlarge-canvas-right")) 60 | 61 | (defonce enlargeRightEventHandler 62 | (doto enlarge-right-button 63 | (.addEventListener "mousedown" 64 | (fn [] 65 | (let [style (.-style canvas) 66 | cw (.-clientWidth canvas)] 67 | (set! (.-width style) (str (int (+ cw 200)) "px"))) 68 | )))) 69 | 70 | 71 | (defn do-update [repaint] 72 | (js/setTimeout 73 | (fn [] 74 | (swap! todo/todo-state 75 | update :todos 76 | conj {:complete? true 77 | :description "third"}) 78 | (repaint) 79 | (do-update repaint)) 80 | 1000)) 81 | 82 | (defn -main [] 83 | (defonce canvas-info (webgl/run 84 | (membrane.component/make-app #'todo/todo-app todo/todo-state) 85 | {:container canvas})) 86 | 87 | (do-update (::webgl/repaint canvas-info))) 88 | 89 | 90 | -------------------------------------------------------------------------------- /test/membrane/defui_test.clj: -------------------------------------------------------------------------------- 1 | (ns membrane.defui-test 2 | (:require [clojure.test :refer :all] 3 | [com.rpl.specter :as spec 4 | :refer [ATOM ALL FIRST LAST MAP-VALS META]] 5 | [clojure.string :as str] 6 | ;; [clojure.spec.gen.alpha :as gen] 7 | [membrane.ui :as ui] 8 | [clojure.test.check.generators :as gen] 9 | [clojure.test.check.properties :as prop] 10 | [clojure.test.check.clojure-test :refer [defspec]] 11 | [clojure.alpha.spec :as s] 12 | [clojure.test.check :as tc] 13 | [membrane.component :refer :all 14 | :as component])) 15 | 16 | 17 | (s/def ::literal boolean?) 18 | 19 | (s/def ::prop simple-keyword?) 20 | 21 | (s/def ::extra map?) 22 | (s/def ::context map?) 23 | (s/def ::defui-call-arg 24 | (s/merge 25 | (s/keys :req [::literal]) 26 | (s/map-of 27 | (s/or :literal #{::literal} 28 | :prop ::prop) 29 | any?) 30 | (s/keys :opt-un [::extra ::context] ))) 31 | 32 | (defui child [{:keys [a b 33 | ^:membrane.component/contextual 34 | c] 35 | :or {a 42} 36 | :as m}] 37 | (ui/on 38 | :mouse-down 39 | (fn [_] 40 | [[:data a b c m]]) 41 | (ui/spacer 10))) 42 | 43 | (defui parent [{:keys [m]}] 44 | (child m)) 45 | 46 | (comment 47 | 48 | (-> (parent {:m {} 49 | :context {:focus 42} 50 | :$context [:foo]}) 51 | (ui/mouse-down [0 0])) 52 | 53 | (-> (parent {:m {:b 42} 54 | :context {:focus 42} 55 | :$context [:foo]}) 56 | (ui/mouse-down [0 0]) 57 | ) 58 | ) 59 | 60 | 61 | (defui seq-nth1 [{:keys [obj]}] 62 | (vec 63 | (for [[k v] obj] 64 | (ui/on 65 | :key-press 66 | (fn [s] 67 | [[:set $v s]]) 68 | )))) 69 | 70 | ;; setting keys doesn't make sense 71 | #_(defui seq-nth2 [{:keys [obj]}] 72 | (vec 73 | (for [[k v] obj] 74 | (ui/on 75 | :key-press 76 | (fn [s] 77 | [[:set $k s]]) 78 | )))) 79 | 80 | (defui seq-nth3 [{:keys [obj]}] 81 | (let [[[k1 v1] [k2 v2] & _more] obj] 82 | (ui/on 83 | :key-press 84 | (fn [s] 85 | [[:set $k1 s] 86 | [:set $v1 s] 87 | [:set $k2 s] 88 | [:set $v2 s]])))) 89 | 90 | (defui seq-nth4 [{:keys [obj]}] 91 | (vec 92 | (for [[k v] obj] 93 | (ui/on 94 | :key-press 95 | (fn [s] 96 | [[:set $v s]]) 97 | )))) 98 | 99 | (deftest seq-nth-test 100 | (testing "seq-nth setting vals" 101 | (let [arg {:obj {:a 1 :b 2}} 102 | intents (ui/key-press 103 | (seq-nth1 arg) 104 | "s")] 105 | (is 106 | (= #{1 2} 107 | (into #{} 108 | (map (fn [[_set path v]] 109 | (spec/select-one (path->spec path) 110 | arg))) 111 | intents))) 112 | 113 | (is 114 | (every? (fn [[_set path v]] 115 | (= :new-val 116 | (spec/select-one (path->spec path) 117 | (spec/setval (path->spec path) 118 | :new-val 119 | arg)))) 120 | intents)) 121 | 122 | (is 123 | (every? 124 | (fn [[_set path v]] 125 | (= 126 | (inc (spec/select-one (path->spec path) 127 | arg)) 128 | (spec/select-one 129 | (path->spec path) 130 | (spec/transform (path->spec path) 131 | inc 132 | arg)))) 133 | intents)))) 134 | 135 | 136 | (let [arg {:obj {:a 1 :b 2 :c 3}} 137 | intents (ui/key-press 138 | (seq-nth3 arg) 139 | "s")] 140 | (is (= #{:a 1 :b 2} 141 | (into #{} 142 | (map (fn [[_set path v]] 143 | (spec/select-one (path->spec path) 144 | arg))) 145 | intents))))) 146 | 147 | 148 | (defui when-let-ui1 [{:keys [obj]}] 149 | (when-let [{:keys [a]} obj] 150 | (ui/on 151 | :key-press 152 | (fn [s] 153 | [[:set $a s]])))) 154 | 155 | (deftest when-let-test 156 | (let [arg {:obj {:a 1}} 157 | intents (ui/key-press 158 | (when-let-ui1 arg) 159 | "s") 160 | [set_ path v :as intent] (first intents)] 161 | (is 162 | (= 1 163 | (spec/select-one (path->spec path) 164 | arg))) 165 | 166 | (is 167 | (= {:obj {:a 2}} 168 | (spec/setval (path->spec path) 169 | 2 170 | arg))) 171 | 172 | (is 173 | (= {:obj {:a 2}} 174 | (spec/transform (path->spec path) 175 | inc 176 | arg)))) 177 | 178 | (let [arg {:obj nil} 179 | intents (ui/key-press 180 | (when-let-ui1 arg) 181 | "s")] 182 | (is (empty? intents)))) 183 | 184 | (defui if-let-ui1 [{:keys [obj]}] 185 | (if-let [{:keys [a]} obj] 186 | (ui/on 187 | :key-press 188 | (fn [s] 189 | [[:set $a s]])))) 190 | 191 | (defui if-let-ui2 [{:keys [obj not-obj]}] 192 | (if-let [{:keys [a]} obj] 193 | (ui/on 194 | :key-press 195 | (fn [s] 196 | [[:set $a s]])) 197 | (ui/on 198 | :key-press 199 | (fn [s] 200 | [[:set $not-obj :obj]])))) 201 | 202 | (deftest if-let-test 203 | (let [arg {:obj {:a 1}} 204 | intents (ui/key-press 205 | (if-let-ui1 arg) 206 | "s") 207 | [set_ path v :as intent] (first intents)] 208 | (is 209 | (= 1 210 | (spec/select-one (path->spec path) 211 | arg))) 212 | 213 | (is 214 | (= {:obj {:a 2}} 215 | (spec/setval (path->spec path) 216 | 2 217 | arg))) 218 | 219 | (is 220 | (= {:obj {:a 2}} 221 | (spec/transform (path->spec path) 222 | inc 223 | arg)))) 224 | 225 | (let [arg {:obj nil} 226 | intents (ui/key-press 227 | (if-let-ui1 arg) 228 | "s")] 229 | (is (empty? intents))) 230 | 231 | (let [arg {:obj {:a 1}} 232 | intents (ui/key-press 233 | (if-let-ui2 arg) 234 | "s") 235 | [set_ path v :as intent] (first intents)] 236 | (is 237 | (= 1 238 | (spec/select-one (path->spec path) 239 | arg))) 240 | 241 | (is 242 | (= {:obj {:a 2}} 243 | (spec/setval (path->spec path) 244 | 2 245 | arg))) 246 | 247 | (is 248 | (= {:obj {:a 2}} 249 | (spec/transform (path->spec path) 250 | inc 251 | arg)))) 252 | 253 | (let [arg {:not-obj 1} 254 | intents (ui/key-press 255 | (if-let-ui2 arg) 256 | "s") 257 | [set_ path v :as intent] (first intents)] 258 | (is 259 | (= 1 260 | (spec/select-one (path->spec path) 261 | arg))) 262 | 263 | (is 264 | (= {:not-obj 2} 265 | (spec/setval (path->spec path) 266 | 2 267 | arg))) 268 | 269 | (is 270 | (= {:not-obj 2} 271 | (spec/transform (path->spec path) 272 | inc 273 | arg))))) 274 | 275 | (defprotocol ITestExtract 276 | :extend-via-metadata true 277 | (-test-extract [elem m])) 278 | 279 | (extend-protocol ITestExtract 280 | nil 281 | (-test-extract [this m] nil) 282 | 283 | Object 284 | (-test-extract [this m] 285 | (mapcat #(-test-extract % m) (ui/children this)))) 286 | 287 | 288 | (defn extract [elem m] 289 | (-test-extract elem m)) 290 | 291 | (defn on-extract [f elem] 292 | (vary-meta elem 293 | assoc `-test-extract (fn [_ m] 294 | (f m)))) 295 | 296 | 297 | (do 298 | (defui non-literal-target [{:keys [a b 299 | has-default 300 | ^::component/contextual 301 | is-context] 302 | :or {has-default 42} 303 | :as arg}] 304 | (on-extract 305 | (fn [m] 306 | (case (:sym m) 307 | a [a $a] 308 | b [b $b] 309 | has-default [has-default $has-default] 310 | is-context [is-context $is-context] 311 | all [[a $a] 312 | [b $b] 313 | [has-default $has-default] 314 | [is-context $is-context]] 315 | arg [arg] 316 | nil)) 317 | [])) 318 | 319 | (defui non-literal-origin [{:keys [m]}] 320 | (let [nle (get extra :foo)] 321 | (non-literal-target (assoc m 322 | :extra nle 323 | ;; :$extra $nle 324 | )))) 325 | (comment 326 | (clojure.pprint/pprint 327 | (extract (non-literal-origin {:m {:a 12 328 | :has-default 4} 329 | :context {:is-context 13}}) 330 | '{:sym arg}))) 331 | , 332 | ) 333 | 334 | 335 | --------------------------------------------------------------------------------