mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import io.github.dsheirer.controller.channel.ChannelModel;
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
|
||||
import io.github.dsheirer.gui.theme.ThemeManager;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
@@ -246,7 +247,8 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
{
|
||||
label.setText(Joiner.on(", ").skipNulls().join(aliases));
|
||||
label.setIcon(mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE));
|
||||
label.setForeground(aliases.get(0).getDisplayColor());
|
||||
label.setForeground(ThemeManager.getInstance().getReadableColor(aliases.get(0).getDisplayColor(),
|
||||
label.getBackground()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -19,15 +19,19 @@
|
||||
|
||||
package io.github.dsheirer.gui.playlist.alias;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasFactory;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.control.MaxLengthUnaryOperator;
|
||||
import io.github.dsheirer.gui.playlist.Editor;
|
||||
import io.github.dsheirer.gui.playlist.IAliasListRefreshListener;
|
||||
import io.github.dsheirer.gui.theme.ThemeManager;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -66,6 +70,7 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javax.swing.UIManager;
|
||||
import jiconfont.IconCode;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.javafx.IconNode;
|
||||
@@ -124,6 +129,20 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
setOrientation(Orientation.VERTICAL);
|
||||
mCurrentEditor = getAliasItemEditor();
|
||||
getItems().addAll(topBox, getAliasItemEditor());
|
||||
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraws the alias color swatches after a change to the application theme.
|
||||
*/
|
||||
@Subscribe
|
||||
public void onPreferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.APPLICATION)
|
||||
{
|
||||
Platform.runLater(() -> getAliasTableView().refresh());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -727,7 +746,11 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
if(alias != null)
|
||||
{
|
||||
rectangle.setVisible(true);
|
||||
rectangle.setFill(ColorUtil.fromInteger(alias.getColor()));
|
||||
java.awt.Color color = alias.getDisplayColor();
|
||||
java.awt.Color readable = ThemeManager.getInstance().getReadableColor(color,
|
||||
UIManager.getColor("TextField.background"));
|
||||
rectangle.setFill(ColorUtil.fromInteger(readable.equals(color) ? alias.getColor() :
|
||||
readable.getRGB()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
100
src/main/java/io/github/dsheirer/gui/theme/ColorContrast.java
Normal file
100
src/main/java/io/github/dsheirer/gui/theme/ColorContrast.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.theme;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Color contrast utilities based on the WCAG relative luminance and contrast ratio definitions.
|
||||
*/
|
||||
public final class ColorContrast
|
||||
{
|
||||
/**
|
||||
* Minimum contrast ratio to treat a color as readable. This is the WCAG threshold for graphics and large text.
|
||||
* The stricter 4.5 threshold for body text would also replace saturated colors like pure red that read fine on a
|
||||
* dark background.
|
||||
*/
|
||||
public static final double MINIMUM_CONTRAST_RATIO = 3.0;
|
||||
|
||||
private static final double BLEND_STEP = 0.05;
|
||||
|
||||
private ColorContrast()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a color that is readable against the background.
|
||||
* @param color that is preferred.
|
||||
* @param background that the color is rendered against.
|
||||
* @return the color when it has sufficient contrast, otherwise its RGB complement, adjusted toward white or black
|
||||
* when the complement also lacks sufficient contrast (e.g. mid-gray is its own complement).
|
||||
*/
|
||||
public static Color getReadable(Color color, Color background)
|
||||
{
|
||||
if(getContrastRatio(color, background) >= MINIMUM_CONTRAST_RATIO)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
Color complement = new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue());
|
||||
Color target = getContrastRatio(Color.WHITE, background) >= getContrastRatio(Color.BLACK, background) ?
|
||||
Color.WHITE : Color.BLACK;
|
||||
|
||||
Color readable = complement;
|
||||
|
||||
for(double fraction = BLEND_STEP; getContrastRatio(readable, background) < MINIMUM_CONTRAST_RATIO &&
|
||||
fraction < 1.0; fraction += BLEND_STEP)
|
||||
{
|
||||
readable = blend(complement, target, fraction);
|
||||
}
|
||||
|
||||
return getContrastRatio(readable, background) < MINIMUM_CONTRAST_RATIO ? target : readable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contrast ratio between two colors.
|
||||
* @return ratio in the range 1.0 (identical luminance) to 21.0 (black and white).
|
||||
*/
|
||||
public static double getContrastRatio(Color a, Color b)
|
||||
{
|
||||
double luminanceA = getRelativeLuminance(a);
|
||||
double luminanceB = getRelativeLuminance(b);
|
||||
return (Math.max(luminanceA, luminanceB) + 0.05) / (Math.min(luminanceA, luminanceB) + 0.05);
|
||||
}
|
||||
|
||||
private static double getRelativeLuminance(Color color)
|
||||
{
|
||||
return 0.2126 * linearize(color.getRed()) + 0.7152 * linearize(color.getGreen()) +
|
||||
0.0722 * linearize(color.getBlue());
|
||||
}
|
||||
|
||||
private static double linearize(int value)
|
||||
{
|
||||
double normalized = value / 255.0;
|
||||
return normalized <= 0.04045 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
private static Color blend(Color from, Color to, double fraction)
|
||||
{
|
||||
return new Color((int)Math.round(from.getRed() + (to.getRed() - from.getRed()) * fraction),
|
||||
(int)Math.round(from.getGreen() + (to.getGreen() - from.getGreen()) * fraction),
|
||||
(int)Math.round(from.getBlue() + (to.getBlue() - from.getBlue()) * fraction));
|
||||
}
|
||||
}
|
||||
@@ -221,6 +221,18 @@ public class ThemeManager
|
||||
return mCurrentTheme != null && mCurrentTheme.isDark();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the color to render user-selected content (e.g. an alias) against a background in the current theme.
|
||||
* @param color selected by the user.
|
||||
* @param background that the color is rendered against.
|
||||
* @return the color, or a readable alternative when dark mode is active and the color lacks contrast with the
|
||||
* background.
|
||||
*/
|
||||
public Color getReadableColor(Color color, Color background)
|
||||
{
|
||||
return isDarkMode() ? ColorContrast.getReadable(color, background) : color;
|
||||
}
|
||||
|
||||
public void register(Scene scene)
|
||||
{
|
||||
if(scene == null)
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.gui.theme.ThemeManager;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
@@ -285,7 +286,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
color = aliases.getFirst().getDisplayColor();
|
||||
color = ThemeManager.getInstance().getReadableColor(
|
||||
aliases.getFirst().getDisplayColor(), label.getBackground());
|
||||
icon = mIconModel.getIcon(aliases.getFirst().getIconName(), IconModel.DEFAULT_ICON_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.theme;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.awt.Color;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ColorContrastTest
|
||||
{
|
||||
private static final Color DARK_BACKGROUND = new Color(0x2B2B2B);
|
||||
private static final Color SELECTION_BACKGROUND = new Color(0x214283);
|
||||
|
||||
@Test
|
||||
void blackBecomesWhite()
|
||||
{
|
||||
assertEquals(Color.WHITE, ColorContrast.getReadable(Color.BLACK, DARK_BACKGROUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lowContrastColorBecomesComplement()
|
||||
{
|
||||
assertEquals(Color.YELLOW, ColorContrast.getReadable(Color.BLUE, DARK_BACKGROUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readableColorsAreUnchanged()
|
||||
{
|
||||
assertEquals(Color.RED, ColorContrast.getReadable(Color.RED, DARK_BACKGROUND));
|
||||
assertEquals(Color.ORANGE, ColorContrast.getReadable(Color.ORANGE, DARK_BACKGROUND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyGrayIsReadable()
|
||||
{
|
||||
for(int value = 0; value < 256; value++)
|
||||
{
|
||||
Color gray = new Color(value, value, value);
|
||||
|
||||
for(Color background: new Color[]{DARK_BACKGROUND, SELECTION_BACKGROUND, new Color(127, 127, 127)})
|
||||
{
|
||||
assertTrue(ColorContrast.getContrastRatio(ColorContrast.getReadable(gray, background), background) >=
|
||||
ColorContrast.MINIMUM_CONTRAST_RATIO, "Gray " + value + " on " + background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user