How to make seesaw support HiDPI screen?

I’m newbie using Seesaw, I got a problem when I launch seesaw. I
found the window and text font size is very small in my MacBook Pro 2015(HiDPI).

I’m wandering is there a way to configure seesaw to support HiDPI screen and is
there a way to detect HiDPI screen in Clojure/Seesaw?


Clojure version: 1.10.1
seesaw version: 1.5.0

What JDK are you using? (early JDKs do not support HIDPI screens properly)

If you’re on a recent JDK, adding -Dglass.gtk.uiScale=200% to your JVM options at startup might help (it helps with JavaFX apps on Mac but I don’t know about Swing).

If you are stuck on a java 1.8 or older jvm, default seesaw won’t be good at high res. However, the Substance theme (I first learned of from the original nightcode editor swing implementation) will scale. It has some really strict UI threading requirements that default swing doesn’t, so interactive gui editing from a repl thread works (technically unsafely) in seesaw / normal swing, but throws concurrent access errors in substance. I have a wrapper called saner substance that should correct this. You just set the platform look and feel like here and you should be good to go. On my hidpi 4k display it looks great.

1 Like

Cool, that led to this: https://github.com/kirill-grouchnikov/radiance which seems like a quite nice set of additions for writing modern Swing apps. Looking at those, almost feels more appealing then JavaFx. Though its Java 9+ only.

Thanks for replying, @seancorfield.

I’m using JVM 14 (OpenJDK).
I tried your option by adding it into Leiningen project.clj:

(defproject hello-seesaw "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url  "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.1"]
                 [seesaw "1.5.0"]]
  :repl-options {:init-ns hello-seesaw.core}
  :jvm-options ["-Dglass.gtk.uiScale=200%"])

Then restart CIDER REPL, run -main method, still small. I also tried command lein run -m hello-seesaw.core. Same small.

Substance and related themes are really nice to see “in action”; since they have cool little animations and subtle effects that are just excellent. I’m sure there are javafx versions/variants, but swing with substance feels advanced (despite being swing, hah).

They were supposed to have fixed hidpi for swing at some point on the jvm; I assumed it had been done. Looks like they did it in jdk 9.

Interesting libs

https://www.formdev.com/blog/flatlaf-flat-look-and-feel/

I can’t understand your previous message about substance very much, still try to study. :slight_smile:

And this FlatLaf and swing-dpi solutions are great. I will try it soon. Thanks very much for your reply.

The link was supposed to point to specific line in the repository:

here
Which shows some code. If you take out the noise, it looks like this:

(ns demo
  (:require [seesaw.core :as s]
                [seesaw.icon :as i]
                [sanersubstance.core :as substance])
  (:import [org.pushingpixels.substance.api SubstanceLookAndFeel]
               [org.pushingpixels.substance.api.skin GraphiteSkin]))

(defn set-theme!  []
  (s/native!)
  (SubstanceLookAndFeel/setSkin (GraphiteSkin.))
  (substance/enforce-event-dispatch)))

If you have seesaw and the saner-substance library in your dependencies, this should set your theme to substance, with the fixes I put in, and hidpi should work (it does for me on older jvms). I have not tested on mac though, but it works on linux and windows.

Thanks for your sharing. :smile_cat:

I added sanersubstance as project dependency. Then copy your code to execute.

I got error on set-theme!

