Google Tag Manager

Showing posts with label TitledBorder. Show all posts
Showing posts with label TitledBorder. Show all posts

2025/05/31

Rounding the Title Background and Border Corners of TitledBorder

Code

/*
- RoundedTitledBorder
    - [TitledBorder(Border, String)](https://docs.oracle.com/javase/8/docs/api/javax/swing/border/TitledBorder.html#TitledBorder-javax.swing.border.Border-java.lang.String-) creates a `TitledBorder` that uses `RoundedBorder` for rounded corners.
    - Additionally, it overrides `TitledBorder#paintBorder(...)` to draw a rounded rectangle with rounded bottom-right corners as the background for the title text.
*/
class RoundedTitledBorder extends TitledBorder {
  private final JLabel label = new JLabel(" ");
  private final int arc;

  protected RoundedTitledBorder(String title, int arc) {
    super(new RoundedBorder(arc), title);
    this.arc = arc;
  }

  @Override public void paintBorder(
      Component c, Graphics g, int x, int y, int width, int height) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Border b = getBorder();
    if (b instanceof RoundedBorder) {
      Dimension d = getLabel(c).getPreferredSize();
      Insets i = b.getBorderInsets(c);
      int a2 = arc * 2;
      int w = d.width + i.left + i.right + a2;
      int h = d.height + i.top + i.bottom;
      g2.setClip(((RoundedBorder) b).getBorderShape(
        x + i.left, y + i.top, w, h));
      g2.setPaint(Color.GRAY);
      Shape titleBg = new RoundRectangle2D.Float(
        x - a2, y - a2, w + a2, h + a2, arc, arc);
      g2.fill(titleBg);
      g2.dispose();
    }
    g2.dispose();
    super.paintBorder(c, g, x, y, width, height);
  }

  private JLabel getLabel(Component c) {
    this.label.setText(getTitle());
    this.label.setFont(getFont(c));
    this.label.setComponentOrientation(c.getComponentOrientation());
    this.label.setEnabled(c.isEnabled());
    return this.label;
  }
}

class RoundedBorder extends EmptyBorder {
  private static final Paint ALPHA_ZERO = new Color(0x0, true);
  private final int arc;

  protected RoundedBorder(int arc) {
    super(2, 2, 2, 2);
    this.arc = arc;
  }

  @Override public void paintBorder(
      Component c, Graphics g, int x, int y, int width, int height) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Shape border = getBorderShape(x, y, width, height);
    g2.setPaint(ALPHA_ZERO);
    Area clear = new Area(new Rectangle2D.Double(x, y, width, height));
    clear.subtract(new Area(border));
    g2.fill(clear);
    g2.setPaint(Color.GRAY);
    g2.setStroke(new BasicStroke(1.5f));
    g2.draw(border);
    g2.dispose();
  }

  protected Shape getBorderShape(int x, int y, int w, int h) {
    return new RoundRectangle2D.Double(x, y, w - 1, h - 1, arc, arc);
  }
}


/*
- TitleLayerUI
- This implementation does not use `TitledBorder` itself, but instead configures a `RoundedBorder` for rounded corners and sets the title to be drawn using a `JLayer`.
    - It overrides `LayerUI#paint(...)` to draw the title text within the component.
    - Within `LayerUI#paint(...)`, it configures the title text to be shown or hidden based on whether the component has focus, and overrides `LayerUI#processFocusEvent` to redraw the entire component and update the border and title text whenever a focus change event occurs.
*/
class TitleLayerUI extends LayerUI<JScrollPane> {
  private final JLabel label;

  protected TitleLayerUI(String title, int arc) {
    super();
    label = new RoundedLabel(title, arc);
    label.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));
    // label.setOpaque(false);
    label.setForeground(Color.WHITE);
    label.setBackground(Color.GRAY);
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      JScrollPane sp = (JScrollPane) ((JLayer<?>) c).getView();
      Rectangle r = SwingUtilities.calculateInnerArea(sp, sp.getBounds());
      if (r != null && !sp.getViewport().getView().hasFocus()) {
        Dimension d = label.getPreferredSize();
        SwingUtilities.paintComponent(
          g, label, sp, r.x - 1, r.y - 1, d.width, d.height);
      }
    }
  }

  @Override public void updateUI(JLayer<? extends JScrollPane> l) {
    super.updateUI(l);
    SwingUtilities.updateComponentTreeUI(label);
  }

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(AWTEvent.FOCUS_EVENT_MASK);
    }
  }

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

  @Override protected void processFocusEvent(
      FocusEvent e, JLayer<? extends JScrollPane> l) {
    super.processFocusEvent(e, l);
    l.getView().repaint();
  }

  private static class RoundedLabel extends JLabel {
    private final int arc;

    public RoundedLabel(String title, int arc) {
      super(title);
      this.arc = arc;
    }

    @Override protected void paintComponent(Graphics g) {
      if (!isOpaque()) {
        Dimension d = getPreferredSize();
        int w = d.width - 1;
        int h = d.height - 1;
        int h2 = h / 2;
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setPaint(getBackground());
        g2.fillRect(0, 0, w, h2);
        g2.fillRoundRect(-arc, 0, w + arc, h, arc, arc);
        g2.dispose();
      }
      super.paintComponent(g);
    }
  }
}

References

2012/09/24

create a gradient titled separator

Code

class TitledSeparator extends JLabel {
  private final String title;
  private final Color target;
  private final int height;
  private final int titlePosition;

  public TitledSeparator(String title, int height, int titlePosition) {
    this(title, null, height, titlePosition);
  }

  public TitledSeparator(
      String title, Color target, int height, int titlePosition) {
    super();
    this.title = title;
    this.target = target;
    this.height = height;
    this.titlePosition = titlePosition;
    updateBorder();
  }

  private void updateBorder() {
    Icon icon = new TitledSeparatorIcon();
    setBorder(BorderFactory.createTitledBorder(
        BorderFactory.createMatteBorder(height, 0, 0, 0, icon), title,
        TitledBorder.DEFAULT_JUSTIFICATION, titlePosition));
  }

  @Override public Dimension getMaximumSize() {
    Dimension d = super.getPreferredSize();
    d.width = Short.MAX_VALUE;
    return d;
  }

  @Override public void updateUI() {
    super.updateUI();
    EventQueue.invokeLater(this::updateBorder);
  }

  private class TitledSeparatorIcon implements Icon {
    private int width = -1;
    private Paint painter1;
    private Paint painter2;

    @Override public void paintIcon(Component c, Graphics g, int x, int y) {
      int w = c.getWidth();
      if (w != width || painter1 == null || painter2 == null) {
        width = w;
        Point2D start = new Point2D.Float();
        Point2D end = new Point2D.Float(width, 0);
        float[] dist = {0f, 1f};
        Color ec = Optional.ofNullable(getBackground())
            .orElse(UIManager.getColor("Panel.background"));
        Color sc = Optional.ofNullable(target).orElse(ec);
        painter1 = new LinearGradientPaint(
            start, end, dist, new Color[] {sc.darker(), ec});
        painter2 = new LinearGradientPaint(
            start, end, dist, new Color[] {sc.brighter(), ec});
      }
      int h = getIconHeight() / 2;
      Graphics2D g2  = (Graphics2D) g.create();
      g2.setPaint(painter1);
      g2.fillRect(x, y, width, getIconHeight());
      g2.setPaint(painter2);
      g2.fillRect(x, y + h, width, getIconHeight() - h);
      g2.dispose();
    }

    @Override public int getIconWidth() {
      return 200; // sample width
    }

    @Override public int getIconHeight() {
      return height;
    }
  }
}

References