All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.weisj.darklaf.settings.ThemeSettingsUI Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
/*
 * MIT License
 *
 * Copyright (c) 2021 Jannis Weis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package com.github.weisj.darklaf.settings;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.List;
import java.util.function.Function;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.SliderUI;

import com.github.weisj.darklaf.LafManager;
import com.github.weisj.darklaf.components.ColoredRadioButton;
import com.github.weisj.darklaf.components.DynamicUI;
import com.github.weisj.darklaf.components.tooltip.ToolTipContext;
import com.github.weisj.darklaf.components.tristate.TristateCheckBox;
import com.github.weisj.darklaf.components.tristate.TristateState;
import com.github.weisj.darklaf.graphics.ThemedColor;
import com.github.weisj.darklaf.listener.UIUpdater;
import com.github.weisj.darklaf.platform.ThemePreferencesHandler;
import com.github.weisj.darklaf.platform.macos.theme.MacOSColors;
import com.github.weisj.darklaf.theme.Theme;
import com.github.weisj.darklaf.theme.info.AccentColorRule;
import com.github.weisj.darklaf.theme.info.FontSizePreset;
import com.github.weisj.darklaf.theme.info.FontSizeRule;
import com.github.weisj.darklaf.theme.info.PreferredThemeStyle;
import com.github.weisj.darklaf.ui.combobox.ComboBoxConstants;
import com.github.weisj.darklaf.ui.slider.DarkSliderUI;
import com.github.weisj.darklaf.ui.tooltip.ToolTipConstants;
import com.github.weisj.darklaf.util.Alignment;

public class ThemeSettingsUI {

    public final static String THEME_LABEL_KEY = "label_theme";
    public final static String SELECTION_COLOR_LABEL_KEY = "label_selection_color";
    public final static String ACCENT_COLOR_LABEL_KEY = "label_accent_color";
    public final static String FONT_SIZE_LABEL_KEY = "label_font_size";
    public final static String GENERAL_LABEL_KEY = "title_general";
    public final static String MONITORING_LABEL_KEY = "title_monitoring";

    private final SettingsUIConfiguration settingsConfiguration;
    private final List listeners = new ArrayList<>();

    private final JComboBox themeComboBox;

    private final RadioColorChooser selectionChooser;
    private final RadioColorChooser accentChooser;

    private final JSlider fontSlider;

    private final TristateCheckBox enabledSystemPreferences;
    private final JCheckBox fontSizeFollowsSystem;
    private final JCheckBox accentColorFollowsSystem;
    private final JCheckBox selectionColorFollowsSystem;
    private final JCheckBox themeFollowsSystem;

    public ThemeSettingsUI() {
        settingsConfiguration = new SettingsUIConfiguration();
        Color currentAccentColor = LafManager.getTheme().getAccentColorRule().getAccentColor();
        Color currentSelectionColor = LafManager.getTheme().getAccentColorRule().getSelectionColor();

        themeComboBox = createThemeComboBox();

        accentChooser = createAccentColorChooser(currentAccentColor);
        selectionChooser = createSelectionColorChooser(currentSelectionColor);

        List accentButtons = accentChooser.getRadioButtons();
        List selectionButtons = selectionChooser.getRadioButtons();
        for (int i = 0; i < accentButtons.size(); i++) {
            ColoredRadioButton button = accentButtons.get(i);
            ColoredRadioButton peer = selectionButtons.get(i);
            button.addActionListener(e -> {
                if (button.isSelected()) peer.setSelected(true);
            });
        }

        fontSlider = createFontSlider();

        enabledSystemPreferences = DynamicUI.withDynamic(new TristateCheckBox(),
                c -> c.setText(UIManager.getString("check_system_preferences", c.getLocale())));
        accentColorFollowsSystem = createSystemSettingCheckBox("check_system_accent_color",
                ThemePreferencesHandler::supportsNativeAccentColor);
        selectionColorFollowsSystem = createSystemSettingCheckBox("check_system_selection_color",
                ThemePreferencesHandler::supportsNativeSelectionColor);
        fontSizeFollowsSystem = createSystemSettingCheckBox("check_system_font",
                ThemePreferencesHandler::supportsNativeFontSize);
        themeFollowsSystem = createSystemSettingCheckBox("check_system_theme",
                ThemePreferencesHandler::supportsNativeTheme);

        setupListeners();
    }

    private void setupListeners() {
        themeComboBox.addItemListener(e -> update());
        enabledSystemPreferences.addChangeListener(e -> {
            if (!enabledSystemPreferences.getTristateModel().isIndeterminate()) {
                boolean selected = enabledSystemPreferences.getTristateModel().isSelected();

                if (themeFollowsSystem.isEnabled()) themeFollowsSystem.setSelected(selected);
                if (accentColorFollowsSystem.isEnabled()) accentColorFollowsSystem.setSelected(selected);
                if (selectionColorFollowsSystem.isEnabled()) selectionColorFollowsSystem.setSelected(selected);
                if (fontSizeFollowsSystem.isEnabled()) fontSizeFollowsSystem.setSelected(selected);
            }
            update();
        });
        themeFollowsSystem.addActionListener(e -> update());

        accentColorFollowsSystem.addActionListener(e -> update());
        selectionColorFollowsSystem.addActionListener(e -> update());
        fontSizeFollowsSystem.addActionListener(e -> update());

        enabledSystemPreferences.setSelected(LafManager.isPreferenceChangeReportingEnabled());
    }

    private void update() {
        boolean enabled = !enabledSystemPreferences.getTristateModel().isDeselected();

        enabledSystemPreferences.setEnabled(settingsConfiguration.isSystemPreferencesSupported());

        themeFollowsSystem.setEnabled(enabled && settingsConfiguration.isSystemThemeSupported());
        accentColorFollowsSystem.setEnabled(enabled && settingsConfiguration.isSystemAccentColorSupported());
        selectionColorFollowsSystem.setEnabled(enabled && settingsConfiguration.isSystemSelectionColorSupported());
        fontSizeFollowsSystem.setEnabled(enabled && settingsConfiguration.isSystemFontSizeSupported());

        settingsConfiguration.setSystemPreferencesEnabled(settingsConfiguration.isSystemPreferencesEnabled());

        accentChooser.setEnabled(!settingsConfiguration.isAccentColorFollowsSystem()
                && settingsConfiguration.getSelectedTheme().supportsCustomAccentColor());
        selectionChooser.setEnabled(!settingsConfiguration.isSelectionColorFollowsSystem()
                && settingsConfiguration.getSelectedTheme().supportsCustomSelectionColor());

        themeComboBox.setEnabled(!settingsConfiguration.isThemeFollowsSystem());
        fontSlider.setEnabled(!settingsConfiguration.isFontSizeFollowsSystem());

        if (!listeners.isEmpty()) {
            ChangeEvent e = new ChangeEvent(this);
            for (ChangeListener listener : listeners) {
                listener.stateChanged(e);
            }
        }
    }

    protected void setAccentColor(final Color accentColor) {
        accentChooser.setColor(accentColor);
    }

    protected void setSelectionColor(final Color selectionColor) {
        selectionChooser.setColor(selectionColor);
    }

    public void loadConfiguration(final SettingsConfiguration configuration) {
        themeComboBox.setModel(LafManager.getThemeComboBoxModel());
        settingsConfiguration.load(configuration);
        settingsConfiguration.setAccentColorRule(settingsConfiguration.getAccentColorRule());
        update();
    }

    public SettingsConfiguration getSettingsConfiguration() {
        return settingsConfiguration;
    }

    /**
     * Add a listener which gets notified when a !potential! change is made to the settings.
     *
     * @param listener the listener to add.
     */
    public void addChangeListener(final ChangeListener listener) {
        listeners.add(listener);
    }

    /**
     * Removes a change listener.
     *
     * @param listener the listener to remove.
     */
    public void removeChangeListener(final ChangeListener listener) {
        listeners.remove(listener);
    }

    public JComboBox getThemeComboBox() {
        return themeComboBox;
    }

    public RadioColorChooser getSelectionChooser() {
        return selectionChooser;
    }

    public RadioColorChooser getAccentChooser() {
        return accentChooser;
    }

    public JSlider getFontSlider() {
        return fontSlider;
    }

    public TristateCheckBox getSystemPreferencesTristateCheckBox() {
        return enabledSystemPreferences;
    }

    public JCheckBox getAccentColorFollowsSystemCheckBox() {
        return accentColorFollowsSystem;
    }

    public JCheckBox getSelectionColorFollowsSystemCheckBox() {
        return selectionColorFollowsSystem;
    }

    public JCheckBox getFontSizeFollowsSystemCheckBox() {
        return fontSizeFollowsSystem;
    }

    public JCheckBox getThemeFollowsSystemCheckBox() {
        return themeFollowsSystem;
    }

    private static JComboBox createThemeComboBox() {
        JComboBox comboBox = new JComboBox<>(LafManager.getThemeComboBoxModel());
        comboBox.setRenderer(LafManager.getThemeListCellRenderer());
        comboBox.putClientProperty(ComboBoxConstants.KEY_DO_NOT_UPDATE_WHEN_SCROLLED, true);
        return comboBox;
    }

    private static RadioColorChooser createAccentColorChooser(final Color currentAccentColor) {
        Color defaultAccentColor = new ThemedColor("themeAccentColor");
        return new RadioColorChooser(
                Arrays.asList(
                        new RadioColorChooser.ColorSpec(
                                ColoredRadioButton.DEFAULT_COLOR, defaultAccentColor, "color_default"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_BLUE, null, "color_blue"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_LILAC, null, "color_lilac"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_ROSE, null, "color_rose"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_RED, null, "color_red"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_ORANGE, null, "color_orange"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_YELLOW, null, "color_yellow"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_GREEN, null, "color_green"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.ACCENT_GRAPHITE, null, "color_gray")),
                new RadioColorChooser.ColorSpec(
                        currentAccentColor, null, "color_custom"),
                currentAccentColor, defaultAccentColor);
    }

    private static RadioColorChooser createSelectionColorChooser(final Color currentSelectionColor) {
        Color defaultSelectionColor = new ThemedColor("themeSelectionColor");
        return new RadioColorChooser(
                Arrays.asList(
                        new RadioColorChooser.ColorSpec(
                                defaultSelectionColor, ColoredRadioButton.DEFAULT_COLOR, "color_default"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_BLUE, MacOSColors.ACCENT_BLUE, "color_blue"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_PURPLE, MacOSColors.ACCENT_LILAC, "color_purple"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_PINK, MacOSColors.ACCENT_ROSE, "color_pink"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_RED, MacOSColors.ACCENT_RED, "color_red"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_ORANGE, MacOSColors.ACCENT_ORANGE, "color_orange"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_YELLOW, MacOSColors.ACCENT_YELLOW, "color_yellow"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_GREEN, MacOSColors.ACCENT_GREEN, "color_green"),
                        new RadioColorChooser.ColorSpec(
                                MacOSColors.SELECTION_GRAPHITE, MacOSColors.ACCENT_GRAPHITE, "color_gray")),
                new RadioColorChooser.ColorSpec(
                        currentSelectionColor, defaultSelectionColor, "color_custom"),
                currentSelectionColor, defaultSelectionColor);
    }

    private static JSlider createFontSlider() {
        JSlider fontSlider = new JSlider() {
            @Override
            public String getToolTipText(final MouseEvent event) {
                return getValue() + "%";
            }
        };
        ToolTipContext context = new ToolTipContext()
                .setAlignment(Alignment.CENTER)
                .setCenterAlignment(Alignment.NORTH)
                .setUseBestFit(true).setToolTipRectSupplier(e -> {
                    SliderUI ui = fontSlider.getUI();
                    if (ui instanceof DarkSliderUI) {
                        Rectangle r = ((DarkSliderUI) ui).getThumbRect();
                        r.x -= 1;
                        return r;
                    }
                    return new Rectangle(0, 0, fontSlider.getWidth(), fontSlider.getHeight());
                });
        fontSlider.putClientProperty(DarkSliderUI.KEY_INSTANT_SCROLL, true);
        fontSlider.putClientProperty(DarkSliderUI.KEY_USE_TRACK_AS_BASELINE, true);
        fontSlider.putClientProperty(ToolTipConstants.KEY_CONTEXT, context);
        fontSlider.putClientProperty(ToolTipConstants.KEY_STYLE, ToolTipConstants.VARIANT_BALLOON);
        fontSlider.setToolTipText(String.valueOf(fontSlider.getValue()));
        fontSlider.setOrientation(JSlider.HORIZONTAL);
        fontSlider.setPaintTicks(true);
        fontSlider.setMinimum(FontSizePreset.TINY.getPercentage());
        fontSlider.setMaximum(FontSizePreset.HUGE.getPercentage());
        int tickSpacing = 25;
        Dictionary dict = fontSlider.createStandardLabels(tickSpacing);
        JLabel min = ((JLabel) dict.get(fontSlider.getMinimum()));
        UIUpdater.registerComponent(min);
        DynamicUI.withDynamic(min, c -> c.setText(UIManager.getString("label_font_smaller", fontSlider.getLocale())));
        min.setAlignmentX(JComponent.LEFT_ALIGNMENT);
        min.putClientProperty(DarkSliderUI.KEY_MANUAL_LABEL_ALIGN, true);

        JLabel mid = ((JLabel) dict.get(fontSlider.getMinimum() + tickSpacing));
        UIUpdater.registerComponent(mid);
        dict.remove(fontSlider.getMinimum() + tickSpacing);
        dict.put(FontSizePreset.NORMAL.getPercentage(), mid);
        DynamicUI.withDynamic(mid, c -> c.setText(UIManager.getString("label_font_default", fontSlider.getLocale())));

        mid.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        mid.setHorizontalTextPosition(JLabel.RIGHT);

        JLabel max = ((JLabel) dict.get(fontSlider.getMaximum()));
        max.putClientProperty(DarkSliderUI.KEY_MANUAL_LABEL_ALIGN, true);
        DynamicUI.withDynamic(max, c -> c.setText(UIManager.getString("label_font_bigger", fontSlider.getLocale())));
        max.setAlignmentX(JComponent.RIGHT_ALIGNMENT);
        max.putClientProperty(DarkSliderUI.KEY_MANUAL_LABEL_ALIGN, true);
        UIUpdater.registerComponent(max);

        dict.remove(FontSizePreset.Small.getPercentage());
        dict.remove(FontSizePreset.SMALL.getPercentage());
        dict.remove(FontSizePreset.Large.getPercentage());
        dict.remove(FontSizePreset.LARGE.getPercentage());
        dict.remove(FontSizePreset.Huge.getPercentage());

        fontSlider.setLabelTable(dict);
        fontSlider.setMajorTickSpacing(tickSpacing);
        fontSlider.setMinorTickSpacing(tickSpacing);
        fontSlider.setPaintLabels(true);
        fontSlider.setSnapToTicks(true);
        return fontSlider;
    }

    private static JCheckBox createSystemSettingCheckBox(final String labelKey,
            final Function enabledCondition) {
        JCheckBox checkBox = DynamicUI.withDynamic(new JCheckBox() {
            @Override
            public void setEnabled(final boolean b) {
                boolean enabled = b && enabledCondition.apply(ThemePreferencesHandler.getSharedInstance());
                super.setEnabled(enabled);
            }
        }, c -> c.setText(UIManager.getString(labelKey, c.getLocale())));
        checkBox.setSelected(false);
        checkBox.setEnabled(false);
        return checkBox;
    }

    private class SettingsUIConfiguration extends SettingsConfiguration {

        @Override
        public boolean isSystemPreferencesEnabled() {
            return !enabledSystemPreferences.getTristateModel().isDeselected() && isSystemPreferencesSupported();
        }

        @Override
        public boolean isAccentColorFollowsSystem() {
            return accentColorFollowsSystem.isSelected() && isSystemAccentColorSupported();
        }

        @Override
        public boolean isFontSizeFollowsSystem() {
            return fontSizeFollowsSystem.isSelected() && isSystemFontSizeSupported();
        }

        @Override
        public boolean isSelectionColorFollowsSystem() {
            return selectionColorFollowsSystem.isSelected() && isSystemSelectionColorSupported();
        }

        @Override
        public boolean isThemeFollowsSystem() {
            return themeFollowsSystem.isSelected() && isSystemThemeSupported();
        }

        @Override
        public boolean isSystemAccentColorSupported() {
            return super.isSystemAccentColorSupported() && getSelectedTheme().supportsCustomAccentColor();
        }

        @Override
        public boolean isSystemSelectionColorSupported() {
            return super.isSystemSelectionColorSupported() && getSelectedTheme().supportsCustomSelectionColor();
        }

        private Color getSelectedColor(final RadioColorChooser chooser) {
            Color c = chooser.getSelectedColor();
            return c == chooser.getDefaultColor() ? null : c;
        }

        @Override
        public AccentColorRule getAccentColorRule() {
            // Avoid unnecessary native calls.
            boolean useSystemAccent = isAccentColorFollowsSystem();
            boolean useSystemSelection = isSelectionColorFollowsSystem();
            PreferredThemeStyle prefStyle = useSystemAccent || useSystemSelection
                    ? LafManager.getPreferredThemeStyle()
                    : null;
            Theme theme = getTheme(prefStyle);
            Color accentColor = null;
            Color selectionColor = null;
            if (theme.supportsCustomAccentColor()) {
                accentColor = useSystemAccent
                        ? prefStyle.getAccentColorRule().getAccentColor()
                        : getSelectedColor(ThemeSettingsUI.this.accentChooser);
            }
            if (theme.supportsCustomSelectionColor()) {
                selectionColor = useSystemSelection
                        ? prefStyle.getAccentColorRule().getSelectionColor()
                        : getSelectedColor(ThemeSettingsUI.this.selectionChooser);
            }
            return AccentColorRule.fromColor(accentColor, selectionColor);
        }

        @Override
        public FontSizeRule getFontSizeRule() {
            return isFontSizeFollowsSystem()
                    ? LafManager.getPreferredThemeStyle().getFontSizeRule()
                    : FontSizeRule.relativeAdjustment(fontSlider.getValue());
        }

        @Override
        public Theme getTheme() {
            return getTheme(null);
        }

        private Theme getTheme(final PreferredThemeStyle preferredThemeStyle) {
            if (isThemeFollowsSystem()) {
                PreferredThemeStyle prefStyle = preferredThemeStyle != null
                        ? preferredThemeStyle
                        : LafManager.getPreferredThemeStyle();
                return LafManager.themeForPreferredStyle(prefStyle);
            } else {
                return getSelectedTheme();
            }
        }

        private Theme getSelectedTheme() {
            Theme selected = (Theme) themeComboBox.getSelectedItem();
            if (selected == null) {
                selected = LafManager.getInstalledTheme();
                selected = selected != null ? selected : LafManager.getTheme();
                setTheme(selected);
            }
            return selected;
        }

        @Override
        public void setSystemPreferencesEnabled(final boolean enabled) {
            TristateState state = TristateState.DESELECTED;
            if (enabled && (isFontSizeFollowsSystem() || !fontSizeFollowsSystem.isEnabled())
                    && (isThemeFollowsSystem() || !themeFollowsSystem.isEnabled())
                    && (isAccentColorFollowsSystem() || !accentColorFollowsSystem.isEnabled())
                    && (isSelectionColorFollowsSystem() || !selectionColorFollowsSystem.isEnabled())) {
                state = TristateState.SELECTED;
            } else if (enabled) {
                enabledSystemPreferences.getTristateModel().setIndeterminate();
                return;
            }
            enabledSystemPreferences.getTristateModel().setState(state);
        }

        @Override
        public void setAccentColorFollowsSystem(final boolean accentColorFollowsSystem) {
            ThemeSettingsUI.this.accentColorFollowsSystem
                    .setSelected(accentColorFollowsSystem && isSystemAccentColorSupported(false));
        }

        @Override
        public void setFontSizeFollowsSystem(final boolean fontSizeFollowsSystem) {
            ThemeSettingsUI.this.fontSizeFollowsSystem
                    .setSelected(fontSizeFollowsSystem && isSystemFontSizeSupported(false));
        }

        @Override
        public void setSelectionColorFollowsSystem(final boolean selectionColorFollowsSystem) {
            ThemeSettingsUI.this.selectionColorFollowsSystem
                    .setSelected(selectionColorFollowsSystem && isSystemSelectionColorSupported(false));
        }

        @Override
        public void setThemeFollowsSystem(final boolean themeFollowsSystem) {
            ThemeSettingsUI.this.themeFollowsSystem
                    .setSelected(themeFollowsSystem && isSystemThemeSupported(false));
        }

        @Override
        public void setAccentColorRule(final AccentColorRule accentColorRule) {
            if (accentColorRule == null) {
                setAccentColorRule(null, null);
            } else {
                setAccentColorRule(accentColorRule.getAccentColor(), accentColorRule.getSelectionColor());
            }
        }

        protected void setAccentColorRule(final Color accentColor, final Color selectionColor) {
            setAccentColor(accentColor);
            setSelectionColor(selectionColor);
        }

        @Override
        public void setFontSizeRule(final FontSizeRule fontSizeRule) {
            if (fontSizeRule == null) {
                fontSlider.setValue(FontSizePreset.NORMAL.getPercentage());
            } else {
                fontSlider.setValue(fontSizeRule.getPercentage());
            }
        }

        @Override
        public void setTheme(final Theme theme) {
            themeComboBox.setSelectedItem(LafManager.getClosestMatchForTheme(theme));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy