JVM | Platform | Status |
---|---|---|
OpenJDK (Temurin) Current | Linux | |
OpenJDK (Temurin) LTS | Linux | |
OpenJDK (Temurin) Current | Windows | |
OpenJDK (Temurin) LTS | Windows |
A customizable, scalable JavaFX rotary dial.
- CSS-styleable rotary dials
- Scalable to any size
- OSGi-ready
- JPMS-ready
- High coverage automated test suite
- ISC license
<dependency>
<groupId>com.io7m.digal</groupId>
<artifactId>com.io7m.digal.core</artifactId>
<version>${latest}</version>
</dependency>
A dial is a rotary knob seen on hardware such as guitar amplifiers, mixing desks, and etc.
In digal
, a dial carries a real value in the range [0, 1]
where 0
means "
turned fully anti-clockwise" and 1
means "turned fully clockwise". Each dial
can be provided with a value converter that converts this internal value to
something else for display purposes.
Dials are constrained to a 270°
range in order to unambiguously indicate the
current value at a glance.
Visually, a dial consists of the following components:
- The indicator is a small notch on the dial that shows which direction the dial is pointing.
- The radial gauge is a filled-in arc segment showing how far away the current dial setting is from the minimum.
- The tick marks are small marks around the dial that can be used to indicate discrete values. The number of tick marks can be customized.
- The shade emulates the way dials are often recessed into hardware. The shade can be disabled.
- The body is the actual physical dial.
The text field below the dial is not part of the dial itself, and is a plain text field bound the dial value for demonstration purposes.
Dials are manipulated by clicking the body and dragging upwards and downwards on the Y axis. Dragging upwards turns the dial clockwise, and dragging downwards turns the dial anti-clockwise.
As mentioned, dials use real values in the range [0,1]
. A dial instance
can be provided with a value converter that allows for converting internal
values to something else for use externally. The package comes with a number
of built-in converters, organized into real and discrete converters. A
real converter maps dial values in the range [0, 1]
to a user-defined
range of real numbers. A discrete converter maps dial values in the range
[0, 1]
to a user-defined range of integers.
dial0.setValueConverter(
new DialValueConverterRealType()
{
@Override
public double convertToDial(
final double x)
{
return x / 12.0;
}
@Override
public double convertFromDial(
final double x)
{
return (double) Math.round(x * 12.0);
}
@Override
public double convertedNext(
final double x)
{
return x + 0.5;
}
@Override
public double convertedPrevious(
final double x)
{
return x - 0.5;
}
});
Note that it would probably also be a good idea to set the number of tick
marks to 12
:
dial0.setTickCount(12);
Some applications may choose to use dials both as a data display and a data
input. For example, a user interface that controls an external audio device
might want dials to always match the state of the external device, but also
need to be adjustable by the user turning the dial onscreen. The dials are
typically configured with a ChangeListener
that is invoked when the user
turns the dial that submits commands to update the external device. Additionally,
the dials are usually set to particular values when state updates are received
from the external device. This can cause a problem due to a circular data
dependency:
- The user turns a dial.
- The
ChangeListener
on the dial sees the dial value change and submits a command to update the value on the external device. - The external device returns the newly set value.
- The dial is updated with the new value.
- The
ChangeListener
on the dial sees the dial value change and submits a command to update the value on the external device. - The external device returns the newly set value...
This problem is sometimes mitigated by the fact that setting a JavaFX property
to a value to which it is already set doesn't result in observers of the
property being called. This isn't always reliable, however, and so the
digal
API provides "quiet" versions of the commands to update dials that
break the cycle of observer updates.
The setRawValueQuietly
and setConvertedValueQuietly
commands will set
the value of a dial and update the dial's UI, but will not call any observers
of the dial's value property.
In the scenario described above, state updates that come from the external
audio device should set dial values using set*Quietly
so that the state
updates do not cause more commands to be submitted to the device.
The dial components can be customized to some extent with CSS. Assuming
a dial with id #dial0
, the following CSS will produce an ugly looking dial:
#dial0
{
dial-body-color: #30a030;
dial-body-stroke-color: #0000ff;
dial-emboss-color: #00000030;
dial-emboss-size: 4.0;
dial-indicator-color: #00ffff;
dial-indicator-size: 3.0;
dial-radial-gauge-color: #ff00ff;
dial-radial-gauge-size: 12.0;
dial-shade-color: #ff000050;
dial-tick-color: #ff0000;
dial-tick-size: 1.0;
}