Title: Draw a hexagonal grid that responds to mouse clicks in Python
My previous post Draw a hexagonal grid in Python shows how you can draw a hexagonal grid and detect the grid cell under the mouse. This post lets you click on a cell to highlight it. There are quite a few changes between this program and the previous one, but most of them are minor. Here I'll described the basic ideas and show you some key. You can download the example to see more details.
The Hex Class
If you want to use this program as a basis for a more full-featured application, you'll probably want to store information in the grid's cells. For example, you may want to store information about a hex's color, terrain type, and any objects or units that might be in the hex.
To store that kind of information, this program uses a Hex class. The following code shows the class's beginning and constructor.
class Hex:
# Define Hex drawing colors.
normal_fill = 'cornsilk'
normal_outline = 'blue'
normal_text_fill = 'black'
highlight_fill = 'pink'
highlight_outline = 'blue'
highlight_text_fill = 'red'
font = ('Arial', 10)
def __init__(self, row, col, canvas, points):
self.row = row
self.col = col
self.canvas = canvas
self.points = points
self._is_highlighted = False
# Create the hexagon.
self.polygon_id = canvas.create_polygon(points,
fill=Hex.normal_fill, outline=Hex.normal_outline)
# Get the hex's center.
self.cx = (points[0][0] + points[3][0]) / 2
self.cy = (points[1][1] + points[4][1]) / 2
# Make a label for this hex.
text = f'({row}, {col})'
self.label_id = canvas.create_text(self.cx, self.cy, text=text,
font=Hex.font, anchor=tk.CENTER)
The class first defines some class variables that hold the colors and font that a hex should have when normal or highlighted.
The constructor saves the hex's row and column, the canvas widget on which it should draw, and the points list that defines its hexagon. It then sets the object's _is_highlighted value to False to indicate that the hex is not initially highlighted.
The constructor then creates its polygon on the canvas widget and saves the returned ID in its polygon_id value.
Next, it calculates the hexagon's center coordinates and saves them in cx and cy. This program won't need those values, but you might in a more complete application.
The constructor then creates a label to display the hex's row and column, and saves the label's ID in the hex's label_id value.
The following code shows how the Hex class provides its is_highlighted property.
@property
def is_highlighted(self):
return self._is_highlighted
@is_highlighted.setter
def is_highlighted(self, value):
# If the value isn't changing, do nothing.
if self._is_highlighted == value:
return
# Save the value.
self._is_highlighted = value
# Update the Hex's appearance.
if self._is_highlighted:
self.canvas.itemconfig(self.polygon_id, fill=Hex.highlight_fill)
self.canvas.itemconfig(self.label_id, fill=Hex.highlight_text_fill)
self.canvas.itemconfig(self.label_id, text=
f'<{self.row}, {self.col}>')
else:
self.canvas.itemconfig(self.polygon_id, fill=Hex.normal_fill)
self.canvas.itemconfig(self.label_id, fill=Hex.normal_text_fill)
self.canvas.itemconfig(self.label_id, text=
f'({self.row}, {self.col})')
The is_highlighted function simply returns the object's _is_highlighted value.
The property setter first compares the new and old values and returns if they are the same. If the values are different, the code saves the new value and then updates the hex's colors and label text appropriately.
Creating Hexes
The rest of the changes are less interesting.
In the previous example, the draw_hex_grid method created the polygons to draw the grid. The new program renames that method to make_hex_grid because it's not just drawing any more. The following code shows this function.
def make_hex_grid(canvas, xmin, ymin, xmax, ymax, hex_height):
'''
Create a hexagonal grid for the indicated area.
Return an array Hex objects.
'''
# (You might be able to draw the hexagons without
# drawing any duplicate edges, but this is a lot easier.)
# Make a list of Hex objects.
hexes = []
# Loop until a hexagon won't fit.
row = 0
while True:
# Get the points for the row's first hexagon.
points = hex_to_points(xmin, ymin, hex_height, row, 0)
# If it doesn't fit, we're done.
if points[4][1] >= ymax:
break
# Add a row to the hexes array.
row_hexes = []
hexes.append(row_hexes)
# Draw the row.
col = 0
while True:
# Get the points for the row's next hexagon.
points = hex_to_points(xmin, ymin, hex_height, row, col)
# If it doesn't fit horizontally, we're done with this row.
if points[3][0] >= xmax:
break
# If it fits vertically, draw it.
if points[4][1] < ymax:
# Make the Hex object.
row_hexes.append(Hex(row, col, canvas, points))
# Move to the next column.
col += 1
# Move to the next row.
row += 1
# Return the hexes array.
return hexes
The function first makes a hexes list. It then uses loops much as before to create the hexes. This time it creates a Hex object for each grid cell and places the new objects in the hexes list.
After it has created all of the hexes, the function returns the list and the main HexGridClickApp saves it.
Mouse Clicks
This program uses the following statement to bind mouse down events on its canvas widget.
p class="code">self.canvas.bind('', self.mouse_down)
When you press the mouse down, the following code executes.
def mouse_down(self, event):
'''Highlight the clicked hex.'''
# Unhilight the previously highlighted hex.
if self.highlighted_hex is not None:
self.highlighted_hex.is_highlighted = False
self.highlighted_hex = None
# Find the clicked hex.
row, col = point_to_hex(event.x, event.y, self.hex_height)
if row < 0 or row >= self.num_rows or \
col < 0 or col >= self.num_cols:
return
# Get the clicked hex and highlight it.
self.highlighted_hex = self.hexes[row][col]
self.highlighted_hex.is_highlighted = True
This code first unhighlights the previously highlighted Hex if there is one. It then uses the previous post's point_to_hex function to find the row and column where you clicked. If the row and column is not on the grid, the method returns.
If you clicked on a hex, the code saves the hex you clicked and sets its is_highlighted property to True. The Hex object takes care of updating the hex's appearance.
Conclusion
Download the example to experiment with it and to see additional details. When I have time, I may expand the example again to draw textures on the hexes. (That's a pretty popular request.)
|