Wednesday, July 7, 2021

SpinnerTileSkin

 Aloha,

I finally found some time to continue working on TilesFX. There was an issue in the TilesFX repo over at github that I would like to do for a long time but never really found the time.

So I now added a new skin called SpinnerTileSkin which is based on this issue/request. It does not look exactly like the requested one but I think it's close enough.

In principle the skin shows a numerical value that when changed will spin through the numbers from 0-9 as if they where on a wheel.

Because a screenshot won't really show the effect, here is a little video:


As you can see it is nothing really special but sometimes it might be exactly what you need.

To set it up you simple need the following code:

Tile tile = TileBuilder.create()
.skinType(SkinType.SPINNER)
.prefSize(300, 300)
.title("SpinnerTile")
.minValue(-50)
.maxValue(50)
.value(0)
.decimals(2)
.text("Animated number spinner")
.animated(false)
.build();
tile.currentValueProperty().addListener((o, ov, nv) -> {
if (nv.doubleValue() < 0) {
tile.setValueColor(Tile.RED);
} else {
tile.setValueColor(Tile.FOREGROUND);
}
});


Switching the color from white to red in case the number is negative is done by the listener attached to the currentValueProperty of the tile and is not the standard behavior.

Because I'm preparing my libraries for the upcoming JDK17 LTS release, this skin can only be found in the JDK16 branch of TilesFX. Not sure if I will backport it to the JDK11 master branch.

At the moment the JDK16 branch is not available on Maven Central but I will probably create a release in the coming days...so stay tuned...and keep coding :)


Friday, July 2, 2021

BubbleGridChart

 Aloha,

Last week I was playing around with some data and could not find the right chart to visualize it.

To give you an idea about the data, let's take the harvest of some fruits as an example. You will have ripe and unripe fruits, fruits that have been eaten by birds or caterpillars. Some of them might have been damaged by hail or did not get enough water, others might be rotten or mouldy. For each fruit you have those different numbers and you have the sum of each fruit and the sum of all fruits.

The best way to compare all those numbers would be a matrix style chart. So I've stumbled upon the so called Bubble Grid Chart. So here is an example that I've found on the web:

As you can see it shows the fruit data that I described above.

The value of each crosspoint e.g. 90 Apples that are ripe will be visualized by the size of the bubble. Sizing the bubbles is a bit tricky because you would like to avoid having a few big bubbles and a lot of tiny bubbles. So you need to make sure that the size of the bubbles has no linear relationship to it's value.

In addition the max size of a bubble is given by either the height of the y-category items or the width of the x-category items, depends on which is smaller.

I also would like to have a grid in the background to make it easier to find specific coordinates. So, long story short, here is my version of the BubbleGridChart:


As you can see I've decided to put the x-category items on the bottom and also added the ability to show not only the values on the bubbles but also on the rows and columns of the chart. I really was impressed on how much information you can get out of one chart. At a glance you can see that the number of all ripe fruits is 215 which is 43% of all fruits. Because in this example the number of each fruit was always 100 you cannot really compare by the x-category but this could be different.

You can see that 90 Apples have been ripe which is 18% of all fruits. This information will be shown in a little info that will popup when you click on the bubble. In addition I've added the ability to sort the chart in x- and y-direction by either their indices or their values.

To be able to sort the items by their index you have to define it upfront.

Let me show you how to set up the x- and y-category items for the chart above.

Y-Category Items:

ChartItem ripe = ChartItemBuilder.create().name("Ripe").index(0).fill(Color.BLUE).build();

ChartItem unripe = ChartItemBuilder.create().name("Unripe").index(0).fill(Color.RED).build();

X-Category Items:

ChartItem peaches = ChartItemBuilder.create().name("Peaches").index(0).fill(Color.ORANGERED).build();

ChartItem apples = ChartItemBuilder.create().name("Apples").index(1).fill(Color.LIMEGREEN).build();

BubbleChart Items:

BubbleGridChartItem peaches1 = BubbleGridChartBuilder.create().categoryXItem(peaches).categoryYItem(ripe).value(60).fill(Color.ORANGERED).build();

