javaswinglook-and-feel

How use UIDefault for create new look and feel for swing


I'm developing a new look and feel for Java; this look and feel imports the Material-style.

I had used the UIDefault for import the change inside the look and feel, so now I must uninstall the look and feel and I have a problem with this operation.

The look and feel con not removed correctly, now I've used for all operation the UIDefault map and I think this is an error because my constant override the old constant and when I go to remove my look and feel. the new look and feel don't override my constant

This is how I'm using the UIDefault

@Override
    protected void initComponentDefaults(UIDefaults table) {
        super.initComponentDefaults(table);

        table.put("Button.highlight", MaterialColors.GRAY_400);
        table.put("Button.opaque", false);
        table.put("Button.border", BorderFactory.createEmptyBorder(7, 17, 7, 17));
        table.put("Button.background", MaterialColors.GRAY_200);
        table.put("Button.foreground", MaterialColors.COSMO_BLACK);
        table.put("Button.disabledBackground", MaterialColors.COSMO_DARK_GRAY);
        table.put("Button.disabledForeground", MaterialColors.BLACK);
        table.put("Button[Default].background", MaterialColors.LIGHT_BLUE_500);
        table.put("Button[Default].foreground", Color.WHITE);
        table.put("Button.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.BOLD));
        //table.put("Button[Default].mouseHoverColor", MaterialColors.LIGHT_BLUE_200);
        table.put("Button.mouseHoverColor", MaterialColors.GRAY_500);
        table.put("Button.mouseHoverEnable", true);
        table.put("Button.focusable", true);
        table.put("Button[focus].color", MaterialColors.GRAY_900);
        table.put("Button.disabledText", MaterialColors.GRAY_600);

        table.put("CheckBox.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.BOLD));
        table.put("CheckBox.background", MaterialColors.WHITE);
        table.put("CheckBox.foreground", MaterialColors.BLACK);
        table.put("CheckBox.disabledText", MaterialColors.COSMO_STRONG_GRAY);
        table.put("CheckBox.icon", new ImageIcon(MaterialImageFactory.getInstance().getImage(MaterialImageFactory.UNCHECKED_BLACK_BOX)));
        table.put("CheckBox.selectedIcon", new ImageIcon(MaterialImageFactory.getInstance().getImage(MaterialImageFactory.CHECKED_BLACK_BOX)));

        table.put("ComboBox.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.REGULAR));
        table.put("ComboBox.background", MaterialColors.WHITE);
        table.put("ComboBox.foreground", MaterialColors.BLACK);
        table.put("ComboBox.border", MaterialBorders.roundedLineColorBorder(MaterialColors.COSMO_BLACK));
        table.put("ComboBox.borderItems", BorderFactory.createEmptyBorder(1, 2, 0, 1));
        table.put("ComboBox.buttonBackground", MaterialColors.WHITE);
        table.put("ComboBox[button].border", BorderFactory.createLineBorder(MaterialColors.WHITE));
        table.put("ComboBox.disabledBackground", MaterialColors.WHITE);
        table.put("ComboBox.disabledForeground", MaterialColors.GRAY_900);
        table.put("ComboBox.selectionBackground", MaterialColors.WHITE);
        table.put("ComboBox.selectionForeground", Color.BLACK);
        table.put("ComboBox.selectedInDropDownBackground", MaterialColors.COSMO_LIGTH_BLUE);
        table.put("ComboBox.mouseHoverColor", MaterialColors.WHITE);
        table.put("ComboBox.unfocusColor", MaterialColors.COSMO_BLACK);
        table.put("ComboBox.focusColor", MaterialColors.LIGHT_BLUE_400);
        table.put("ComboBox.mouseHoverEnabled", false);

        table.put("Menu.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.REGULAR));
        table.put("Menu.border", BorderFactory.createEmptyBorder(5, 5, 5, 5));
        table.put("Menu.background", Color.WHITE);
        table.put("Menu.foreground", Color.BLACK);
        table.put("Menu.opaque", true);
        table.put("Menu.selectionBackground", MaterialColors.GRAY_200);
        table.put("Menu.selectionForeground", MaterialColors.BLACK);
        table.put("Menu.disabledForeground", new Color(0, 0, 0, 100));
        table.put("Menu.menuPopupOffsetY", 3);
        table.put("Menu[MouseOver].enable", true); //TODO adding into master

        table.put("MenuBar.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.BOLD));
        table.put("MenuBar.background", Color.WHITE);
        table.put("MenuBar.border", MaterialBorders.LIGHT_SHADOW_BORDER);
        table.put("MenuBar.foreground", MaterialColors.BLACK);

        table.put("MenuItem.disabledForeground", new Color(0, 0, 0, 100));
        table.put("MenuItem.selectionBackground", MaterialColors.GRAY_200);
        table.put("MenuItem.selectionForeground", Color.BLACK);
        table.put("MenuItem.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.MEDIUM));
        table.put("MenuItem.background", Color.WHITE);
        table.put("MenuItem.foreground", Color.BLACK);
        table.put("MenuItem.border", BorderFactory.createEmptyBorder(5, 0, 5, 0));
    }

This is my app with my look and feel

This is my app when changing the look and feel

This is the code for changing the look and feel

public class DemoGUITest extends JFrame {

    static {
        try {
            UIManager.setLookAndFeel(new MaterialLookAndFeel());
            JDialog.setDefaultLookAndFeelDecorated(true);
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    private static final DemoGUITest SINGLETON = new DemoGUITest();


    public void reloadUI(){
        SwingUtilities.updateComponentTreeUI(this);
    }

    public void changeThemeWith(BasicLookAndFeel lookAndFeel){
        try {
            UIManager.setLookAndFeel(lookAndFeel);
            this.reloadUI();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SINGLETON.initComponent();
            }
        });
    }
}

This is a minimal example

Class main

import com.sun.java.swing.plaf.gtk.GTKLookAndFeel;
import mdlaf.utils.MaterialColors;
import mdlaf.utils.MaterialFontFactory;

import javax.swing.*;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
import java.awt.event.ActionEvent;

/**
 * @author https://github.com/vincenzopalazzo
 */
public class MaterialMain extends JFrame {

    public static MaterialMain SINGLETON = new MaterialMain();

    static {
        try {
            UIManager.setLookAndFeel(new LookAndFeelTest());
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }


    public void init() {
        JMenuBar menuBar = new JMenuBar();
        JMenu file = new JMenu("File");
        menuBar.add(file);
        this.setJMenuBar(menuBar);
        JPanel panel = new JPanel();

        JButton changeTheme = new JButton();
        changeTheme.setAction(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    UIManager.setLookAndFeel(new GTKLookAndFeel());
                } catch (UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
                SwingUtilities.updateComponentTreeUI(SINGLETON);
            }
        });
        changeTheme.setText("Set GTK");

        panel.add(changeTheme);

        setTitle("Look and feel");

        setDefaultCloseOperation(EXIT_ON_CLOSE);

        setSize(630, 360);

        add(panel);

        setLocationRelativeTo(null);

        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SINGLETON.init();
            }
        });
    }

    public static class LookAndFeelTest extends MetalLookAndFeel{

        @Override
        protected void initClassDefaults(UIDefaults table) {
            super.initClassDefaults(table);
           table.put("MenuUI", MenuTestUI.class.getCanonicalName());
           table.put("ButtonUI", JButtonUI.class.getCanonicalName());
        }

        @Override
        protected void initComponentDefaults(UIDefaults table) {
            super.initComponentDefaults(table);

            //table.put("Menu.font", new FontUIResource(MaterialFontFactory.getInstance().getFont(MaterialFontFactory.REGULAR)));
            table.put("Menu.border", new BorderUIResource(BorderFactory.createEmptyBorder(5, 5, 5, 5)));
            table.put("Menu.background", (Color.ORANGE));
            table.put("Menu.foreground", (Color.BLACK));
            table.put("Menu.opaque", true);
            table.put("Menu.selectionBackground", new ColorUIResource(Color.YELLOW));
            table.put("Menu.selectionForeground", new ColorUIResource(Color.BLACK));
            table.put("Menu.disabledForeground", new ColorUIResource(new Color(0, 0, 0, 100)));
            table.put("Menu.menuPopupOffsetY", 3);

            //table.put("MenuBar.font", MaterialFontFactory.getInstance().getFont(MaterialFontFactory.BOLD));
            table.put("MenuBar.background", (Color.ORANGE));
            //table.put("MenuBar.border", MaterialBorders.LIGHT_SHADOW_BORDER);
            table.put("MenuBar.foreground", (Color.BLACK));

            table.put("Button.highlight", Color.ORANGE);
            table.put("Button.opaque", false);
            table.put("Button.border", BorderFactory.createEmptyBorder(7, 17, 7, 17));
            table.put("Button.background", Color.ORANGE);
            table.put("Button.foreground", Color.BLACK);
            table.put("Button.focusable", true);
            table.put("Button[focus].color", Color.GREEN);
        }
    }
}

MenuUI

import mdlaf.animation.MaterialUIMovement;
import mdlaf.components.menu.MaterialMenuUI;
import mdlaf.utils.MaterialManagerListener;

import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuUI;
import java.awt.*;

/**
 * @author https://github.com/vincenzopalazzo
 */
public class MenuTestUI extends BasicMenuUI {
    public static ComponentUI createUI (JComponent c) {
        return new MaterialMenuUI();
    }

    @Override
    public void installUI (JComponent c) {
        super.installUI (c);

        JMenu menu = (JMenu) c;
        menu.setFont (UIManager.getFont ("Menu.font"));
        menu.setBorder (UIManager.getBorder ("Menu.border"));
        menu.setBackground (UIManager.getColor ("Menu.background"));
        menu.setForeground (UIManager.getColor ("Menu.foreground"));
        menu.setOpaque (UIManager.getBoolean ("Menu.opaque"));
        c.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));


    }

    @Override
    public void uninstallUI(JComponent c) {

        menuItem.setFont (null);
        menuItem.setBackground (null);
        menuItem.setForeground (null);
        menuItem.setBorder (null);
        menuItem.setCursor(null);

        MaterialManagerListener.removeAllMaterialMouseListener(menuItem);

        super.uninstallUI(menuItem);
    }
}

