Summary

JTextComponentの選択ハイライトや検索ハイライトなどを角丸で描画するHighlightPainterを作成します。

Source Code Examples

class RoundedSelectionHighlightPainter extends DefaultHighlightPainter {
  protected RoundedSelectionHighlightPainter() {
    super(null);
  }

  @Override public void paint(
        Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
    Rectangle alloc = bounds.getBounds();
    try {
      // --- determine locations ---
      TextUI mapper = c.getUI();
      Rectangle p0 = mapper.modelToView(c, offs0);
      Rectangle p1 = mapper.modelToView(c, offs1);
      // --- render ---
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      Color color = getColor();
      if (color == null) {
        g2.setColor(c.getSelectionColor().brighter());
      } else {
        g2.setColor(color);
      }
      if (p0.y == p1.y) { // same line, render a rectangle
        Rectangle r = p0.union(p1);
        g2.fillRoundRect(r.x, r.y, r.width, r.height, 5, 5);
      } else { // different lines
        int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
        g2.fillRoundRect(p0.x, p0.y, p0ToMarginWidth, p0.height, 5, 5);
        g2.fillRect(p0.x + 5, p0.y, p0ToMarginWidth - 5, p0.height);
        int maxY = p0.y + p0.height;
        if (maxY != p1.y) {
          g2.fillRect(alloc.x, maxY, alloc.width, p1.y - maxY);
        }
        g2.fillRect(alloc.x, p1.y, p1.x - alloc.x - 5, p1.height);
        g2.fillRoundRect(alloc.x, p1.y, p1.x - alloc.x, p1.height, 5, 5);
      }
      g2.dispose();
    } catch (BadLocationException ex) {
      // can't render
      Logger.getGlobal().severe(ex::getMessage);
    }
  }

  @Override public Shape paintLayer(
        Graphics g, int offs0, int offs1, Shape bounds,
        JTextComponent c, View view) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    Color color = getColor();
    if (color == null) {
      g2.setColor(c.getSelectionColor());
    } else {
      g2.setColor(color);
    }
    Rectangle r = null;
    if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) {
      // Contained in view, can just use bounds.
      if (bounds instanceof Rectangle) {
        r = (Rectangle) bounds;
      } else {
        r = bounds.getBounds();
      }
    } else {
      // Should only render part of View.
      try {
        // --- determine locations ---
        Shape shape = view.modelToView(
            offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds);
        r = shape instanceof Rectangle ? (Rectangle) shape : shape.getBounds();
      } catch (BadLocationException ex) {
        // can't render
        Logger.getGlobal().severe(ex::getMessage);
      }
    }
    if (r != null) {
      // If we are asked to highlight, we should draw something even
      // if the model-to-view projection is of zero width (6340106).
      r.width = Math.max(r.width, 1);
      g2.fillRoundRect(r.x, r.y, r.width - 1, r.height - 1, 5, 5);
      g2.setColor(c.getSelectionColor().darker());
      g2.drawRoundRect(r.x, r.y, r.width - 1, r.height - 1, 5, 5);
    }
    g2.dispose();
    return r;
  }
}
View in GitHub: Java, Kotlin

Explanation

  • 選択ハイライト: RoundedSelectionHighlightPainter
    • JTextComponentの選択ハイライトを変更と同様に、Caret#getSelectionPainter()メソッドをオーバーライドして選択ハイライトを描画するHighlightPainterを変更
    • DefaultHighlightPainter#paintLayer(...)をオーバーライドしてGraphics#fillRect(...)Graphics#fillRoundRect(...)に置換して各選択ハイライト領域の角を丸めて描画
    • DefaultHighlighter#setDrawsLayeredHighlights(false)が設定されている場合、DefaultHighlightPainter#paint(...)でハイライトが描画されるので、こちらをオーバーライドして選択ハイライトを角丸に変更
      • 選択が同一行内に収まる場合はGraphics#fillRect(...)Graphics#fillRoundRect(...)に置換して角丸矩形を描画
      • 選択が複数行に渡る場合は先頭と末尾の選択行領域を描画するGraphics#fillRect(...)Graphics#fillRoundRect(...)に置換して角丸矩形を描画
    • 選択行領域にJTreeのノード選択で生成された直角多角形の角を丸めると同様の平坦化を適用すれば、Visual Studio Codeと同様の角丸選択を描画することも可能
  • 検索ハイライト: RoundedHighlightPainter
    • JTextAreaの検索ハイライトに縁を描画すると同様に、Highlighter#addHighlight(...)メソッドで検索にヒットした文字列のハイライトを描画するHighlightPainterを指定
    • 選択ハイライトと同様にDefaultHighlighter#setDrawsLayeredHighlights(...)の設定を考慮してDefaultHighlightPainter#paintLayer(...)DefaultHighlightPainter#paint(...)の両方をオーバーライドしてハイライトを角丸で描画
class RoundedHighlightPainter extends DefaultHighlightPainter {
  protected RoundedHighlightPainter() {
    super(new Color(0x0, true));
  }

  @Override public void paint(
      Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
    try {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      TextUI mapper = c.getUI();
      Rectangle p0 = mapper.modelToView(c, offs0);
      Rectangle p1 = mapper.modelToView(c, offs1);
      if (p0.y == p1.y) { // same line, render a rectangle
        Shape s = makeRoundRectangle(p0.union(p1));
        g2.setColor(Color.PINK);
        g2.fill(s);
        g2.setPaint(Color.RED);
        g2.draw(s);
      } else { // different lines
        Rectangle alloc = bounds.getBounds();
        int p0ToMarginWidth = alloc.x + alloc.width - p0.x;
        g2.setColor(Color.PINK);
        g2.fillRoundRect(p0.x, p0.y, p0ToMarginWidth, p0.height, 5, 5);
        g2.fillRect(p0.x + 5, p0.y, p0ToMarginWidth - 5, p0.height);
        int maxY = p0.y + p0.height;
        if (maxY != p1.y) {
          g2.fillRect(alloc.x, maxY, alloc.width, p1.y - maxY);
        }
        g2.fillRect(alloc.x, p1.y, p1.x - alloc.x - 5, p1.height);
        g2.fillRoundRect(alloc.x, p1.y, p1.x - alloc.x, p1.height, 5, 5);
      }
      g2.dispose();
    } catch (BadLocationException ex) {
      // can't render
      Logger.getGlobal().severe(ex::getMessage);
    }
  }

  @Override public Shape paintLayer(
      Graphics g, int offs0, int offs1, Shape bounds,
      JTextComponent c, View view) {
    Shape s = super.paintLayer(g, offs0, offs1, bounds, c, view);
    if (s instanceof Rectangle) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(Color.ORANGE);
      Shape r = makeRoundRectangle(s.getBounds());
      g2.fill(r);
      g2.setPaint(Color.RED);
      g2.draw(r);
      g2.dispose();
    }
    return s;
  }

  private static RoundRectangle2D makeRoundRectangle(Rectangle r) {
    return new RoundRectangle2D.Float(r.x, r.y, r.width - 1, r.height - 1, 5f, 5f);
  }
}

Reference

Comment