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: 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 ""
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url  ""}
  :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

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:

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]
               [ GraphiteSkin]))

(defn set-theme!  []
  (SubstanceLookAndFeel/setSkin (GraphiteSkin.))

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)

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

Then I execute one small code piece by one.

Result: #object[ 0x18db38c5 "[email protected]"]

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

Result: `nil.

Do I need to download some theme files for substance?


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

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


1. Unhandled java.lang.NullPointerException
   (No message)  759  org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getDefaultBackgroundColor  661  org.pushingpixels.substance.internal.utils.SubstanceColorUtilities/getBackgroundFillColor   74  org.pushingpixels.substance.internal.ui.SubstancePanelUI/__org__pushingpixels__substance__internal__ui__SubstancePanelUI__installDefaults   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/installDefaults   62  javax.swing.plaf.basic.BasicPanelUI/installUI   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/__org__pushingpixels__substance__internal__ui__SubstancePanelUI__installUI   -1  org.pushingpixels.substance.internal.ui.SubstancePanelUI/installUI
   685  javax.swing.JComponent/setUI
       150  javax.swing.JPanel/setUI
       126  javax.swing.JPanel/updateUI
        86  javax.swing.JPanel/<init>
       109  javax.swing.JPanel/<init>
       117  javax.swing.JPanel/<init>
    517  javax.swing.JRootPane/createGlassPane
    344  javax.swing.JRootPane/<init>
       279  javax.swing.JFrame/createRootPane
                       nil:   -1  seesaw.core.proxy$javax.swing.JFrame$Tag$fd407141/createRootPane
       258  javax.swing.JFrame/frameInit
                       nil:   -1  seesaw.core.proxy$javax.swing.JFrame$Tag$fd407141/frameInit
       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
       512  clojure.lang.RestFn/invoke
d9894a6e20d3260853f359a297ddffa5afcb90f563adf375d5b4d927bbbeb460-init.clj:   15  hello-seesaw.core/eval23563
d9894a6e20d3260853f359a297ddffa5afcb90f563adf375d5b4d927bbbeb460-init.clj:   15  hello-seesaw.core/eval23563
    7177  clojure.lang.Compiler/eval
    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
          152  clojure.lang.AFn/applyToHelper
          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*
       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
       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
      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
           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
           22  clojure.lang.AFn/run
       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
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