Coder Social home page Coder Social logo

humbleui's Introduction

“When you design a new user interface, you have to start off humbly”

— Steve Jobs presenting Aqua

Humble UI is a desktop UI framework for Clojure. No Electron. No JavaScript. Only JVM and native code.

Goal

  • A way to build high-quality desktop UIs
  • Build better apps that’s possible with web now, while staying cross-platform
  • Build them faster in Clojure

Motivation

  • Create desktop apps in Clojure
  • Electron is a great landmark
    • People prefer native apps to web apps
      • Normal shortcuts, icon, its own window, file system access, notifications, OS integrations
    • Developing real native apps is expensive
    • JavaScript + Chrome have huge overhead, can be slow
  • Java has “UI curse”
    • Previous attempts were cumbersome for user
    • Mimicking native look and feel didn’t work
    • Looked bad
  • Time of declarative UIs is now
    • React
    • Flutter
    • SwiftUI
    • Jetpack Compose
  • Web + Electron cleared the way for non-native look and feel
    • Write once, run everywhere is no longer rejected by users
    • Even native apps have many custom UI elements
  • Flutter proved new UI stack is a feasible task
  • Clojure is the best language for UI development
    • Performant enough to not noticeably lag
    • Access to full power of your computer (multithreaded)
    • REPL + interactive development == instant feedback loop
    • Proven itself great in CLJS world, can do the same on desktop

How is it going to be different

  • No goal to look native, aim for web look (write once, run everywhere)
  • Embrace platform differences in OS integration
  • Declarative UI API is much more pleasant to work with (+plays well with FP)
  • Expose low-level APIs along with high-level API
    • People can solve non-trivial problems in their own way, without hacks
  • Superpowers of Clojure

Architecture

  • Leverage Skia via Skija for high-performance GPU-accelerated graphics
  • JWM (Java Window Manager) for OS integration (simple, common ground, embrace the differences)

Status

Work in progress. No docs, and everything changes every day.

Resources

Slack:

Posts:

Videos:

Sample apps:

Development

Run REPL server:

./script/repl.py

To reload demo app using tools.namespace, evaluate:

(user/reload)

Examples

(require '[io.github.humbleui.ui :as ui])

(def ui
  (ui/default-theme {}
    (ui/center
      (ui/label "Hello from Humble UI! 👋"))))

(ui/start-app!
  (ui/window
    {:title "Humble 🐝 UI"}
    #'ui))

humbleui's People

Contributors

alexander-panin avatar folcon avatar frenchy64 avatar igorhub avatar jelmerderonde avatar jerems avatar oakmac avatar pangloss avatar quezion avatar the-alchemist avatar tonsky avatar trueneu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

humbleui's Issues

nrepl init fails on Windows 10

OS Name: Microsoft Windows 10 Pro
OS Version: 10.0.19045 N/A Build 19045

C:\HumbleUI>git rev-parse HEAD
69faab0123f61c242e34061e8bcaaf772b46d0e1
C:\HumbleUI>python3.exe script\nrepl.py
nREPL server started on port 58078 on host 127.0.0.1 - nrepl://127.0.0.1:58078
nREPL 1.0.0
Clojure 1.11.1
Java HotSpot(TM) 64-Bit Server VM 19.0.1+10-21
Interrupt: Control+C
Exit:      Control+D or (exit) or (quit)
[ 33 ] LayerD3D12.cc:44 Failed to init DX12Device
io.github.humbleui.jwm.LayerNotSupportedException: Failed to init DX12 device
        at io.github.humbleui.jwm.LayerD3D12._nAttach(Native Method)
        at io.github.humbleui.jwm.LayerD3D12.attach(LayerD3D12.java:18)
        at io.github.humbleui.jwm.skija.LayerD3D12Skija.attach(LayerD3D12Skija.java:18)
        at io.github.humbleui.jwm.Window.setLayer(Window.java:64)
        at io.github.humbleui.window$make.invokeStatic(window.clj:114)
        at io.github.humbleui.window$make.invoke(window.clj:30)
        at io.github.humbleui.ui.window$window.invokeStatic(window.clj:42)
        at io.github.humbleui.ui.window$window.invoke(window.clj:9)
        at user$_main$fn__7914$fn__7915.invoke(user.clj:128)
        at clojure.lang.AFn.run(AFn.java:22)
        at io.github.humbleui.jwm.App.lambda$start$1(App.java:35)
        at io.github.humbleui.jwm.App._nStart(Native Method)
        at io.github.humbleui.jwm.App.start(App.java:28)
        at io.github.humbleui.app$start.invokeStatic(app.clj:16)
        at io.github.humbleui.app$start.invoke(app.clj:15)
        at user$_main$fn__7914.invoke(user.clj:125)
        at clojure.core$binding_conveyor_fn$fn__5823.invoke(core.clj:2047)
        at clojure.lang.AFn.call(AFn.java:18)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1589)
user=>

`NixOS/Wayland` debug session: with multiple text inputs, text inputs are occasionally unfocused

Hey! Set up HumbleUI today and tried out on my Wayland/NixOS machine.

Had some minor issues with the Wordle game and text input: it seems that my cursor would inconsistently lose focus when multiple text inputs were visible on the screen, preventing me from continuing to type.

Here's the nix environment I used to run HumbleUI (happy to contribute/support Nix upstream!): https://github.com/jakeisnt/HumbleUI/blob/390efdf14a0351e0bc35962eb0b116fb64c29c7b/flake.nix

Here's the screencast I took of the debugging session: https://youtu.be/h7_An8ZbfZA
I tried using a program to show what keys I was pressing, but had limited success with the tiled WM (Sway).

No errors were shown in the REPL besides in that error tab.

Happy to debug further and/or supply more information!

It also might be helpful to get some 'ground truth' video of all of the demos or other important sample applications to compare to - this seems like a much lower lift than any sort of UI testing ATM, and it would make assessing whether behavior is incorrect very easy.

Crash on two nested dynamics

(copied from #13 (comment), cc @Folcon)

PS: Just testing out the new commit:

By doing this:
(def label
  (ui/dynamic ctx [{:keys [font-ui leading fill-text]} ctx]
    (ui/label "Hello from Humble UI! 👋" font-ui fill-text)))

(def app
  (ui/dynamic ctx [scale (:scale ctx)]
    label))

It's possible to create a fatal error:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000014da70c19, pid=51097, tid=775
#
# JRE version: OpenJDK Runtime Environment Temurin-17.0.1+12 (17.0.1+12) (build 17.0.1+12)
# Java VM: OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (17.0.1+12, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-amd64)
# Problematic frame:
# C  [libskija_x64.dylib+0x270c19]  SkFont::refTypefaceOrDefault() const+0x9
#

I mean not passing ui/dynamic vars is bad, but probably shouldn't kill the jvm =)...

It's surprisingly easy to do when you're fiddling around in the REPL, once I had my app structure it was fine, but send one wrong REPL form and 💥.

UI Fragments

So I've been laying out two different application UI views and I'm finding I'm really missing the ability to add ui fragments

They're really useful to be able to conditionally insert collections:

(when something?
  (for [item some-coll]
    (ui/label item font fill)))

How do I start the app over the repl?

I have a simple main function

(defn -main [& args]
  (apply nrepl/-main args))

I'm starting the app with env JWM_VERBOSE=true clj -M:macos-x64:dev and then after connecting I evaluate this in the repl

(do
  (hui/init)
  (def *window (atom nil))
  (hui/doui
   (reset! *window
           (doto (window/make
                  {:on-close #(reset! *window nil)
                   :on-paint (fn [^Canvas canvas]
                               {:pre [(some? canvas) (some? *window)]}
                               (when-some [window (deref *window)]
                                 (.clear canvas (unchecked-int 0xFFEEEEEE))
                                 (with-open [paint (Paint.)]
                                   (.setColor paint (unchecked-int 0xFFCC3333))
                                   (when-some [jwm-window (window/jwm-window window)]
                                     (let [bounds  (.getContentRect jwm-window)
                                           scale   (.getScale (.getScreen jwm-window))
                                           rect    (Rect/makeXYWH (* -5 scale) (* -5 scale) (* 10 scale) (* 10 scale))
                                           angle   (mod (/ (System/currentTimeMillis) 5) 360)]
                                       (doseq [x [(* 15 scale) (- (.getWidth bounds) (* 15 scale))]
                                               y [(* 15 scale) (- (.getHeight bounds) (* 15 scale))]]
                                         (.save canvas)
                                         (.translate canvas x y)
                                         (.rotate canvas angle)
                                         (.drawRect canvas rect paint)
                                         (.restore canvas)))))
                                 (window/request-frame window)))})
             (window/set-title "Hello from Humble UI")
             (window/set-visible true)
             (window/set-z-order :normal)
             (window/request-frame))))
  (hui/doui (hui/start)))

The terminal prints Loading /var/folders/mh/4bm2381x1vnftjrgqwdnmf4m0000gp/T/jwm_0.2.6/libjwm_x64.dylib but nothing else happens.
If I kill the repl with sesman-quit then the terminal prints some stuff but I think it's just noise and not very related or helpful.

Creating a canvas for drawing

I might be wrong about this, but it seems like a simple stopgap measure is creating something like a Canvas that fits in as a UI widget.

Which is then used to draw stuff in? So I can wrap circles and rects in clickables etc, while being able to absolutely position them. While still being able to put buttons and things on the more panel parts of the UI.

This should allow me to create a "camera" with say simple 2d rendering to start where I can get mouse and keyboard scrolling/zoom working with the ability clip stuff that's "off-screen".

Maybe something akin to this:

(deftype DrawCanvas [on-paint on-event width height ^:unsynchronized-mutable child-rect]
  ui/IComponent
  (-layout [_ ctx cs]
    (set! child-rect (IRect/makeXYWH 0 0 width height))
    (IPoint. width height))

  (-draw [_ ctx canvas]
    (on-paint ctx canvas))

  (-event [_ event]
    (on-event event child-rect))

  AutoCloseable
  (close [_]
    (ui/child-close child)))

(defn canvas [{:keys [on-paint on-event width height] :as args}]
  (DrawCanvas. on-paint on-event width height nil))

Which is really rough / definitely broken =)... I'm still trying to get a sense of how this is going to work, I'm not overly concerned about committing you to any API, more just trying to get some broad strokes so I can start working out how to splat stuff up on the screen. I can always rewrite this bit once you have a clearer idea of how you want this stuff to work =)... This particular part for me is all circles, rectangles, arcs and lines.

I've written stuff in quil so I'm ok working with reasonably basic building blocks. Just trying to work out how to plug bits together =)...

