Radiance 3.5.0

October 5th, 2020

It gives me great pleasure to announce the fifth major release of Radiance. Let’s get to what’s been fixed, and what’s been added. First, I’m going to use emojis to mark different parts of it like this:

💔 marks an incompatible API / binary change
😻 marks new features
🤷‍♀️ marks bug fixes and general improvements

Dependencies for core libraries

  • Gradle from 6.4.1 to 6.6.1
  • Kotlin from 1.3.72 to 1.4.10
  • Kotlin coroutines from 1.3.7 to 1.3.9

Substance

  • 💔 Remove support for watermarks
  • 💔 Convert SubstanceSkin.ColorSchemes into an interface
  • 😻 Support for overlay colors with SubstanceSkin.setOverlayColor
  • 💔 Support for specifying derived colors in color scheme files
  • 😻 New API to mark a label as title pane text
  • 😻 Text highlights that respect decoration areas
  • 💔 Moved the Green Magic skin from substance-extras to the core substance module (see the screenshot of this skin above)
  • 💔 Aligned signatures of ComponentState.getActiveStates and ComponentState.getAllStates
  • 🤷‍♀️ Improved menu search widget UX
  • 🤷‍♀️ Correct layout for edit context menu under RTL
  • 🤷‍♀️ Fix concurrent modification exception thrown when ghost icon animations are enabled

Flamingo

  • 💔 Pass command projection instead of command in ribbon contextual menu listener
  • 💔 Remove AbstractCommandButton class. Everything is in the JCommandButton class now.
  • 😻 New CommandButtonPresentationModel.Builder.setPopupHorizontalGravity API to contol horizontal alignment of command button popups
  • 🤷‍♀️ Fix crash in opening a command popup menu from taskbar
  • 🤷‍♀️ Fix memory leaks caused by model listeners
  • 🤷‍♀️ Fix for root key tip chain not showing popup key tips of anchored commands
  • 🤷‍♀️ Fix for overlays on ribbon popup content in the title pane / taskbar

Trident

  • 🤷‍♀️ Fix inconsistent usage of conversion from duration fraction to timeline position

This release has mostly been focused on stabilizing and improving the overall API surface of the various Radiance modules. There’s still a long road ahead to continue exploring the never-ending depths of what it takes to write elegant and high-performing desktop applications in Swing. If you’re in the business of writing just such apps, I’d love for you to take this Radiance release for a spin. Click here to get the instructions on how to add Radiance to your builds. And don’t forget that all of the modules require Java 9 to build and run.

 

Radiance 3.0.0

May 31st, 2020

It gives me great pleasure to announce the fourth major release of Radiance. Let’s get to what’s been fixed, and what’s been added. First, I’m going to use emojis to mark different parts of it like this:

💔 marks an incompatible API / binary change
😻 marks new features
🤷‍♀️ marks bug fixes and general improvements

Dependencies for core libraries

  • Gradle from 5.6.1 to 6.4.1
  • Kotlin from 1.3.50 to 1.3.72
  • Kotlin coroutines from 1.3.0 to 1.3.7
  • Batik from 1.11 to 1.13

Substance

  • 😻 A more flexible skin accent system
  • 😻 New skins – Graphite Sienna, Sentinel and Harvest
  • 😻 Support color references in color scheme files
  • 😻 New Caps Lock indication on focused password fields
  • 😻 New association kind for checkbox and radio button “boxes”
  • 💔 Revisit APIs for loading color scheme bundles
  • 💔 Remove the title pane heap status widget
  • 🤷‍♀️ Use Helvetica Neue on macOS Catalina
  • 🤷‍♀️ Visual refresh of checkbox marks
  • 🤷‍♀️ Support for fallback fonts (CJK, etc)
  • 🤷‍♀️ Fix for incorrect usage of HIGHLIGHT_TEXT association kind on renderers.
  • 🤷‍♀️ Fix for background of popup menus opened from toolbar buttons.
  • 🤷‍♀️ Fix improved contrast across core skins.
  • 🤷‍♀️ Multiple fixes for table rollover hightlights and animations
  • 🤷‍♀️ Fix for some components under very large font sizes
  • 🤷‍♀️ Performance fix for column selection in large tables
  • 🤷‍♀️ Fix for icons in file chooser drop downs
  • 🤷‍♀️ Fix for incorrect bounds of maximized frames on secondary monitors