ButtonUI


import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicButtonUI;
import java.awt.*;

/**
 * @author https://github.com/vincenzopalazzo
 */
public class JButtonUI extends BasicButtonUI {

    public static final String UI_KEY = "ButtonUI";

    public static ComponentUI createUI(final JComponent c) {
        return new JButtonUI();
    }

    private AbstractButton button;
    private Color foreground;
    private Color background;
    private Color disabledBackground;
    private Color disabledForeground;
    private Color defaultBackground;
    private Color defaultForeground;
    private Boolean isDefaultButton = null;

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        AbstractButton button = (AbstractButton) c;
        button.setOpaque(UIManager.getBoolean("Button.opaque"));
        button.setBorder(UIManager.getBorder("Button.border"));
        foreground = UIManager.getColor("Button.foreground");
        background = UIManager.getColor("Button.background");
        disabledBackground = UIManager.getColor("Button.disabledBackground");
        disabledForeground = UIManager.getColor("Button.disabledForeground");
        defaultBackground = UIManager.getColor("Button[Default].background");
        defaultForeground = UIManager.getColor("Button[Default].foreground");
        button.setBackground(background);
        button.setForeground(foreground);
        button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button.setFocusable(UIManager.getBoolean("Button.focusable"));

        this.button = button;
    }

    @Override
    public void uninstallUI(JComponent c) {
        super.uninstallUI(c);

        AbstractButton button = (AbstractButton) c;
        button.setBorder(null);
        foreground = null;
        background = null;
        disabledBackground = null;
        disabledForeground = null;
        defaultBackground = null;
        defaultForeground = null;
        button.setBackground(null);
        button.setForeground(null);
        button.setCursor(null);

    }

    @Override
    public void paint(Graphics g, JComponent c) {
        JButton b = (JButton) c;
        if (b.isContentAreaFilled()) {
            paintBackground(g, b);
        }
        if (isDefaultButton == null && b.isEnabled()) {
            isDefaultButton = ((JButton) button).isDefaultButton();
            if (isDefaultButton) {
                paintStateButton(c, g);
            }
        }
        super.paint(g, c);
    }

    private void paintBackground(Graphics g, JComponent c) {
        Graphics2D graphics = (Graphics2D) g.create();
        g.setColor(c.getBackground());
        JButton b = (JButton) c;
        if (!UIManager.getBoolean("Button[border].toAll") && (button.getIcon() != null)) {
            g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), 7, 7);
        } else {
            g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), 7, 7);
            if (isDefaultButton != null && isDefaultButton) {
                g.setColor(UIManager.getColor("Button[Default].background"));
                if(UIManager.getBoolean("Button[Default].shadowEnable")){
                    paintShadow(g, button);
                }
                return;
            }

            if(UIManager.getBoolean("Button[border].enable")){
                paintBorderButton(graphics, b);
            }
        }

        paintStateButton(c, g);

    }

    @Override
    protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
        // driveLine(g, (JButton) b);
        paintFocusRing(g, (JButton) b);
        //paintShadow(MaterialDrawingUtils.getAliasedGraphics(g), button);
    }

    @Override
    public void update(Graphics g, JComponent c) {
        super.update(g, c);
        //c.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    }

    @Override
    protected void paintButtonPressed(Graphics g, AbstractButton b) {
        g.fillRoundRect(0, 0, b.getWidth(), b.getHeight(), 7, 7);
    }

    protected void paintFocusRing(Graphics g, JButton b) {
        Stroke dashed = new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, new float[]{0f, 3f}, 10.0f);
        //Stroke dashed = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{9}, 0);
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setStroke(dashed);
        if (isDefaultButton) {
            g2.setColor(UIManager.getColor("Button[Default][focus].color"));
        } else {
            g2.setColor(UIManager.getColor("Button[focus].color"));
        }

        g2.drawRoundRect(5, 5, b.getWidth() - 10, b.getHeight() - 10, 7, 7);

        g2.dispose();
    }

    protected void paintShadow(Graphics g, JComponent c) {
        int topOpacity = 80;
        int pixels = UIManager.getInt("Button[Default].shadowPixel");
        JButton b = (JButton) c;
        int valueRed = 255;
        int valueGreen = 255;
        int valueBlue = 255;
        for (int i = pixels; i >= 0; i--) {
            if(valueBlue > 70){
                valueRed -= 70;
                valueGreen -= 70;
                valueBlue -= 70;
            }else{
                valueBlue -= valueBlue;
                valueGreen -= valueGreen;
                valueRed -= valueRed;
            }

            Color result = new Color(valueRed, valueGreen, valueBlue, topOpacity);
            g.setColor(result);
            g.drawRoundRect(i, i, b.getWidth() - ((i * 2) + 1), b.getHeight() - ((i * 2) + 1), 7, 7);
        }

    }

    protected void paintBorderButton(Graphics2D graphics, JButton b) {
        graphics.setStroke(new BasicStroke(2f));

        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int w = b.getWidth() - 1;
        int h = b.getHeight() - 1;
        int arc = 7;

        graphics.setColor(UIManager.getColor("Button[border].color"));
        graphics.drawRoundRect(0, 0, w, h, arc, arc);
    }

    protected void paintStateButton(JComponent component, Graphics graphics) {
        if (component == null || graphics == null) {
            throw new IllegalArgumentException("Input null");
        }
        JButton b = (JButton) component;
        if (b.isEnabled() && (isDefaultButton != null && isDefaultButton) && !b.isSelected()) {
            //MaterialManagerListener.removeAllMaterialMouseListener(b);
            //b.addMouseListener(MaterialUIMovement.getMovement(b, MaterialColors.LIGHT_BLUE_100));
            b.setBackground(defaultBackground);
            b.setForeground(defaultForeground);
            return;
        }
        if (!b.isEnabled()) {
            b.setBackground(disabledBackground);
            b.setForeground(disabledForeground);
            return;
        }
    }

}

But this code does not reproduced the problem even though I copied my code; a demo with the error is here

I wanted to ask you if the problem is the one described above and especially if I used UIDefault badly


Solution

  • This could be an issue of your usage of L&F. All the resources, you put into the UIManager (except the primitives), must be the instances of javax.swing.plaf.UIResource intreface. Swing offers some predefined classes like ColorUIResource, BorderUIResource, IconUIResource, etc., that you can use in your code.

    Why is it required? This interface tells the next Look-n-Feel that this setting can be changed when it applies its settings.

    Code example:

    table.put("OptionPane.warningDialog.titlePane.shadow", new ColorUIResource(MaterialColors.COSMO_STRONG_GRAY));
    table.put("FormattedTextField.border", new BorderUIResource(BorderFactory.createEmptyBorder(3, 5, 2, 5)));
    table.put("List.font", new FontUIResource(MaterialFontFactory.getInstance().getFont(MaterialFontFactory.MEDIUM)));
    

    Probably it would be better when your factories provide the resources in form of UIResource instances.