If necessary I could just use roguelike UI to start, after all I can render text and emoji, which I think can go reasonably far, but I'd like to know if that's where I should focus on to start or whether I can go 2d graphics for now.

Or this is completely the wrong way to think about this?

Using `with-open` blows up

(ui/default-theme
  {}
  (with-open [purple (paint/fill 0xFF9F2B68)]
    (ui/rect purple
      (ui/label (str "Apple")))))

Blows up with:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0000000152a3c309, pid=70244, tid=775
#
# JRE version: OpenJDK Runtime Environment Homebrew (19.0.1) (build 19.0.1)
# Java VM: OpenJDK 64-Bit Server VM Homebrew (19.0.1, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-amd64)
# Problematic frame:
# C  [libskija.dylib+0x2f0309]  _ZNK7SkPaint13nothingToDrawEv+0x9
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/folcon/Code/pankration/hs_err_pid70244.log
#
# If you would like to submit a bug report, please visit:
#   https://github.com/Homebrew/homebrew-core/issues
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

However, the below does not:

(ui/default-theme
  {}
  (ui/rect (paint/fill 0xFF9F2B68)
    (ui/label (str "Apple"))))

Both return io.github.humbleui.ui.dynamic.Contextual and io.github.humbleui.ui.rect.Rect:

;; From the first form
=> #object[io.github.humbleui.ui.dynamic.Contextual 0x19813f6 "io.github.humbleui.ui.dynamic.Contextual@19813f6"]

;; From the second form
=> #object[io.github.humbleui.ui.dynamic.Contextual 0x2aff357a "io.github.humbleui.ui.dynamic.Contextual@2aff357a"]

This could just be because io.github.humbleui.skija.Paint is closed with the with-open version when it comes time to render.

I'm only mentioning it as it's something I picked up from your canvas example, it might be worthwhile changing that to use colours set using with-context higher up the app so reusing this sort of logic doesn't break? Unless there's another reason for it?

Nested ui/draggable break themselves

When nesting draggables the parent draggable size expands outside of the window (see screenshot).
The child draggable does not work at all.

Expected behaviour:

  • Parent size stays the same
  • Child dragging should be prioritised when, parent shouldn't move.

Example code for the examples.backdrop/square function to trigger the behaviour:

(defn square [name filter color]
  (let [color (unchecked-int color)
        a     (Color/getA color)
        r     (Color/getR color)
        g     (Color/getG color)
        b     (Color/getB color)]
    (ui/clip-rrect 8
      (ui/backdrop filter
        (ui/stack
          (ui/rect (paint/fill color)
            (ui/gap 100 100))
          (ui/center
            (ui/column
              (ui/label name)
              (ui/gap 0 10)
              (ui/label (format "Fill: #%02X%02X%02X" r g b))
              (ui/gap 0 10)
              (ui/label (format "Opacity: %d%%" (math/round (/ a 2.55))))
              (ui/gap 0 20)
              (ui/draggable {:pos (core/ipoint 10 10)}
                            (ui/rect (paint/fill 0xFFFFFF00)
                                     (ui/gap 10 10))))))))))

Screenshot 2022-12-22 at 21 54 58

Nested vscrolls crash

(copied from #13 (comment), cc @Folcon)

vscroll doesn't seem to like nesting too much:

java.lang.IllegalArgumentException: IRect::makeXYWH expected h >= 0, got: -18
        at io.github.humbleui.types.IRect.makeXYWH(IRect.java:57)
        at io.github.humbleui.ui.WithContext._draw(ui.clj:438)
        at io.github.humbleui.ui.Column._draw(ui.clj:144)
        at io.github.humbleui.ui.Contextual._draw(ui.clj:393)
        at io.github.humbleui.ui$draw.invokeStatic(ui.clj:14)
        at io.github.humbleui.ui$draw.invoke(ui.clj:12)
        at fruit_economy.core$on_paint.invokeStatic(core.clj:579)
        at fruit_economy.core$on_paint.invoke(core.clj:574)
        at clojure.lang.Var.invoke(Var.java:388)
        at io.github.humbleui.window$make$reify__16155.accept(window.clj:55)
        at io.github.humbleui.jwm.Window.accept(Window.java:352)
        at io.github.humbleui.jwm.skija.LayerMetalSkija.frame(LayerMetalSkija.java:27)
        at io.github.humbleui.jwm.Window.accept(Window.java:347)
        at io.github.humbleui.jwm.Window.accept(Window.java:357)
        at io.github.humbleui.jwm.Window.accept(Window.java:11)
        at io.github.humbleui.jwm.WindowMac._nSetWindowSize(Native Method)
        at io.github.humbleui.jwm.WindowMac.setWindowSize(WindowMac.java:52)
        at io.github.humbleui.window$set_window_size.invokeStatic(window.clj:89)
        at io.github.humbleui.window$set_window_size.invokePrim(window.clj)
        at fruit_economy.core$make_window.invokeStatic(core.clj:661)
        at fruit_economy.core$make_window.invoke(core.clj:643)
        at fruit_economy.core$_main.invokeStatic(core.clj:673)
        at fruit_economy.core$_main.doInvoke(core.clj:666)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at clojure.lang.Var.invoke(Var.java:380)
        at user$eval15905.invokeStatic(form-init1419638689647398828.clj:1)
        at user$eval15905.invoke(form-init1419638689647398828.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7181)
        at clojure.lang.Compiler.eval(Compiler.java:7171)
        at clojure.lang.Compiler.load(Compiler.java:7640)
        at clojure.lang.Compiler.loadFile(Compiler.java:7578)
        at clojure.main$load_script.invokeStatic(main.clj:475)
        at clojure.main$init_opt.invokeStatic(main.clj:477)
        at clojure.main$init_opt.invoke(main.clj:477)
        at clojure.main$initialize.invokeStatic(main.clj:508)
        at clojure.main$null_opt.invokeStatic(main.clj:542)
        at clojure.main$null_opt.invoke(main.clj:539)
        at clojure.main$main.invokeStatic(main.clj:664)
        at clojure.main$main.doInvoke(main.clj:616)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:40)

The above stacktrace was produced with this, I've left the vscrollbar commented out, but putting back there doesn't change anything, if blow-up? is true, it blows up, otherwise it doesn't:

(def app
  (ui/dynamic ctx [scale (:scale ctx)]
    (let [font-ui   (Font. face-default (float (* 13 scale)))
          leading   (-> font-ui .getMetrics .getCapHeight (/ scale) Math/ceil)
          fill-text (doto (Paint.) (.setColor (unchecked-int 0xFF000000)))
          blow-up? false]
      (ui/stretch
        (ui/column
          (ui/row
            (ui/label "Some text" font-ui fill-text))
          (ui/stretch
            (ui/with-context {:font-ui   font-ui
                              :leading   leading
                              :fill-text fill-text}
              (ui/row
                (ui/label "Some Other Text" font-ui fill-text))))
          (ui/row
            (if blow-up?
              ;(ui/vscrollbar)
              (ui/vscroll
                (ui/column
                  (ui/label "Some Other Text" font-ui fill-text)))
              (ui/label "Some Other Text" font-ui fill-text))))))))

This should be reproducible, I made sure that make_window and on_paint listed are straight copies of the ones found here.

