| Author | DaveJarvis <email> |
|---|---|
| Date | 2020-07-23 19:32:51 GMT-0700 |
| Commit | 60b35796349e2e757ba170d9ef5f84ead6955b83 |
| Parent | f9402e1 |
| hwSwitch, hwState, switchValue ); | ||
| - // Clear the text before redrawing to prevent discord between the | ||
| - // label colour and the hardware switch's background. | ||
| - if( switchState.isSwitchState( SWITCH_RELEASED ) ) { | ||
| - getHardwareComponent( switchState ).removeAll(); | ||
| - } | ||
| - | ||
| updateSwitchState( switchState ); | ||
| if( state.isModifier() ) { | ||
| - final var hwSwitch = state.getHardwareSwitch(); | ||
| - final var switchName = hwSwitch.toTitleCase(); | ||
| - | ||
| - final var label = new AutofitLabel( switchName, LABEL_FONT, keyColour ); | ||
| - label.setVisible( false ); | ||
| - label.setHorizontalAlignment( CENTER ); | ||
| - label.setVerticalAlignment( CENTER ); | ||
| - container.removeAll(); | ||
| - container.add( label ); | ||
| - label.setVisible( true ); | ||
| + updateLabel( state, keyColour ); | ||
| } | ||
| else if( state.isSwitchState( SWITCH_PRESSED ) ) { | ||
| // Label for "Num", "Back", "Tab", and other dual-labelled keys. | ||
| - final var sup = new AutofitLabel( s[ 0 ], LABEL_FONT, keyColour ); | ||
| + final var sup = new AutofitLabel( s[ 0 ], LABEL_FONT ); | ||
| sup.setVisible( false ); | ||
| + sup.setForeground( keyColour ); | ||
| sup.setVerticalAlignment( TOP ); | ||
| // Label for number pad keys or icon glyphs. | ||
| - final var main = new AutofitLabel( s[ 1 ], LABEL_FONT, keyColour ); | ||
| + final var main = new AutofitLabel( s[ 1 ], LABEL_FONT ); | ||
| main.setVisible( false ); | ||
| + main.setForeground( keyColour ); | ||
| main.setHorizontalAlignment( CENTER ); | ||
| main.setVerticalAlignment( CENTER ); | ||
| } | ||
| else { | ||
| - // Single keys need no tweaking and can be added to the container | ||
| - // directly. The horizontal and vertical alignments | ||
| - final var label = new AutofitLabel( keyValue, LABEL_FONT, keyColour ); | ||
| - label.setVisible( false ); | ||
| - label.setHorizontalAlignment( CENTER ); | ||
| - label.setVerticalAlignment( CENTER ); | ||
| container.removeAll(); | ||
| - container.add( label ); | ||
| - label.setVisible( true ); | ||
| + updateLabel( state, keyColour ); | ||
| } | ||
| + } | ||
| + else { | ||
| + container.removeAll(); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates the label if it does not already exist. | ||
| + * | ||
| + * @param state The state of the hardware switch to look up. | ||
| + */ | ||
| + private void updateLabel( | ||
| + final HardwareSwitchState state, | ||
| + final Color keyColour ) { | ||
| + final var container = getHardwareComponent( state ); | ||
| + final var value = state.getValue(); | ||
| + final AutofitLabel label; | ||
| + | ||
| + if( container.getComponentCount() == 0 ) { | ||
| + // Regular keys will have labels recreated each time to auto-fit the text. | ||
| + label = new AutofitLabel( value, LABEL_FONT ); | ||
| + label.setHorizontalAlignment( CENTER ); | ||
| + label.setVerticalAlignment( CENTER ); | ||
| + label.setForeground( keyColour ); | ||
| + container.add( label ); | ||
| + } | ||
| + else { | ||
| + // Modifier keys can reuse labels. | ||
| + label = (AutofitLabel) container.getComponent( 0 ); | ||
| + label.setForeground( keyColour ); | ||
| + label.setText( value ); | ||
| } | ||
| } | ||
| private HardwareComponent<HardwareSwitchState, Image> getHardwareComponent( | ||
| final HardwareSwitchState state ) { | ||
| return mHardwareImages.get( state.getHardwareSwitch() ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public String toString() { | ||
| + return getClass().getName() + "{" + | ||
| + "mHardwareImages=" + mHardwareImages + | ||
| + '}'; | ||
| } | ||
| } | ||
| import static com.whitemagicsoftware.kmcaster.ui.Constants.INSETS_EMPTY; | ||
| -import static com.whitemagicsoftware.kmcaster.ui.Constants.TRANSPARENT; | ||
| /** | ||
| public HardwareComponent() { | ||
| this( INSETS_EMPTY ); | ||
| - setBackground( TRANSPARENT ); | ||
| - setOpaque( false ); | ||
| } | ||
| @Override | ||
| protected void paintComponent( final Graphics graphics ) { | ||
| - super.paintComponent( graphics ); | ||
| - | ||
| - final var g = (Graphics2D) graphics.create(); | ||
| + final var g = graphics.create(); | ||
| g.drawImage( getActiveImage(), 0, 0, this ); | ||
| + g.dispose(); | ||
| } | ||
| getStateImages().put( state, image ); | ||
| - // No need to issue a repaint request, change the state directly. | ||
| + // Change the state variable directly, no need to issue a repaint request. | ||
| mState = state; | ||
| } | ||
| SWITCH_RELEASED; | ||
| - private final static String BOOLEAN_FALSE = FALSE.toString(); | ||
| + /** | ||
| + * Convenience constant. | ||
| + */ | ||
| + public final static String BOOLEAN_FALSE = FALSE.toString(); | ||
| /** |
| * was pressed. | ||
| * | ||
| - * @param hardwareSwitch A {@link HardwareSwitch} that represents the type of | ||
| - * switch having the given status. | ||
| - * @param hardwareState Defines whether the switch is pressed or released. | ||
| - * @param value The value associated with the switch in the given | ||
| - * state. For example, this could be human-readable | ||
| - * text representing a pressed key code. | ||
| + * @param hwSwitch A {@link HardwareSwitch} that represents the type of | ||
| + * switch having the given status. | ||
| + * @param hwState Defines whether the switch is pressed or released. | ||
| + * @param value The value associated with the switch in the given | ||
| + * state. For example, this could be human-readable | ||
| + * text representing a pressed key code. | ||
| */ | ||
| public HardwareSwitchState( | ||
| - final HardwareSwitch hardwareSwitch, | ||
| - final HardwareState hardwareState, | ||
| + final HardwareSwitch hwSwitch, | ||
| + final HardwareState hwState, | ||
| final String value ) { | ||
| - assert hardwareSwitch != null; | ||
| - assert hardwareState != null; | ||
| + assert hwSwitch != null; | ||
| + assert hwState != null; | ||
| assert value != null; | ||
| - mHardwareSwitch = hardwareSwitch; | ||
| - mHardwareState = hardwareState; | ||
| - mValue = value; | ||
| + mHardwareSwitch = hwSwitch; | ||
| + mHardwareState = hwState; | ||
| + mValue = hwSwitch.isModifier() ? hwSwitch.toTitleCase() : value; | ||
| } | ||
| result = 31 * result + mHardwareState.hashCode(); | ||
| return result; | ||
| + } | ||
| + | ||
| + @Override | ||
| + public String toString() { | ||
| + return getClass().getName() + "{" + | ||
| + "mHardwareSwitch=" + mHardwareSwitch + | ||
| + ", mHardwareState=" + mHardwareState + | ||
| + ", mValue='" + mValue + '\'' + | ||
| + '}'; | ||
| } | ||
| } | ||
| import com.kitfox.svg.SVGException; | ||
| import com.kitfox.svg.SVGUniverse; | ||
| -import com.whitemagicsoftware.kmcaster.ui.ScalableDimension; | ||
| import com.whitemagicsoftware.kmcaster.ui.DimensionTuple; | ||
| +import com.whitemagicsoftware.kmcaster.ui.ScalableDimension; | ||
| import java.awt.*; | ||
| import java.awt.image.BufferedImage; | ||
| import java.net.URL; | ||
| import java.util.Map; | ||
| import static java.awt.RenderingHints.*; | ||
| -import static java.awt.image.BufferedImage.TYPE_INT_ARGB; | ||
| +import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR; | ||
| /** | ||
| final var wScaled = (int) scaled.getWidth(); | ||
| final var hScaled = (int) scaled.getHeight(); | ||
| - final var image = new BufferedImage( wScaled, hScaled, TYPE_INT_ARGB ); | ||
| + final var image = new BufferedImage( wScaled, hScaled, TYPE_4BYTE_ABGR ); | ||
| final var graphics = image.createGraphics(); | ||
| graphics.setRenderingHints( RENDERING_HINTS ); | ||
| public void mouseDragged( final MouseEvent e ) { | ||
| - final Point dragCoordinates = e.getLocationOnScreen(); | ||
| - mFrame.setLocation( dragCoordinates.x - mCoordinates.x, | ||
| - dragCoordinates.y - mCoordinates.y ); | ||
| + // Race-condition guards. | ||
| + final var frame = mFrame; | ||
| + final var coordinates = mCoordinates; | ||
| + | ||
| + // Used to calculate delta between current and previous mouse position. | ||
| + final var dragCoordinates = e.getLocationOnScreen(); | ||
| + | ||
| + if( frame != null && coordinates != null ) { | ||
| + frame.setLocation( dragCoordinates.x - coordinates.x, | ||
| + dragCoordinates.y - coordinates.y ); | ||
| + } | ||
| } | ||
| } |
| import java.util.Map; | ||
| +import static com.whitemagicsoftware.kmcaster.HardwareState.BOOLEAN_FALSE; | ||
| import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*; | ||
| -import static java.lang.Boolean.FALSE; | ||
| import static java.util.Map.entry; | ||
| import static org.jnativehook.keyboard.NativeKeyEvent.getKeyText; | ||
| @Override | ||
| public void nativeKeyReleased( final NativeKeyEvent e ) { | ||
| - updateRegular( getDisplayText( e ), FALSE.toString() ); | ||
| + updateRegular( getDisplayText( e ), BOOLEAN_FALSE ); | ||
| updateModifiers( e ); | ||
| } | ||
| final var state = mSwitches.get( key ); | ||
| - // By default, the keys are all "false", so fire fake events indicating | ||
| - // that has one has just transitioned to "false". This will cause the | ||
| - // GUI to repaint with the text label affixed to each key, drawn in the | ||
| - // released state. This happens before the frame is set to visible. | ||
| + // All modifiers keys are "false" by default, so firing fake transition | ||
| + // events from "true" to "false" will cause the GUI to repaint with the | ||
| + // text label affixed to each key, drawn in the released state. This | ||
| + // happens before the frame is set to visible. | ||
| tryFire( key, !state, state ); | ||
| } | ||
| * </p> | ||
| * | ||
| - * @param text The text to write on the container's graphics context. | ||
| - * @param font The font to use when writing the text. | ||
| - * @param color The colour to use when writing hte text. | ||
| + * @param text The text to write on the container's graphics context. | ||
| + * @param font The font to use when writing the text. | ||
| */ | ||
| - public AutofitLabel( final String text, final Font font, final Color color ) { | ||
| + public AutofitLabel( final String text, final Font font ) { | ||
| super( text ); | ||
| setDoubleBuffered( true ); | ||
| setFont( font ); | ||
| - setForeground( color ); | ||
| addHierarchyListener( e -> { | ||
| float scaledPt = scaledFont.getSize(); | ||
| - while( maxSizePt - minSizePt > 1f ) { | ||
| - scaledFont = scaledFont.deriveFont( scaledPt ); | ||
| + // TextLayout cannot suffer null or empty values, so return the default | ||
| + // font size if the label is cleared out. | ||
| + if( text != null && !text.isEmpty() ) { | ||
| + while( maxSizePt - minSizePt > 1f ) { | ||
| + scaledFont = scaledFont.deriveFont( scaledPt ); | ||
| - final var layout = new TextLayout( text, scaledFont, frc ); | ||
| - final var metrics = scaledFont.getLineMetrics( text, frc ); | ||
| - final var fontWidthPx = layout.getVisibleAdvance(); | ||
| - final var fontHeightPx = metrics.getHeight(); | ||
| + final var layout = new TextLayout( text, scaledFont, frc ); | ||
| + final var metrics = scaledFont.getLineMetrics( text, frc ); | ||
| + final var fontWidthPx = layout.getVisibleAdvance(); | ||
| + final var fontHeightPx = metrics.getHeight(); | ||
| - if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) { | ||
| - maxSizePt = scaledPt; | ||
| - } | ||
| - else { | ||
| - minSizePt = scaledPt; | ||
| - } | ||
| + if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) { | ||
| + maxSizePt = scaledPt; | ||
| + } | ||
| + else { | ||
| + minSizePt = scaledPt; | ||
| + } | ||
| - scaledPt = (minSizePt + maxSizePt) / 2; | ||
| + scaledPt = (minSizePt + maxSizePt) / 2; | ||
| + } | ||
| } | ||
| * Application dimensions in pixels. Images are scaled to these dimensions, | ||
| * maintaining aspect ratio. The height constrains the width, so as long as | ||
| - * the width is sufficiently large, the application's window will adjust to | ||
| - * fit. | ||
| + * the width is large enough, the application's window will adjust to fit. | ||
| */ | ||
| public static final Dimension APP_DIMENSIONS = new Dimension( 1024, 90 ); |
| Delta | 114 lines added, 79 lines removed, 35-line increase |
|---|