Flamingo

  • 💔 General evolution of command button APIs
  • 😻 Support for toggle split buttons
  • 😻 Add API to wire notification on ribbon spinner changes
  • 😻 Add API to wire notification on ribbon task selection
  • 🤷‍♀️ Multiple focus traversal fixes for ribbon content
  • 🤷‍♀️ Fixes for clipped wrapped ribbon components
  • 🤷‍♀️ Fix to not use round corners on command buttons in menus
  • 🤷‍♀️ Fix for crash in narrow command button panels
  • 🤷‍♀️ Fix for crash in showing keytips on toggle anchored ribbon commands
  • 🤷‍♀️ Fix for crash on showing keytips on undecorated windows
  • 🤷‍♀️ Fix for dynamic changes to ribbon gallery content
  • 🤷‍♀️ Fix for large icons on internal frames
  • 🤷‍♀️ Use the public Taskbar API to set the ribbon frame dock icon

Trident

  • 💔 Move all public APIs to org.pushingpixels.trident.api package
  • 💔 Remove generic UI toolkit support and leave only Swing support
  • 😻 Add support to provide dynamically computed from / to values on timelines.

Neon

  • 💔 Move all public APIs to org.pushingpixels.neon.api package

Photon

Plasma

This release marks a special milestone for me. The very first Substance release happened exactly fifteen years ago, on May 30th 2005. I didn’t quite imagine that I’d be here today, still working on the same codebase.

There’s still a long road ahead to continue exploring the never-ending depths of what it takes to write elegant and high-performing desktop applications in Swing. If you’re in the business of writing just such apps, I’d love for you to take this Radiance release for a spin. Click here to get the instructions on how to add Radiance to your builds. And don’t forget that all of the modules require Java 9 to build and run.

Here’s to the next fifteen years!

Radiance comes with a number of sample / demo apps that showcase the flexibility and power of its APIs. One of those demos is Lumen. Its main goal is to highlight the feature set of the Trident animation library. Lumen uses MusicBrainz JSON web service to search for all albums of the specific artist, and for the list of tracks on individual albums. Sending requests and parsing responses is done with Retrofit and Moshi. Lucent is the port of Lumen to Kotlin.

Let’s see how it works together in Kotlin.

We start by adding the build dependencies on Retrofit and Moshi:

dependencies {
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
}

Next, we define our service interface that maps to MusicBrainz APIs:


    private interface MusicBrainzService {
        @GET("/ws/2/release?type=album&fmt=json")
        fun getReleases(@Query("artist") artistId: String): Call

        @GET("/ws/2/release/{release}?inc=recordings&fmt=json")
        fun getRelease(@Path("release") releaseId: String): Call

        companion object {
            const val API_URL = "https://musicbrainz.org/"
        }
    }

Note the usage of fmt=json attribute in all @GET functions, and usage of @Query and @Path that matches the expected endpoint contracts.

The data classes map to the matching MusicBrainz entities, using @field:Json annotation with the matching name attribute, along with @Json annotation on one of the data classes to properly map it to the matching JSON tags:


data class SearchResultRelease(
        @field:Json(name = "id") val id: String?,
        @field:Json(name = "title") val title: String?,
        @field:Json(name = "artist") var artist: String?,
        @field:Json(name = "date") val date: String?,
        @field:Json(name = "release-events") val releaseEvents: List,
        @field:Json(name = "asin") val asin: String?)

data class Area(
        @field:Json(name = "disambiguation") val disambiguation: String?,
        @field:Json(name = "id") val id: String?,
        @field:Json(name = "name") var name: String?,
        @field:Json(name = "sort-name") val sortName: String?,
        @field:Json(name = "iso-3166-1-codes") val iso31661Codes: List)