The reason I wanted to insert a vscroll there is to have a message log of sorts centered at the bottom of the window.

Hover gets deselected on re-render

This is probably an obvious issue, but when you have a button that's in its hover state:
image

If you click on it, and doing so updates a value which triggers a re-render, the button re-draw means it's no longer being hovered, which means you have to move the mouse to regain the hover.

Is this a:

  • There's someway of passing that the button is already being hovered over during the render, or
  • Ensure that buttons can never be re-drawn by clicking on them by careful use of ui/dynamic, or
  • Ensure you always correctly set button hover state at the top level using ui/with-context

Though the latter option seems like it requires a lot of bookkeeping.

EDIT:
Ok it looks like it can be done by being a lot more careful around how I redraw the buttons, but it would still be nice if it's possible to pass to the button that it's already being hovered over.

EDIT:
Now I remember why this bugs me, the checkbox does not behave like this. So maybe there's some bug in the button?
🤦 because it doesn't have a hover state, don't mind me.

Isolate component exceptions

The recent changes seem to be throwing this up a lot, makes working in the REPL challenging.

It seems to happen when I'm just clicking around in the UI.

java.lang.NullPointerException: Cannot invoke "io.github.humbleui.types.IRect.contains(io.github.humbleui.types.IPoint)" because "this.child_rect" is null
        at io.github.humbleui.ui.Clickable._event(ui.clj:591)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.Row$fn__18986.invoke(ui.clj:325)
        at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
        at clojure.core$reduce.invokeStatic(core.clj:6885)
        at clojure.core$reduce.invoke(core.clj:6868)
        at io.github.humbleui.ui.Row._event(ui.clj:323)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.Column$fn__18916.invoke(ui.clj:263)
        at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
        at clojure.core$reduce.invokeStatic(core.clj:6885)
        at clojure.core$reduce.invoke(core.clj:6868)
        at io.github.humbleui.ui.Column._event(ui.clj:261)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.Column$fn__18916.invoke(ui.clj:263)
        at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
        at clojure.core$reduce.invokeStatic(core.clj:6885)
        at clojure.core$reduce.invoke(core.clj:6868)
        at io.github.humbleui.ui.Column._event(ui.clj:261)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.KeyListener._event(ui.clj:759)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.WithContext._event(ui.clj:39)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.Row$fn__18986.invoke(ui.clj:325)
        at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
        at clojure.core$reduce.invokeStatic(core.clj:6885)
        at clojure.core$reduce.invoke(core.clj:6868)
        at io.github.humbleui.ui.Row._event(ui.clj:323)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.WithContext._event(ui.clj:39)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.WithContext._event(ui.clj:39)
        at io.github.humbleui.core$event_child.invokeStatic(core.clj:152)
        at io.github.humbleui.core$event_child.invoke(core.clj:150)
        at io.github.humbleui.ui.dynamic.Contextual._event(dynamic.clj:28)
        at io.github.humbleui.core$event.invokeStatic(core.clj:148)
        at io.github.humbleui.core$event.invoke(core.clj:147)
        at fruit_economy.core$on_event.invokeStatic(core.clj:2062)
        at fruit_economy.core$on_event.invoke(core.clj:2061)
        at clojure.lang.Var.invoke(Var.java:388)
        at io.github.humbleui.window$make$reify__18438.accept(window.clj:35)
        at io.github.humbleui.jwm.Window.accept(Window.java:369)
        at io.github.humbleui.jwm.Window.accept(Window.java:11)
        at io.github.humbleui.jwm.App._nStart(Native Method)
        at io.github.humbleui.jwm.App.start(App.java:28)
        at io.github.humbleui.app$start.invokeStatic(app.clj:12)
        at io.github.humbleui.app$start.invoke(app.clj:11)
        at fruit_economy.core$_main.invokeStatic(core.clj:2163)
        at fruit_economy.core$_main.doInvoke(core.clj:2156)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at clojure.lang.Var.invoke(Var.java:380)
        at user$eval17949.invokeStatic(form-init4942226896247872941.clj:1)
        at user$eval17949.invoke(form-init4942226896247872941.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7194)
        at clojure.lang.Compiler.eval(Compiler.java:7184)
        at clojure.lang.Compiler.load(Compiler.java:7653)
        at clojure.lang.Compiler.loadFile(Compiler.java:7591)
        at clojure.main$load_script.invokeStatic(main.clj:475)
        at clojure.main$init_opt.invokeStatic(main.clj:477)
        at clojure.main$init_opt.invoke(main.clj:477)
        at clojure.main$initialize.invokeStatic(main.clj:508)
        at clojure.main$null_opt.invokeStatic(main.clj:542)
        at clojure.main$null_opt.invoke(main.clj:539)
        at clojure.main$main.invokeStatic(main.clj:664)
        at clojure.main$main.doInvoke(main.clj:616)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:40)

Let me know if you need a specific reproducible case.

EDIT:

Same issue comes up on VScroll

with-bounds jitter

Oh cool! I was going to participate too, but timing this year is really bad

Shame you couldn't do it.

I've been using with-bounds:

(ui/with-bounds ::bounds
    (ui/dynamic ctx [width (:width (::bounds ctx))
                     height (:height (::bounds ctx))]
   ...))

And it seems to make using dynamic irrelevant. My cpu spins back up to ~80%.

I'm only mentioning it as it seems to be the only component that's behaving this way.

It's not a big problem, I'm working around it by passing the sizes down from the top level, but it's a little clunkier than it should be I think.

I'm going to experiment a bit more tomorrow just in case I'm using it incorrectly, but happy to open an issue if this is a bug.

This is strange, I took WithBounds and tried to print out the measurement values it was seeing and it seems there's some jitter effect:

(-measure [_ ctx cs]
  (let [width  (-> (:width cs) (/ (:scale ctx)))
        height (-> (:height cs) (/ (:scale ctx)))
        point (IPoint. width height)
        val (get ctx key)
        _ (println :val val :point point)]
    (if-not val
      (-measure child (assoc ctx key point) cs)
      (-measure child ctx cs))))

printout while I'm not doing anything so this shouldn't happen from what I understand:

:val nil :point #object[io.github.humbleui.types.IPoint 0x73e8a384 IPoint(_x=888, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x1f2fe8b1 IPoint(_x=794, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x6dbc65ef IPoint(_x=888, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x1638733c IPoint(_x=794, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x20fa06fc IPoint(_x=888, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x40f00832 IPoint(_x=794, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x4eed8ede IPoint(_x=888, _y=494)]
:val nil :point #object[io.github.humbleui.types.IPoint 0x1b99a5ac IPoint(_x=794, _y=494)]

I don't have time right now to try and repro this with a smaller example, but I'll try and do that later and see if this is being caused by me or something happening in the lib.

Originally posted by @Folcon in #13 (comment)

On Linux, I don’t receive EventMouseMove unless a button is pressed

I test it by inserting a println in on-event function and then moving my mouse across the window.

(defn on-event [window event]
  (println event)
  ;; ...
  )

I get lots of events like these when I press one of the buttons (or both):


#object[io.github.humbleui.jwm.EventMouseMove 0x45801322 EventMouseMove(_x=106, _y=712, _buttons=1, _modifiers=0)]
#object[io.github.humbleui.jwm.EventMouseMove 0x7e3d2ebd EventMouseMove(_x=161, _y=602, _buttons=2, _modifiers=0)]
#object[io.github.humbleui.jwm.EventMouseMove 0x272778ae EventMouseMove(_x=258, _y=283, _buttons=3, _modifiers=0)]

Not a single event with _buttons=0.

Mouse scroll events with mouse coordinates

Hey, I'd like to improve the VScroll object to only scroll when the mouse is over the control being scrolled. As it is now, if there are multiple vscroll areas, they all respond to all mouse scroll events and scroll in unison.

To implement this I think the scroll event needs to include the mouse coordinates. If the coordinates are included, it should be easy to use them to filter the events for the control.

An alternative may be to use hoverable to decide when to respond to scroll events, but this doesn't work quite right at the moment either, because mouse-move is only received when the window is focused. To feel correct, scroll functionality should work the same regardless of focus. I think this option is less good because it also entails the app receiving a lot more events when the window is not focused.

Thanks!
Darrick

Jack-in fails on Windows 11

I get the below error when I try to jack-in from VSC on Windows 11. Needless to say this is a 64 OS and platform so I am confused by the error.

