Summary

TitledBorderのタイトル背景を右下隅を丸めたラウンド矩形で塗りつぶし、ボーダーも四隅を丸めたラウンド矩形で描画します。

Source Code Examples

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);
  }
}
View in GitHub: Java, Kotlin

Explanation

  • RoundedTitledBorder
  • TitleLayerUI
    • TitledBorderは使用せず、RoundedBorderを設定してボーダーを角丸に設定し、タイトルはJLayerで描画するよう設定
    • LayerUI#paint(...)をオーバーライドしてコンポーネント内部にタイトル文字列を描画
    • コンポーネントのフォーカスの有無でタイトル文字列の表示・非表示を切り替えるようLayerUI#paint(...)内で設定し、LayerUI#processFocusEventもオーバーライドしてフォーカス移動イベントが発生したらコンポーネント全体を再描画してボーダーとタイトル文字列を更新
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);
    }
  }
}

Reference

Comment