data class Medium(
        @field:Json(name = "tracks") val tracks: List)

data class Release(
        @field:Json(name = "id") val id: String?,
        @field:Json(name = "title") val title: String?,
        @field:Json(name = "date") val date: String?,
        @field:Json(name = "media") val media: List,
        @field:Json(name = "asin") val asin: String?)

data class ReleaseEvent(
        @field:Json(name = "date") val date: String?,
        @field:Json(name = "area") val area: Area?)

@Json(name = "release-list")
data class ReleaseList(
        @field:Json(name = "count") val count: Int?,
        @field:Json(name = "releases") val releases: List)

data class Track(
        @field:Json(name = "title") val title: String?,
        @field:Json(name = "length") val length: Int?)

Now we can create a Retrofit object and fire off our request:


        val retrofit = Retrofit.Builder()
                .baseUrl(MusicBrainzService.API_URL)
                .client(getHttpClient())
                .addConverterFactory(MoshiConverterFactory.create())
                .build()

        val service = retrofit.create(MusicBrainzService::class.java)

        val releaseResponse = service.getReleases(artistId).execute()
        val releases = releaseResponse.body()

And to get the list of tracks for the specific album:


    fun doTrackSearch(releaseId: String): List {
        val retrofit = Retrofit.Builder()
                .baseUrl(MusicBrainzService.API_URL)
                .client(getHttpClient())
                .addConverterFactory(MoshiConverterFactory.create())
                .build()

        val service = retrofit.create(MusicBrainzService::class.java)

        val releaseResponse = service.getRelease(releaseId).execute()
        val release = releaseResponse.body()

        return release!!.media[0].tracks
    }

Where the OkHttpClient is configured like this:


    private fun getHttpClient(): OkHttpClient {
        val okHttpBuilder = OkHttpClient.Builder()
        okHttpBuilder.addInterceptor { chain ->
            val requestWithUserAgent = chain.request().newBuilder()
                    .header("User-Agent", "My custom user agent")
                    .build()
            chain.proceed(requestWithUserAgent)
        }
        return okHttpBuilder.build()
    }

This is it. No messy handling of HTTP requests, no manual parsing of JSON responses. All driven by metadata and encapsulated by Kotlin data classes.

Radiance 2.5.0

September 3rd, 2019

It gives me great pleasure to announce the third major release of Radiance. Let’s get to what’s been fixed, and what’s been added. First, I’m going to use emojis to mark different parts of it like this:

💔 marks an incompatible API / binary change
😻 marks new features
🤷‍♀️ marks bug fixes and general improvements

Substance

  • 😻 New skins – Nebula Amethyst, Night Shade and Graphite Sunset
  • 🤷‍♀️ Fix for disappearing internal frame title pane buttons
  • 🤷‍♀️ Fix for crash during initialization
  • 🤷‍♀️ Fix for OutOfMemoryError on sliders with large model ranges
  • 🤷‍♀️ Fix for slider tracks under dark skins
  • 💔 Fix for incorrect tracking of state-based alpha values in color scheme bundles
  • 🤷‍♀️ Fix for drop shadows under some skins
  • 🤷‍♀️ Fix for contrast ratio of highlighted content under Sahara skin
  • 🤷‍♀️ Fix for antialiased rendering of pasted text content

Flamingo

Trident

  • 😻 DSL for Trident
  • 🤷‍♀️ Fix for combining looping timelines with .fromCurrent()

Photon

The first Radiance release focused on bringing all the different Swing open-source projects that I’ve been working on since 2005 under one roof. The second Radiance release was about making them work much better together. And this one (code-named Coral) is about covering major functionality gaps that were missing up until now.

There’s still a long road ahead to continue exploring the never-ending depths of what it takes to write elegant and high-performing desktop applications in Swing. If you’re in the business of writing just such apps, I’d love for you to take this third Radiance release for a spin. Click here to get the instructions on how to add Radiance to your Gradle / Maven / Ivy / Leiningen / Bazel builds. And don’t forget that all of the modules require Java 9 to build and run.