Part 3. Breathing life into our JavaFX Dial
Aeons ago when I wrote part 2, JavaFX was in it’s infancy at version 1.
Now we have leaped forward to version 1.1, (
). The 1.1 release is clearly more of a bugfix, and by designed more of a teaser for some of the missing elements. We have the SDK where are the phones? And more importantly to the OS community where is my formal Linux release (fortunately there are work arounds).
Where we left off
In Part 2 of this article we took the very flat drawing, and turned it into a much more pretty dial. This only touched on the graphical horse power of JavaFX. In this article, I address adding functionality into the JavaFX dial, by hitting some of the key functionality of JavaFX. From Animation, and Multimedia, to Event Handling to Transformations, this has been somewhat of a bumpy road.
The Good, the Bad, and the Ugly
Yes, the road has been very bumpy. Maybe my lack of experience, or maybe it is the
youth of the JavaFX environment, but I have learned alot by adding functionality to
the JavaFX dial.
The Good: Clean, logical code, that reads very well.
I didn’t find that I had to create any creative logic flows (man do I miss the goto command (simple, yet confusing), or for those C(++) programmers out there, man do I miss troubleshooting pointers (there is nothing that can expedite gray hair and baldness quite like it)).
Also anyone familiar with Event Handlers in Java will love this:
{
if (enableAudio) { sndMuted = true; }
}
Yes that’s it, I released the mouse button, and (if I had enabled sound) then the sweet
clicking sound pilfered from my daughter’s toy would stop (more appropriately mute, a topic best left to the ugly).
Click the mouse again, and the sndMuted variable which is bound to the MediaPlayer itself, gets set to false,
and the clicking knob audio continues.
Another short snippet that is new, and very intuitive:
{
keyFrames:
[
at(0ms) { currAngle => currAngle }
at(5s) { currAngle =>
dialRotation tween Interpolator.LINEAR }
]
}
That is purely and simply animation. The dial is a’ spin’in.
The Bad - the lessons learned
So admittedly I am my own worse enemy. Rotating the dial represented a series of interesting challenges. First challenge: rotating the dial with the mouse. I appeal to anyone to provide a more simplistic answer, and will gladly throw them some street cudos. When I started pondering rotating an object based on the distance of the pointer, my head begain spinning (that’s when I realized that the speed at which my head was spinning was the more I thought about the problem). Then came the math (not my forte). So I thought myself into a triangle, and realized the three points as where the mouse was, where the mouse is, and the center of the circle.
The reasoning here is that the furhur you are from the center of the circle, the slowere the dial would rotate.
Nevertheless, I found the Law of Cosines to Trigonometrically solve my puzzle.
Yes that was ugly:
pointCurrent = Point2D
{
x: event.x;
y: event.y;
}
sideA = pointHub.distance(pointClick);
sideB = pointHub.distance(pointCurrent);
sideC = pointClick.distance(pointCurrent);
var cosC: Number = 0;
cosC = (Math.pow(sideA, 2) +
Math.pow(sideB, 2) -
Math.pow(sideC, 2)) /
(2 * sideA * sideB);
// Radians to Degrees + pointer alignment factor.
dialRotation +=
cosC * 57.2957795 + 30;
if (dialRotation < 0)
dialRotation = dialRotation + 360;
if (dialRotation > 360)
dialRotation = dialRotation - 360;
The second challenge: rotating a circle within an invisible box is seamless, but the second you add a pointer protruding outside the radius, you have a problem. The problem makes complete sense, the second you rotate the dial and the pointer hits the containing box, the entire dial shifts just enough to keep itself contained within the invisible box. My workaround was I cheated. I started going down the path of maybe extending the containing scene, but then I realized that I just preferred the look of a smaller pointer. Yes, of course it worked but the lesson here is to be aware of the bounding rectangle to each object when a transform is applied. The last “Bad” in the rotation challenge is around using the transforms. I wanted to utilize the rotate transforms to manage the load of constantly executing the trigonometric functions. Ultimately I ended up using Animation (I hate to say it, but without direct support for Threads in JavaFX, I can see using Animation as a process controller).
The Ugly - the smoking mirrors.
So there are really two points where I became hung up, and found myself fishing for a work around. The first work around: The illusory init method which was discussed in part 2. I am still not exactly sure what the role is with the init and update methods are with the UIStub Class.
There is a definite method to the madness, and maybe the correct answer would’ve been to extend the javafx_dialUI class. Any feedback would be appreciated, commented, and inserted. The second work around: Sound. The MediaPlayer has an issue, I have poked around to find that
I wasn’t the only person out there asking the questions around getting my WAV file to play. First and foremost: How ever you create your audio, I would strongly recommend saving it at 1411 kbps as a WAV file. Next until they fix this, the sound needs to have a length greater than two seconds. I would recommend if you have a sound or need to create a sound to look to using Audacity. This is an excellent program with a lot of functionality, and still easy enough to use. Nevertheless, the audio clip was over two seconds long which just running the audio when someone rotated the dial doesn’t make much sense. So here is where I used the functionality of the builtin MediaPlayer object. The media player has two attributes that I needed to implement, one was the repeatCount, and two was the mute. Fortunately for me the click wav file I created from my daughter’s teething toy, was a consistent sound. This allows me to repeat the sound with having to worry where in the sound I was at. I am assuming that if the sound was not loop-able, or if I needed to chain different sounds together I could just use the MediaPlayer currentTime attribute to select where I needed to be in the audio file. So what also I did was bind the mute attribute of the player to the sndMuted variable I created, as described above. Well once I got past these hurdles I was able to enable the basic functionality of the dial.
Enhancing the Functionality
After some pondering I figured on an assignment for this dial, which I will unveil in my next article (cheap teaser I know). I did have to add a lot of additional public writable attributes:
// To calculate the value on the
// currAngle, use public function currentValue(): Number {
// instead.
public var currAngle: Number;
// Don’t want the Pointer, remove it.
public var hasPointer: Boolean = true;
// Minimum and Maximum are the values that the Dial represents,
// while spinning from the 0th degree, to the 360th degree.
public var minimum: Number = 0.0;
public var maximum: Number = 100.0;
// enable Audio I have already discussed allows you to turn
// the sound off on the dial, and rotationWav
// allows you to change the sound made.
public var enableAudio: Boolean = true;
public var rotationWav = Media { source: “{__DIR__}click2.wav” }
// The incKey, decKey, are used for adding key handlers
// instead of using just the mouse.
// They need to be assigned to a KeyCode Object.
public var incKey: KeyCode = KeyCode.VK_MINUS;
public var decKey: KeyCode = KeyCode.VK_EQUALS;
// keyPressChange and mouseWheelChange, are the
// degree changes that will occur with each
// execution of either of those events to the dial.
public var keyPressChange = 5;
public var mouseWheelChange = 10;
For the Java files, or for the SVG Dial . just click.
< previous Part 2. Turning an SVG drawing into a JavaFX Dial
javafxdial