1. Unhandled java.lang.NullPointerException
   (No message)

          LookAndFeel.java:  243  javax.swing.LookAndFeel/uninstallBorder
  SubstanceRootPaneUI.java:  296  org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI/uninstallBorder
  SubstanceRootPaneUI.java:  683  org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI/uninstallClientDecorations
  SubstanceRootPaneUI.java:  260  org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI/__org__pushingpixels__substance__internal__ui__SubstanceRootPaneUI__uninstallUI
  SubstanceRootPaneUI.java:   -1  org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI/uninstallUI
           JComponent.java:  700  javax.swing.JComponent/uninstallUIAndProperties
           JComponent.java:  675  javax.swing.JComponent/setUI
            JRootPane.java:  445  javax.swing.JRootPane/setUI
            JRootPane.java:  455  javax.swing.JRootPane/updateUI
       SwingUtilities.java: 1363  javax.swing.SwingUtilities/updateComponentTreeUI0
       SwingUtilities.java: 1378  javax.swing.SwingUtilities/updateComponentTreeUI0
       SwingUtilities.java: 1354  javax.swing.SwingUtilities/updateComponentTreeUI
 SubstanceLookAndFeel.java: 2171  org.pushingpixels.substance.api.SubstanceLookAndFeel/setSkin
 SubstanceLookAndFeel.java:   92  org.pushingpixels.substance.api.SubstanceLookAndFeel/access$000
 SubstanceLookAndFeel.java: 2136  org.pushingpixels.substance.api.SubstanceLookAndFeel$3/run
      InvocationEvent.java:  306  java.awt.event.InvocationEvent/dispatch
           EventQueue.java:  770  java.awt.EventQueue/dispatchEventImpl
           EventQueue.java:  721  java.awt.EventQueue$4/run
           EventQueue.java:  715  java.awt.EventQueue$4/run
     AccessController.java:  391  java.security.AccessController/doPrivileged
     ProtectionDomain.java:   85  java.security.ProtectionDomain$JavaSecurityAccessImpl/doIntersectionPrivilege
           EventQueue.java:  740  java.awt.EventQueue/dispatchEvent
  EventDispatchThread.java:  203  java.awt.EventDispatchThread/pumpOneEventForFilters
  EventDispatchThread.java:  124  java.awt.EventDispatchThread/pumpEventsForFilter
  EventDispatchThread.java:  113  java.awt.EventDispatchThread/pumpEventsForHierarchy
  EventDispatchThread.java:  109  java.awt.EventDispatchThread/pumpEvents
  EventDispatchThread.java:  101  java.awt.EventDispatchThread/pumpEvents
  EventDispatchThread.java:   90  java.awt.EventDispatchThread/run

Then I execute one small code piece by one.

(GraphiteSkin.)
Result: #object[org.pushingpixels.substance.api.skin.GraphiteSkin 0x18db38c5 "org.pushingpixels.substance.api.skin.GraphiteSkin@18db38c5"]

(SubstanceLookAndFeel/setSkin (GraphiteSkin.))
Result: false