java -jar ".calva\deps.clj.jar" -Sdeps "{:deps {nrepl/nrepl {:mvn/version,""1.0.0""},cider/cider-nrepl {:mvn/version,""0.28.5""}}}" -M:dev
Could not find C:\Users\marti\.deps.clj\1.11.1.1155\ClojureTools\clojure-tools-1.11.1.1155.jar
Downloading tools jar from https://download.clojure.org/install/clojure-tools-1.11.1.1155.zip to C:\Users\marti\.deps.clj\1.11.1.1155\ClojureTools
Exception in thread "main" 
Syntax error macroexpanding at (core.clj:27:3).
        at clojure.lang.Compiler$StaticMethodExpr.eval(Compiler.java:1750)
        at clojure.lang.Compiler$DefExpr.eval(Compiler.java:457)
        at clojure.lang.Compiler.eval(Compiler.java:7199)
        at clojure.lang.Compiler.load(Compiler.java:7653)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:372)
        at clojure.lang.RT.load(RT.java:459)
        at clojure.lang.RT.load(RT.java:424)
        at clojure.core$load$fn__6908.invoke(core.clj:6161)
        at clojure.core$load.invokeStatic(core.clj:6160)
        at clojure.core$load.doInvoke(core.clj:6144)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invokeStatic(core.clj:5933)
        at clojure.core$load_one.invoke(core.clj:5928)
        at clojure.core$load_lib$fn__6850.invoke(core.clj:5975)
        at clojure.core$load_lib.invokeStatic(core.clj:5974)
        at clojure.core$load_lib.doInvoke(core.clj:5953)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$load_libs.invokeStatic(core.clj:6016)
        at clojure.core$load_libs.doInvoke(core.clj:6000)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$require.invokeStatic(core.clj:6038)
        at clojure.core$require.doInvoke(core.clj:6038)
        at clojure.lang.RestFn.invoke(RestFn.java:551)
        at io.github.humbleui.debug$eval150$loading__6789__auto____151.invoke(debug.clj:1)
        at io.github.humbleui.debug$eval150.invokeStatic(debug.clj:1)
        at io.github.humbleui.debug$eval150.invoke(debug.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7194)
        at clojure.lang.Compiler.eval(Compiler.java:7183)
        at clojure.lang.Compiler.load(Compiler.java:7653)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:372)
        at clojure.lang.RT.load(RT.java:459)
        at clojure.lang.RT.load(RT.java:424)
        at clojure.core$load$fn__6908.invoke(core.clj:6161)
        at clojure.core$load.invokeStatic(core.clj:6160)
        at clojure.core$load.doInvoke(core.clj:6144)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invokeStatic(core.clj:5933)
        at clojure.core$load_one.invoke(core.clj:5928)
        at clojure.core$load_lib$fn__6850.invoke(core.clj:5975)
        at clojure.core$load_lib.invokeStatic(core.clj:5974)
        at clojure.core$load_lib.doInvoke(core.clj:5953)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$load_libs.invokeStatic(core.clj:6016)
        at clojure.core$load_libs.doInvoke(core.clj:6000)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$require.invokeStatic(core.clj:6038)
        at clojure.core$require.doInvoke(core.clj:6038)
        at clojure.lang.RestFn.invoke(RestFn.java:436)
        at examples.settings$eval144$loading__6789__auto____145.invoke(settings.clj:1)
        at examples.settings$eval144.invokeStatic(settings.clj:1)
        at examples.settings$eval144.invoke(settings.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7194)
        at clojure.lang.Compiler.eval(Compiler.java:7183)
        at clojure.lang.Compiler.load(Compiler.java:7653)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:372)
        at clojure.lang.RT.load(RT.java:459)
        at clojure.lang.RT.load(RT.java:424)
        at clojure.core$load$fn__6908.invoke(core.clj:6161)
        at clojure.core$load.invokeStatic(core.clj:6160)
        at clojure.core$load.doInvoke(core.clj:6144)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invokeStatic(core.clj:5933)
        at clojure.core$load_one.invoke(core.clj:5928)
        at clojure.core$load_lib$fn__6850.invoke(core.clj:5975)
        at clojure.core$load_lib.invokeStatic(core.clj:5974)
        at clojure.core$load_lib.doInvoke(core.clj:5953)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$load_libs.invokeStatic(core.clj:6016)
        at clojure.core$load_libs.doInvoke(core.clj:6000)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$require.invokeStatic(core.clj:6038)
        at clojure.core$require.doInvoke(core.clj:6038)
        at clojure.lang.RestFn.invoke(RestFn.java:930)
        at user$eval138$loading__6789__auto____139.invoke(user.clj:1)
        at user$eval138.invokeStatic(user.clj:1)
        at user$eval138.invoke(user.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7194)
        at clojure.lang.Compiler.eval(Compiler.java:7183)
        at clojure.lang.Compiler.load(Compiler.java:7653)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:368)
        at clojure.lang.RT.maybeLoadResourceScript(RT.java:364)
        at clojure.lang.RT.doInit(RT.java:486)
        at clojure.lang.RT.init(RT.java:467)
        at clojure.main.main(main.java:38)
Caused by: java.lang.UnsatisfiedLinkError: C:\Users\marti\AppData\Local\Temp\skija_0.105.0\skija.dll: Can't load AMD 64-bit .dll on a IA 32-bit platform
        at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
        at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:384)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:228)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:170)
        at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2389)
        at java.base/java.lang.Runtime.load0(Runtime.java:755)
        at java.base/java.lang.System.load(System.java:1953)
        at io.github.humbleui.skija.impl.Library.load(Library.java:73)
        at io.github.humbleui.skija.impl.Library.staticLoad(Library.java:47)
        at io.github.humbleui.skija.shaper.Shaper.<clinit>(Shaper.java:15)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:167)
        at clojure.lang.Compiler$StaticMethodExpr.eval(Compiler.java:1743)
        ... 93 more
Jack-in process exited. Status: 1

Example app won't run (Can't find primary screen)

Example app crashes at start with java.lang.IllegalStateException.

I'm on Ubuntu, in case it's important.

It used to work when I tried HumbleUI the last time a few weeks back.

{:clojure.main/message
 "Execution error (IllegalStateException) at io.github.humbleui.jwm.App/getPrimaryScreen (App.java:110).\nCan't find primary screen\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.IllegalStateException,
  :clojure.error/line 110,
  :clojure.error/cause "Can't find primary screen",
  :clojure.error/symbol io.github.humbleui.jwm.App/getPrimaryScreen,
  :clojure.error/source "App.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type java.lang.IllegalStateException,
    :message "Can't find primary screen",
    :at [io.github.humbleui.jwm.App getPrimaryScreen "App.java" 110]}],
  :trace
  [[io.github.humbleui.jwm.App getPrimaryScreen "App.java" 110]
   [io.github.humbleui.core$primary_screen invokeStatic "core.clj" 77]
   [io.github.humbleui.core$primary_screen invoke "core.clj" 76]
   [user$make_window invokeStatic "user.clj" 100]
   [user$make_window invoke "user.clj" 93]
   [user$_main invokeStatic "user.clj" 119]
   [user$_main doInvoke "user.clj" 116]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.core$apply invokeStatic "core.clj" 667]
   [clojure.main$main_opt invokeStatic "main.clj" 514]
   [clojure.main$main_opt invoke "main.clj" 510]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "Can't find primary screen"}}

ClojureScript plus CanvasKit = Browser target?

I have been perusing the next-gen UI toolkit domain for a while, and I think the two most interesting efforts are this project and Makepad.

Makepad is targeting desktop as well as the browser. The browser is a somewhat degraded experience, but plenty good enough to be useful for demos.

Do you think HumbleUI has any chance of supporting the browser as a target? At first glance, it looks like Skija+JWM can be replaced with CanvasKit getting called through ClojureScript...

`*window` is `nil` after `cider-connect-clj` and middleware is `cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor`

The demo ui with 4 rotating boxes comes up but about a second after I connect with emacs, the app visibly freezes (the boxes stop rotating). I can deref *window and see that it's nil.

$ git diff
diff --git a/deps.edn b/deps.edn
index 2842623..c541b04 100644
--- a/deps.edn
+++ b/deps.edn
@@ -19,4 +19,6 @@

   :dev
   {:extra-paths ["dev"]
-   :extra-deps  {nrepl/nrepl {:mvn/version "0.8.3"}}}}}
\ No newline at end of file
+   :extra-deps {cider/cider-nrepl {:mvn/version "0.25.9"}
+                refactor-nrepl/refactor-nrepl {:mvn/version "3.1.0"}}
+   :main-opts ["-m" "user" "--middleware" "[cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor]"]}}}
$ clj -A:windows:dev
WARNING: Use of :main-opts with -A is deprecated. Use -M instead.
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: refactor-nrepl.inlined-deps.orchard.v0v7v3.orchard.misc, being replaced by: #'refactor-nrepl.inlined-deps.orchard.v0v7v3.orchard.misc/update-vals
WARNING: update-keys already refers to: #'clojure.core/update-keys in namespace: refactor-nrepl.inlined-deps.orchard.v0v7v3.orchard.misc, being replaced by: #'refactor-nrepl.inlined-deps.orchard.v0v7v3.orchard.misc/update-keys
nREPL server started on port 54259 on host kubernetes.docker.internal - nrepl://kubernetes.docker.internal:54259
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: cider.nrepl.inlined-deps.orchard.v0v6v5.orchard.misc, being replaced by: #'cider.nrepl.inlined-deps.orchard.v0v6v5.orchard.misc/update-vals
WARNING: update-keys already refers to: #'clojure.core/update-keys in namespace: cider.nrepl.inlined-deps.orchard.v0v6v5.orchard.misc, being replaced by: #'cider.nrepl.inlined-deps.orchard.v0v6v5.orchard.misc/update-keys
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-vals
WARNING: update-keys already refers to: #'clojure.core/update-keys in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-keys
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-vals
WARNING: update-keys already refers to: #'clojure.core/update-keys in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-keys
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.passes, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-vals
WARNING: update-vals already refers to: #'clojure.core/update-vals in namespace: refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.passes.uniquify, being replaced by: #'refactor-nrepl.inlined-deps.toolsanalyzer.v1v0v0.clojure.tools.analyzer.utils/update-vals

