Friday, March 4, 2016

Friday Fun XXIV

Last weekend I was playing around with my self-made mood light based on an ESP8266 in combination with MQTT and JavaFX on mobile using Gluon Mobile.
In my first version I used 3 sliders to select the color for the mood light. Well that worked BUT it looked aweful. And because of that I was thinking about a way to choose a color that makes more sense and came up with a drawing that looks like this...

One can select a color by clicking/touching on the color ring and the center circle will show the selected color. You could also drag the mouse/move the finger over the ring and the inner circle will change it's color. The selected color will only be set when the mouse/finger is released. I've implemented it like this because I need to send the selected color to the ESP8266 and I want to avoid sending too many messages.
So I've added a listener to the selectedColorProperty() of the control and send a message to my ESP8266 when the selected color changes.
Clicking the ring will directly change the color.
The tricky parts when creating this control are the conical gradient and the ring itself. For the conical gradient I've created a solution some time ago which I used here.
The ring is a bit special, you could either fake a ring by using a big circle and draw another circle on top of it and fill it with the background color. This is the easiest solution which will definitely work and is easy to scale.
But because I would like to create a real ring and have the space between the inner circle and the ring transparent I needed a different approach.
When drawing in vector programs you simply subtract a small circle from a big circle and you get a ring. This ring can be exported as SVG and you can use it in your CSS file (-fx-scale-shape: true; and -fx-shape: "SVG PATH STRING" are your friends here). This solution works and is also scalable by setting the preferredSize of the CSS-styled Region.
But there is another way of realizing can use
double center = PREFERRED_WIDTH * 0.5;
Shape  ring   = Shape.subtract(new Circle(center, center, center), 
                               new Circle(center, center, PREFERRED_WIDTH * 0.25));
This solution will give you a shape that is a ring...sounds perfect right? Well it comes with a little drawback, the scalability is not that easy as with the other solutions. Because a JavaFX Shape doesn't have parameters like preferredSize, witdh and height, the only way you can scale it is using a Transform.
That's in principle not a big deal but because I use my own ConicalGradient to fill it, it was a bit tricky to figure out how to scale it correctly.
Usually I use something similar like the following code to place a node on the scene graph...

ring.setTranslateX((size - ring.getLayoutBounds().getWidth()) * 0.5);
ring.setTranslateY((size - ring.getLayoutBounds().getHeight()) * 0.5);
But in this case that won't work because the Scale transformation doesn't affect the layout bounds of the shape. Means we always get back the same layout matter how the scaling is.
The solution here is to define a pivot point for the Scale transformation to place it correctly.
So the code to scale and place the ring in the right way looks like this...
double scaleFactor = size / PREFERRED_WIDTH;
ring.getTransforms().setAll(new Scale(scaleFactor, scaleFactor, 0, 0));
With this code the ring will always be scaled and located in the center of the control.

The plain control will look like follows...

So it uses the following colors for visualization...

  • Red               Color.rgb(255, 0, 0)
  • Orange          Color.rgb(255, 127, 0)
  • Yellow           Color.rgb(255, 255, 0)
  • Yellow-Green Color.rgb(127, 255, 0)
  • Green            Color.rgb(0, 255, 0)
  • Cyan              Color.rgb(0, 255, 255)
  • Blue               Color.rgb(0, 0, 255)
  • Magenta         Color.rgb(255, 0, 255)
  • Red                Color.rgb(255, 0, 0)

But the ColorSelector control also has a constructor that takes a list of Stop Objects so that you can use the colors of your choice. So here are some examples...
Stop[] stops = { new Stop(0.0, Color.rgb(255,255,0)),
                 new Stop(0.125, Color.rgb(255,0,0)),
                 new Stop(0.375, Color.rgb(255,0,255)),
                 new Stop(0.5, Color.rgb(0,0,255)),
                 new Stop(0.625, Color.rgb(0,255,255)),
                 new Stop(0.875, Color.rgb(0,255,0)),
                 new Stop(1.0, Color.rgb(255,255,0))};

ColorSelector colorSelector = new ColorSelector(stops);
colorSelector.setPrefSize(400, 400);
Which will look like this...

or a monochromatic version...

Stop[] stops = { new Stop(0.0, Color.rgb(63, 63, 242)),
                 new Stop(0.125, Color.rgb(48, 92, 246)),
                 new Stop(0.25, Color.rgb(32, 120, 249)),
                 new Stop(0.375, Color.rgb(16, 149, 252)),
                 new Stop(0.5, Color.rgb(0, 177, 255)),
                 new Stop(0.625, Color.rgb(16, 149, 252)),
                 new Stop(0.75, Color.rgb(32, 120, 249)),
                 new Stop(0.875, Color.rgb(48, 92, 246)),
                 new Stop(1.0, Color.rgb(63, 63, 242))};

ColorSelector colorSelector = new ColorSelector(stops);
which gives you the following look...

Keep in mind that when using the ConicalGradient you should make sure that the first and the last Color are always the same.

I'm not sure if this control is useful for you but I like it...

The code can be found as always on github...

keep coding... :)

No comments:

Post a Comment