Leo.Qin
1 year ago
5 changed files with 375 additions and 12 deletions
@ -0,0 +1,344 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatSliderUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.formdev.flatlaf.util.HiDPIUtils; |
||||
import com.formdev.flatlaf.util.UIScale; |
||||
import com.fr.stable.AssistUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JSlider; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Rectangle; |
||||
import java.awt.Shape; |
||||
import java.awt.geom.Ellipse2D; |
||||
import java.awt.geom.Path2D; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
import java.util.Dictionary; |
||||
import java.util.Enumeration; |
||||
|
||||
/** |
||||
* 滑块slider UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public class FineSliderUI extends FlatSliderUI { |
||||
|
||||
private final int DEFAULT_LABEL_HEIGHT = 13; |
||||
private Color defaultForeground; |
||||
private int defaultLabelHeight; |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineSliderUI(); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(JSlider slider) { |
||||
super.installDefaults(slider); |
||||
defaultForeground = UIManager.getColor("Slider.foreground"); |
||||
defaultLabelHeight = FineUIUtils.getAndScaleInt("Slider.labelHeight", DEFAULT_LABEL_HEIGHT); |
||||
} |
||||
|
||||
@Override |
||||
protected void calculateLabelRect() { |
||||
|
||||
if (slider.getPaintLabels()) { |
||||
calLabelRectWhenPaint(); |
||||
} else { |
||||
calLabelRectWhenNotPaint(); |
||||
} |
||||
} |
||||
|
||||
private void calLabelRectWhenPaint() { |
||||
labelRect.y = 0; |
||||
|
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
labelRect.x = tickRect.x - trackBuffer; |
||||
labelRect.width = tickRect.width + (trackBuffer * 2); |
||||
labelRect.height = getHeightOfTallestLabel(); |
||||
} else { |
||||
if (isLeftToRight(slider)) { |
||||
labelRect.x = tickRect.x + tickRect.width; |
||||
labelRect.width = getWidthOfWidestLabel(); |
||||
} else { |
||||
labelRect.width = getWidthOfWidestLabel(); |
||||
labelRect.x = tickRect.x - labelRect.width; |
||||
} |
||||
labelRect.height = tickRect.height + (trackBuffer * 2); |
||||
} |
||||
} |
||||
|
||||
private void calLabelRectWhenNotPaint() { |
||||
labelRect.y = 0; |
||||
|
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
labelRect.x = tickRect.x; |
||||
labelRect.width = tickRect.width; |
||||
labelRect.height = 0; |
||||
} else { |
||||
if (isLeftToRight(slider)) { |
||||
labelRect.x = tickRect.x + tickRect.width; |
||||
} else { |
||||
labelRect.x = tickRect.x; |
||||
} |
||||
labelRect.width = 0; |
||||
labelRect.height = tickRect.height; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void calculateTrackRect() { |
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
calHorizontalTrackRect(); |
||||
} else { |
||||
calVerticalTrackRect(); |
||||
} |
||||
} |
||||
|
||||
private void calVerticalTrackRect() { |
||||
int centerSpacing; |
||||
centerSpacing = thumbRect.width; |
||||
if (isLeftToRight(slider)) { |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing += getTickLength(); |
||||
} |
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing += getWidthOfWidestLabel(); |
||||
} |
||||
} else { |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing -= getTickLength(); |
||||
} |
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing -= getWidthOfWidestLabel(); |
||||
} |
||||
} |
||||
trackRect.x = contentRect.x + getWidthOfWidestLabel() + (contentRect.width - centerSpacing - 1) / 2; |
||||
trackRect.y = contentRect.y + trackBuffer; |
||||
trackRect.width = thumbRect.width; |
||||
trackRect.height = contentRect.height - (trackBuffer * 2); |
||||
} |
||||
|
||||
private void calHorizontalTrackRect() { |
||||
int centerSpacing; |
||||
centerSpacing = thumbRect.height; |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing += getTickLength(); |
||||
} |
||||
|
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing += getHeightOfTallestLabel(); |
||||
} |
||||
trackRect.x = contentRect.x + trackBuffer; |
||||
trackRect.y = contentRect.y + getHeightOfTallestLabel() + (contentRect.height - centerSpacing - 1) / 2; |
||||
trackRect.width = contentRect.width - (trackBuffer * 2); |
||||
trackRect.height = thumbRect.height; |
||||
} |
||||
|
||||
@Override |
||||
protected int getHeightOfTallestLabel() { |
||||
Dictionary dictionary = slider.getLabelTable(); |
||||
int tallest = 0; |
||||
if (dictionary != null) { |
||||
Enumeration keys = dictionary.keys(); |
||||
while (keys.hasMoreElements()) { |
||||
JComponent label = (JComponent) dictionary.get(keys.nextElement()); |
||||
tallest = Math.max(label.getPreferredSize().height, tallest); |
||||
} |
||||
} |
||||
return Math.min(tallest, defaultLabelHeight); |
||||
} |
||||
|
||||
@Override |
||||
protected int getWidthOfWidestLabel() { |
||||
Dictionary dictionary = slider.getLabelTable(); |
||||
int widest = 0; |
||||
if (dictionary != null) { |
||||
Enumeration keys = dictionary.keys(); |
||||
while (keys.hasMoreElements()) { |
||||
JComponent label = (JComponent) dictionary.get(keys.nextElement()); |
||||
widest = Math.max(label.getPreferredSize().width, widest); |
||||
} |
||||
} |
||||
return Math.min(widest, defaultLabelHeight); |
||||
} |
||||
|
||||
/** |
||||
* Convenience function for determining ComponentOrientation. Helps us |
||||
* avoid having Munge directives throughout the code. |
||||
*/ |
||||
static boolean isLeftToRight(Component c) { |
||||
return c.getComponentOrientation().isLeftToRight(); |
||||
} |
||||
|
||||
@Override |
||||
public void paintThumb(Graphics g) { |
||||
Color thumbColor = getThumbColor(); |
||||
Color color = stateColor(slider, thumbHover, thumbPressed, thumbColor, disabledThumbColor, null, hoverThumbColor, pressedThumbColor); |
||||
color = FlatUIUtils.deriveColor(color, thumbColor); |
||||
|
||||
Color foreground = slider.getForeground(); |
||||
Color borderColor = (thumbBorderColor != null && foreground == defaultForeground) ? stateColor(slider, false, false, thumbBorderColor, disabledThumbBorderColor, focusedThumbBorderColor, null, null) : null; |
||||
|
||||
Color focusedColor = FlatUIUtils.deriveColor(this.focusedColor, (foreground != defaultForeground) ? foreground : focusBaseColor); |
||||
|
||||
paintThumb(g, slider, thumbRect, isRoundThumb(), color, borderColor, focusedColor, thumbBorderWidth, focusWidth); |
||||
} |
||||
|
||||
/** |
||||
* Paints the thumb. |
||||
* |
||||
* @param g the graphics context |
||||
* @param slider the slider |
||||
* @param thumbRect the thumb rectangle |
||||
* @param roundThumb whether the thumb should be round |
||||
* @param thumbColor the thumb color |
||||
* @param thumbBorderColor the thumb border color |
||||
* @param focusedColor the focused color |
||||
* @param thumbBorderWidth the thumb border width |
||||
* @param focusWidth the focus width |
||||
*/ |
||||
public static void paintThumb(Graphics g, JSlider slider, Rectangle thumbRect, boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth) { |
||||
double systemScaleFactor = UIScale.getSystemScaleFactor((Graphics2D) g); |
||||
int scaleFactor2 = 2; |
||||
if (systemScaleFactor != 1 && systemScaleFactor != scaleFactor2) { |
||||
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
|
||||
HiDPIUtils.paintAtScale1x((Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, (g2d, x2, y2, width2, height2, scaleFactor) -> { |
||||
paintThumbImpl(g, slider, x2, y2, width2, height2, roundThumb, thumbColor, thumbBorderColor, focusedColor, (float) (thumbBorderWidth * scaleFactor), (float) (focusWidth * scaleFactor)); |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
paintThumbImpl(g, slider, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, roundThumb, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, focusWidth); |
||||
|
||||
} |
||||
|
||||
private static void paintThumbImpl(Graphics g, JSlider slider, int x, int y, int width, int height, boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, float focusWidth) { |
||||
int fw = Math.round(UIScale.scale(focusWidth)); |
||||
int tx = x + fw; |
||||
int ty = y + fw; |
||||
int tw = width - fw - fw; |
||||
int th = height - fw - fw; |
||||
boolean focused = FlatUIUtils.isPermanentFocusOwner(slider); |
||||
|
||||
if (roundThumb) { |
||||
paintRoundThumb(g, x, y, width, height, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, focused, tx, ty, tw, th); |
||||
} else { |
||||
paintDirectionalThumb(g, slider, x, y, width, height, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, tw, th, focused, fw); |
||||
} |
||||
} |
||||
|
||||
private static void paintDirectionalThumb(Graphics g, JSlider slider, int x, int y, int width, int height, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int tw, int th, boolean focused, int fw) { |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
g2.translate(x, y); |
||||
if (slider.getOrientation() == JSlider.VERTICAL) { |
||||
if (slider.getComponentOrientation().isLeftToRight()) { |
||||
g2.translate(0, height); |
||||
g2.rotate(Math.toRadians(270)); |
||||
} else { |
||||
g2.translate(width, 0); |
||||
g2.rotate(Math.toRadians(90)); |
||||
} |
||||
|
||||
// rotate thumb width/height
|
||||
int temp = tw; |
||||
tw = th; |
||||
th = temp; |
||||
} |
||||
|
||||
paintDirectionalThumbImpl(thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, tw, th, focused, fw, g2); |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} |
||||
|
||||
private static void paintDirectionalThumbImpl(Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int tw, int th, boolean focused, int fw, Graphics2D g2) { |
||||
// paint thumb focus border
|
||||
if (focused) { |
||||
g2.setColor(focusedColor); |
||||
g2.fill(createDirectionalThumbShape(0, 0, tw + fw + fw, th + fw + fw + (fw * 0.4142f), fw)); |
||||
} |
||||
|
||||
if (thumbBorderColor != null) { |
||||
// paint thumb border
|
||||
g2.setColor(thumbBorderColor); |
||||
g2.fill(createDirectionalThumbShape(fw, fw, tw, th, 0)); |
||||
|
||||
// paint thumb background
|
||||
float lw = UIScale.scale(thumbBorderWidth); |
||||
g2.setColor(thumbColor); |
||||
g2.fill(createDirectionalThumbShape(fw + lw, fw + lw, tw - lw - lw, th - lw - lw - (lw * 0.4142f), 0)); |
||||
} else { |
||||
// paint thumb background
|
||||
g2.setColor(thumbColor); |
||||
g2.fill(createDirectionalThumbShape(fw, fw, tw, th, 0)); |
||||
} |
||||
} |
||||
|
||||
private static void paintRoundThumb(Graphics g, int x, int y, int width, int height, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, boolean focused, int tx, int ty, int tw, int th) { |
||||
// paint thumb focus border
|
||||
if (focused) { |
||||
g.setColor(focusedColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(x, y, width, height)); |
||||
} |
||||
|
||||
if (thumbBorderColor != null) { |
||||
// paint thumb border
|
||||
g.setColor(thumbBorderColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx, ty, tw, th)); |
||||
|
||||
// paint thumb background
|
||||
float lw = UIScale.scale(thumbBorderWidth); |
||||
g.setColor(thumbColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx + lw, ty + lw, tw - lw - lw, th - lw - lw)); |
||||
} else { |
||||
// paint thumb background
|
||||
g.setColor(thumbColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx, ty, tw, th)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 无标签下创建圆形Thumb形状 |
||||
*/ |
||||
public static Shape createRoundThumbShape(float x, float y, float w, float h) { |
||||
if (AssistUtils.equals(w, h)) { |
||||
return new Ellipse2D.Float(x, y, w, h); |
||||
} else { |
||||
float arc = Math.min(w, h); |
||||
return new RoundRectangle2D.Float(x, y, w, h, arc, arc); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 有标签下创建Thumb形状 |
||||
*/ |
||||
public static Shape createDirectionalThumbShape(float x, float y, float w, float h, float arc) { |
||||
|
||||
float wh = w / 2; |
||||
Path2D path = new Path2D.Float(Path2D.WIND_NON_ZERO, 9); |
||||
path.moveTo(x + wh, y); // 移到反转后的位置
|
||||
path.lineTo(x, y + wh); // 线到反转后的位置
|
||||
path.lineTo(x, y + h - arc); // 线到反转后的位置
|
||||
path.quadTo(x, y + h, x + arc, y + h); // 贝塞尔曲线到反转后的位置
|
||||
path.lineTo(x + (w - arc), y + h); // 线到反转后的位置
|
||||
path.quadTo(x + w, y + h, x + w, y + h - arc); // 贝塞尔曲线到反转后的位置
|
||||
path.lineTo(x + w, y + wh); // 线到反转后的位置
|
||||
path.closePath(); // 关闭路径
|
||||
|
||||
return path; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue