diff --git a/pom.xml b/pom.xml index 02b83f5..8dff179 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openautonomousconnection OACSwing - 1.0.0-BETA.1.0 + 1.0.0-BETA.1.1 Open Autonomous Connection https://open-autonomous-connection.org/ diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACComboBox.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACComboBox.java index fa9151f..7f3a62d 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACComboBox.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACComboBox.java @@ -5,8 +5,10 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; +import javax.swing.border.EmptyBorder; import java.awt.*; import java.util.Vector; @@ -27,6 +29,40 @@ public class OACComboBox extends JComboBox implements OACComponent { super(); } + @Override + public void init() { + setOpaque(true); + setRenderer(createDesignRenderer()); + } + + @Override + public void updateUI() { + super.updateUI(); + setRenderer(createDesignRenderer()); + } + + private ListCellRenderer createDesignRenderer() { + Color fallbackBg = getBackground() != null ? getBackground() : UIManager.getColor("ComboBox.background"); + Color fallbackFg = getForeground() != null ? getForeground() : UIManager.getColor("ComboBox.foreground"); + if (fallbackBg == null) fallbackBg = Color.DARK_GRAY; + if (fallbackFg == null) fallbackFg = Color.LIGHT_GRAY; + + Color bg = DesignManager.resolveBackground(OACComboBox.class, fallbackBg); + Color fg = DesignManager.resolveForeground(OACComboBox.class, fallbackFg); + Color selBg = DesignManager.resolveHovered(OACButton.class, bg.darker()); + Color selFg = fg; + + DefaultListCellRenderer base = new DefaultListCellRenderer(); + return (list, value, index, isSelected, cellHasFocus) -> { + JLabel label = (JLabel) base.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + label.setOpaque(true); + label.setBorder(new EmptyBorder(4, 8, 4, 8)); + label.setBackground(isSelected ? selBg : bg); + label.setForeground(isSelected ? selFg : fg); + return label; + }; + } + @Override public Component add(Component comp) { return OACComponent.super.add(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACDialog.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACDialog.java index e5dc967..05e963c 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACDialog.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACDialog.java @@ -2,12 +2,30 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; import org.openautonomousconnection.oacswing.component.design.DesignManager; -import org.openautonomousconnection.oacswing.component.design.OACColor; import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; public class OACDialog extends JDialog implements OACComponent { + private static final int TITLE_HEIGHT = 30; + private static final EmptyBorder TITLE_INSET = new EmptyBorder(TITLE_HEIGHT, 0, 0, 0); + private static final String CONTENT_BASE_BORDER_PROPERTY = "oac.dialog.content.baseBorder"; + private static final String CONTENT_BORDER_CAPTURED_PROPERTY = "oac.dialog.content.baseBorderCaptured"; + + private OACPanel titleRoot; + private OACLabel titleLabel; + private boolean titleBarInstalled; + private boolean titleHandlersInstalled; + private Point dragStartOnScreen; + private Point dragStartDialogLocation; + public OACDialog() { super(); initDialog(); @@ -54,10 +72,151 @@ public class OACDialog extends JDialog implements OACComponent { } private void initDialog() { + setUndecorated(true); DesignManager.apply(this); - setBackground(OACColor.DARK_BACKGROUND.getColor()); - getContentPane().setBackground(OACColor.DARK_BACKGROUND.getColor()); - getContentPane().setForeground(OACColor.DARK_TEXT.getColor()); + Color dialogBackground = DesignManager.resolveBackground(OACDialog.class, getBackground()); + Color dialogForeground = DesignManager.resolveForeground(OACDialog.class, getForeground()); + setBackground(dialogBackground); + getContentPane().setBackground(dialogBackground); + getContentPane().setForeground(dialogForeground); + + installTitleHandlersIfNeeded(); + updateTitleBar(); + } + + private void installTitleHandlersIfNeeded() { + if (titleHandlersInstalled) { + return; + } + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + if (titleRoot != null) { + titleRoot.setBounds(0, 0, getWidth(), TITLE_HEIGHT); + } + } + + @Override + public void componentShown(ComponentEvent e) { + if (titleRoot != null) { + titleRoot.setBounds(0, 0, getWidth(), TITLE_HEIGHT); + } + } + }); + titleHandlersInstalled = true; + } + + private void ensureTitleComponents() { + if (titleRoot != null && titleLabel != null) { + return; + } + titleRoot = new OACPanel(new BorderLayout()); + titleRoot.setOpaque(true); + titleRoot.setBorder(new EmptyBorder(6, 12, 6, 12)); + + titleLabel = new OACLabel(); + titleRoot.add(titleLabel, BorderLayout.WEST); + + MouseAdapter drag = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + dragStartOnScreen = e.getLocationOnScreen(); + dragStartDialogLocation = getLocation(); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (dragStartOnScreen == null || dragStartDialogLocation == null) { + return; + } + Point now = e.getLocationOnScreen(); + int dx = now.x - dragStartOnScreen.x; + int dy = now.y - dragStartOnScreen.y; + setLocation(dragStartDialogLocation.x + dx, dragStartDialogLocation.y + dy); + } + }; + titleRoot.addMouseListener(drag); + titleRoot.addMouseMotionListener(drag); + } + + private void updateTitleBar() { + String title = getTitle(); + boolean hasTitle = title != null && !title.isBlank(); + JLayeredPane layeredPane = getLayeredPane(); + + if (!hasTitle) { + if (titleBarInstalled && titleRoot != null && layeredPane != null) { + layeredPane.remove(titleRoot); + layeredPane.revalidate(); + layeredPane.repaint(); + titleBarInstalled = false; + } + applyContentInset(false); + return; + } + + ensureTitleComponents(); + + Color bg = DesignManager.resolveBackground(OACDialog.class, getBackground()); + Color fg = DesignManager.resolveForeground(OACDialog.class, getForeground()); + titleRoot.setBackground(bg); + titleLabel.setForeground(fg); + titleLabel.setText(title); + + if (!titleBarInstalled && layeredPane != null) { + layeredPane.add(titleRoot, JLayeredPane.DRAG_LAYER); + titleBarInstalled = true; + } + + titleRoot.setBounds(0, 0, getWidth(), TITLE_HEIGHT); + applyContentInset(true); + } + + private void applyContentInset(boolean hasTitle) { + Container content = super.getContentPane(); + if (!(content instanceof JComponent jContent)) { + return; + } + + if (!Boolean.TRUE.equals(jContent.getClientProperty(CONTENT_BORDER_CAPTURED_PROPERTY))) { + jContent.putClientProperty(CONTENT_BASE_BORDER_PROPERTY, jContent.getBorder()); + jContent.putClientProperty(CONTENT_BORDER_CAPTURED_PROPERTY, Boolean.TRUE); + } + + Border baseBorder = (Border) jContent.getClientProperty(CONTENT_BASE_BORDER_PROPERTY); + if (hasTitle) { + jContent.setBorder(baseBorder == null ? TITLE_INSET : new CompoundBorder(TITLE_INSET, baseBorder)); + } else { + jContent.setBorder(baseBorder); + } + + jContent.revalidate(); + jContent.repaint(); + } + + @Override + public void setContentPane(Container contentPane) { + super.setContentPane(contentPane); + + if (contentPane instanceof Component component) { + component.setBackground(DesignManager.resolveBackground(OACDialog.class, component.getBackground())); + component.setForeground(DesignManager.resolveForeground(OACDialog.class, component.getForeground())); + } + if (contentPane instanceof JComponent jContent) { + jContent.putClientProperty(CONTENT_BORDER_CAPTURED_PROPERTY, Boolean.FALSE); + jContent.putClientProperty(CONTENT_BASE_BORDER_PROPERTY, null); + } + if (contentPane instanceof OACComponent oacComponent) { + oacComponent.initDesign(); + } + + updateTitleBar(); + } + + @Override + public void setTitle(String title) { + super.setTitle(title); + updateTitleBar(); } @Override diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACList.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACList.java index 84e4b67..20c5aab 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACList.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACList.java @@ -5,6 +5,7 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; @@ -27,6 +28,30 @@ public class OACList extends JList implements OACComponent { super(); } + @Override + public void init() { + applyDesignColors(); + } + + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + } + + private void applyDesignColors() { + Color bg = DesignManager.resolveBackground(OACList.class, getBackground()); + Color fg = DesignManager.resolveForeground(OACList.class, getForeground()); + Color selBg = DesignManager.resolveHovered(OACButton.class, bg.darker()); + Color selFg = fg; + + setOpaque(true); + setBackground(bg); + setForeground(fg); + setSelectionBackground(selBg); + setSelectionForeground(selFg); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenu.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenu.java index e8f26be..f6cb01d 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenu.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenu.java @@ -4,10 +4,18 @@ package org.openautonomousconnection.oacswing.component; +import org.openautonomousconnection.oacswing.component.design.DesignManager; + import javax.swing.*; import java.awt.*; +import java.awt.event.ContainerAdapter; +import java.awt.event.ContainerEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; public class OACMenu extends JMenu implements OACComponent { + private JPopupMenu observedPopup; + public OACMenu() { super(); } @@ -24,6 +32,61 @@ public class OACMenu extends JMenu implements OACComponent { super(s, b); } + @Override + public void init() { + applyDesignColors(); + setOpaque(true); + installPopupAutoApply(); + } + + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + installPopupAutoApply(); + } + + private void applyDesignColors() { + setBackground(DesignManager.resolveBackground(OACMenu.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACMenu.class, getForeground())); + applyPopupDesign(); + } + + private void installPopupAutoApply() { + JPopupMenu popup = getPopupMenu(); + if (observedPopup == popup) { + return; + } + popup.addContainerListener(new ContainerAdapter() { + @Override + public void componentAdded(ContainerEvent e) { + applyPopupDesign(); + } + }); + popup.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + applyPopupDesign(); + } + }); + observedPopup = popup; + } + + private void applyPopupDesign() { + JPopupMenu popup = getPopupMenu(); + popup.setBackground(DesignManager.resolveBackground(OACPopupMenu.class, popup.getBackground())); + popup.setForeground(DesignManager.resolveForeground(OACPopupMenu.class, popup.getForeground())); + for (Component child : popup.getComponents()) { + if (child instanceof OACComponent oac) { + oac.initDesign(); + } else if (child instanceof JComponent jc) { + jc.setOpaque(true); + jc.setBackground(DesignManager.resolveBackground(OACMenuItem.class, jc.getBackground())); + jc.setForeground(DesignManager.resolveForeground(OACMenuItem.class, jc.getForeground())); + } + } + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuBar.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuBar.java index a3b4701..e9fa050 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuBar.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuBar.java @@ -5,6 +5,7 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; @@ -14,6 +15,20 @@ public class OACMenuBar extends JMenuBar implements OACComponent { super(); } + @Override + public void init() { + setOpaque(true); + setBackground(DesignManager.resolveBackground(OACMenuBar.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACMenuBar.class, getForeground())); + } + + @Override + public void updateUI() { + super.updateUI(); + setBackground(DesignManager.resolveBackground(OACMenuBar.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACMenuBar.class, getForeground())); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuItem.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuItem.java index e7bee9e..89f1aeb 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuItem.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACMenuItem.java @@ -5,6 +5,7 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; @@ -34,6 +35,23 @@ public class OACMenuItem extends JMenuItem implements OACComponent { super(text, mnemonic); } + @Override + public void init() { + applyDesignColors(); + setOpaque(true); + } + + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + } + + private void applyDesignColors() { + setBackground(DesignManager.resolveBackground(OACMenuItem.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACMenuItem.class, getForeground())); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACOptionPane.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACOptionPane.java index 519b407..9e4c798 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACOptionPane.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACOptionPane.java @@ -59,7 +59,7 @@ public class OACOptionPane extends JOptionPane implements OACComponent { Object[] options, Object initialValue) throws HeadlessException { AtomicInteger result = new AtomicInteger(CLOSED_OPTION); - OACDialog dialog = new OACDialog(JOptionPane.getFrameForComponent(parentComponent), title, true); + OACDialog dialog = new OACDialog(JOptionPane.getFrameForComponent(parentComponent), null, true); dialog.setUndecorated(true); dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); dialog.setContentPane(buildDialogContent(title, message, icon, optionType, options, result, dialog)); @@ -78,6 +78,71 @@ public class OACOptionPane extends JOptionPane implements OACComponent { return result.get(); } + public static int showConfirmDialog(Component parentComponent, Object message) throws HeadlessException { + return showConfirmDialog(parentComponent, message, "Select an Option", YES_NO_CANCEL_OPTION); + } + + public static int showConfirmDialog(Component parentComponent, + Object message, + String title, + int optionType) throws HeadlessException { + return showConfirmDialog(parentComponent, message, title, optionType, QUESTION_MESSAGE); + } + + public static int showConfirmDialog(Component parentComponent, + Object message, + String title, + int optionType, + int messageType) throws HeadlessException { + return showConfirmDialog(parentComponent, message, title, optionType, messageType, null); + } + + public static int showConfirmDialog(Component parentComponent, + Object message, + String title, + int optionType, + int messageType, + Icon icon) throws HeadlessException { + return showOptionDialog( + parentComponent, + message, + title, + optionType, + messageType, + icon != null ? icon : resolveDefaultIcon(messageType), + null, + null + ); + } + + public static void showMessageDialog(Component parentComponent, Object message) throws HeadlessException { + showMessageDialog(parentComponent, message, "Message", INFORMATION_MESSAGE); + } + + public static void showMessageDialog(Component parentComponent, + Object message, + String title, + int messageType) throws HeadlessException { + showMessageDialog(parentComponent, message, title, messageType, null); + } + + public static void showMessageDialog(Component parentComponent, + Object message, + String title, + int messageType, + Icon icon) throws HeadlessException { + showOptionDialog( + parentComponent, + message, + title, + DEFAULT_OPTION, + messageType, + icon != null ? icon : resolveDefaultIcon(messageType), + new Object[]{"OK"}, + "OK" + ); + } + private static Container buildDialogContent(String title, Object message, Icon icon, @@ -87,7 +152,6 @@ public class OACOptionPane extends JOptionPane implements OACComponent { OACDialog dialog) { OACPanel root = new OACPanel(new BorderLayout()); Color background = DesignManager.resolveBackground(OACOptionPane.class, root.getBackground()); - Color headerBackground = DesignManager.resolveBackground(OACTitleBar.class, background.darker()); Color foreground = DesignManager.resolveForeground(OACOptionPane.class, Color.LIGHT_GRAY); Color borderColor = DesignManager.resolveBorderColor(foreground.darker()); @@ -95,12 +159,8 @@ public class OACOptionPane extends JOptionPane implements OACComponent { root.setBorder(BorderFactory.createLineBorder(borderColor, 1)); OACPanel header = new OACPanel(new BorderLayout()); - header.setBorder(new EmptyBorder(10, 12, 10, 12)); - header.setBackground(headerBackground); - JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL); - separator.setForeground(borderColor); - separator.setBackground(borderColor); - header.add(separator, BorderLayout.SOUTH); + header.setBorder(new EmptyBorder(0, 12, 8, 12)); + header.setBackground(background); OACLabel titleLabel = new OACLabel(title == null ? "" : title); titleLabel.setForeground(foreground); @@ -116,14 +176,24 @@ public class OACOptionPane extends JOptionPane implements OACComponent { center.add(iconLabel, BorderLayout.WEST); } - JTextArea messageArea = new JTextArea(String.valueOf(message)); - messageArea.setEditable(false); - messageArea.setLineWrap(true); - messageArea.setWrapStyleWord(true); - messageArea.setOpaque(false); - messageArea.setForeground(foreground); - messageArea.setBorder(null); - center.add(messageArea, BorderLayout.CENTER); + if (message instanceof Component messageComponent) { + if (messageComponent instanceof OACComponent oacComponent) { + oacComponent.initDesign(); + } else { + messageComponent.setBackground(background); + messageComponent.setForeground(foreground); + } + center.add(messageComponent, BorderLayout.CENTER); + } else { + JTextArea messageArea = new JTextArea(String.valueOf(message)); + messageArea.setEditable(false); + messageArea.setLineWrap(true); + messageArea.setWrapStyleWord(true); + messageArea.setOpaque(false); + messageArea.setForeground(foreground); + messageArea.setBorder(null); + center.add(messageArea, BorderLayout.CENTER); + } OACPanel buttons = new OACPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8)); buttons.setBackground(background); @@ -177,6 +247,15 @@ public class OACOptionPane extends JOptionPane implements OACComponent { }; } + private static Icon resolveDefaultIcon(int messageType) { + return switch (messageType) { + case ERROR_MESSAGE -> UIManager.getIcon("OptionPane.errorIcon"); + case WARNING_MESSAGE -> UIManager.getIcon("OptionPane.warningIcon"); + case QUESTION_MESSAGE -> UIManager.getIcon("OptionPane.questionIcon"); + default -> UIManager.getIcon("OptionPane.informationIcon"); + }; + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACPopupMenu.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACPopupMenu.java index 874f806..450e876 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACPopupMenu.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACPopupMenu.java @@ -5,6 +5,7 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; @@ -18,6 +19,20 @@ public class OACPopupMenu extends JPopupMenu implements OACComponent { super(label); } + @Override + public void init() { + setOpaque(true); + setBackground(DesignManager.resolveBackground(OACPopupMenu.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACPopupMenu.class, getForeground())); + } + + @Override + public void updateUI() { + super.updateUI(); + setBackground(DesignManager.resolveBackground(OACPopupMenu.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACPopupMenu.class, getForeground())); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollBar.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollBar.java index 9641399..c0dbeb0 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollBar.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollBar.java @@ -5,8 +5,10 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; +import javax.swing.plaf.basic.BasicScrollBarUI; import java.awt.*; public class OACScrollBar extends JScrollBar implements OACComponent { @@ -22,6 +24,90 @@ public class OACScrollBar extends JScrollBar implements OACComponent { super(); } + @Override + public void init() { + applyDesignColors(); + } + + @Override + public void updateUI() { + super.updateUI(); + setUI(createDesignUI()); + applyDesignColors(); + } + + private void applyDesignColors() { + setBackground(DesignManager.resolveBackground(OACScrollBar.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACScrollBar.class, getForeground())); + setOpaque(true); + setBorder(null); + } + + private BasicScrollBarUI createDesignUI() { + Color track = DesignManager.resolveBackground(OACScrollBar.class, getBackground()); + Color thumb = DesignManager.resolveHovered(OACButton.class, track.darker()); + if (thumb == null || thumb.equals(track)) { + thumb = DesignManager.resolveBorderColor(track.brighter()); + } + final Color trackColor = track; + final Color thumbColor = thumb; + + return new BasicScrollBarUI() { + @Override + protected JButton createDecreaseButton(int orientation) { + return createZeroButton(); + } + + @Override + protected JButton createIncreaseButton(int orientation) { + return createZeroButton(); + } + + @Override + protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) { + g.setColor(trackColor); + g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); + } + + @Override + protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { + if (thumbBounds.isEmpty() || !scrollbar.isEnabled()) { + return; + } + Graphics2D g2 = (Graphics2D) g.create(); + try { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(thumbColor); + int arc = scrollbar.getOrientation() == Adjustable.VERTICAL ? thumbBounds.width : thumbBounds.height; + g2.fillRoundRect( + thumbBounds.x + 2, + thumbBounds.y + 2, + Math.max(0, thumbBounds.width - 4), + Math.max(0, thumbBounds.height - 4), + arc, + arc + ); + } finally { + g2.dispose(); + } + } + + private JButton createZeroButton() { + JButton button = new JButton(); + button.setOpaque(false); + button.setFocusable(false); + button.setBorderPainted(false); + button.setContentAreaFilled(false); + button.setBorder(null); + Dimension zero = new Dimension(0, 0); + button.setPreferredSize(zero); + button.setMinimumSize(zero); + button.setMaximumSize(zero); + return button; + } + }; + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollPane.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollPane.java index 3ae2fec..3018a1b 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollPane.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACScrollPane.java @@ -5,8 +5,10 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; +import javax.swing.plaf.UIResource; import java.awt.*; public class OACScrollPane extends JScrollPane implements OACComponent { @@ -26,6 +28,142 @@ public class OACScrollPane extends JScrollPane implements OACComponent { super(); } + @Override + public JScrollBar createVerticalScrollBar() { + return new OACScrollBar(JScrollBar.VERTICAL); + } + + @Override + public JScrollBar createHorizontalScrollBar() { + return new OACScrollBar(JScrollBar.HORIZONTAL); + } + + @Override + protected JViewport createViewport() { + return new OACViewport(); + } + + @Override + public void init() { + applyDesignColors(); + ensureOACSubcomponents(); + } + + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + ensureOACSubcomponents(); + } + + private void applyDesignColors() { + setOpaque(true); + Color bg = DesignManager.resolveBackground(OACScrollPane.class, getBackground()); + Color fg = DesignManager.resolveForeground(OACScrollPane.class, getForeground()); + setBackground(bg); + setForeground(fg); + setBorder(null); + setViewportBorder(null); + + JViewport viewport = getViewport(); + if (viewport != null) { + viewport.setOpaque(true); + viewport.setBackground(bg); + viewport.setForeground(fg); + } + + JScrollBar vertical = getVerticalScrollBar(); + if (vertical != null) { + vertical.setBackground(DesignManager.resolveBackground(OACScrollBar.class, bg)); + vertical.setForeground(DesignManager.resolveForeground(OACScrollBar.class, fg)); + } + + JScrollBar horizontal = getHorizontalScrollBar(); + if (horizontal != null) { + horizontal.setBackground(DesignManager.resolveBackground(OACScrollBar.class, bg)); + horizontal.setForeground(DesignManager.resolveForeground(OACScrollBar.class, fg)); + } + + setCorner(UPPER_LEFT_CORNER, createCorner(bg)); + setCorner(UPPER_RIGHT_CORNER, createCorner(bg)); + setCorner(LOWER_LEFT_CORNER, createCorner(bg)); + setCorner(LOWER_RIGHT_CORNER, createCorner(bg)); + + applyViewportViewDesign(bg, fg); + } + + private void ensureOACSubcomponents() { + if (!(getVerticalScrollBar() instanceof OACScrollBar)) { + JScrollBar old = getVerticalScrollBar(); + OACScrollBar replacement = new OACScrollBar(JScrollBar.VERTICAL); + replacement.setModel(old.getModel()); + setVerticalScrollBar(replacement); + } + if (!(getHorizontalScrollBar() instanceof OACScrollBar)) { + JScrollBar old = getHorizontalScrollBar(); + OACScrollBar replacement = new OACScrollBar(JScrollBar.HORIZONTAL); + replacement.setModel(old.getModel()); + setHorizontalScrollBar(replacement); + } + if (!(getViewport() instanceof OACViewport)) { + JViewport oldViewport = getViewport(); + OACViewport replacement = new OACViewport(); + replacement.setView(oldViewport.getView()); + setViewport(replacement); + } + + if (getVerticalScrollBar() instanceof OACComponent oacVertical) { + oacVertical.initDesign(); + } + if (getHorizontalScrollBar() instanceof OACComponent oacHorizontal) { + oacHorizontal.initDesign(); + } + if (getViewport() instanceof OACComponent oacViewport) { + oacViewport.initDesign(); + } + applyViewportViewDesign( + DesignManager.resolveBackground(OACScrollPane.class, getBackground()), + DesignManager.resolveForeground(OACScrollPane.class, getForeground()) + ); + } + + private static JComponent createCorner(Color color) { + JPanel corner = new JPanel(new BorderLayout()); + corner.setOpaque(true); + corner.setBackground(color); + corner.setBorder(null); + return corner; + } + + private void applyViewportViewDesign(Color bg, Color fg) { + JViewport viewport = getViewport(); + if (viewport == null) { + return; + } + Component view = viewport.getView(); + if (view == null) { + return; + } + + if (view instanceof OACComponent oacView) { + oacView.initDesign(); + return; + } + + if (view instanceof JComponent jView) { + if (jView.getBackground() == null || jView.getBackground() instanceof UIResource) { + jView.setBackground(bg); + } + if (jView.getForeground() == null || jView.getForeground() instanceof UIResource) { + jView.setForeground(fg); + } + return; + } + + view.setBackground(bg); + view.setForeground(fg); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACTabbedPane.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACTabbedPane.java index a331611..ba17c60 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACTabbedPane.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACTabbedPane.java @@ -5,11 +5,15 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; +import javax.swing.plaf.basic.BasicTabbedPaneUI; import java.awt.*; public class OACTabbedPane extends JTabbedPane implements OACComponent { + private boolean selectionSyncInstalled; + public OACTabbedPane() { super(); } @@ -22,22 +26,136 @@ public class OACTabbedPane extends JTabbedPane implements OACComponent { super(tabPlacement, tabLayoutPolicy); } + @Override + public void init() { + applyDesignColors(); + } + + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + } + + private void applyDesignColors() { + Color bg = DesignManager.resolveBackground(OACTabbedPane.class, getBackground()); + Color fg = DesignManager.resolveForeground(OACTabbedPane.class, getForeground()); + Color selectedBg = DesignManager.resolveHovered(OACButton.class, bg.darker()); + Color selectedFg = fg; + Color border = DesignManager.resolveBorderColor(bg.darker()); + + setOpaque(true); + setBackground(bg); + setForeground(fg); + setUI(new DesignTabbedPaneUI(bg, selectedBg, border)); + applyTabItemColors(bg, fg, selectedBg, selectedFg); + installSelectionSyncIfNeeded(); + } + + private void applyTabItemColors(Color bg, Color fg, Color selectedBg, Color selectedFg) { + int selectedIndex = getSelectedIndex(); + for (int i = 0; i < getTabCount(); i++) { + boolean selected = i == selectedIndex; + setBackgroundAt(i, selected ? selectedBg : bg); + setForegroundAt(i, selected ? selectedFg : fg); + } + repaint(); + } + + private void installSelectionSyncIfNeeded() { + if (selectionSyncInstalled) { + return; + } + addChangeListener(e -> { + Color bg = DesignManager.resolveBackground(OACTabbedPane.class, getBackground()); + Color fg = DesignManager.resolveForeground(OACTabbedPane.class, getForeground()); + Color selectedBg = DesignManager.resolveHovered(OACButton.class, bg.darker()); + applyTabItemColors(bg, fg, selectedBg, fg); + }); + selectionSyncInstalled = true; + } + + private static final class DesignTabbedPaneUI extends BasicTabbedPaneUI { + private final Color background; + private final Color selectedBackground; + private final Color borderColor; + + private DesignTabbedPaneUI(Color background, Color selectedBackground, Color borderColor) { + this.background = background; + this.selectedBackground = selectedBackground; + this.borderColor = borderColor; + } + + @Override + protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, + int x, int y, int w, int h, boolean isSelected) { + g.setColor(isSelected ? selectedBackground : background); + g.fillRect(x, y, w, h); + } + + @Override + protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, + int x, int y, int w, int h, boolean isSelected) { + g.setColor(borderColor); + g.drawRect(x, y, w, h); + } + + @Override + protected void paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] rects, + int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected) { + // no-op to avoid bright LAF focus ring + } + + @Override + protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { + int width = tabPane.getWidth(); + int height = tabPane.getHeight(); + Insets insets = tabPane.getInsets(); + + int x = insets.left; + int y = insets.top; + int w = width - insets.right - insets.left; + int h = height - insets.top - insets.bottom; + + switch (tabPlacement) { + case LEFT -> { + x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); + w -= (x - insets.left); + } + case RIGHT -> w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); + case BOTTOM -> h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + default -> { + y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); + h -= (y - insets.top); + } + } + + g.setColor(background); + g.fillRect(x, y, w, h); + g.setColor(borderColor); + g.drawRect(x, y, Math.max(0, w - 1), Math.max(0, h - 1)); + } + } + @Override public void addTab(String title, Component component) { this.initOther(component); super.addTab(title, component); + applyDesignColors(); } @Override public void addTab(String title, Icon icon, Component component) { this.initOther(component); super.addTab(title, icon, component); + applyDesignColors(); } @Override public void addTab(String title, Icon icon, Component component, String tip) { this.initOther(component); super.addTab(title, icon, component, tip); + applyDesignColors(); } @Override diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACTextArea.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACTextArea.java index d7ac742..1f90af9 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACTextArea.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACTextArea.java @@ -38,6 +38,7 @@ public class OACTextArea extends JTextArea implements OACComponent { @Override public void init() { + applyDesignColors(); setOpaque(true); setMargin(new Insets(6, 10, 6, 10)); setBorder(DesignManager.createTextComponentBorder()); @@ -46,6 +47,18 @@ public class OACTextArea extends JTextArea implements OACComponent { setSelectedTextColor(getBackground().brighter()); } + @Override + public void updateUI() { + super.updateUI(); + applyDesignColors(); + setBorder(DesignManager.createTextComponentBorder()); + } + + private void applyDesignColors() { + setBackground(DesignManager.resolveBackground(OACTextArea.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACTextArea.class, getForeground())); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACTextField.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACTextField.java index 0c6f5ef..32fcb1d 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACTextField.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACTextField.java @@ -36,6 +36,7 @@ public class OACTextField extends JTextField implements OACComponent { @Override public void init() { + applyDesignColors(); setOpaque(true); applyInputBorder(); setCaretColor(getForeground()); @@ -47,9 +48,15 @@ public class OACTextField extends JTextField implements OACComponent { @Override public void updateUI() { super.updateUI(); + applyDesignColors(); applyInputBorder(); } + private void applyDesignColors() { + setBackground(DesignManager.resolveBackground(OACTextField.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACTextField.class, getForeground())); + } + private void applyInputBorder() { setBorder(new CompoundBorder( new RoundedBorder(DesignManager.resolveBorderColor(getForeground()), 10, 2), diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/OACViewport.java b/src/main/java/org/openautonomousconnection/oacswing/component/OACViewport.java index fe3df64..05abd93 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/OACViewport.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/OACViewport.java @@ -5,6 +5,7 @@ package org.openautonomousconnection.oacswing.component; import lombok.NonNull; +import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; @@ -14,6 +15,20 @@ public class OACViewport extends JViewport implements OACComponent { super(); } + @Override + public void init() { + setOpaque(true); + setBackground(DesignManager.resolveBackground(OACViewport.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACViewport.class, getForeground())); + } + + @Override + public void updateUI() { + super.updateUI(); + setBackground(DesignManager.resolveBackground(OACViewport.class, getBackground())); + setForeground(DesignManager.resolveForeground(OACViewport.class, getForeground())); + } + @Override public Component add(Component comp) { this.initOther(comp); diff --git a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java index af7dbf9..afffbd0 100644 --- a/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java +++ b/src/main/java/org/openautonomousconnection/oacswing/component/design/DesignManager.java @@ -42,27 +42,31 @@ public class DesignManager { false )); - Design.DARK.getElements().put(OACCheckBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD)); - Design.DARK.getElements().put(OACCheckBoxMenuItem.class, new DesignFlags(OACColor.DARK_ITEM)); + Design.DARK.getElements().put(OACCheckBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACCheckBoxMenuItem.class, new DesignFlags(OACColor.DARK_INPUT_BUTTON, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACTextField.class, new DesignFlags(OACColor.DARK_INPUT_FIELD, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACTextArea.class, new DesignFlags(OACColor.DARK_INPUT_FIELD, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACColorChooser.class, new DesignFlags(OACColor.DARK_SECTION)); - Design.DARK.getElements().put(OACComboBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD)); + Design.DARK.getElements().put(OACComboBox.class, new DesignFlags(OACColor.DARK_INPUT_FIELD, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACTabbedPane.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACFrame.class, new DesignFlags(OACColor.DARK_BACKGROUND)); - Design.DARK.getElements().put(OACLabel.class, new DesignFlags(OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACLabel.class, new DesignFlags(OACColor.DARK_BACKGROUND, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACLayeredPane.class, new DesignFlags(OACColor.DARK_BACKGROUND)); - Design.DARK.getElements().put(OACList.class, new DesignFlags(OACColor.DARK_SECTION)); - Design.DARK.getElements().put(OACMenu.class, new DesignFlags(OACColor.DARK_INPUT_BUTTON)); - Design.DARK.getElements().put(OACMenuBar.class, new DesignFlags(OACColor.DARK_SECTION)); - Design.DARK.getElements().put(OACMenuItem.class, new DesignFlags(OACColor.DARK_ITEM)); + Design.DARK.getElements().put(OACList.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACMenu.class, new DesignFlags(OACColor.DARK_INPUT_BUTTON, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACMenuBar.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACMenuItem.class, new DesignFlags(OACColor.DARK_INPUT_BUTTON, OACColor.DARK_TEXT)); Design.DARK.getElements().put(OACOptionPane.class, new DesignFlags(OACColor.DARK_BACKGROUND)); Design.DARK.getElements().put(OACPanel.class, new DesignFlags(OACColor.DARK_BACKGROUND, false)); Design.DARK.getElements().put(OACPasswordField.class, new DesignFlags(OACColor.DARK_INPUT_FIELD, OACColor.DARK_TEXT)); - Design.DARK.getElements().put(OACPopupMenu.class, new DesignFlags(OACColor.DARK_BACKGROUND)); - Design.DARK.getElements().put(OACProgressBar.class, new DesignFlags(OACColor.DARK_ITEM)); - Design.DARK.getElements().put(OACRadioButton.class, new DesignFlags(OACColor.DARK_BUTTON)); - Design.DARK.getElements().put(OACDialog.class, new DesignFlags(OACColor.DARK_BACKGROUND)); - Design.DARK.getElements().put(OACTitleBar.class, new DesignFlags(OACColor.DARK_HEADER)); + Design.DARK.getElements().put(OACPopupMenu.class, new DesignFlags(OACColor.DARK_BACKGROUND, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACProgressBar.class, new DesignFlags(OACColor.DARK_ITEM, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACRadioButton.class, new DesignFlags(OACColor.DARK_BUTTON, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACScrollPane.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACScrollBar.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACViewport.class, new DesignFlags(OACColor.DARK_SECTION, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACDialog.class, new DesignFlags(OACColor.DARK_BACKGROUND, OACColor.DARK_TEXT)); + Design.DARK.getElements().put(OACTitleBar.class, new DesignFlags(OACColor.DARK_HEADER, OACColor.DARK_TEXT)); Design.DARK.border = new RoundedBorder(OACColor.DARK_BORDERS.getColor(), 18, 1); globalDesign = Design.DARK; @@ -122,7 +126,12 @@ public class DesignManager { if (hasBorder) { jComponent.setBorder(globalDesign.getBorder()); jComponent.setOpaque(false); - } else if (!(jComponent instanceof OACTitleBar) && !(jComponent instanceof JTextComponent)) { + } else if (!(jComponent instanceof OACTitleBar) + && !(jComponent instanceof JTextComponent) + && !(jComponent instanceof JViewport) + && !(jComponent instanceof JScrollPane) + && !(jComponent instanceof JScrollBar) + && !(jComponent instanceof JTabbedPane)) { Border currentBorder = jComponent.getBorder(); if (currentBorder == null || hasZeroInsets(currentBorder, jComponent)) { jComponent.setBorder(INVISIBLE_BORDER); diff --git a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java index 4b5519c..a706499 100644 --- a/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java +++ b/src/test/java/org/openautonomousconnection/oacswing/test/AnimationTests.java @@ -20,9 +20,9 @@ public class AnimationTests { public synchronized void testSimpleAnimatedPanel() throws InterruptedException { DesignManager.setGlobalDesign(Design.DARK); - OACFrame frame = TestUtils.mockOacFrame(); + // OACFrame frame = TestUtils.mockOacFrame(); - AnimationPath animationPath = new AnimationPath(50); + // AnimationPath animationPath = new AnimationPath(50); // This test was too simple // animationPath.add(new KeyFrame(new Point(400, 400), 400, 400)); @@ -30,18 +30,18 @@ public class AnimationTests { // animationPath.add(new KeyFrame(new Point(100, 100), 400, 400)); // animationPath.add(new KeyFrame(new Point(400, 400), 400, 400)); - animationPath.add(new KeyFrame(new Point(400, 400), 400, 400, KeyFrame.PathMethod.EASE_OUT)); - animationPath.add(new KeyFrame(new Point(400, 300), 400, 400)); + // animationPath.add(new KeyFrame(new Point(400, 400), 400, 400, KeyFrame.PathMethod.EASE_OUT)); + // animationPath.add(new KeyFrame(new Point(400, 300), 400, 400)); - JAnimatedPanel animatedPanel = new JAnimatedPanel(animationPath); + // JAnimatedPanel animatedPanel = new JAnimatedPanel(animationPath); - animatedPanel.setBackground(Color.BLACK); + // animatedPanel.setBackground(Color.BLACK); - frame.add(animatedPanel); + // frame.add(animatedPanel); - animatedPanel.play(5, true); + // animatedPanel.play(5, true); - frame.setVisible(true); + // frame.setVisible(true); wait(10000); } diff --git a/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java b/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java index 6a2ffa5..90872ae 100644 --- a/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java +++ b/src/test/java/org/openautonomousconnection/oacswing/test/CustomizedTests.java @@ -30,16 +30,16 @@ public class CustomizedTests { frame.add(navBar); frame.setVisible(true); Object[] options = {"Continue", "Cancel"}; - OACOptionPane.showOptionDialog( - frame, - "You never connected to this INS before!\n" + - "Fingerprint: " + "caFingerprint" + "\nDo you want to connect?", - "INS Connection", - OACOptionPane.YES_NO_OPTION, - OACOptionPane.INFORMATION_MESSAGE, - null, - options, - options[0] - ); wait(15000); +// OACOptionPane.showOptionDialog( +// frame, +// "You never connected to this INS before!\n" + +// "Fingerprint: " + "caFingerprint" + "\nDo you want to connect?", +// "INS Connection", +// OACOptionPane.YES_NO_OPTION, +// OACOptionPane.INFORMATION_MESSAGE, +// null, +// options, +// options[0] +// ); wait(15000); } }