BubbleGridChartItem peaches2 = BubbleGridChartBuilder.create().categoryXItem(peaches).categoryYItem(unripe).value(5).fill(Color.ORANGERED).build();

BubbleGridChartItem apples1 = BubbleGridChartBuilder.create().categoryXItem(apples).categoryYItem(ripe).value(90).fill(Color.LIMEGREEN).build();

BubbleGridChartItem apples2 = BubbleGridChartBuilder.create().categoryXItem(apples).categoryYItem(ripe).value(0).fill(Color.LIMEGREEN).build();

The index that you define will later be used to sort the items, just make sure you don't have duplicate indices because I do not check that at the moment (I just needed something that works quickly). So you first create the x- and y-category items and from those you create the actual BubbleGridChart items that will be used to visualize the chart.

The BubbleGridChart itself can be create as follows:

BubbleGridChart bubbleGridChart = 
    BubbleGridChartBuidler.create()
                          .chartBackground(Color.web("#0e0e0e"))
                          .textColor(Color.WHITE)
                          .gridColor(Color.rgb(255, 255, 255, 0.1))
                          .showGrid(true)
                          .showValues(true)
                          .showPercentage(true)
                          .items(bubbleGridChartItems)
                          .sortXCategoryItemsByIndexAscending()
                          .sortYCategoryItemsByIndexDescending()
                          .useXCategoryFill()
                          .useGradientFill(false)
                          .gradient(new LinearGradient(0, 0, 1, 0, true
                                    CycleMethod.NO_CYCLE
                                    new Stop(0, Color.BLUE), 
                                    new Stop(1, Color.RED))
                          .build();

Sorting the categories via the build only works if you also provide the items, otherwise you have to call the sorting methods like sortXCategoryItemsByIndexAscending() after you have added the bubbleGridChartItems. For me the charts now works fine but you might have other requirements, so please do not hesitate to file issues/request over at github.

The BubbleGridChart can be find in the current jdk16 branch which is also available on maven central. The jdk16 branch is more a temporary branch that I use to test stuff before JDK17 will come out in September. Because this will be the next long term stable version I will create a jdk17 branch in the future which will become the new main branch then.

That's it for today, so enjoy the upcoming weekend and...keep coding... :)

Wednesday, June 16, 2021

Fun Selector

 Aloha,

Time flies, it's already mid of June so it's time for another little blogpost about a fun control I've created last week.

It's a selector between two states and the fun is the animation when switching between the two states.

Here is a little demonstration of the control...


So the light green ball defines the selected state. It's not really something special but I like the idea of using animations in a fun way. Always keep in mind that such effects are nice in tools that you use once or twice but you don't want to use these things in a business app every day :)

As always the code is available over at github.

And that's it for today...so keep coding...

Friday, April 9, 2021

Friday Fun LXIII - JDKMon

 Aloha,

I've took some days off this week and continued working on a little tool I wrote, JDKMon. Because I have a couple of JDK's on my machine and I usually don't use tools like sdkman or other installers but instead install the JDK's by hand I always wanted to have a tool that keeps track on the latest available versions and inform me about updates.

Well because I've created the Disco API for foojay I'm now able to use this api to create this tool. In principle the tool will try to find all JDK's installed in a folder (that you can define) and checks the Disco API for updates for each of the JDK's found. If an update was found it will tell you the latest available version for the distribution and give you the ability to download the different available versions.

Just to be clear, the tool won't scan your whole harddrive for installed JDK's but only the given folder with all it's subfolders. For me that works fine because I have all JDK's installed in the same folder. 

On MacOS this folder usually can be found at /Library/Java/JavaVirtualMachines where on Windows it can be C:\Program Files\Java and on Linux it might be /usr/lib/jvm. But like mentioned you can choose the folder JDKMon should look for JDK's.

JDKMon will scan for new updates every 3 hours and will show you a notification if there are updates available. The app makes use of FXTrayIcon which makes it possible to have the app running in the SystemTray (if available). On MacOS and Windows that works fine. On Linux it won't work so in this case it is simply an app that comes with a menu.

After the app started it will scan for JDK's and will then show a window with all JDK's found. On MacOS that window will like look like follows:


