Google Tag Manager

Showing posts with label NimbusLookAndFeel. Show all posts
Showing posts with label NimbusLookAndFeel. Show all posts

2022/03/30

Paint the major tick marks inside the JSlider track and the current value inside the knob

Code

JSlider slider = new JSlider();
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(10);
slider.addMouseMotionListener(new MouseAdapter() {
  @Override public void mouseDragged(MouseEvent e) {
    super.mouseDragged(e);
    e.getComponent().repaint();
  }
});

UIDefaults d = new UIDefaults();
d.put("Slider.thumbWidth", 24);
d.put("Slider.thumbHeight", 24);
Painter<JSlider> thumbPainter = (g, c, w, h) -> {
  g.setPaint(new Color(0x21_98_F6));
  g.fillOval(0, 0, w, h);
  NumberIcon icon = new NumberIcon(c.getValue());
  int xx = (w - icon.getIconWidth()) / 2;
  int yy = (h - icon.getIconHeight()) / 2;
  icon.paintIcon(c, g, xx, yy);
};
d.put("Slider:SliderThumb[Disabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Enabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+Pressed].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Pressed].backgroundPainter", thumbPainter);

d.put("Slider:SliderTrack[Enabled].backgroundPainter", new Painter<JSlider>() {
  @Override public void paint(Graphics2D g, JSlider c, int w, int h) {
    int arc = 10;
    int thumbSize = 24;
    int trackHeight = 8;
    int tickSize = 4;
    int trackWidth = w - thumbSize;
    int fillTop = (thumbSize - trackHeight) / 2;
    int fillLeft = thumbSize / 2;

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.setColor(new Color(0xC6_E4_FC));
    g.fillRoundRect(fillLeft, fillTop + 2, trackWidth, trackHeight - 4, arc, arc);

    // Paint track
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.setColor(new Color(0xC6_E4_FC));
    g.fillRoundRect(fillLeft, fillTop + 2, trackWidth, trackHeight - 4, arc, arc);

    int fillBottom = fillTop + trackHeight;
    Rectangle r = new Rectangle(fillLeft, fillTop, trackWidth, fillBottom - fillTop);

    // Paint the major tick marks on the track
    g.setColor(new Color(0x31_A8_F8));
    int value = c.getMinimum();
    while (value <= c.getMaximum()) {
      int xpt = getXPositionForValue(c, r, value);
      g.fillOval(xpt, (int) r.getCenterY() - tickSize / 2, tickSize, tickSize);
      // Overflow checking
      if (Integer.MAX_VALUE - c.getMajorTickSpacing() < value) {
        break;
      }
      value += c.getMajorTickSpacing();
    }

    // JSlider.isFilled
    int fillRight = getXPositionForValue(c, r, c.getValue());
    g.setColor(new Color(0x21_98_F6));
    g.fillRoundRect(fillLeft, fillTop, fillRight - fillLeft, fillBottom - fillTop, arc, arc);
  }

  // @see javax/swing/plaf/basic/BasicSliderUI#xPositionForValue(int value)
  private int getXPositionForValue(JSlider slider, Rectangle trackRect, float value) {
    float min = slider.getMinimum();
    float max = slider.getMaximum();
    float pixelsPerValue = trackRect.width / (max - min);
    int trackLeft = trackRect.x;
    int trackRight = trackRect.x + trackRect.width - 1;
    int pos = trackLeft + Math.round(pixelsPerValue * (value - min));
    return Math.max(trackLeft, Math.min(trackRight, pos));
  }
});
slider.putClientProperty("Nimbus.Overrides", d);

References

2020/11/30

Create switchable buttons in JSlider

Use JSlider to create switch buttons that can be toggled on and off by clicking the mouse or dragging the knob

Code

UIDefaults d = new UIDefaults();
d.put("Slider.thumbHeight", 40);
d.put("Slider.thumbWidth", 40);
d.put("Slider:SliderTrack[Enabled].backgroundPainter", (Painter) (g, c, w, h) -> {
  int arc = 40;
  int fillLeft = 2;
  int fillTop = 2;
  int trackWidth = w - fillLeft - fillLeft;
  int trackHeight = h - fillTop - fillTop;
  int baseline = trackHeight - fillTop - fillTop; // c.getBaseline(w, h);
  String off = "Off";

  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  g.setColor(Color.GRAY);
  g.fillRoundRect(fillLeft, fillTop, trackWidth, trackHeight, arc, arc);
  g.setPaint(Color.WHITE);
  g.drawString(off, w - g.getFontMetrics().stringWidth(off) - fillLeft * 5, baseline);

  int fillRight = getXPositionForValue(c, new Rectangle(fillLeft, fillTop, trackWidth, trackHeight));
  g.setColor(Color.ORANGE);
  g.fillRoundRect(fillLeft + 1, fillTop, fillRight - fillLeft, trackHeight, arc, arc);

  g.setPaint(Color.WHITE);
  if (fillRight - fillLeft > 0) {
    g.drawString("On", fillLeft * 5, baseline);
  }
  g.setStroke(new BasicStroke(2.5f));
  g.drawRoundRect(fillLeft, fillTop, trackWidth, trackHeight, arc, arc);
});

Painter thumbPainter = (g, c, w, h) -> {
  int fillLeft = 8;
  int fillTop = 8;
  int trackWidth = w - fillLeft - fillLeft;
  int trackHeight = h - fillTop - fillTop;
  g.setPaint(Color.WHITE);
  g.fillOval(fillLeft, fillTop, trackWidth, trackHeight);
};
d.put("Slider:SliderThumb[Disabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Enabled].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused+Pressed].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Focused].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[MouseOver].backgroundPainter", thumbPainter);
d.put("Slider:SliderThumb[Pressed].backgroundPainter", thumbPainter);

JSlider slider = new JSlider(0, 1, 0) {
  @Override public Dimension getPreferredSize() {
    return new Dimension(100, 40);
  }
};
slider.setFont(slider.getFont().deriveFont(Font.BOLD, 32f));
slider.putClientProperty("Nimbus.Overrides", d);

Explanation

  • Default
    • Set JSlider to Min 0, Max 1
    • Change component size by overriding getPreferredSize()
  • Thumb size
    • Resize knobs by setting Slider.thumbWidth, Slider.thumbHeight
  • SliderTrack
    • Create a Painter to draw the track background and change the background color, border, and On/Off text
    • Pressing a track turns On/Off
    • Change the drawing of the knob by setting Painter
    • ​Add MouseMotionListener to redraw the whole thing while dragging, since some afterimages may appear when you drag the knob
    • ​Pressing or clicking the knob does not cause the `On/Off 'toggle
  • JSlider + JLayer
    • ​The drawing of the track and knob is identical to the above SliderTrack
    • ​Use JLayer to set JSlider to switch On/Off regardless of track or knob
    • ​Redraw while dragging the knob is also done in LayerUI#processMouseMotionEvent(...)
class ToggleSwitchLayerUI extends LayerUI {
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer l) {
    if (e.getID() == MouseEvent.MOUSE_PRESSED && SwingUtilities.isLeftMouseButton(e)) {
      e.getComponent().dispatchEvent(new MouseEvent(
          e.getComponent(),
          e.getID(), e.getWhen(),
          InputEvent.BUTTON3_DOWN_MASK, // e.getModifiers(),
          e.getX(), e.getY(),
          e.getXOnScreen(), e.getYOnScreen(),
          e.getClickCount(),
          e.isPopupTrigger(),
          MouseEvent.BUTTON3)); // e.getButton());
      e.consume();
    } else if (e.getID() == MouseEvent.MOUSE_CLICKED && SwingUtilities.isLeftMouseButton(e)) {
      JSlider slider = l.getView();
      int v = slider.getValue();
      if (slider.getMinimum() == v) {
        slider.setValue(slider.getMaximum());
      } else if (slider.getMaximum() == v) {
        slider.setValue(slider.getMinimum());
      }
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
    l.getView().repaint();
  }
}

References