(substance/enforce-event-dispatch)
Result: `nil.

Do I need to download some theme files for substance?

UPDATE:

After I execute (set-theme!). Then I execute bellowing code got another error:

(-> (frame :title "Hello",
              :content "Hello, Seesaw",
              :on-close :dispose)
       pack!
       show!)

Error:

1. Unhandled java.lang.NullPointerException
   (No message)

SubstanceColorUtilities.java:  759  org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getDefaultBackgroundColor
SubstanceColorUtilities.java:  661  org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getBackgroundFillColor
     SubstancePanelUI.java:   74  org.pushingpixels.substance.internal.ui.SubstancePanelUI/__org__pushingpixels__substance__internal__ui__SubstancePanelUI__installDefaults
     SubstancePanelUI.java:   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/installDefaults
         BasicPanelUI.java:   62  javax.swing.plaf.basic.BasicPanelUI/installUI
     SubstancePanelUI.java:   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/__org__pushingpixels__substance__internal__ui__SubstancePanelUI__installUI
     SubstancePanelUI.java:   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/installUI
           JComponent.java:  685  javax.swing.JComponent/setUI
               JPanel.java:  150  javax.swing.JPanel/setUI
               JPanel.java:  126  javax.swing.JPanel/updateUI
               JPanel.java:   86  javax.swing.JPanel/<init>
               JPanel.java:  109  javax.swing.JPanel/<init>
               JPanel.java:  117  javax.swing.JPanel/<init>
            JRootPane.java:  517  javax.swing.JRootPane/createGlassPane
            JRootPane.java:  344  javax.swing.JRootPane/<init>
               JFrame.java:  279  javax.swing.JFrame/createRootPane
                       nil:   -1  seesaw.core.proxy$javax.swing.JFrame$Tag$fd407141/createRootPane
               JFrame.java:  258  javax.swing.JFrame/frameInit
                       nil:   -1  seesaw.core.proxy$javax.swing.JFrame$Tag$fd407141/frameInit
               JFrame.java:  181  javax.swing.JFrame/<init>
                       nil:   -1  seesaw.core.proxy$javax.swing.JFrame$Tag$fd407141/<init>
                  core.clj: 2875  seesaw.core/frame
                  core.clj: 2821  seesaw.core/frame
               RestFn.java:  512  clojure.lang.RestFn/invoke
d9894a6e20d3260853f359a297ddffa5afcb90f563adf375d5b4d927bbbeb460-init.clj:   15  hello-seesaw.core/eval23563
d9894a6e20d3260853f359a297ddffa5afcb90f563adf375d5b4d927bbbeb460-init.clj:   15  hello-seesaw.core/eval23563
             Compiler.java: 7177  clojure.lang.Compiler/eval
             Compiler.java: 7132  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  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:  665  clojure.core/apply
                  core.clj: 1973  clojure.core/with-bindings*
                  core.clj: 1973  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:  665  clojure.core/apply
                  core.clj:  660  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:  832  java.lang.Thread/run

Looks like it’s a problem with jdk 14 (I run on 8, since I target some legacy systems).

On a demo project with clojure 1.10.1, and saner-substance as the only dependencies, everything works fine. The difference is in this line:

(SubstanceLookAndFeel/setSkin (GraphiteSkin.))
I get true (e.g. it was successful). I don’t know what changed with swing and substance in Java 14, hmm.

similar problem

possibly related

I got true after restart CIDER REPL, eval it again, then I got false.
Don’t know is this a problem. From your words, it should be right.

================
And the the second error.
When I eval frame code, I got Unhandled java.lang.NullPointerException in org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getDefaultBackgroundColor.

I tried to execute it manually, but don’t know what argument does it accept. So can test it.

I will try to switch to older JDK version. Hope got correct result. Thanks @joinr
– UPDATE–
I tried JDK 11 version. Same error: Unhandled java.lang.NullPointerException, org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getDefaultBackgroundColor as upper second pasted backtrace.

I don’t think JDK 11 is “old” enough. That is, it looks like things changed around Java 9 (when they started going toward faster releases, introduce the module system, etc.). It could have something to do with how swing is or isn’t being initialized in Java 9+, I am not sure. Perhaps there are classes that are being elided. You could always try JDK 8 just to see (like install adopt open jdk). It may be possible to coax out the correct behavior, although the new version of substance (under radiance now) is only meant to be JDK 9+. So probably the better bet would be to use the radiance library, set the substance theme that way (slightly different than the code I posted), and see how things work out. unfortunately, I do not have a wrapper like saner-substance for the radiance/substance stuff, so you will have to invoke everything on the swing dispatch thread I believe. I think it’s seesaw.core/invoke-later or seesaw.core/invoke-now for async or sync variants.

Here’s a demo repository that works with Java 14 on windows. It’s using the newer substance (which is java 9 or greater), and is not using the convenience wrappers from saner substance, so everything that seesaw does to create swing components “has” to be done on the event dispatch thread (e.g. via seesaw.core/invoke-now or invoke-later), or else substance will throw a bunch of concurrent access exceptions at you when rendering components. As an aside, on Java 14, Windows, my font is already HiDPI scaled perfectly (prior to using substance). There may be some platform-specific problems going on that I’m not familiar with.

Yes, I would not mess around with “saner substance”; if you are using Swing, you should get used to working with the way Swing works, and only try to manipulate UI elements on the Swing event dispatch thread, using invoke-now from the REPL (if you want to see the result) or invoke-later if you don’t care to.

I’ve been using Substance with the Raven skin to great effect in my Beat Link Trigger project, and sneakily getting more and more people involved in performing music and light/laser shows to learn a little Clojure. It works with JDK 9 or later on all OSes; I embed an OpenJDK 14 distribution in my macOS and Windows installers.

“saner substance” obeys swing’s idioms; it just forces component creation to default to ensuring EDT. The type of repl driven development that seesaw encourages will work fine, right up until you run into substance’s hardcore thread protection. None of this is an issue with stock swing stuff (at least, it’s not presented as an issue via exceptions). IME this is overkill. So you are now back to doing everything manually on the EDT, particularly if you’re doing a lot of interactive development and prototyping. Substance’s complaining about non-EDT invocation gets old fast, hence wrapper classes that have worked fine for ~4 years in production (in a legacy Java 8 environment though). Were I to migrate to Java 9+, I would wrap the classes again.

Thanks for creating a demo testing project. :relaxed:

I installed JDK8, the scale is not working, but the theme seems work. Because the background color turns into dark.
Here is a screenshot.

And then I tried your test project on JDK14. also attached a screenshot:

Does it related to my Linux GTK/KDE settings? I have setted KDE global scale to 175%.

In theory, JDK 14 should render correctly with or without substance. I am afraid you are entering territory I am unfamiliar with regarding the native platform interactions with display scaling :frowning:

I guess too. I temporary put this problem aside, try to figure it out again one day.
Thanks for your long time help, @joinr