Using this as a lib or not launching via python script

This may be too early in the project for this to be viable, but I'm trying to see how to start either using this as a lib or using the code in this lib without having to call the python scripts.

I'm using leiningen which might not be the best way to do this.

Here's the code which I'm going to try and see if I can get working =)...

%  lein trampoline repl
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
Compiling 9 source files to ~/Code/clojure/fruit-economy/target/classes
~/Code/clojure/fruit-economy/src/java/module-info.java:4: error: module not found: lombok
    requires static lombok;
                    ^
~/Code/clojure/fruit-economy/src/java/module-info.java:5: error: module not found: org.jetbrains.annotations
    requires static org.jetbrains.annotations;
                                 ^
~/Code/clojure/fruit-economy/src/java/module-info.java:6: error: module not found: clojure
    requires clojure;
             ^
3 errors
Compilation of Java sources(lein javac) failed.

Not quite sure what part of the python script I'm missing, hopefully will have a chance this weekend to dig in and find out. Pretty excited about this.

I can't figure out how to set the window size at startup.

I'm basically asking for help debugging this... The behavior is clearly absurd though.

(ns user
  (:require
   [clojure.core.async :as async :refer [>! <! >!! <!!]]
   [clojure.spec.alpha :as s]
   [io.github.humbleui.core :as hui]
   [io.github.humbleui.window :as window]
   [lentes.core :as l]
   [nrepl.cmdline :as nrepl]
   [rum.core :as rum]
   [environ.core :as env])
  (:import
   [io.github.humbleui.jwm
    App
    Event
    EventFrame
    EventKey
    EventWindowMove
    EventWindowResize]
   [io.github.humbleui.window Window]
   [io.github.humbleui.skija
    BackendRenderTarget
    Canvas
    Color4f
    ColorSpace
    DirectContext
    Font
    FontStyle
    FontMgr
    FramebufferFormat
    Paint
    PaintMode
    PaintStrokeCap
    Rect
    Surface
    SurfaceColorFormat
    SurfaceOrigin
    Typeface]
   [io.github.humbleui.jwm
    MouseCursor]
   [java.io FileNotFoundException]
   ))

(def *window (atom nil))

(defn persist-to-disk
  [data file-name]
  {:pre [(string? file-name)]}
  (spit file-name (pr-str data)))

(defn unpersist-from-disk
  [file-name]
  {:pre [(string? file-name)]}
  (try (read-string (slurp file-name))
       (catch FileNotFoundException e)))

(def app-state-save-location "app-state.edn")
(def app-state (atom (or (unpersist-from-disk app-state-save-location) {})))
(def last-win-pos (rum/cursor-in app-state [:last-win-pos]))
(def app-state-save-queue (async/chan (async/sliding-buffer 1)))
(def stdout (async/chan (async/dropping-buffer 100)))

(s/def :win/left int?)
(s/def :win/top int?)
(s/def :win/width int?)
(s/def :win/height int?)
(s/def ::window-position (s/keys :req [:win/left :win/top :win/width :win/height]))

(s/fdef set-window-position!
  :args (s/cat :window-position ::window-position)
  :ret nil)

(defn set-window-position!
  [^Window window {:win/keys [left top width height] :as m}]
  {:pre [(int? left) (int? top) (int? width) (int? height)]}
  (>!! stdout (format "setting window position to %s" (str m)))
  (.setWindowPosition (window/jwm-window window) left top)
  (.setWindowSize (window/jwm-window window) width height))

(defn ^:redef on-paint [^Canvas canvas]
  (when-some [^Window window @*window]
    (.clear canvas (unchecked-int 0xFFEEEEEE))
    (with-open [paint (Paint.)]
      (.setColor paint (unchecked-int 0xFFCC3333))
      (when-some [jwm-window (window/jwm-window window)]
        (let [bounds  (.getContentRect jwm-window)
              scale   (.getScale (.getScreen jwm-window))
              rect    (Rect/makeXYWH (* -5 scale) (* -5 scale) (* 10 scale) (* 10 scale))
              angle   (mod (/ (System/currentTimeMillis) 5) 360)]
          (doseq [x [(* 15 scale) (- (.getWidth bounds) (* 15 scale))]
                  y [(* 15 scale) (- (.getHeight bounds) (* 15 scale))]]
            (.save canvas)
            (.translate canvas x y)
            (.rotate canvas angle)
            (.drawRect canvas rect paint)
            (.restore canvas)))))
    (window/request-frame window)))

(defn ^:redef on-event
  [^Event event]
  (cond
    (instance? EventWindowMove event)
    (doto
        (swap! last-win-pos merge {:win/left (.-_windowLeft event)
                                   :win/top (.-_windowTop event)})
        #(>!! stdout (format "window moved to %s" (str %))))
    (instance? EventWindowResize event)
    (doto
        (swap! last-win-pos merge {:win/width (.-_windowWidth event)
                                   :win/height (.-_windowHeight event)})
        #(>!! stdout (format "window resized to %s" (str %))))))

(defn make-window []
  (>!! stdout (format "last window position in make-window %s" (pr-str (deref last-win-pos))))
  (doto
      (window/make
       {:on-close #(reset! *window nil)
        :on-paint #'on-paint
        :on-event #'on-event})
      (window/set-title "Hello from Humble UI")
      (window/set-visible true)
      (window/set-z-order :normal)
      ;; (set-window-position! #:win{:width 974, :height 1047, :left 972, :top 2160})
      (set-window-position! (deref last-win-pos))
      (window/request-frame)))

(defn -main [& args]
  (future (apply nrepl/-main args))
  (hui/init)
  (reset! *window (make-window))
  (add-watch app-state :save-app-state-to-disk (fn [k r old new] (>!! app-state-save-queue new)))
  (add-watch last-win-pos :last-window-position (fn [k r old new] (>!! stdout (format "updated last window position to %s" new))))
  (async/go-loop []
    (let [app-state (<! app-state-save-queue)]
      (>! stdout (format "read app-state %s from app-state-save-queue" (take 20 (pr-str app-state))))
      (persist-to-disk app-state app-state-save-location)
      (>! stdout (format "saved app state to %s" app-state-save-location)))
    (recur))
  (async/go-loop []
    (println (<! stdout))
    (recur))
  (hui/start))
$clj -A:windows:dev
nREPL server started on port 60370 on host kubernetes.docker.internal - nrepl://kubernetes.docker.internal:60370
last window position in make-window nil
setting window position to #:win{:width 2880, :height 1554, :left 468, :top 468}
updated last window position to #:win{:width 2880, :height 1554, :left -10, :top 0}
updated last window position to #:win{:width 1940, :height 2110, :left -10, :top 0}
read app-state clojure.lang.LazySeq@4638271 from app-state-save-queue
saved app state to app-state.edn
read app-state clojure.lang.LazySeq@4638271 from app-state-save-queue
saved app state to app-state.edn
$ clj -A:windows:dev
nREPL server started on port 60371 on host kubernetes.docker.internal - nrepl://kubernetes.docker.internal:60371
last window position in make-window #:win{:width 1940, :height 2110, :left -10, :top 0}
setting window position to #:win{:width 2880, :height 1554, :left 494, :top 494}

in summary
I ran the app and since I try to slurp a file that doesn't exist, it's nil at first. The app comes up in some default place 2880 1554 468 468, I snap it to the left half of the screen 1940 2110 -10 0, I close it, re launch it from the terminal, it prints that it read 1940 2110 -10 0 from the config (which is the correct value) and then logs some default position/size instead of the saved/correct value.

code tldr
-main
calls
make-window
which calls
(set-window-position! (deref last-win-pos))
where
last-win-pos is (def last-win-pos (rum/cursor-in app-state [:last-win-pos]))
where
app-state is (def app-state (atom (or (unpersist-from-disk app-state-save-location) {})))
where
app-state-save-location is "app-state.edn"
and
unpersist-from-disk is

(defn unpersist-from-disk
  [file-name]
  (try (read-string (slurp file-name))
       (catch FileNotFoundException e)))

I'm looking at two back to back print statements with the same parameter and different vales coming out. That's what's confusing me. At the start of make-window I print it and then again as the first thing in set-window-position.

I could deref it once and bind that in a let.

(defn make-window []
  (let [w (deref last-win-pos)]
    (>!! stdout (format "last window position in make-window %s" (pr-str w)))
    (doto
        (window/make
         {:on-close #(reset! *window nil)
          :on-paint #'on-paint
          :on-event #'on-event})
        (window/set-title "Hello from Humble UI")
        (window/set-visible true)
        (window/set-z-order :normal)
        (set-window-position! w)
        (window/request-frame))))

It works now...
I do not know what I am doing.

Black window on Debian-based GNU/Linux

Hi!

I've tried to run it on a Debian-based GNU/Linux system (PureOS, to be specific), and it seems that the rendering doesn't work:
image

The REPL is started, and no exception is printed:

$ ./script/run.py 
nREPL server started on port 36783 on host localhost - nrepl://localhost:36783
nREPL 0.9.0
Clojure 1.11.0-rc1
OpenJDK 64-Bit Server VM 11.0.13+8-post-Debian-1deb11u1
Interrupt: Control+C
Exit:      Control+D or (exit) or (quit)
user=> 

Extra system info:

$ uname -a
Linux puffy 5.10.0-11-amd64 #1 SMP Debian 5.10.92-1 (2022-01-18) x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID:	PureOS
Description:	PureOS
Release:	10.0
Codename:	byzantium

$ echo $XDG_SESSION_TYPE
wayland

Any debugging suggestions?

Window only rerendered when resized with xmonad

With xmonad as window manager the window of the examples is not redrawn on request-frame. I need to resize the window after request-frame to be correctly shown.

i am on archlinux with xmonad 0.17.1

UnsatisfiedLinkError: Library file skija.dll not found in windows if version difference

This is a relatively minor issue, but I thought I should flag it, when io.github.humbleui/skija-shared's library version is out of sync with io.github.humbleui/skija-windows as is the case right now with it being 0.109.1 vs 0.109.0, we get the link error below:

Exception in thread "main" Syntax error macroexpanding at (core.clj:33:3).
	at clojure.lang.Compiler$StaticMethodExpr.eval(Compiler.java:1750)
	at clojure.lang.Compiler$DefExpr.eval(Compiler.java:457)
	at clojure.lang.Compiler.eval(Compiler.java:7199)
	at clojure.lang.Compiler.load(Compiler.java:7653)
	at clojure.lang.RT.loadResourceScript(RT.java:381)
	at clojure.lang.RT.loadResourceScript(RT.java:372)
	at clojure.lang.RT.load(RT.java:459)
	at clojure.lang.RT.load(RT.java:424)
	at clojure.core$load$fn__6908.invoke(core.clj:6161)
	at clojure.core$load.invokeStatic(core.clj:6160)
	at clojure.core$load.doInvoke(core.clj:6144)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5933)
	at clojure.core$load_one.invoke(core.clj:5928)
	at clojure.core$load_lib$fn__6850.invoke(core.clj:5975)
	at clojure.core$load_lib.invokeStatic(core.clj:5974)
	at clojure.core$load_lib.doInvoke(core.clj:5953)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6016)
	at clojure.core$load_libs.doInvoke(core.clj:6000)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6038)
	at clojure.core$require.doInvoke(core.clj:6038)
	at clojure.lang.RestFn.invoke(RestFn.java:482)
	at io.github.humbleui.window$eval1686$loading__6789__auto____1687.invoke(window.clj:1)
	at io.github.humbleui.window$eval1686.invokeStatic(window.clj:1)
	at io.github.humbleui.window$eval1686.invoke(window.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7194)
	at clojure.lang.Compiler.eval(Compiler.java:7183)
	at clojure.lang.Compiler.load(Compiler.java:7653)
	at clojure.lang.RT.loadResourceScript(RT.java:381)
	at clojure.lang.RT.loadResourceScript(RT.java:372)
	at clojure.lang.RT.load(RT.java:459)
	at clojure.lang.RT.load(RT.java:424)
	at clojure.core$load$fn__6908.invoke(core.clj:6161)
	at clojure.core$load.invokeStatic(core.clj:6160)
	at clojure.core$load.doInvoke(core.clj:6144)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5933)
	at clojure.core$load_one.invoke(core.clj:5928)
	at clojure.core$load_lib$fn__6850.invoke(core.clj:5975)
	at clojure.core$load_lib.invokeStatic(core.clj:5974)
	at clojure.core$load_lib.doInvoke(core.clj:5953)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$load_libs.invokeStatic(core.clj:6016)
	at clojure.core$load_libs.doInvoke(core.clj:6000)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:669)
	at clojure.core$require.invokeStatic(core.clj:6038)
	at clojure.core$require.doInvoke(core.clj:6038)
	at clojure.lang.RestFn.invoke(RestFn.java:482)
	at user$eval138$loading__6789__auto____139.invoke(user.clj:1)
	at user$eval138.invokeStatic(user.clj:1)
	at user$eval138.invoke(user.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7194)
	at clojure.lang.Compiler.eval(Compiler.java:7183)
	at clojure.lang.Compiler.load(Compiler.java:7653)
	at clojure.lang.RT.loadResourceScript(RT.java:381)
	at clojure.lang.RT.loadResourceScript(RT.java:368)
	at clojure.lang.RT.maybeLoadResourceScript(RT.java:364)
	at clojure.lang.RT.doInit(RT.java:486)
	at clojure.lang.RT.init(RT.java:467)
	at clojure.main.main(main.java:38)
Caused by: java.lang.UnsatisfiedLinkError: Library file skija.dll not found in io/github/humbleui/skija/windows/x64/
	at io.github.humbleui.skija.impl.Library._extract(Library.java:160)
	at io.github.humbleui.skija.impl.Library.load(Library.java:101)
	at io.github.humbleui.skija.impl.Library.staticLoad(Library.java:20)
	at io.github.humbleui.skija.shaper.Shaper.<clinit>(Shaper.java:14)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:167)
	at clojure.lang.Compiler$StaticMethodExpr.eval(Compiler.java:1743)
	... 65 more

The obvious workaround is to downgrade io.github.humbleui/skija-shared to match window's when working in windows, just wondering if there's a more elegant way to handle this?

Closing old dead window kills new window.

I have a *window atom with a lively on-paint and everything. If I reset the *window to a new window then the old window is still visible but frozen. Unexpectedly, if I close the old frozen window then the new window freezes.

I can get what I want by hiding the old window just before reseting my *window atom to a new window and then never need to close the old window. Out of sight out of mind?

I'm on macos.

How do I import org.jetbrains.skija?

Sorry, I'm having trouble getting started. I was looking at the clojure examples in the skija project and tried adding ... (:import [org.jetbrains.skija BackendRenderTarget ... but I get java.lang.ClassNotFoundException: org.jetbrains.skija.BackendRenderTarget. I added common.fetch_maven("org.jetbrains.skija", "skija-windows", "0.93.4"), to the run.py file.
How do I import skija?
Thanks!

Is it safe to dedupe `ui/gap` calls?

In the calculator example, I added a single global gap

(def gap (ui/gap 0 padding))

and then replaced all (ui/gap 0 padding) calls with gap.

Some of the lines between buttons are now missing:

Screen Shot 2022-09-05 at 12 07 04 AM

Is this expected? I found this while refactoring this namespace with (interleave (ui/gap 0 padding) ...) calls which seem to work in other examples, but not here.

Restarting the window in REPL blows up

Just starting to get back into this again and it's great to see stuff has progressed a lot!

Looking at the REPL based workflow, one thing that's a bit unclear is how to restart the window now?

I'm asking this because if I change a component low down in the hierarchy such as a label within a checkbox in the app, reloading that component in the REPL doesn't change it, only restarting the REPL seems to, which is not ideal as there's a clear pause that occurs when I kill the app, about a 3rd of the time which seems to temporarily lock up the UI on occasion on my mac. I'm assuming there's some delay in letting go of the graphics card.

Trying to restart the window for example using:

(io.github.humbleui.core/thread
  (let [screen (last (app/screens))]
    (reset! state/*window
        (ui/window
            {:title    "Humble 🐝 UI"
               :mac-icon "resources/images/icon.icns"
               :screen   (:id screen)
               :width    600
               :height   400
               :x        (if (= (:id screen) (:id (app/primary-screen))) :left :center)
               :y        :center}
            state/*app))))

Blows up:

2022-12-10 02:27:25.660 java[28657:9612981] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!'
*** First throw call stack:
(
        0   CoreFoundation                      0x00007fff2f8d1b57 __exceptionPreprocess + 250
        1   libobjc.A.dylib                     0x00007fff685825bf objc_exception_throw + 48
        2   CoreFoundation                      0x00007fff2f8fa34c -[NSException raise] + 9
        3   AppKit                              0x00007fff2caf45ec -[NSWindow(NSWindow_Theme) _postWindowNeedsToResetDragMarginsUnlessPostingDisabled] + 310
        4   AppKit                              0x00007fff2cadc052 -[NSWindow _initContent:styleMask:backing:defer:contentView:] + 1416
        5   AppKit                              0x00007fff2cadbac3 -[NSWindow initWithContentRect:styleMask:backing:defer:] + 42
        6   libjwm_x64.dylib                    0x000000010bab9e77 _ZN3jwm9WindowMac4initEv + 263
        7   libjwm_x64.dylib                    0x000000010baba3e3 Java_io_github_humbleui_jwm_WindowMac__1nMake + 227
        8   ???                                 0x000000011bbdb051 0x0 + 4760383569
)
libc++abi.dylib: terminating with uncaught exception of type NSException
/bin/sh: line 1: 28657 Abort trap: 6           lein with-profile +dev trampoline run
make: *** [dev] Error 134

EDIT:
Ok, I found io.github.humbleui.app/doui, which seems to work, I'm going to have to do some overrides so that the quit on close behaviour is disabled on development.

Not sure if there's a more elegant approach?

EDIT2:
Ok, it doesn't seem to consistently work, so there's still a reloading issue, not sure if it's because the components aren't being reloaded properly or something else, I'm going to investigate using clojure.tools.namespace.repl.

How do I use deps.edn to get an nrepl?

So far, I'm just editing user.clj and running run.py but I want to add some dependencies. How can I launch an nrepl with clj and the deps.edn config?
I tried adding something to the windows alias...

:aliases
 {:windows
  {:extra-deps {io.github.humbleui.skija/skija-windows {:mvn/version "0.96.0"}}
   :main-opts ["-m" "nrepl.cmdline"
               "--middleware" "[cider.nrepl/cider-middleware]"]}}

clj -A:windows gives me WARNING: Use of :main-opts with -A is deprecated. Use -M instead. Execution error (FileNotFoundException) at clojure.main/main (main.java:40). Could not locate nrepl/cmdline__init.class, nrepl/cmdline.clj or nrepl/cmdline.cljc on classpath.

I feel a little guilty since this isn't related to the project.

Event handlers for inputs and other widgets

When building my 7 Humble GUIs project, I ended up copying and modifying the text-field component to accept an on-change event handler in addition to the atom that would track the state, so that I could change other parts of a form when the user was typing into the input and ignore updates to the state that were caused by other things.

This made the code for the temperature converter quite simple to write:
https://github.com/lilactown/7-humble-guis/blob/a69a600a11373ca0dffe18ec5da887ee4120d45a/src/town/lilac/humble/app/gui_2.clj#L66-L84

What do you think of adopting this for the text-input/text-field and other stateful widgets?

Moving to a more event-oriented system could also allow for more customization of behavior. For example, if a user wanted to disallow anything but numbers entered into a text-field, they would again have to fork the widget and write that logic. If instead, the widget didn't mutate the atom when typing if an on-change handler was passed in, a user could encode this logic easily themselves:

(let [*num (atom {:text "23"})]
  (ui/text-field
    {:on-change #(when (Float/parseFloat %) (swap! *num assoc :text %))}
    *num))

Text field inside of dynamic loses focus when re-rendered

Currently, if you use a text-field inside of a dynamic component, the text-field will lose focus each time it is newly created.

A minimal example:

(def *counter (atom 0))
(def *text (atom {:text ""}))

(reset! state/*app
        (ui/default-theme
         {}
         (ui/dynamic
          ctx
          [counter @*counter]
          (ui/column
           (ui/label counter)
           (ui/text-field *text)))))

(defn timer []
  (future
   (Thread/sleep 1000)
   (swap! *counter inc)
   (timer)))

(timer)

SIGSEGV in dynamic when key not in context

Ok, good to know, also if you accidentally require a key in ui/dynamic which is not in context the jvm dies with a SIGSEGV (0xb) at pc=0x00000001526f15f9, pid=46668, tid=775.

I've been assuming that you've been doing this for perf reasons, but I thought I'd just check because at times it's non-obvious which component is missing what.

Originally posted by @Folcon in #13 (comment)

Not working for me on windows.

I'm using clj installed in windows powershell. If I start a repl from cider in emacs and eval make then I get an error.
e.g.

(make
 {:on-close         (fn [])
  :on-screen-change (fn [])
  :on-resize        (fn [{:keys [window-width window-height content-width content-height]}])
  :on-frame         (fn [])
  :on-paint         (fn [canvas])
  :on-event         (fn [event])})
  Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (16 frames hidden)

2. Unhandled clojure.lang.Compiler$CompilerException
   Error compiling c:/Users/richie/notMyCode/HumbleUI/HumbleUI/src/io/github/humbleui/window.clj at (107:1)
   #:clojure.error{:phase :compile-syntax-check,
                   :line 107,
                   :column 1,
                   :source
                   "c:/Users/richie/notMyCode/HumbleUI/HumbleUI/src/io/github/humbleui/window.clj"}
             Compiler.java: 7652  clojure.lang.Compiler/load
                      REPL:    1  user/eval8225
                      REPL:    1  user/eval8225
             Compiler.java: 7181  clojure.lang.Compiler/eval
             Compiler.java: 7136  clojure.lang.Compiler/eval
                  core.clj: 3202  clojure.core/eval
                  core.clj: 3198  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1977  clojure.core/with-bindings*
                  core.clj: 1977  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj:  662  clojure.core/apply
                regrow.clj:   20  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  831  java.lang.Thread/run

1. Caused by java.lang.UnsatisfiedLinkError
   'long io.github.humbleui.jwm.WindowWin32._nMake()'

          WindowWin32.java:   -2  io.github.humbleui.jwm.WindowWin32/_nMake
          WindowWin32.java:   14  io.github.humbleui.jwm.WindowWin32/<init>
                  App.java:   43  io.github.humbleui.jwm.App/makeWindow
                window.clj:   22  io.github.humbleui.window/make
                window.clj:   14  io.github.humbleui.window/make
                window.clj:  107  io.github.humbleui.window/eval8338
                window.clj:  107  io.github.humbleui.window/eval8338
             Compiler.java: 7181  clojure.lang.Compiler/eval
             Compiler.java: 7640  clojure.lang.Compiler/load
                      REPL:    1  user/eval8225
                      REPL:    1  user/eval8225
             Compiler.java: 7181  clojure.lang.Compiler/eval
             Compiler.java: 7136  clojure.lang.Compiler/eval
                  core.clj: 3202  clojure.core/eval
                  core.clj: 3198  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1977  clojure.core/with-bindings*
                  core.clj: 1977  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj:  662  clojure.core/apply
                regrow.clj:   20  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  831  java.lang.Thread/run

Also, if I run .\script\run.py I get an error saying... well, I'm not sure what.

PS C:\Users\richie\notMyCode\HumbleUI\HumbleUI> clojure
Clojure 1.11.0-alpha2
user=>
PS C:\Users\richie\notMyCode\HumbleUI\HumbleUI> clj
Clojure 1.11.0-alpha2
user=>
PS C:\Users\richie\notMyCode\HumbleUI\HumbleUI> .\script\run.py
Traceback (most recent call last):
  File "C:\Users\richie\notMyCode\HumbleUI\HumbleUI\script\run.py", line 15, in <module>
    sys.exit(main())
  File "C:\Users\richie\notMyCode\HumbleUI\HumbleUI\script\run.py", line 8, in main
    subprocess.check_call(["clojure",
  File "C:\Python39\lib\subprocess.py", line 368, in check_call
    retcode = call(*popenargs, **kwargs)
  File "C:\Python39\lib\subprocess.py", line 349, in call
    with Popen(*popenargs, **kwargs) as p:
  File "C:\Python39\lib\subprocess.py", line 951, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "C:\Python39\lib\subprocess.py", line 1420, in _execute_child
    hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
FileNotFoundError: [WinError 2] The system cannot find the file specified

I'm on windows 10. I followed these instructions to install clojure.
Thanks!

Key events don't respect input methods

Hi, I'm just poking around since I can see the project is really early but looks nice so far. Good work!

I noticed that when I type in your wordle demo using the dvorak keymap, the keypresses are still recognized as querty.

I've run into this before when using raw keyboard events rather than the higher level key events provided by the OS, but that was a totally different environment, so just mentioning it in case it helps.

`[first experience]` Exception in `start-app!`

(= :center x) (-> (:width work-area) (- (* width scale)) (quot 2))

Hi! I appreciate this undertaking, it's impressive and an area in which I have an interest.

I was taken by the attractiveness of the simple example at the bottom of the readme:

HumbleUI/README.md

Lines 110 to 122 in 127b399

```clj
(require '[io.github.humbleui.ui :as ui])
(def ui
(ui/default-theme {}
(ui/center
(ui/label "Hello from Humble UI! 👋"))))
(ui/start-app!
(ui/window
{:title "Humble 🐝 UI"}
#'ui))
```

I tried it myself, and encountered an exception:

> (require '[io.github.humbleui.ui :as ui])
nil
> (def ui
   (ui/default-theme {}
     (ui/center
       (ui/label "Hello from Humble UI! 👋"))))
#'ui
> ui
#object[io.github.humbleui.ui.dynamic.Contextual 0x46742e8f "io.github.humbleui.ui.dynamic.Contextual@46742e8f"]
> (ui/start-app!
   (ui/window
     {:title "Humble 🐝 UI"}
     #'ui))
#<Future@6c73825a: :pending>java.lang.NullPointerException
	at clojure.lang.Numbers.ops(Numbers.java:1095)
	at clojure.lang.Numbers.minus(Numbers.java:164)
	at io.github.humbleui.ui.window$window.invokeStatic(window.clj:57)
	at io.github.humbleui.ui.window$window.invoke(window.clj:9)
	at eval38357$fn__38358$fn__38359.invoke(form-init6195262237181650313.clj:22)
	at clojure.lang.AFn.run(AFn.java:22)
	at io.github.humbleui.jwm.App.lambda$start$1(App.java:35)
	at io.github.humbleui.jwm.App._nStart(Native Method)
	at io.github.humbleui.jwm.App.start(App.java:28)
	at io.github.humbleui.app$start.invokeStatic(app.clj:16)
	at io.github.humbleui.app$start.invoke(app.clj:15)
	at eval38357$fn__38358.invoke(form-init6195262237181650313.clj:21)
	at clojure.core$binding_conveyor_fn$fn__5823.invoke(core.clj:2047)
	at clojure.lang.AFn.call(AFn.java:18)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)

I'm sure I've screwed something up. Any thought on what it might be?

Thanks again for your time and consideration.

Improve component exceptions

So I spent the last few hours not realising that the below error was because I had not set ui/default-theme while updating an old version of the code base and so calling ui/button was causing the below exception:

        io.github.humbleui.jwm.Window::accept                   Window.java             :11
        io.github.humbleui.jwm.Window::accept                   Window.java             :402
        io.github.humbleui.window/make/reify--15273/accept      window.clj              :53
        io.github.humbleui.window/make/reify--15273/fn--15274   window.clj              :54
        io.github.humbleui.ui.window/window/event-fn--17061     window.clj              :39
        io.github.humbleui.core/event                           core.clj                :559
        io.github.humbleui.ui.dynamic.Contextual/-event         dynamic.clj             :12
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.with-context.WithContext/-event   with_context.clj        :8
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.dynamic.Contextual/-event         dynamic.clj             :12
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.dynamic.Contextual/-event         dynamic.clj             :12
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.align.VAlign/-event               align.clj               :27
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.align.HAlign/-event               align.clj               :6
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.containers.Column/-event          containers.clj          :23
        clojure.core/reduce                                     core.clj                :6885
        clojure.lang.PersistentVector::reduce                   PersistentVector.java   :343
        io.github.humbleui.ui.containers.Column/fn--15701       containers.clj          :23
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.with-context.WithContext/-event   with_context.clj        :8
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.containers.Column/-event          containers.clj          :23
        clojure.core/reduce                                     core.clj                :6885
        clojure.lang.PersistentVector::reduce                   PersistentVector.java   :343
        io.github.humbleui.ui.containers.Column/fn--15701       containers.clj          :23
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.align.HAlign/-event               align.clj               :6
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.containers.Row/-event             containers.clj          :63
        clojure.core/reduce                                     core.clj                :6885
        clojure.lang.PersistentVector::reduce                   PersistentVector.java   :343
        io.github.humbleui.ui.containers.Row/fn--15741          containers.clj          :63
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.dynamic.Contextual/-event         dynamic.clj             :12
        io.github.humbleui.core/event-child                     core.clj                :563
        io.github.humbleui.ui.clickable.Clickable/-event        clickable.clj           :8
        io.github.humbleui.core/rect-contains?                  core.clj                :355
AssertionError: Assert failed: (some? rect)

I managed to work out which component was causing it, but had no idea it was due to ui/default-theme not being set.

`tools.namespace` dependency isn't available from clojars

https://repo.clojars.org/org/clojure/tools.namespace/1.3.0/tools.namespace-1.3.0.jar link is dead: currently shows

<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Key>
org/clojure/tools.namespace/1.3.0/tools.namespace-1.3.0.jar
</Key>
<RequestId>6778X9N6YHYJA636</RequestId>
<HostId>
YeFF8pUj12TGQPZdYreSzK3F5rZmHDAsbj+bylqo6gKmbnLkcrmcqvhJfQ8Kw8hIi5o/t0bXKL+lJSYbtL74tQ==
</HostId>
</Error>

Trace running nrepl.py:

[nix-shell:~/HumbleUI]$ ./script/nrepl.py
Downloading https://repo1.maven.org/maven2/io/github/humbleui/types/0.1.2/types-0.1.2-clojure.jar
Downloading https://repo1.maven.org/maven2/io/github/humbleui/jwm/0.4.10/jwm-0.4.10.jar
Downloading https://repo1.maven.org/maven2/io/github/humbleui/skija-shared/0.106.0/skija-shared-0.106.0.jar
Downloading https://repo1.maven.org/maven2/io/github/humbleui/skija-linux/0.106.0/skija-linux-0.106.0.jar
Downloading https://repo.clojars.org/nrepl/nrepl/1.0.0/nrepl-1.0.0.jar
Downloading https://repo.clojars.org/org/clojure/tools.namespace/1.3.0/tools.namespace-1.3.0.jar
Traceback (most recent call last):
  File "/home/jake/HumbleUI/./script/nrepl.py", line 20, in <module>
    sys.exit(main())
  File "/home/jake/HumbleUI/./script/nrepl.py", line 9, in main
    build_utils.fetch_maven("org.clojure", "tools.namespace", "1.3.0", repo = common.clojars),
  File "/home/jake/HumbleUI/script/build_utils.py", line 91, in fetch_maven
    fetch(repo + '/' + path, file)
  File "/home/jake/HumbleUI/script/build_utils.py", line 82, in fetch
    data = urllib.request.urlopen(url).read()
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 525, in open
    response = meth(req, response)
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 634, in http_response
    response = self.parent.error(
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 563, in error
    return self._call_chain(*args)
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 496, in _call_chain
    result = func(*args)
  File "/nix/store/zdba9frlxj2ba8ca095win3nphsiiqhb-python3-3.10.8/lib/python3.10/urllib/request.py", line 643, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 404: Not Found

Crashing on `Surface._nGetCanvas`. Is my code bad?

(ns rgkirch.main
  (:require [io.github.humbleui.core :as hui]
            [io.github.humbleui.window :as window])
  (:import
   [io.github.humbleui.skija
    BackendRenderTarget
    Canvas
    Color4f
    ColorSpace
    DirectContext
    Font
    FontStyle
    FontMgr
    FramebufferFormat
    Paint
    PaintMode
    PaintStrokeCap
    Rect
    Surface
    SurfaceColorFormat
    SurfaceOrigin
    Typeface]
   [io.github.humbleui.jwm
    MouseCursor]))

(defn render [canvas]
  (.drawCircle canvas 10 10 30
               (doto (Paint.)
                 (.setColor (unchecked-int 0xFFFF0000)))))

(defn render-fn [canvas] (render canvas))

(do
  (when @user/*window (hui/doui (window/close @user/*window)))
  (reset! user/*window
          (hui/doui
           (doto
               (window/make
                {:on-close #(reset! user/*window nil)
                 :on-paint render-fn})
               (window/set-title "Hello from Humble UI")
               (window/set-visible true)
               (window/set-z-order :floating)))))

A blank window opens when I run the run script. I connect with cider, eval the buffer, and a new window replaces the old one and I see a red circle in the corner. As soon as I resize the window by snaping it to the right or left side of the screen or make it fullscreen or minimize it, it crashes.

I'm guessing that canvas is null somewhere?

C:\Users\richie\notMyCode\HumbleUI\HumbleUI>.\script\run.py
nREPL server started on port 59889 on host kubernetes.docker.internal - nrepl://kubernetes.docker.internal:59889
nREPL 0.8.3
Clojure 1.11.0-alpha2
OpenJDK 64-Bit Server VM 17-panama+3-167
Interrupt: Control+C
Exit:      Control+D or (exit) or (quit)
user=> #
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffa0f3dd59a, pid=3412, tid=23460
#
# JRE version: OpenJDK Runtime Environment (17.0+3) (build 17-panama+3-167)
# Java VM: OpenJDK 64-Bit Server VM (17-panama+3-167, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# C  [skija.dll+0x19d59a]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\richie\notMyCode\HumbleUI\HumbleUI\hs_err_pid3412.log
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  io.github.humbleui.skija.Surface._nGetCanvas(J)J+0

https://pastebin.com/e9U9nhRZ

I thought I had this exact code working previously...

Unrelated, my thought is that I need to create a render-fn that doesn't get redefined and that would get captured in window/make and then I could redefine render all I want and it'll still work. It works but I'm wondering if this is the right way to do it.

Thanks for the help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.