From 022b18ae6d0c74e6506f81a7871f7b1b150eb283 Mon Sep 17 00:00:00 2001 From: "Leo.Qin" Date: Tue, 19 Dec 2023 19:47:10 +0800 Subject: [PATCH] =?UTF-8?q?REPORT-107973=20=E4=B8=BB=E9=A1=B5=E5=8F=8A?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E8=A7=86=E8=A7=89=E6=A0=B7=E5=BC=8F=E7=BF=BB?= =?UTF-8?q?=E6=96=B0=20=E3=80=90=E9=97=AE=E9=A2=98=E5=8E=9F=E5=9B=A0?= =?UTF-8?q?=E3=80=91rt=20=E3=80=90=E6=94=B9=E5=8A=A8=E6=80=9D=E8=B7=AF?= =?UTF-8?q?=E3=80=91=E7=BF=BB=E6=96=B0=E5=B8=A6=E6=A0=87=E7=AD=BE=E5=B8=A6?= =?UTF-8?q?=E6=BB=91=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fine/theme/light/ui/FineSliderUI.java | 344 ++++++++++++++++++ .../fr/design/mainframe/JFormSliderPane.java | 7 +- .../theme/light/ui/laf/FineLaf.properties | 5 +- .../light/ui/laf/FineLightLaf.properties | 11 +- .../components/SliderStoryBoard.java | 20 +- 5 files changed, 375 insertions(+), 12 deletions(-) create mode 100644 designer-base/src/main/java/com/fine/theme/light/ui/FineSliderUI.java diff --git a/designer-base/src/main/java/com/fine/theme/light/ui/FineSliderUI.java b/designer-base/src/main/java/com/fine/theme/light/ui/FineSliderUI.java new file mode 100644 index 000000000..6917f66c9 --- /dev/null +++ b/designer-base/src/main/java/com/fine/theme/light/ui/FineSliderUI.java @@ -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; + } + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/JFormSliderPane.java b/designer-base/src/main/java/com/fr/design/mainframe/JFormSliderPane.java index 4629b6068..ec87157ca 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/JFormSliderPane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/JFormSliderPane.java @@ -1,6 +1,7 @@ package com.fr.design.mainframe; import com.fine.theme.icon.LazyIcon; +import com.fine.theme.utils.FineUIScale; import com.formdev.flatlaf.FlatDarculaLaf; import com.fr.base.BaseUtils; import com.fr.design.gui.ibutton.UIButton; @@ -48,8 +49,6 @@ public class JFormSliderPane extends JPanel { private static final int HUNDRED = 100; private static final int TWO_HUNDRED = 200; private static final int FOUR_HUNDRED = 400; - private static final int SHOWVALBUTTON_WIDTH = 40; - private static final int SHOWVALBUTTON_HEIGHTH = 20; private static final String SUFFIX = "%"; private static final int TOOLTIP_Y = 30; private static final Color BACK_COLOR = new Color(245, 245, 247); @@ -93,7 +92,6 @@ public class JFormSliderPane extends JPanel { }; slider.setValue(HALF_HUNDRED); slider.addChangeListener(listener); - slider.setPreferredSize(new Dimension(220, 20)); //去掉虚线框 slider.setFocusable(false); slider.setToolTipText(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Scale_Slider")); @@ -124,7 +122,8 @@ public class JFormSliderPane extends JPanel { private void initShowValField() { showValField = new UINumberField(); showValField.setValue(showValue); - showValField.setPreferredSize(new Dimension(SHOWVALBUTTON_WIDTH, SHOWVALBUTTON_HEIGHTH)); + Dimension dimension = new Dimension(UIManager.getInt("FormSliderPane.showValueWidth"), UIManager.getInt("FormSliderPane.showValueHeight")); + showValField.setPreferredSize(FineUIScale.scale(dimension)); showValField.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { diff --git a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties index ed39afd70..32e958fa3 100644 --- a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties +++ b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties @@ -28,7 +28,7 @@ RootPaneUI=com.formdev.flatlaf.ui.FlatRootPaneUI ScrollBarUI=com.formdev.flatlaf.ui.FlatScrollBarUI ScrollPaneUI=com.formdev.flatlaf.ui.FlatScrollPaneUI SeparatorUI=com.formdev.flatlaf.ui.FlatSeparatorUI -SliderUI=com.formdev.flatlaf.ui.FlatSliderUI +SliderUI=com.fine.theme.light.ui.FineSliderUI SpinnerUI=com.formdev.flatlaf.ui.FlatSpinnerUI SplitPaneUI=com.formdev.flatlaf.ui.FlatSplitPaneUI TabbedPaneUI=com.formdev.flatlaf.ui.FlatTabbedPaneUI @@ -45,5 +45,4 @@ TreeUI=com.fine.theme.light.ui.UIFlatTreeUI ViewportUI=com.formdev.flatlaf.ui.FlatViewportUI HeadGroupUI=com.fine.theme.light.ui.FineHeadGroupUI ButtonGroupUI= com.fine.theme.light.ui.FineButtonGroupUI -SelectBoxUI= com.fine.theme.light.ui.FineSelectBoxUI - +SelectBoxUI= com.fine.theme.light.ui.FineSelectBoxUI \ No newline at end of file diff --git a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLightLaf.properties b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLightLaf.properties index 59cc81afe..e1a2c1965 100644 --- a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLightLaf.properties +++ b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLightLaf.properties @@ -688,17 +688,24 @@ Separator.foreground = shade(@background,15%) Slider.focusInsets = 0,0,0,0 Slider.trackWidth = 2 Slider.thumbSize = 12,12 -Slider.focusWidth = 4 +Slider.focusWidth=0 Slider.trackValueColor=$brand.normal Slider.trackColor=$border.divider Slider.thumbColor=$fill.normal Slider.thumbBorderColor=$border.divider Slider.tickColor = @disabledForeground -Slider.focusedColor = fade(changeLightness($Component.focusColor,75%,derived),50%,derived) +Slider.focusedColor=$border.divider +Slider.focusedThumbBorderColor=$border.divider Slider.hoverThumbColor=$fill.hover Slider.pressedThumbColor=$fill.hover Slider.disabledTrackColor = darken(@background,13%) Slider.disabledThumbColor = $Slider.disabledTrackColor +Slider.labelHeight=13 +#---- FormSliderPane ---- +FormSliderPane.showValueWidth=40 +FormSliderPane.showValueHeight=20 +FormSliderPane.sliderWidth=220 +FormSliderPane.sliderHeight=20 #---- Spinner ---- diff --git a/designer-base/src/test/java/com/fr/design/gui/storybook/components/SliderStoryBoard.java b/designer-base/src/test/java/com/fr/design/gui/storybook/components/SliderStoryBoard.java index 8238ff83e..26a86fdd1 100644 --- a/designer-base/src/test/java/com/fr/design/gui/storybook/components/SliderStoryBoard.java +++ b/designer-base/src/test/java/com/fr/design/gui/storybook/components/SliderStoryBoard.java @@ -1,8 +1,9 @@ package com.fr.design.gui.storybook.components; import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.islider.UISlider; +import com.fr.design.gui.storybook.Story; import com.fr.design.gui.storybook.StoryBoard; -import com.fr.design.mainframe.JFormSliderPane; import static com.fine.swing.ui.layout.Layouts.*; @@ -13,12 +14,25 @@ import static com.fine.swing.ui.layout.Layouts.*; * @since 11.0 * Created on 2023/12/14 */ +@Story public class SliderStoryBoard extends StoryBoard { public SliderStoryBoard() { super("滑块"); add( - cell(new UILabel("面板缩放滑块")).with(this::h3), - row(cell(new JFormSliderPane())) + cell(new UILabel("无标签滑块")).with(this::h3), + row(cell(new UISlider()).with(it -> { + it.setValue(50); + it.setMaximum(100); + it.setMinimum(0); + })), + cell(new UILabel("带标签滑块")).with(this::h3), + row(cell(new UISlider()).with(it -> { + it.setPaintLabels(true); + it.setMajorTickSpacing(90); + it.setValue(40); + it.setMaximum(90); + it.setMinimum(-90); + })) ); } }