On the screenshot above you see the alphabetical list of JDK's found in the folder that is shown on top. The distributions where the tool found updates will have additional info like the arrow followed by the latest available version and some colored buttons. The buttons on the right will show the available archive types for each distribution. Once you click on one button it will ask you for a folder to download the package to and after you have selected one it will download the selected package to that folder.

So the tool won't install automatically the downloaded JDK...this is up to you. The tool will also adopt to the selected screen mode (dark/bright) on MacOS and Windows. I've also tried to make the main window and notifications look like the native windows. Here is a screenshot of the Windows version on a bright themed Windows 10 installation:


The supported distributions at the moment are:

- Adoptium (but there are no packages yet)

- AdoptOpenJDK HotSpot

- AdoptOpenJDK OpenJ9

- Corretto

- Dragonwell

- GraalVM CE

- Liberica

- Liberica Native

- Mandrel

- Microsoft Build of OpenJDK*

- OJDK Build

- Oracle JDK (no direct download)

- Oracle OpenJDK

- RedHat (no direct download)

- SAP Machine

- Trava

- Zulu

* The Microsoft Build of OpenJDK is currently not available on the public Disco API but only on my own server which is the reason why you can see it on the Windows screenshot. But it will come with the next deployment.


Like already mentioned this is just a tool that I needed for my own machine and I also used it to test the detection of dark/light mode etc.

Because the Disco API still is under development it might come to situation where you don't find the latest available JDK of a distribution directly but I'm working on that so that it will hopefully be up to date most of the times.

As always the source code is available over at github where you can also find the installers/jars in the releases section.

I'm using github actions to build and upload the artifacts with each build so that you can also find the latest available artifacts for MacOS and Windows in the actions section.

The tool is not finished yet because it needs a bit more love for Linux which will be the next task to do. At least it should look not like a MacOS app on Linux forever. So I will try to adjust it to maybe the Ubuntu UI.

That's it for today, so enjoy the upcoming weekend and...keep coding... 


Saturday, February 6, 2021

"Poor man's" dark mode detection

 Aloha,

I recently read a lot about how to detect the dark mode on MacOS or Windows from Java which is really interesting when you develop desktop applications in Java (yes they still exists).

And there are different ways in figuring this out, one of them is to use jSystemThemeDetector which not only detects the theme but also gives you the ability to listen to changes of the theme. This little library makes use of different other libraries like OSHI, JFA, Jetbrains Annotations and JNA. 

Because I also wrote a little tool that helps me to figure out the system color theme I thought I might share this with you.

So my approach is a bit different in the way that I simply make use of the already existing operating systems tools to get the information I need. If you don't need to listen to theme switches (which is usually the case because users do not change their theme all the time) but only need to know if the operating system is currently using the dark theme or the light theme then you might want to use my approach.

I simply call operating system routines on the command line using Java's Runtime.getRuntime().exec() method and parse the result.

Because I'm on MacOS I've also added the ability to get the current accent color that is used in MacOS which is useful if your application should be as close to the native MacOS apps as possible.

Because the JavaFX stage does not recognize the current MacOS theme means you have to draw the window frame on your own dependent on the current theme but that's fine. I might add another blogpost about the native looking MacOS windows frame I've created.

The thing that I like most about my little tool is that it is only one class that offers some utility methods and everything is plain Java without any dependencies. 

It works on MacOS and Windows 10 and for those of you that are interested in that tool, I've created a little gist.

At the moment this utility class is made for JavaFX but it should be easy to change the Color definitions from JavaFX to Java Swing if you need them :)

That's it for today, enjoy your weekend and keep coding... :)

Wednesday, January 6, 2021

Neumorphism...just for the fun of it...

 Aloha,

First of all happy new year to all of you. I took this week off which gave me some time to play around with new stuff and as you can already guess it's about neumorphism.

Well you might ask yourself what the heck is neumorphism and how is it related to ui stuff. If you remember the early days of the the iPhone, there was a ui style that was called skeumorphism which means the ui design looked really realistic. Sometimes a bit too realistic but to be honest I'm still a fan of skeumorphism, especially if you take a look at older people that are not that used to modern technology. They really have a hard time to understand all the new technology. But if you show them a ui that looks like things they know from the real world, they recognize them and know how to use them.

At some point the whole skeumorphism was a bit too much and Apple decided to radically change the complete ui design to the so called flat ui. And to be honest the first version was simply terrible. Even for experienced users the ui was not self explaining any more but you have to learn that colored text might be a button but doesn't have to be. I really like the clean style of a flat ui but it comes with a drawback when you look at the usability. The self explanation of a ui simply got lost. 

Over the years Apple learned that the pure flat ui was a bit too flat and decided to make the whole thing a bit more usable again which leads us to the current ui design (which still is hard to learn for unexperienced people).

At some point neumorphism became a thing which is a bit like a mixture from skeumorphism and flat ui design. So there is no real definition of skeumorphism and for that reason you will find ui's that look not all the same as in the flat ui era but have different looks (which is good).

If you are interested in some examples you might want to take a look at dribbble.

Neumorphism makes use of shadows to define ui component borders which gives a button in principle two states an embossed or rised and a sunken state. In combination with rounded corners the whole ui becomes a more soft touch which can work for specific apps.

The problem with neumorphism is that it does not work with all color combinations. This is because of the shadows it uses, they won't work on white or black backgrounds. On some colors the shadows work great on others not so neumorphism is not the general solution for ui design but just another ui design style that can be useful and refreshing for some applications.

So I've spend every day around 1.5 hours in the early morning to play around with JavaFX and neumorphism to get figure out how to make it work. I would love to use css for this because in principle this would mean you could simply load another css file and you are done...BUT...unfortunately in the JavaFX css you cannot chain effects as you can do in code.

Meaning to say in JavaFX code you can chain effects like a DropShadow that can have an input of another DropShadow. With this you can simply create an effect of a sunken button etc.

But this is not possible in JavaFX css where you can only have one effect on one node which is not enough :(

So for that reason I've decided to create my own controls (at least a few to play around with). And again I used the JavaFX Canvas node for that. I found myself more and more using the Canvas node for all kinds of things simply because it saves nodes on the scene graph, it's fast and if you need it you can simply port it to HTML.

And this is what I came up with...


I've created a Button, ToggleButton, TextField, RadioButton, CheckBox, ChoiceBox, Switch and a Container.

Just keep in mind that these controls are not meant to be used in production because they miss some features but they might work for some demos (at least I will use them for demos) :)

The Container component which you can see in the lower row of components can be used to give other controls the ability to make use of the shadow effects. If you would like to use a circular Medusa gauge in a neumorphic ui you could add it to a circular Container.

To see how it can be used you might want to take a look at the Demo class.

The code is as always available over at github.

So that's it for today and don't forget...keep coding...

Sunday, December 27, 2020

JavaFX monitor component

 Aloha,

There was always something I would like to do but never found the time to do it...a control that looks more or less like a heart rate monitor. Well it's not really about monitoring the heart rate but I like the heart rate signal, so it's more about the older tube based oscilloscopes. And the thing that makes me want to create such a control is the fade out of the beam when it moves across the screen. In the tube based devices this was coming from a metallized layer that was applied to the front of the glass tube. When electrons hit this layer, it absorbed a part of the kinetic energy of the electron and released it in the form of light (so called fluorescence). The layer on the glass is metallized so that the electric charge of the electrons can flow off.

Now when the electron beam was moving across the screen the electrons left behind a fluorescent line which formed the signal. Ok so that is the thing that I would like to create...not an oscilloscope but this fading effect when visualizing a signal.

So what we need is a line that fades out over time...sounds easy (and in the end the solution is easy) but getting the right idea on how to implement it took me some walks with the dog :)

The first idea is you simple draw a line and apply a horizontal linear gradient to the stroke that fades out to transparency. As long as the signal has a predominantly horizontal orientation this works really fine. But as soon as the signal also has significant vertical values we will run into a problem. Let me try to visualize this in a little graphic:


On the picture above you see two signals that have the same horizontal width but a different signal length. On top I've added two rectangles that shows the horizontal linear gradient that is used to fade out the signal. The effect of fading out should be related to the length of the line and not to the width of the signal. 

