Summary

JTextAreaJEditorPaneなどのJTextComponentが使用するCaretに文字列選択ハイライトのすべての角を丸め、かつ半透明で描画する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) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
      RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    int rgba = c.getSelectionColor().getRGB() & 0xFF_FF_FF | (64 << 24);
    g2.setColor(new Color(rgba, true));
    try {
      Area area = getLinesArea(c, offs0, offs1);
      for (Area a : GeomUtils.singularization(area)) {
        List<Point2D> lst = GeomUtils.convertAreaToPoint2DList(a);
        GeomUtils.flatteningStepsOnRightSide(lst, 3d * 2d);
        g2.fill(GeomUtils.convertRoundedPath(lst, 3d));
      }
    } catch (BadLocationException ex) {
      // can't render
      Logger.getGlobal().severe(ex::getMessage);
    }
    g2.dispose();
  }

  private static Area getLinesArea(JTextComponent c, int offs0, int offs1)
      throws BadLocationException {
    TextUI mapper = c.getUI();
    Area area = new Area();
    int cur = offs0;
    do {
      int startOffset = Utilities.getRowStart(c, cur);
      int endOffset = Utilities.getRowEnd(c, cur);
      Rectangle p0 = mapper.modelToView(c, Math.max(startOffset, offs0));
      Rectangle p1 = mapper.modelToView(c, Math.min(endOffset, offs1));
      if (p0.x == p1.x) {
        p0.width += 6;
        addRectToArea(area, p0);
      } else {
        addRectToArea(area, p0.union(p1));
      }
      cur = endOffset + 1;
    } while (cur < offs1);
    return area;
  }

  private static void addRectToArea(Area area, Rectangle rect) {
    area.add(new Area(rect));
  }
}
View in GitHub: Java, Kotlin

Explanation

  • すべての角を丸める
    • JTextComponentの選択ハイライトを角丸で描画すると同様に、DefaultHighlighter#setDrawsLayeredHighlights(false)を設定
      • DefaultHighlightPainter#paint(...)をオーバーライドして選択ハイライト領域を取得して角丸に変更するHighlightPainterを作成
      • このHighlightPainterを文字列選択で使用するCaretを作成してJTextComponentに設定
    • 角の丸め方法はJTextComponentの選択ハイライトを角丸で描画するのような行単位ではなく、一まとまりの領域単位で適用するのでJTreeのノード選択で生成された直角多角形の角を丸めると同様に、重複点やラウンド直径より小さい段差などで尖点が発生しないよう選択領域図形を平坦化してから実施
    • 空行や行末の改行のみが選択された場合はその行の選択領域幅を3(ラウンド半径) * 26pxに拡張してから角丸に変換
    • 複数行に渡って文字列選択されている場合、DefaultHighlighter#setDrawsLayeredHighlights(false)を設定した場合のデフォルトであるJTextComponentの行幅全体をハイライトするのではなく、文字列部分のみ選択ハイライトするよう変更するため、Utilities.getRowStart(...)Utilities.getRowEnd(...)で取得した行頭、行末オフセットの位置を選択ハイライト領域としている
    • デフォルトのDefaultCaretでの選択ハイライト再描画では領域右側隅の丸め部分が更新できないので、複数行選択の場合はDefaultCaret#damage(...)をオーバーライドして選択開始行、終了行の行幅全体を含む領域を再描画している
      class RoundedSelectionCaret extends DefaultCaret {
        @Override protected HighlightPainter getSelectionPainter() {
          return new RoundedSelectionHighlightPainter();
        }
      
        @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel")
        @Override protected synchronized void damage(Rectangle r) {
          JTextComponent c = getComponent();
          int startOffset = c.getSelectionStart();
          int endOffset = c.getSelectionEnd();
          if (startOffset == endOffset) {
            super.damage(r);
          } else {
            TextUI mapper = c.getUI();
            try {
              Rectangle p0 = mapper.modelToView(c, startOffset);
              Rectangle p1 = mapper.modelToView(c, endOffset);
              int h = (int) (p1.getMaxY() - p0.getMinY());
              c.repaint(new Rectangle(0, p0.y, c.getWidth(), h));
            } catch (BadLocationException ex) {
              UIManager.getLookAndFeel().provideErrorFeedback(c);
            }
          }
        }
      }
      
  • 選択ハイライトを半透明化
    • JEditorPaneで選択した文字列の色反転を無効化ではJTextComponent#setSelectedTextColor(null)JTextComponent#setSelectionColor(new Color(0x64_88_AA_AA, true))を設定して選択文字色の変更は無し、選択ハイライトを半透明に設定しているが、このサンプルではDefaultCaret#getSelectionPainter()をオーバーライドしているのでDefaultHighlightPainter#paint(...)内で角丸化と同時に半透明化を実施している

Reference

Comment