diff --git a/src/main/java/com/weis/darklaf/components/border/TextBubbleBorder.java b/src/main/java/com/weis/darklaf/components/border/TextBubbleBorder.java index 044c9d9e..65a5217a 100644 --- a/src/main/java/com/weis/darklaf/components/border/TextBubbleBorder.java +++ b/src/main/java/com/weis/darklaf/components/border/TextBubbleBorder.java @@ -1,12 +1,14 @@ package com.weis.darklaf.components.border; import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.util.GraphicsUtil; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import javax.swing.border.AbstractBorder; import java.awt.*; import java.awt.geom.Area; +import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; /** @@ -17,10 +19,6 @@ import java.awt.geom.RoundRectangle2D; */ public class TextBubbleBorder extends AbstractBorder { - private static final long serialVersionUID = 1L; - @NotNull - private final RenderingHints hints; - @NotNull private final Insets insets; private Alignment pointerSide = Alignment.NORTH; private Color color; @@ -58,9 +56,6 @@ public class TextBubbleBorder extends AbstractBorder { this.radius = radius; this.pointerSize = pointerSize; this.pointerWidth = pointerSize; - - hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); insets = new Insets(0, 0, 0, 0); setThickness(thickness); } @@ -147,7 +142,7 @@ public class TextBubbleBorder extends AbstractBorder { */ @NotNull public TextBubbleBorder setThickness(final int n) { - thickness = n < 0 ? 0 : n; + thickness = Math.max(n, 0); stroke = new BasicStroke(thickness); return setPointerSize(pointerSize); } @@ -190,30 +185,28 @@ public class TextBubbleBorder extends AbstractBorder { */ @NotNull public TextBubbleBorder setPointerSize(final int size) { - pointerSize = size < 0 ? 0 : size; - final int pad = radius / 2 + thickness; - final int pointerSidePad = pad + pointerSize + thickness; - int left = pad; - int right = pad; - int bottom = pad; - int top = pad; + pointerSize = Math.max(size, 0); + int left = thickness; + int right = thickness; + int bottom = thickness; + int top = thickness; switch (pointerSide) { case NORTH: case NORTH_WEST: case NORTH_EAST: - top = pointerSidePad; + top += pointerSize; break; case SOUTH: case SOUTH_WEST: case SOUTH_EAST: - bottom = pointerSidePad; + bottom += pointerSize; break; case WEST: - left = pointerSidePad; + left += pointerSize; break; case EAST: - right = pointerSidePad; + right += pointerSize; break; default: break; @@ -259,107 +252,99 @@ public class TextBubbleBorder extends AbstractBorder { return getBorderInsets(c); } - @Override - public void paintBorder(@NotNull final Component c, final Graphics g, - final int x, final int y, final int width, final int height) { + public void paintBorder(final Graphics g, final Area innerArea) { final Graphics2D g2 = (Graphics2D) g; - var bubble = calculateBubbleRect(width, height); - int pointerPad; - switch (pointerSide) { - case WEST: - case EAST: - pointerPad = (int) (pointerPadPercent * (height - 2 * radius - 5 * pointerSize)); - break; - case CENTER: - pointerPad = 0; - break; - default: - pointerPad = (int) (pointerPadPercent * (width - 2 * radius - 5 * pointerSize)); - break; - } - final Polygon pointer = creatPointerShape(width, height, pointerPad, bubble); - final Area area = new Area(bubble); - area.add(new Area(pointer)); - g2.setRenderingHints(hints); - g2.setColor(c.getBackground()); - g2.fill(area); + var config = GraphicsUtil.setupStrokePainting(g); g2.setColor(color); g2.setStroke(stroke); - g2.draw(area); + g2.draw(innerArea); + config.restore(); } - @NotNull - @Contract("_, _ -> new") - private RoundRectangle2D.Double calculateBubbleRect(final int width, final int height) { - int rx = thickness; - int ry = thickness; - int rw = width - thickness; - int rh = height - thickness; + @Override + public void paintBorder(@NotNull final Component c, final Graphics g, + final int x, final int y, final int width, final int height) { + var area = getInnerArea(x, y, width, height); + paintBorder(g, area); + } + + @Contract(pure = true) + private double calculatePointerPad(final int width, final int height) { + double pointerPad; switch (pointerSide) { case WEST: - rx += pointerSize; - rw -= pointerSize + thickness; - rh -= thickness; - break; case EAST: - rw -= pointerSize; - rh -= thickness; + pointerPad = radius + (height - insets.top - insets.bottom - 2 * radius) / 2.0; break; case NORTH: case NORTH_WEST: case NORTH_EAST: - ry += pointerSize; - rh -= pointerSize + thickness; - rw -= thickness; - break; case SOUTH: case SOUTH_WEST: case SOUTH_EAST: - rh -= pointerSize; - rw -= thickness; + pointerPad = radius + (pointerPadPercent * (width - insets.left - insets.right - 2 * radius)); break; default: + pointerPad = 0; break; } - return new RoundRectangle2D.Double(rx, ry, rw, rh, radius, radius); + return pointerPad; + } + + + public Area getInnerArea(final int x, final int y, final int width, final int height) { + var bubble = calculateBubbleRect(x, y, width, height); + final Area area = new Area(bubble); + if (pointerSide != Alignment.CENTER) { + double pointerPad = calculatePointerPad(width, height); + Path2D pointer = creatPointerShape(pointerPad, bubble); + area.add(new Area(pointer)); + } + return area; + } + + @Contract("_, _, _, _ -> new") + public RoundRectangle2D.@NotNull Double calculateBubbleRect(final int x, final int y, + final int width, final int height) { + return new RoundRectangle2D.Double(x + insets.left, y + insets.top, width - insets.left - insets.right, + height - insets.top - insets.bottom, radius, radius); } @NotNull - private Polygon creatPointerShape(final int width, final int height, final int pointerPad, - @NotNull final RoundRectangle2D.Double bubble) { - final int basePad = 2 * pointerSize + thickness + radius + pointerPad; - final int widthPad = pointerWidth / 2; - final Polygon pointer = new Polygon(); + private Path2D creatPointerShape(final double pointerPad, @NotNull final RoundRectangle2D.Double bubble) { + final double w = pointerWidth / 2.0; + final Path2D pointer = new Path2D.Double(Path2D.WIND_EVEN_ODD); + double x = bubble.x; + double y = bubble.y; switch (pointerSide) { case WEST: - pointer.addPoint((int) bubble.x, basePad - widthPad);// top - pointer.addPoint((int) bubble.x, basePad + pointerSize + widthPad);// bottom - pointer.addPoint(thickness, basePad + pointerSize / 2); + pointer.moveTo(x, y + pointerPad - w); //Top + pointer.lineTo(x - pointerSize, y + pointerPad); + pointer.lineTo(x, y + pointerPad + w);// bottom break; case EAST: - int x = (int) (bubble.x + bubble.width); - pointer.addPoint(x, basePad - widthPad);// top - pointer.addPoint(x, basePad + pointerSize + widthPad);// bottom - pointer.addPoint(width - thickness, basePad + pointerSize / 2); + pointer.moveTo(x + bubble.width, y + pointerPad - w);// top + pointer.lineTo(x + bubble.width + pointerSize, y + pointerPad); + pointer.lineTo(x + bubble.width, y + pointerPad + w);// bottom break; case NORTH: case NORTH_WEST: case NORTH_EAST: - pointer.addPoint(basePad - widthPad, (int) bubble.y);// left - pointer.addPoint(basePad + pointerSize + widthPad, (int) bubble.y);// right - pointer.addPoint(basePad + (pointerSize / 2), thickness); + pointer.moveTo(x + pointerPad - w, y);// left + pointer.lineTo(x + pointerPad, y - pointerSize); + pointer.lineTo(x + pointerPad + w, y);// right break; case SOUTH: case SOUTH_WEST: case SOUTH_EAST: - int y = (int) (bubble.y + bubble.height); - pointer.addPoint(basePad - widthPad, y);// left - pointer.addPoint(basePad + pointerSize + widthPad, y);// right - pointer.addPoint(basePad + (pointerSize / 2), height - thickness); + pointer.moveTo(x + pointerPad - w, y + bubble.height);// left + pointer.lineTo(x + pointerPad, y + bubble.height + pointerSize); + pointer.lineTo(x + pointerPad + w, y + bubble.height);// right break; default: break; } + pointer.closePath(); return pointer; } } \ No newline at end of file diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/ColorWheelPanel.java b/src/main/java/com/weis/darklaf/ui/colorchooser/ColorWheelPanel.java index 02f0bd4b..2bcc45f5 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/ColorWheelPanel.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/ColorWheelPanel.java @@ -29,7 +29,7 @@ public class ColorWheelPanel extends JPanel { myBrightnessComponent = new SlideComponent("Brightness", true); myBrightnessComponent.setToolTipText("Brightness"); myBrightnessComponent.addListener(value -> { - myColorWheel.setBrightness(1f - (value / 255f)); + myColorWheel.setBrightness(1 - (value / 255f)); myColorWheel.repaint(); }); diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/RecentSwatchPanel.java b/src/main/java/com/weis/darklaf/ui/colorchooser/RecentSwatchPanel.java index 706df39d..ad9f7e1d 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/RecentSwatchPanel.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/RecentSwatchPanel.java @@ -1,8 +1,11 @@ package com.weis.darklaf.ui.colorchooser; +import org.jetbrains.annotations.NotNull; + import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; +import java.util.Objects; class RecentSwatchPanel extends SwatchPanel { private Color defaultRecentColor; @@ -25,16 +28,16 @@ class RecentSwatchPanel extends SwatchPanel { } public void setMostRecentColor(final Color c) { - if (colors[0].equals(c)) return; + if (Objects.equals(colors[0], c)) return; System.arraycopy(colors, 0, colors, 1, colors.length - 1); colors[0] = c; repaint(); } @Override - public String getToolTipText(final MouseEvent e) { + public String getToolTipText(@NotNull final MouseEvent e) { Color color = getColorForLocation(e.getX(), e.getY()); - if (color == defaultRecentColor) return null; + if (color == defaultRecentColor || color == null) return null; return color.getRed() + ", " + color.getGreen() + ", " + color.getBlue(); } } diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/SlideComponent.java b/src/main/java/com/weis/darklaf/ui/colorchooser/SlideComponent.java index 11ce4367..b4080789 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/SlideComponent.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/SlideComponent.java @@ -1,5 +1,6 @@ package com.weis.darklaf.ui.colorchooser; +import com.weis.darklaf.components.alignment.Alignment; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -22,12 +23,10 @@ class SlideComponent extends JComponent { private final boolean myVertical; private final String myTitle; private final List> myListeners = new ArrayList<>(); - //Todo. -// private LightweightHint myTooltipHint; - private final JLabel myLabel = new JLabel(); private int myPointerValue = 0; private int myValue = 0; private Unit myUnit = Unit.LEVEL; + private JToolTip tooltip; SlideComponent(final String title, final boolean vertical) { myTitle = title; @@ -41,28 +40,13 @@ class SlideComponent extends JComponent { }); addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - processMouse(e); - } - - @Override - public void mouseEntered(final MouseEvent e) { - updateBalloonText(); - } - - @Override - public void mouseMoved(final MouseEvent e) { - updateBalloonText(); - } - @Override public void mouseExited(final MouseEvent e) { - //Todo -// if (myTooltipHint != null) { -// myTooltipHint.hide(); -// myTooltipHint = null; -// } + var p = e.getPoint(); + p = SwingUtilities.convertPoint(e.getComponent(), p, SlideComponent.this); + if (tooltip != null && !contains(p)) { + tooltip.setVisible(false); + } } }); @@ -89,15 +73,14 @@ class SlideComponent extends JComponent { repaint(); } }); + + setToolTipText(getToolTipText(null)); } private static void drawKnob(@NotNull final Graphics2D g2d, int x, int y, final boolean vertical) { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - //Todo Colors if (vertical) { y -= 6; - Polygon arrowShadow = new Polygon(); arrowShadow.addPoint(x - 5, y + 1); arrowShadow.addPoint(x + 7, y + 7); @@ -138,36 +121,39 @@ class SlideComponent extends JComponent { myUnit = unit; } - private void updateBalloonText() { + @Override + public String getToolTipText(final MouseEvent event) { + return myTitle + ": " + Unit.formatValue(myValue, myUnit); + } + + @Override + public Point getToolTipLocation(final MouseEvent e) { + if (tooltip == null) { + createToolTip(); + tooltip.setTipText(getToolTipText(e)); + } final Point point = myVertical ? new Point(0, myPointerValue) : new Point(myPointerValue, 0); - myLabel.setText(myTitle + ": " + Unit.formatValue(myValue, myUnit)); - //Todo -// if (myTooltipHint == null) { -// myTooltipHint = new LightweightHint(myLabel); -// myTooltipHint.setCancelOnClickOutside(false); -// myTooltipHint.setCancelOnOtherWindowOpen(false); -// -// final HintHint hint = new HintHint(this, point) -// .setPreferredPosition(myVertical ? Balloon.Position.atLeft : Balloon -// .Position.above) -// .setBorderColor(Color.BLACK) -// .setAwtTooltip(true) -// .setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD)) -// .setTextBg(HintUtil.getInformationColor()) -// .setShowImmediately(true); -// -// final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); -// myTooltipHint.show(this, point.x, point.y, owner instanceof JComponent ? (JComponent)owner : null, hint); -// } -// else { -// myTooltipHint.setLocation(new RelativePoint(this, point)); -// } + if (myVertical) { + point.x -= tooltip.getPreferredSize().width - 7; + point.y -= (tooltip.getPreferredSize().height) / 2 - 4; + } else { + point.x -= tooltip.getPreferredSize().width / 2; + point.y -= tooltip.getPreferredSize().height - 7; + } + return point; } @Override - protected void processMouseMotionEvent(final MouseEvent e) { - super.processMouseMotionEvent(e); - updateBalloonText(); + public JToolTip createToolTip() { + tooltip = super.createToolTip(); + if (myVertical) { + tooltip.setPreferredSize(new Dimension(130, 39)); + } else { + tooltip.setPreferredSize(new Dimension(120, 46)); + } + tooltip.putClientProperty("JToolTip.insets", new Insets(3, 0, 3, 0)); + tooltip.putClientProperty("JToolTip.pointerLocation", myVertical ? Alignment.EAST : Alignment.SOUTH); + return tooltip; } private void processMouse(final MouseEvent e) { @@ -198,8 +184,10 @@ class SlideComponent extends JComponent { return myValue; } - // 0 - 255 public void setValue(final int value) { + if (value < 0 || value > 255) { + throw new IllegalArgumentException("Value " + value + " not in range [0,255]"); + } myPointerValue = valueToPointerValue(value); myValue = value; } @@ -207,8 +195,8 @@ class SlideComponent extends JComponent { private int pointerValueToValue(int pointerValue) { pointerValue -= OFFSET; final int size = myVertical ? getHeight() : getWidth(); - float proportion = (size - 23) / 255f; - return Math.round((pointerValue / proportion)); + double proportion = (size - 23) / 255f; + return (int) Math.round((pointerValue / proportion)); } private int valueToPointerValue(final int value) { @@ -229,11 +217,6 @@ class SlideComponent extends JComponent { : new Dimension(50, 22); } - @Override - public final void setToolTipText(final String text) { - //disable tooltips - } - @Override protected void paintComponent(final Graphics g) { final Graphics2D g2d = (Graphics2D) g; @@ -274,8 +257,11 @@ class SlideComponent extends JComponent { } private static String formatValue(final int value, final Unit unit) { - return String.format("%d%s", (int) (getMaxValue(unit) / LEVEL_MAX_VALUE * value), - unit.equals(PERCENT) ? "%" : ""); + if (unit == PERCENT) { + return String.format("%d%s", (int) ((getMaxValue(unit) / LEVEL_MAX_VALUE * value)), "%"); + } else { + return String.format("%d", (int) (LEVEL_MAX_VALUE - ((getMaxValue(unit) / LEVEL_MAX_VALUE * value)))); + } } } } \ No newline at end of file diff --git a/src/main/java/com/weis/darklaf/ui/colorchooser/SwatchPanel.java b/src/main/java/com/weis/darklaf/ui/colorchooser/SwatchPanel.java index b91dc255..265f5ae7 100644 --- a/src/main/java/com/weis/darklaf/ui/colorchooser/SwatchPanel.java +++ b/src/main/java/com/weis/darklaf/ui/colorchooser/SwatchPanel.java @@ -1,6 +1,10 @@ package com.weis.darklaf.ui.colorchooser; +import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.util.GraphicsUtil; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -8,6 +12,7 @@ import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; abstract class SwatchPanel extends JPanel { @@ -19,6 +24,7 @@ abstract class SwatchPanel extends JPanel { private int selRow; private int selCol; + private JToolTip tooltip; public SwatchPanel() { initValues(); @@ -28,7 +34,16 @@ abstract class SwatchPanel extends JPanel { setBackground(UIManager.getColor("ColorChooser.swatchGridColor")); setFocusable(true); setInheritsPopupMenu(true); - + addMouseListener(new MouseAdapter() { + @Override + public void mouseExited(final MouseEvent e) { + var p = e.getPoint(); + p = SwingUtilities.convertPoint(e.getComponent(), p, SwatchPanel.this); + if (tooltip != null && !contains(p)) { + tooltip.setVisible(false); + } + } + }); addFocusListener(new FocusAdapter() { public void focusGained(final FocusEvent e) { repaint(); @@ -101,35 +116,43 @@ abstract class SwatchPanel extends JPanel { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); for (int row = 0; row < numSwatches.height; row++) { - int y = row * (swatchSize.height + gap.height); + int y = getYForRow(row); for (int column = 0; column < numSwatches.width; column++) { Color c = getColorForCell(column, row); + g.setColor(c); - int x; - if (!this.getComponentOrientation().isLeftToRight()) { - x = (numSwatches.width - column - 1) * (swatchSize.width + gap.width); - } else { - x = column * (swatchSize.width + gap.width); - } + int x = getXForColumn(column); g.fillRect(x, y, swatchSize.width, swatchSize.height); - if (selRow == row && selCol == column && this.isFocusOwner()) { - Color c2 = new Color(c.getRed() < 125 ? 225 : 30, - c.getGreen() < 125 ? 225 : 30, - c.getBlue() < 125 ? 225 : 30); + if (selRow == row && selCol == column && this.isFocusOwner() && c != null) { + Color c2 = new Color(255 - c.getRed(), 255 - c.getGreen(), 255 - c.getBlue()); g.setColor(c2); - - g.drawLine(x, y, x + swatchSize.width, y); - g.drawLine(x, y, x, y + swatchSize.height); - g.drawLine(x + swatchSize.width, y, x + swatchSize.width, y + swatchSize.height); - g.drawLine(x, y + swatchSize.height, x + swatchSize.width, y + swatchSize.height); - g.drawLine(x, y, x + swatchSize.width, y + swatchSize.height); - g.drawLine(x, y + swatchSize.height, x + swatchSize.width, y); + g.fillRect(x, y, swatchSize.width, 1); + g.fillRect(x, y + swatchSize.height - 1, swatchSize.width - 1, 1); + g.fillRect(x, y, 1, swatchSize.height); + g.fillRect(x + swatchSize.width - 1, y, 1, swatchSize.height); + + GraphicsUtil.setupStrokePainting(g); + g.drawLine(x + 1, y + 1, x + swatchSize.width - 1, y + swatchSize.height - 1); + g.drawLine(x + 1, y + swatchSize.height - 1, x + swatchSize.width - 1, y + 1); } } } } + @Contract(pure = true) + private int getYForRow(final int row) { + return row * (swatchSize.height + gap.height); + } + + private int getXForColumn(final int column) { + if (!this.getComponentOrientation().isLeftToRight()) { + return (numSwatches.width - column - 1) * (swatchSize.width + gap.width); + } else { + return column * (swatchSize.width + gap.width); + } + } + public Dimension getPreferredSize() { int x = numSwatches.width * (swatchSize.width + gap.width) - 1; int y = numSwatches.height * (swatchSize.height + gap.height) - 1; @@ -139,11 +162,36 @@ abstract class SwatchPanel extends JPanel { protected void initColors() { } - public String getToolTipText(final MouseEvent e) { + public String getToolTipText(@NotNull final MouseEvent e) { Color color = getColorForLocation(e.getX(), e.getY()); + if (color == null) return null; return color.getRed() + ", " + color.getGreen() + ", " + color.getBlue(); } + @Override + public Point getToolTipLocation(final MouseEvent e) { + if (tooltip == null) { + createToolTip(); + tooltip.setTipText(getToolTipText(e)); + } + var p = getCoordinatesForLocation(e.getX(), e.getY()); + int x = getXForColumn(p.x); + int y = getYForRow(p.y); + x += swatchSize.width / 2; + y += swatchSize.height / 2; + x -= tooltip.getPreferredSize().width / 2; + return new Point(x, y); + } + + @Override + public JToolTip createToolTip() { + tooltip = super.createToolTip(); +// tooltip.putClientProperty("JToolTip.pointerWidth", 10); +// tooltip.putClientProperty("JToolTip.pointerHeight", 7); + tooltip.putClientProperty("JToolTip.pointerLocation", Alignment.NORTH); + return tooltip; + } + public void setSelectedColorFromLocation(final int x, final int y) { if (!this.getComponentOrientation().isLeftToRight()) { selCol = numSwatches.width - x / (swatchSize.width + gap.width) - 1; @@ -154,7 +202,7 @@ abstract class SwatchPanel extends JPanel { repaint(); } - public Color getColorForLocation(final int x, final int y) { + public Point getCoordinatesForLocation(final int x, final int y) { int column; if (!this.getComponentOrientation().isLeftToRight()) { column = numSwatches.width - x / (swatchSize.width + gap.width) - 1; @@ -162,10 +210,19 @@ abstract class SwatchPanel extends JPanel { column = x / (swatchSize.width + gap.width); } int row = y / (swatchSize.height + gap.height); - return getColorForCell(column, row); + return new Point(column, row); + } + + public Color getColorForLocation(final int x, final int y) { + var p = getCoordinatesForLocation(x, y); + return getColorForCell(p.x, p.y); } + @Nullable + @Contract(pure = true) private Color getColorForCell(final int column, final int row) { - return colors[(row * numSwatches.width) + column]; // (STEVE) - change data orientation here + int index = (row * numSwatches.width) + column; + if (index >= colors.length) return null; + return colors[(row * numSwatches.width) + column]; } } diff --git a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java new file mode 100644 index 00000000..b2fe94cd --- /dev/null +++ b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipBorder.java @@ -0,0 +1,96 @@ +package com.weis.darklaf.ui.tooltip; + +import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.components.border.TextBubbleBorder; +import org.jdesktop.swingx.border.DropShadowBorder; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.plaf.UIResource; +import java.awt.*; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; + +public class DarkTooltipBorder implements Border, UIResource { + + private final DropShadowBorder shadowBorder = new DropShadowBorder(Color.BLACK, 10, 0.4f, 10, + false, true, + true, true); + private final TextBubbleBorder bubbleBorder; + + public DarkTooltipBorder() { + bubbleBorder = new TextBubbleBorder(UIManager.getColor("Tooltip.borderColor")); + bubbleBorder.setThickness(1); + bubbleBorder.setPointerSize(8); + bubbleBorder.setPointerWidth(12); + bubbleBorder.setPointerSide(Alignment.EAST); + } + + + public Area getBackgroundArea(final int width, final int height) { + var ins = shadowBorder.getBorderInsets(null); + return bubbleBorder.getInnerArea(ins.left, ins.top, + width - ins.left - ins.right, + height - ins.top - ins.bottom); + } + + @Override + public void paintBorder(final Component c, final Graphics g, + final int x, final int y, final int width, final int height) { + if (c instanceof JToolTip && ((JToolTip) c).getTipText() == null) return; + var ins = shadowBorder.getBorderInsets(c); + var bubbleArea = bubbleBorder.getInnerArea(x + ins.left, y + ins.top, + width - ins.left - ins.right, + height - ins.top - ins.bottom); + var oldClip = g.getClip(); + var clip = new Area(new Rectangle2D.Double(x, y, width, height)); + clip.subtract(bubbleArea); + g.setClip(clip); + + ins = bubbleBorder.getBorderInsets(c); + shadowBorder.paintBorder(c, g, x + ins.left, y + ins.top, + width - ins.left - ins.right, height - ins.top - ins.bottom); + + g.setClip(oldClip); + bubbleBorder.paintBorder(g, bubbleArea); + } + + @Override + public Insets getBorderInsets(final Component c) { + var ins = (Insets) bubbleBorder.getBorderInsets(c).clone(); + var si = shadowBorder.getBorderInsets(c); + var uIns = getUserInsets(c); + ins.left += 5 + si.left + uIns.left; + ins.top += 2 + si.top + uIns.top; + ins.right += 5 + si.right + uIns.right; + ins.bottom += 2 + si.bottom + uIns.bottom; + return ins; + } + + protected Insets getUserInsets(final Component c) { + if (c instanceof JComponent) { + var obj = ((JComponent) c).getClientProperty("JToolTip.insets"); + if (obj instanceof Insets) { + return (Insets) obj; + } + } + return new Insets(0, 0, 0, 0); + } + + @Override + public boolean isBorderOpaque() { + return false; + } + + public void setPointerLocation(final Alignment side) { + bubbleBorder.setPointerSide(side); + } + + public void setPointerWidth(final int width) { + bubbleBorder.setPointerWidth(width); + } + + public void setPointerHeight(final int height) { + bubbleBorder.setPointerSize(height); + } +} diff --git a/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipUI.java b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipUI.java new file mode 100644 index 00000000..f269e2b6 --- /dev/null +++ b/src/main/java/com/weis/darklaf/ui/tooltip/DarkTooltipUI.java @@ -0,0 +1,120 @@ +package com.weis.darklaf.ui.tooltip; + +import com.weis.darklaf.components.alignment.Alignment; +import com.weis.darklaf.util.DarkUIUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.basic.BasicToolTipUI; +import javax.swing.text.View; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListener { + + @NotNull + @Contract("_ -> new") + public static ComponentUI createUI(@NotNull final JComponent c) { + if (Boolean.TRUE.equals(c.getClientProperty("JComponent.plainTooltip"))) { + return BasicToolTipUI.createUI(c); + } else { + return new DarkTooltipUI(); + } + } + + @Override + public void installUI(final JComponent c) { + super.installUI(c); + } + + @Override + public void paint(@NotNull final Graphics g, @NotNull final JComponent c) { + if (((JToolTip) c).getTipText() == null) return; + g.setColor(c.getBackground()); + if (c.getBorder() instanceof DarkTooltipBorder) { + var area = ((DarkTooltipBorder) c.getBorder()).getBackgroundArea(c.getWidth(), c.getHeight()); + ((Graphics2D) g).fill(area); + } + super.paint(g, c); + } + + @Override + protected void installDefaults(final JComponent c) { + super.installDefaults(c); + c.setOpaque(false); + } + + @Override + protected void installListeners(final JComponent c) { + super.installListeners(c); + c.addHierarchyListener(e -> { + var w = SwingUtilities.getWindowAncestor(c); + if (w != null && !c.isLightweight() && !isDecorated(w)) { + w.setBackground(DarkUIUtil.TRANSPARENT_COLOR); + } + }); + c.addPropertyChangeListener(this); + } + + protected boolean isDecorated(final Window w) { + if (w instanceof Dialog) { + return !((Dialog) w).isUndecorated(); + } + if (w instanceof Frame) { + return !((Frame) w).isUndecorated(); + } + return false; + } + + public Dimension getPreferredSize(@NotNull final JComponent c) { + Font font = c.getFont(); + FontMetrics fm = c.getFontMetrics(font); + Insets insets = c.getInsets(); + + Dimension prefSize = new Dimension(insets.left + insets.right, + insets.top + insets.bottom); + String text = ((JToolTip) c).getTipText(); + + if ((text != null) && !text.equals("")) { + View v = (View) c.getClientProperty("html"); + if (v != null) { + prefSize.width += (int) v.getPreferredSpan(View.X_AXIS) + 6; + prefSize.height += (int) v.getPreferredSpan(View.Y_AXIS); + } else { + prefSize.width += fm.stringWidth(text) + 6; + prefSize.height += fm.getHeight(); + } + } + return prefSize; + } + + @Override + public void propertyChange(final PropertyChangeEvent evt) { + var key = evt.getPropertyName(); + if (evt.getSource() instanceof JToolTip) { + var tooltip = (JToolTip) evt.getSource(); + if (tooltip.getBorder() instanceof DarkTooltipBorder) { + var border = (DarkTooltipBorder) tooltip.getBorder(); + var newVal = evt.getNewValue(); + if ("JToolTip.pointerLocation".equals(key)) { + if (newVal instanceof Alignment) { + border.setPointerLocation((Alignment) newVal); + } else { + border.setPointerLocation(Alignment.CENTER); + } + } else if ("JToolTip.pointerHeight".equals(key)) { + if (newVal instanceof Integer) { + border.setPointerHeight((Integer) newVal); + } + } else if ("JToolTip.pointerWidth".equals(key)) { + if (newVal instanceof Integer) { + border.setPointerWidth((Integer) newVal); + } + } + } + } + } +} diff --git a/src/main/resources/com/weis/darklaf/darcula.properties b/src/main/resources/com/weis/darklaf/darcula.properties index ea168339..cd184d85 100644 --- a/src/main/resources/com/weis/darklaf/darcula.properties +++ b/src/main/resources/com/weis/darklaf/darcula.properties @@ -408,8 +408,11 @@ StatusBarUI = com.weis.darklaf.ui. StatusBar.topColor = 555555 StatusBar.background = 3c3f41 - -ToolTip.background = 3C3f41 +#ToolTip +ToolTipUI = com.weis.darklaf.ui.tooltip.DarkTooltipUI +Tooltip.background = 4B4D4D +Tooltip.borderColor = 5B5D5F +ToolTip.border = com.weis.darklaf.ui.tooltip.DarkTooltipBorder #InternalFrame InternalFrameUI = com.bulenkov.darcula.ui.DarculaInternalFrameUI diff --git a/src/test/java/ColorChooserDemo.java b/src/test/java/ColorChooserDemo.java index 7f4d130c..64255bd3 100644 --- a/src/test/java/ColorChooserDemo.java +++ b/src/test/java/ColorChooserDemo.java @@ -9,7 +9,7 @@ public final class ColorChooserDemo { SwingUtilities.invokeLater(() -> { LafManager.loadLaf(LafManager.Theme.Dark); JColorChooser.showDialog(null, "Color Chooser without transparency", - Color.RED, false); + Color.RED, true); }); } } diff --git a/src/test/java/ColorModelTest.java b/src/test/java/ColorModelTest.java deleted file mode 100644 index 8b100796..00000000 --- a/src/test/java/ColorModelTest.java +++ /dev/null @@ -1,41 +0,0 @@ -import com.weis.darklaf.color.DarkColorModel; -import com.weis.darklaf.color.DarkColorModelCMYK; -import com.weis.darklaf.color.DarkColorModelHSB; -import com.weis.darklaf.color.DarkColorModelHSL; -import org.jetbrains.annotations.NotNull; - -import java.awt.*; -import java.util.Arrays; - -public class ColorModelTest { - - public static void main(final String[] args) { - var color = new Color(10, 20, 30); - - var m1 = new DarkColorModel(); - var m2 = new DarkColorModelHSB(); - var m3 = new DarkColorModelHSL(); - var m4 = new DarkColorModelCMYK(); -// test(m1); - test(m2); -// test(m3); -// test(m4); - } - - private static void test(@NotNull final DarkColorModel model) { - System.out.println("Testing " + model.toString()); - for (int r = model.getMinimum(0); r < model.getMaximum(0); r++) { - for (int g = model.getMinimum(1); g < model.getMaximum(1); g++) { - for (int b = model.getMinimum(2); b < model.getMaximum(2); b++) { - var c = new int[]{r, g, b}; - var interm = model.getColorFromValues(c); - var nc = model.getValuesFromColor(interm); - if (!Arrays.equals(c, nc)) { - throw new RuntimeException( - "Not equals " + Arrays.toString(c) + " : " + interm + " : " + Arrays.toString(nc)); - } - } - } - } - } -} diff --git a/src/test/java/GenerateColors.java b/src/test/java/GenerateColors.java index 88e9aaf9..7eaa7ef6 100644 --- a/src/test/java/GenerateColors.java +++ b/src/test/java/GenerateColors.java @@ -2,15 +2,18 @@ import com.weis.darklaf.color.DarkColorModelHSL; import java.awt.*; -public class GenerateColors { +/** + * Usd to generate the color swatches for ColorChooser. + */ +public final class GenerateColors { public static void main(final String[] args) { int cols = 30; int rows = 20; System.out.println("{"); - int r = 255; - int g = 255; - int b = 255; + int r; + int g; + int b; for (int i = 1; i < rows + 1; i++) { r = g = b = (int) ((i - 1) * 255.0 / (rows - 1)); System.out.println(r + "," + g + "," + b + ",");