To achieve this behavior I need to fade out the signal along the line and not only in horizontal direction. So the fade out should follow the line. My idea was to use a queue with a fixed size, when filled up to the given size it should remove the head element when a new element was added to the end. With such a queue in place I could simply draw a line between each element of the queue and fade out these line segments when I'm iterating over them during the drawing process.

I simply created a FixedSizeQueue that is based on an ArrayBlockingQueue and added the "auto-remove" functionality in the add() method. So the queue looks like this:

public class FixedSizeQueue<E> extends ArrayBlockingQueue<E> {
private int size;

public FixedSizeQueue(final int size) {
super(size);
this.size = size;
}

@Override public boolean add(final E element) {
if (super.size() == this.size) { this.remove(); }
return super.add(element);
}

public E getElementAt(final int index) {
if (index < 0 || index > size() - 1) { throw new IllegalArgumentException("Index out of bounds."); }
return (E) toArray()[index];
}
}

With this queue in place I can now use it as some kind of a signal buffer. I was playing around with different factors to fade out the signal but ended up with a simply linear approach. So I calculate the opacity factor by simply dividing 1/noOfElements. And when iterating through the points in the queue I just multiply the current index with the opacity factor as you can see in the code snippet below:

final Point[] points = queue.toArray(new Point[0]);
double opacityFactor = 1.0 / points.length;
for (int i = 0; i < length - 1; i += 1) {
if (points[i].x < points[i + 1].x) {
lineCtx.setStroke(Color.color(red, green, blue, i * opacityFactor));
lineCtx.strokeLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
}
}

You could because I draw the lines from left to right the opacity of the elements will in this case fade in the line segments by increasing their opacity from 0 - 1.

So the result looks good enough to me:


But this is only the effect of fading out a line along it's elements...what about the rest of the control?

For the monitor control I use three Canvas nodes and an ImageView that are placed inside a Region. So the whole control uses 5 nodes on the scene graph. 

It's good practice to think about which elements in your control needs to be drawn and when. This is sometimes a bit boring because with the given compute power of today's machines you won't really see a big difference between the optimized drawing and the let's call it brute force drawing. Optimized drawing only draws the elements that are needed where brute force drawing simply draws everything everytime. A good way of testing your controls is to run it on an older Raspberry Pi. This device is fast for it's size but slow compared to your desktop computer and on such a Raspberry Pi you will directly get a visual feedback on how efficient your code is. On an older Pi everything counts...means if you only draw the stuff that is important you will really see the effect on the Pi.

So as I mentioned I use three Canvas nodes

  • background (rect filled with background color and raster with text)
  • line (the line segments)
  • dot (the leading dot with the glow effect)
You might argue I should draw the dot in the same Canvas as the line but this only works for the mode where I fade out the signal line. The monitor control also has another mode where it always leaves the signal on the screen and only removes the part in front of the current position. This mode is common in todays heart rate monitors as you know it from the local hospital. For this mode I don't want to clear the background of the line Canvas on each draw and so I need to draw the dot on a separate Canvas.

Because I separated the background with the raster from the foreground with the line and the dot I only need to draw it when either a parameter has changed (e.g. the background color etc.) or when the control was resized. This is what I mean with optimized drawing, I could also draw the background all the time and put it in the same Canvas as the line but this is just not efficient.

In addition to the Canvas nodes I also use an ImageView to overlay the whole control with what I call a crystal overlay. The main idea is to make the whole UI look more realistic. The main thing here is noise...adding noise to a surface makes it look more natural because in the real world nearly no surface is perfect. For example if you take a look at a liquid crystal display (lcd), you will see some kind of structure on the background. This structure I try to imitate with a an image that contains semi-transparent noise. If you simply put such an image on top of your control it will look more realistic.

Here is an example screenshot of the monitor control with both variants:


As you can see the upper image has the crystal overlay effect switched off where the lower image has it switched on. In the monitor control you can decide if you would like to use it or not.

So in principle we simply stack all the different layers over each other and so get the final result.

I have also added some themes that contain common used color combinations for oscilloscopes but you can of course also set all the different colors separately.

There is a Demo class in the code that gives you the ability to play around with the parameters and if you would like to see it in action here is a little video:

As always the code/binary is available in the following places:

github

bintray

maven central

I guess that's it for today...so keep coding...