Summary

JButtonの形状を正六角形に設定し、これらを蜂の巣状(ハニカム)に配置するLayoutManagerを作成します。

Source Code Examples

// Honeycomb hexagon button layout manager
// Row pattern
// Even rows (0, 2, ...): 2n-1 buttons, offset right by half cell width
// Odd  rows (1, 3, ...): 2n buttons, flush left
class HoneycombLayout implements LayoutManager {
  private static final double RATIO = Math.sqrt(3d) / 2d;
  private final int rows;
  private final int evenCols; // Button count for even rows (2n-1)
  private final int oddCols; // Button count for odd rows (2n)
  // Visual gap between adjacent hexagon edges, in pixels.
  // gap = 0 : edges touch perfectly
  // gap > 0 : uniform spacing
  private final int gap;

  protected HoneycombLayout(int rows, int evenCols, int oddCols, int gap) {
    this.rows = rows;
    this.evenCols = evenCols;
    this.oddCols = oddCols;
    this.gap = gap;
  }

  @Override public void layoutContainer(Container parent) {
    Insets insets = parent.getInsets();
    int maxWidth = parent.getWidth() - insets.left - insets.right;
    int maxHeight = parent.getHeight() - insets.top - insets.bottom;

    Dimension buttonSize = getButtonSize(maxWidth, maxHeight);
    int slotW = buttonSize.width + gap; // Horizontal pitch
    int slotH = buttonSize.height + gap; // Vertical base

    // Center the grid inside the panel
    int gridW = oddCols * slotW;
    int gridH = (int) (slotH * (.25 + .75 * rows));
    int marginX = insets.left + (maxWidth - gridW) / 2;
    int marginY = insets.top + (maxHeight - gridH) / 2;

    int compIdx = 0;
    for (int r = 0; r < rows; r++) {
      boolean isEvenRow = r % 2 == 0;
      int colsInRow = isEvenRow ? evenCols : oddCols;

      // Y position: step by 75% of slot height
      int y = marginY + (int) (r * slotH * .75 + gap / 2d);
      // Even rows shift right by half a slot
      int rowOffsetX = isEvenRow ? slotW / 2 : 0;

      for (int col = 0; col < colsInRow; col++) {
        if (compIdx >= parent.getComponentCount()) {
          break;
        }
        Component c = parent.getComponent(compIdx);
        int x = marginX + rowOffsetX + col * slotW + gap / 2;
        c.setBounds(x, y, buttonSize.width, buttonSize.height);
        compIdx += 1;
      }
    }
  }

  private Dimension getButtonSize(int maxWidth, int maxHeight) {
    // Derive cellW,cellH from horizontal constraint
    double cwFromWidth = (double) maxWidth / oddCols - gap;
    double chFromWidth = cwFromWidth / RATIO;

    // Derive cellW,cellH from vertical constraint
    double chFromHeight = maxHeight / (.25 + .75 * rows) - gap;
    double cwFromHeight = chFromHeight * RATIO;

    // Adopt the smaller to satisfy both constraints
    double cellW;
    double cellH;
    if (cwFromWidth <= cwFromHeight) {
      cellW = cwFromWidth;
      cellH = chFromWidth;
    } else {
      cellW = cwFromHeight;
      cellH = chFromHeight;
    }
    int buttonW = Math.max(1, (int) cellW);
    int buttonH = Math.max(1, (int) cellH);
    return new Dimension(buttonW, buttonH);
  }

  @Override public Dimension preferredLayoutSize(Container parent) {
    return new Dimension(500, 400);
  }

  @Override public Dimension minimumLayoutSize(Container parent) {
    return new Dimension(200, 150);
  }

  @Override public void addLayoutComponent(String name, Component comp) {
    // not needed
  }

  @Override public void removeLayoutComponent(Component comp) {
    // not needed
  }
}
View in GitHub: Java, Kotlin

Description

  • 正六角形ボタンを蜂の巣状(ハニカム)に配置するHoneycombLayout(LayoutManagerを実装)を作成
    • 偶数行(0, 2, ...)には2n-1個の正六角形ボタンを半セル分右にオフセットしながら配置
    • 奇数行(1, 3, ...)には2n個の正六角形ボタンを左揃えで配置
    • 尖り上正六角形の外接円半径をRとすると、そのバウンディングボックス幅はW = R * sqrt(3)、バウンディングボックス高はH = R * 2
    • 列のステップ幅をslotW = W + gap、行ステップの基準をslotH = H + gapとすると、行ステップはstep = slotH * 0.75となり行は高さの3/4(75%)でかみ合う
  • 尖り上正六角形JButton
    • コンポーネント境界にぴったり収まる尖り上正六角形Polygonを作成
    • -PI/2P(12時方向)から60°(PI/3)刻みで頂点を打つ
    • JButtonの形を変更

Reference

Comment