概要

JListで作成したカレンダーでセルを複数選択した領域のすべての角を丸めて描画します。

サンプルコード

/**
  Rounding the corners of a Rectilinear Polygon.
*/
public static Path2D convertRoundedPath(List<Point2D> list, double arc) {
  double kappa = 4d * (Math.sqrt(2d) - 1d) / 3d; // = 0.55228...;
  double akv = arc - arc * kappa;
  int sz = list.size();
  Point2D pt0 = list.get(0);
  Path2D path = new Path2D.Double();
  path.moveTo(pt0.getX() + arc, pt0.getY());
  for (int i = 0; i < sz; i++) {
    Point2D prv = i == 0 ? list.get(sz - 1) : list.get(i - 1);
    Point2D cur = list.get(i);
    Point2D nxt = i < sz - 1 ? list.get(i + 1) : list.get(0);
    double dx0 = Math.signum(cur.getX() - prv.getX());
    double dy0 = Math.signum(cur.getY() - prv.getY());
    double dx1 = Math.signum(nxt.getX() - cur.getX());
    double dy1 = Math.signum(nxt.getY() - cur.getY());
    path.curveTo(
        cur.getX() - dx0 * akv, cur.getY() - dy0 * akv,
        cur.getX() + dx1 * akv, cur.getY() + dy1 * akv,
        cur.getX() + dx1 * arc, cur.getY() + dy1 * arc
    );
    path.lineTo(nxt.getX() - dx1 * arc, nxt.getY() - dy1 * arc);
  }
  path.closePath();
  return path;
}
View in GitHub: Java, Kotlin

解説

  • JListで月のカーソルキー移動や、週を跨いた日付を範囲選択が可能なカレンダーを作成するJListを使用
  • セルの選択状態、フォーカス状態に関係なく背景色を描画しないセルレンダラーを設定し、代わりにJList#paintComponent(...)をオーバーライドしてセルの選択状態を描画
  • ListSelectionModel.SINGLE_INTERVAL_SELECTION1つの連続区間のみを選択可能に設定
    • JList#getCellBounds(...)で選択セル領域を取得し、Area#add(new Area(Rectangle))でひとつのAreaにまとめる
    • まとめられた選択領域のAreaは直角多角形(任意の内角が90°または270°で辺が直角に交わる)になるので、これらの角(Point2D)をすべてPath2D.curveTo(...)で丸めたPath2Dに変換
public final JList<LocalDate> monthList = new JList<LocalDate>() {
  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(HORIZONTAL_WRAP);
    setVisibleRowCount(CalendarViewListModel.ROW_COUNT);
    setFixedCellWidth(size.width);
    setFixedCellHeight(size.height);
    setCellRenderer(new CalendarListRenderer());
    setOpaque(false);
    getSelectionModel().setSelectionMode(
        ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    addListSelectionListener(e -> repaint());
  }

  @Override protected void paintComponent(Graphics g) {
    int[] indices = getSelectedIndices();
    if (indices != null && indices.length > 0) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(
          RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(SELECTED_COLOR);
      Area area = new Area();
      Arrays.stream(indices)
          .mapToObj(i -> getCellBounds(i, i))
          .forEach(r -> area.add(new Area(r)));
      List<Point2D> lst = GeomUtils.convertAreaToPoint2DList(area);
      g2.fill(GeomUtils.convertRoundedPath(lst, 4d));
      g2.dispose();
    }
    super.paintComponent(g);
  }
};

参考リンク

コメント