Skip to content

io7m-com/digal

Repository files navigation

digal

Maven Central Maven Central (snapshot) Codecov Java Version

com.io7m.digal

JVM Platform Status
OpenJDK (Temurin) Current Linux Build (OpenJDK (Temurin) Current, Linux)
OpenJDK (Temurin) LTS Linux Build (OpenJDK (Temurin) LTS, Linux)
OpenJDK (Temurin) Current Windows Build (OpenJDK (Temurin) Current, Windows)
OpenJDK (Temurin) LTS Windows Build (OpenJDK (Temurin) LTS, Windows)

Digal

A customizable, scalable JavaFX rotary dial.

digal

Features

  • CSS-styleable rotary dials
  • Scalable to any size
  • OSGi-ready
  • JPMS-ready
  • High coverage automated test suite
  • ISC license

Maven

<dependency>
  <groupId>com.io7m.digal</groupId>
  <artifactId>com.io7m.digal.core</artifactId>
  <version>${latest}</version>
</dependency>

What Is A Dial?

A dial is a rotary knob seen on hardware such as guitar amplifiers, mixing desks, and etc.

digal

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:

digal

  • 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.

Value Converters

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);

Data Flow

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:

  1. The user turns a dial.
  2. The ChangeListener on the dial sees the dial value change and submits a command to update the value on the external device.
  3. The external device returns the newly set value.
  4. The dial is updated with the new value.
  5. The ChangeListener on the dial sees the dial value change and submits a command to update the value on the external device.
  6. 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.

CSS

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;
}

ugly