Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/kmcaster.git

Refactor label into own scalable component

Author DaveJarvis <email>
Date 2020-07-19 16:16:55 GMT-0700
Commit b931a58b5fe5646520a53768f3ec2f98bf4301da
Parent a8294b1
src/main/com/whitemagicsoftware/kmcaster/EventHandler.java
package com.whitemagicsoftware.kmcaster;
-import javax.swing.*;
+import com.whitemagicsoftware.kmcaster.ui.AutofitLabel;
+
import java.awt.*;
-import java.awt.font.TextLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
final var switchValue = e.getNewValue().toString();
- // True or false indicates a non-regular key was pressed.
+ // True indicates a modifier key was pressed; false indicates a key was
+ // released (doesn't matter what kind of key).
final var context =
(!"false".equals( switchValue ) && !"true".equals( switchValue ))
if( !"false".equals( value ) ) {
- final var bounds = component.getBounds();
- final var insets = component.getInsets();
-
- bounds.x += insets.left;
- bounds.y += insets.top;
- bounds.width -= insets.right + insets.left;
- bounds.height -= insets.bottom + insets.top;
-
- final var label = createLabel( value, bounds, component.getGraphics() );
-
- label.setLocation( insets.left, insets.top );
+ final var label = new AutofitLabel( value, LABEL_FONT, LABEL_COLOUR );
component.add( label );
- component.repaint();
- }
- }
- }
-
- /**
- * Creates a label that can fit within bounds defined by the given
- * {@link Rectangle} using the given {@link Graphics} context for deriving
- * the actualized font dimensions (for a particular font point size).
- *
- * @param text The label's text.
- * @param r The label's maximum width and height.
- * @param graphics The graphics context used to determine font scale (must
- * be a {@link Graphics2D instance}).
- * @return A label adjusted to the given dimensions.
- */
- private JLabel createLabel(
- final String text, final Rectangle r, final Graphics graphics ) {
- assert text != null;
- assert r != null;
- assert graphics != null;
-
- final int width = (int) r.getWidth();
- final int height = (int) r.getHeight();
-
- final var label = new JLabel( text );
- label.setFont( LABEL_FONT );
- label.setSize( width, height );
- label.setForeground( LABEL_COLOUR );
-
- final var scaledFont = scaleFont( label, r, graphics );
- label.setFont( scaledFont );
-
- return label;
- }
-
- /**
- * Adjusts the given {@link Font}/{@link String} size such that it fits
- * within the bounds of the given {@link Rectangle}.
- *
- * @param label Contains the text and font to scale.
- * @param dst The bounds for fitting the string.
- * @param graphics The context for rendering the string.
- * @return A new {@link Font} instance that is guaranteed to write the given
- * string within the bounds of the given {@link Rectangle}.
- */
- public Font scaleFont(
- final JLabel label, final Rectangle dst, final Graphics graphics ) {
- assert label != null;
- assert dst != null;
- assert graphics != null;
- final var frc = ((Graphics2D) graphics).getFontRenderContext();
-
- final var dstWidthPx = dst.getWidth();
- final var dstHeightPx = dst.getHeight();
-
- final var text = label.getText();
-
- var minSizePt = 1f;
- var maxSizePt = 1000f;
- var scaledFont = label.getFont();
- float scaledPt = scaledFont.getSize();
-
- while( maxSizePt - minSizePt > 1f ) {
- scaledFont = scaledFont.deriveFont( scaledPt );
-
- final var layout = new TextLayout( text, scaledFont, frc );
- final var fontWidthPx = layout.getVisibleAdvance();
- final var metrics = scaledFont.getLineMetrics( text, frc );
- final var fontHeightPx = metrics.getHeight();
-
- if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
- maxSizePt = scaledPt;
- }
- else {
- minSizePt = scaledPt;
}
-
- scaledPt = (minSizePt + maxSizePt) / 2;
}
-
- // Round down to guarantee fit.
- return scaledFont.deriveFont( (float) Math.floor( scaledPt ) );
}
}
src/main/com/whitemagicsoftware/kmcaster/HardwareImages.java
*/
public class HardwareImages {
-
private final static String DIR_IMAGES = "/images";
private final static String DIR_IMAGES_KEYBOARD = DIR_IMAGES + "/key";
private final static String DIR_IMAGES_MOUSE = DIR_IMAGES + "/mouse";
private final static SvgRasterizer sRasterizer = new SvgRasterizer();
private final Map<HardwareSwitch, HardwareComponent<HardwareState, Image>>
mSwitches = new HashMap<>();
- private final Dimension mDimension;
+
+ /**
+ * 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.
+ */
+ private final Dimension mDimension = new Dimension( 1024, 60 );
/**
* Constructs an enumerated type that represents the different types of
* images shown when keyboard and mouse events are triggered.
- *
- * @param dimension The image will be scaled to the given dimensions, aspect
- * ratio is maintained.
*/
- public HardwareImages( final Dimension dimension ) {
- assert dimension != null;
-
- mDimension = dimension;
-
+ public HardwareImages() {
final var mouseReleased = mouseImage( "0" );
final var mouseStates = new HardwareComponent<HardwareState, Image>();
private Image createImage( final String path ) {
- assert mDimension != null;
-
final var resource = format( "%s.svg", path );
src/main/com/whitemagicsoftware/kmcaster/KmCaster.java
@SuppressWarnings("unused")
public class KmCaster extends JFrame {
- private static final Dimension FRAME_DIMENSIONS = new Dimension( 484, 70 );
- private static final Dimension HARDWARE_DIMENSIONS = new Dimension(
- (int) FRAME_DIMENSIONS.getWidth(),
- (int) (FRAME_DIMENSIONS.getHeight() - 10) );
private static final Color TRANSPARENT = new Color( 0, 0, 0, 0 );
private static final Color TRANSLUCENT = new Color( .2f, .2f, .2f, 0.5f );
private final static float TYPING_SPEED_CPMS = TYPING_SPEED_CPS / 1000;
- private final HardwareImages mHardwareImages =
- new HardwareImages( HARDWARE_DIMENSIONS );
+ private final HardwareImages mHardwareImages = new HardwareImages();
private final EventHandler mEventHandler =
new EventHandler( mHardwareImages );
setAlwaysOnTop( true );
setBackground( TRANSPARENT );
- setSize( FRAME_DIMENSIONS );
setShape( createShape() );
}
src/main/com/whitemagicsoftware/kmcaster/ui/AutofitLabel.java
+/*
+ * Copyright 2020 White Magic Software, Ltd.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.whitemagicsoftware.kmcaster.ui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+
+import static java.awt.event.HierarchyEvent.PARENT_CHANGED;
+
+/**
+ * Responsible for changing the {@link JLabel}'s font size
+ */
+public class AutofitLabel extends JLabel {
+
+ /**
+ * Constructs an instance of {@link AutofitLabel} that will rescale itself
+ * to the parent {@link Container}, automatically.
+ *
+ * @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.
+ */
+ public AutofitLabel( final String text, final Font font, final Color color ) {
+ super( text );
+ setFont( font );
+ setForeground( color );
+
+ addHierarchyListener( e -> {
+ if( (e.getChangeFlags() & PARENT_CHANGED) != 0 ) {
+ if( getParent() == e.getChangedParent() ) {
+ rescale();
+ }
+ }
+ } );
+ }
+
+ /**
+ * Rescales the constructed font to fit within the bounds of the parent
+ * {@link Container}. This must only be called after the parent
+ * {@link Container} has been set, otherwise there will be no
+ * {@link Graphics} context available to compute the maximum {@link Font}
+ * size that will fit the parent's {@link Rectangle} bounds.
+ */
+ private void rescale() {
+ final var component = getParent();
+ final var bounds = component.getBounds();
+ final var insets = component.getInsets();
+
+ bounds.x += insets.left;
+ bounds.y += insets.top;
+ bounds.width -= insets.left + insets.right;
+ bounds.height -= insets.top + insets.bottom;
+
+ setLocation( insets.left, insets.top );
+ setSize( bounds.width, bounds.height );
+ setFont( computeScaledFont() );
+ }
+
+ /**
+ * Calculates a new {@link Font} size such that it fits within the bounds
+ * of this label instance. This uses the label's current size, which must
+ * be set prior to calling this method.
+ *
+ * @return A new {@link Font} instance that is guaranteed to write the given
+ * string within the bounds of the given {@link Rectangle}.
+ */
+ private Font computeScaledFont() {
+ final var frc = getFontRenderContext();
+ final var text = getText();
+
+ final var dstWidthPx = getWidth();
+ final var dstHeightPx = getHeight();
+
+ var minSizePt = 1f;
+ var maxSizePt = 200f;
+ var scaledFont = getFont();
+ float scaledPt = scaledFont.getSize();
+
+ while( maxSizePt - minSizePt > 1f ) {
+ scaledFont = scaledFont.deriveFont( scaledPt );
+
+ final var layout = new TextLayout( text, scaledFont, frc );
+ final var fontWidthPx = layout.getVisibleAdvance();
+ final var metrics = scaledFont.getLineMetrics( text, frc );
+ final var fontHeightPx = metrics.getHeight();
+
+ if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
+ maxSizePt = scaledPt;
+ }
+ else {
+ minSizePt = scaledPt;
+ }
+
+ scaledPt = (minSizePt + maxSizePt) / 2;
+ }
+
+ // Round down to guarantee fit.
+ return scaledFont.deriveFont( (float) Math.floor( scaledPt ) );
+ }
+
+ /**
+ * Gets the {@link FontRenderContext} for the parent {@link Container}'s
+ * {@link Graphics} context, casting it to a {@link Graphics2D} context.
+ *
+ * @return The parent's {@link Graphics2D} context.
+ */
+ private FontRenderContext getFontRenderContext() {
+ return ((Graphics2D) getGraphics()).getFontRenderContext();
+ }
+}
Delta 151 lines added, 114 lines removed, 37-line increase