Refactor HUD and renderer to support dynamic screen resizing and update input handling for simulation view
This commit is contained in:
parent
2b1b348cb1
commit
d5d44c5d14
@ -6,7 +6,7 @@ from config.constants import *
|
|||||||
|
|
||||||
|
|
||||||
class InputHandler:
|
class InputHandler:
|
||||||
def __init__(self, camera, world):
|
def __init__(self, camera, world, sim_view_rect):
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
self.world = world
|
self.world = world
|
||||||
|
|
||||||
@ -27,6 +27,13 @@ class InputHandler:
|
|||||||
self.default_tps = DEFAULT_TPS
|
self.default_tps = DEFAULT_TPS
|
||||||
self.sprint_mode = False
|
self.sprint_mode = False
|
||||||
|
|
||||||
|
# sim-view rect for mouse position calculations
|
||||||
|
self.sim_view_rect = sim_view_rect
|
||||||
|
|
||||||
|
def update_sim_view_rect(self, sim_view_rect):
|
||||||
|
"""Update the sim_view rectangle."""
|
||||||
|
self.sim_view_rect = sim_view_rect
|
||||||
|
|
||||||
def handle_events(self, events, ui_manager):
|
def handle_events(self, events, ui_manager):
|
||||||
"""Process all pygame events and return game state."""
|
"""Process all pygame events and return game state."""
|
||||||
running = True
|
running = True
|
||||||
@ -115,20 +122,27 @@ class InputHandler:
|
|||||||
"""Process object selection logic."""
|
"""Process object selection logic."""
|
||||||
self.selecting = False
|
self.selecting = False
|
||||||
|
|
||||||
# Convert screen to world coordinates
|
# Map screen to sim_view coordinates
|
||||||
x1, y1 = self.camera.get_real_coordinates(*self.select_start)
|
sx1 = self.select_start[0] - self.sim_view_rect.left
|
||||||
x2, y2 = self.camera.get_real_coordinates(*self.select_end)
|
sy1 = self.select_start[1] - self.sim_view_rect.top
|
||||||
|
sx2 = self.select_end[0] - self.sim_view_rect.left
|
||||||
|
sy2 = self.select_end[1] - self.sim_view_rect.top
|
||||||
|
|
||||||
|
# Convert sim_view to world coordinates
|
||||||
|
x1, y1 = self.camera.get_real_coordinates(sx1, sy1)
|
||||||
|
x2, y2 = self.camera.get_real_coordinates(sx2, sy2)
|
||||||
|
|
||||||
# Check if selection is a click or drag
|
# Check if selection is a click or drag
|
||||||
if (abs(self.select_start[0] - self.select_end[0]) < SELECTION_THRESHOLD and
|
if (abs(sx1 - sx2) < SELECTION_THRESHOLD and
|
||||||
abs(self.select_start[1] - self.select_end[1]) < SELECTION_THRESHOLD):
|
abs(sy1 - sy2) < SELECTION_THRESHOLD):
|
||||||
self._handle_click_selection()
|
self._handle_click_selection()
|
||||||
else:
|
else:
|
||||||
self._handle_drag_selection(x1, y1, x2, y2)
|
self._handle_drag_selection(x1, y1, x2, y2)
|
||||||
|
|
||||||
def _handle_click_selection(self):
|
def _handle_click_selection(self):
|
||||||
"""Handle single click selection."""
|
"""Handle single click selection."""
|
||||||
mouse_world_x, mouse_world_y = self.camera.get_real_coordinates(*self.select_start)
|
sx, sy = self.select_start[0] - self.sim_view_rect.left, self.select_start[1] - self.sim_view_rect.top
|
||||||
|
mouse_world_x, mouse_world_y = self.camera.get_real_coordinates(sx, sy)
|
||||||
obj = self.world.query_closest_object(mouse_world_x, mouse_world_y)
|
obj = self.world.query_closest_object(mouse_world_x, mouse_world_y)
|
||||||
self.selected_objects = []
|
self.selected_objects = []
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,17 @@ from world.base.brain import CellBrain
|
|||||||
|
|
||||||
|
|
||||||
class Renderer:
|
class Renderer:
|
||||||
def __init__(self, screen):
|
def __init__(self, render_area):
|
||||||
self.screen = screen
|
self.render_area = render_area
|
||||||
|
self.render_height = render_area.get_height()
|
||||||
|
self.render_width = render_area.get_width()
|
||||||
|
|
||||||
def clear_screen(self):
|
def clear_screen(self, main_screen=None):
|
||||||
"""Clear the screen with a black background."""
|
"""Clear the screen with a black background."""
|
||||||
self.screen.fill(BLACK)
|
if main_screen:
|
||||||
|
main_screen.fill(BLACK)
|
||||||
|
|
||||||
|
self.render_area.fill(BLACK)
|
||||||
|
|
||||||
def draw_grid(self, camera, showing_grid=True):
|
def draw_grid(self, camera, showing_grid=True):
|
||||||
"""Draw the reference grid."""
|
"""Draw the reference grid."""
|
||||||
@ -28,8 +33,8 @@ class Renderer:
|
|||||||
grid_world_height = GRID_HEIGHT * effective_cell_size
|
grid_world_height = GRID_HEIGHT * effective_cell_size
|
||||||
|
|
||||||
# Calculate grid position relative to camera (with grid centered at 0,0)
|
# Calculate grid position relative to camera (with grid centered at 0,0)
|
||||||
grid_center_x = SCREEN_WIDTH // 2 - camera.x * camera.zoom
|
grid_center_x = self.render_width // 2 - camera.x * camera.zoom
|
||||||
grid_center_y = SCREEN_HEIGHT // 2 - camera.y * camera.zoom
|
grid_center_y = self.render_height // 2 - camera.y * camera.zoom
|
||||||
|
|
||||||
grid_left = grid_center_x - grid_world_width // 2
|
grid_left = grid_center_x - grid_world_width // 2
|
||||||
grid_top = grid_center_y - grid_world_height // 2
|
grid_top = grid_center_y - grid_world_height // 2
|
||||||
@ -37,20 +42,20 @@ class Renderer:
|
|||||||
grid_bottom = grid_top + grid_world_height
|
grid_bottom = grid_top + grid_world_height
|
||||||
|
|
||||||
# Check if grid is visible on screen
|
# Check if grid is visible on screen
|
||||||
if (grid_right < 0 or grid_left > SCREEN_WIDTH or
|
if (grid_right < 0 or grid_left > self.render_width or
|
||||||
grid_bottom < 0 or grid_top > SCREEN_HEIGHT):
|
grid_bottom < 0 or grid_top > self.render_height):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Fill the grid area with dark gray background
|
# Fill the grid area with dark gray background
|
||||||
grid_rect = pygame.Rect(
|
grid_rect = pygame.Rect(
|
||||||
max(0, grid_left),
|
max(0, grid_left),
|
||||||
max(0, grid_top),
|
max(0, grid_top),
|
||||||
min(SCREEN_WIDTH, grid_right) - max(0, grid_left),
|
min(self.render_width, grid_right) - max(0, grid_left),
|
||||||
min(SCREEN_HEIGHT, grid_bottom) - max(0, grid_top),
|
min(self.render_height, grid_bottom) - max(0, grid_top),
|
||||||
)
|
)
|
||||||
|
|
||||||
if grid_rect.width > 0 and grid_rect.height > 0:
|
if grid_rect.width > 0 and grid_rect.height > 0:
|
||||||
pygame.draw.rect(self.screen, DARK_GRAY, grid_rect)
|
pygame.draw.rect(self.render_area, DARK_GRAY, grid_rect)
|
||||||
|
|
||||||
# Draw grid lines only if zoom is high enough
|
# Draw grid lines only if zoom is high enough
|
||||||
if effective_cell_size > 4:
|
if effective_cell_size > 4:
|
||||||
@ -65,30 +70,30 @@ class Renderer:
|
|||||||
# Vertical lines
|
# Vertical lines
|
||||||
if i <= GRID_WIDTH:
|
if i <= GRID_WIDTH:
|
||||||
line_x = grid_left + i * effective_cell_size
|
line_x = grid_left + i * effective_cell_size
|
||||||
if 0 <= line_x <= SCREEN_WIDTH:
|
if 0 <= line_x <= self.render_width:
|
||||||
start_y = max(0, grid_top)
|
start_y = max(0, grid_top)
|
||||||
end_y = min(SCREEN_HEIGHT, grid_bottom)
|
end_y = min(self.render_height, grid_bottom)
|
||||||
if start_y < end_y:
|
if start_y < end_y:
|
||||||
vertical_lines.append(((line_x, start_y), (line_x, end_y)))
|
vertical_lines.append(((line_x, start_y), (line_x, end_y)))
|
||||||
|
|
||||||
# Horizontal lines
|
# Horizontal lines
|
||||||
if i <= GRID_HEIGHT:
|
if i <= GRID_HEIGHT:
|
||||||
line_y = grid_top + i * effective_cell_size
|
line_y = grid_top + i * effective_cell_size
|
||||||
if 0 <= line_y <= SCREEN_HEIGHT:
|
if 0 <= line_y <= self.render_height:
|
||||||
start_x = max(0, grid_left)
|
start_x = max(0, grid_left)
|
||||||
end_x = min(SCREEN_WIDTH, grid_right)
|
end_x = min(self.render_width, grid_right)
|
||||||
if start_x < end_x:
|
if start_x < end_x:
|
||||||
horizontal_lines.append(((start_x, line_y), (end_x, line_y)))
|
horizontal_lines.append(((start_x, line_y), (end_x, line_y)))
|
||||||
|
|
||||||
# Draw all lines
|
# Draw all lines
|
||||||
for start, end in vertical_lines:
|
for start, end in vertical_lines:
|
||||||
pygame.draw.line(self.screen, GRAY, start, end)
|
pygame.draw.line(self.render_area, GRAY, start, end)
|
||||||
for start, end in horizontal_lines:
|
for start, end in horizontal_lines:
|
||||||
pygame.draw.line(self.screen, GRAY, start, end)
|
pygame.draw.line(self.render_area, GRAY, start, end)
|
||||||
|
|
||||||
def render_world(self, world, camera):
|
def render_world(self, world, camera):
|
||||||
"""Render all world objects."""
|
"""Render all world objects."""
|
||||||
world.render_all(camera, self.screen)
|
world.render_all(camera, self.render_area)
|
||||||
|
|
||||||
def render_interaction_radius(self, world, camera, selected_objects, show_radius=False):
|
def render_interaction_radius(self, world, camera, selected_objects, show_radius=False):
|
||||||
"""Render interaction radius and debug vectors for objects."""
|
"""Render interaction radius and debug vectors for objects."""
|
||||||
@ -108,7 +113,7 @@ class Renderer:
|
|||||||
|
|
||||||
if screen_radius > 0:
|
if screen_radius > 0:
|
||||||
# Draw interaction radius circle
|
# Draw interaction radius circle
|
||||||
pygame.draw.circle(self.screen, RED, (screen_x, screen_y), screen_radius, 1)
|
pygame.draw.circle(self.render_area, RED, (screen_x, screen_y), screen_radius, 1)
|
||||||
|
|
||||||
# Draw direction arrow
|
# Draw direction arrow
|
||||||
self._draw_direction_arrow(obj, screen_x, screen_y, camera)
|
self._draw_direction_arrow(obj, screen_x, screen_y, camera)
|
||||||
@ -125,7 +130,7 @@ class Renderer:
|
|||||||
end_y = screen_y + arrow_length * math.sin(math.radians(rotation_angle))
|
end_y = screen_y + arrow_length * math.sin(math.radians(rotation_angle))
|
||||||
|
|
||||||
# Draw arrow line
|
# Draw arrow line
|
||||||
pygame.draw.line(self.screen, WHITE, (screen_x, screen_y), (end_x, end_y), 2)
|
pygame.draw.line(self.render_area, WHITE, (screen_x, screen_y), (end_x, end_y), 2)
|
||||||
|
|
||||||
# Draw arrowhead
|
# Draw arrowhead
|
||||||
tip_size = DIRECTION_TIP_SIZE * camera.zoom
|
tip_size = DIRECTION_TIP_SIZE * camera.zoom
|
||||||
@ -135,7 +140,7 @@ class Renderer:
|
|||||||
right_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle - 150 + 180))
|
right_tip_y = end_y - tip_size * math.sin(math.radians(rotation_angle - 150 + 180))
|
||||||
|
|
||||||
pygame.draw.polygon(
|
pygame.draw.polygon(
|
||||||
self.screen, WHITE,
|
self.render_area, WHITE,
|
||||||
[(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)]
|
[(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -167,7 +172,7 @@ class Renderer:
|
|||||||
angular_acc_end_x = end_x + angular_accel_magnitude * math.cos(math.radians(angular_direction))
|
angular_acc_end_x = end_x + angular_accel_magnitude * math.cos(math.radians(angular_direction))
|
||||||
angular_acc_end_y = end_y + angular_accel_magnitude * math.sin(math.radians(angular_direction))
|
angular_acc_end_y = end_y + angular_accel_magnitude * math.sin(math.radians(angular_direction))
|
||||||
|
|
||||||
pygame.draw.line(self.screen, LIGHT_BLUE, (end_x, end_y), (angular_acc_end_x, angular_acc_end_y), 2)
|
pygame.draw.line(self.render_area, LIGHT_BLUE, (end_x, end_y), (angular_acc_end_x, angular_acc_end_y), 2)
|
||||||
|
|
||||||
# Draw arrowhead
|
# Draw arrowhead
|
||||||
self._draw_arrowhead(angular_acc_end_x, angular_acc_end_y, angular_direction,
|
self._draw_arrowhead(angular_acc_end_x, angular_acc_end_y, angular_direction,
|
||||||
@ -184,7 +189,7 @@ class Renderer:
|
|||||||
acc_end_x = screen_x + acc_vector_length * math.cos(math.radians(acc_direction))
|
acc_end_x = screen_x + acc_vector_length * math.cos(math.radians(acc_direction))
|
||||||
acc_end_y = screen_y + acc_vector_length * math.sin(math.radians(acc_direction))
|
acc_end_y = screen_y + acc_vector_length * math.sin(math.radians(acc_direction))
|
||||||
|
|
||||||
pygame.draw.line(self.screen, RED, (screen_x, screen_y), (acc_end_x, acc_end_y), 2)
|
pygame.draw.line(self.render_area, RED, (screen_x, screen_y), (acc_end_x, acc_end_y), 2)
|
||||||
self._draw_arrowhead(acc_end_x, acc_end_y, acc_direction,
|
self._draw_arrowhead(acc_end_x, acc_end_y, acc_direction,
|
||||||
ARROW_TIP_SIZE * camera.zoom, RED)
|
ARROW_TIP_SIZE * camera.zoom, RED)
|
||||||
|
|
||||||
@ -199,7 +204,7 @@ class Renderer:
|
|||||||
vel_end_x = screen_x + vel_vector_length * math.cos(math.radians(vel_direction))
|
vel_end_x = screen_x + vel_vector_length * math.cos(math.radians(vel_direction))
|
||||||
vel_end_y = screen_y + vel_vector_length * math.sin(math.radians(vel_direction))
|
vel_end_y = screen_y + vel_vector_length * math.sin(math.radians(vel_direction))
|
||||||
|
|
||||||
pygame.draw.line(self.screen, BLUE, (screen_x, screen_y), (vel_end_x, vel_end_y), 2)
|
pygame.draw.line(self.render_area, BLUE, (screen_x, screen_y), (vel_end_x, vel_end_y), 2)
|
||||||
self._draw_arrowhead(vel_end_x, vel_end_y, vel_direction,
|
self._draw_arrowhead(vel_end_x, vel_end_y, vel_direction,
|
||||||
ARROW_TIP_SIZE * camera.zoom, BLUE)
|
ARROW_TIP_SIZE * camera.zoom, BLUE)
|
||||||
|
|
||||||
@ -211,24 +216,29 @@ class Renderer:
|
|||||||
right_tip_y = end_y - tip_size * math.sin(math.radians(direction - 150 + 180))
|
right_tip_y = end_y - tip_size * math.sin(math.radians(direction - 150 + 180))
|
||||||
|
|
||||||
pygame.draw.polygon(
|
pygame.draw.polygon(
|
||||||
self.screen, color,
|
self.render_area, color,
|
||||||
[(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)]
|
[(end_x, end_y), (left_tip_x, left_tip_y), (right_tip_x, right_tip_y)]
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_selection_rectangle(self, selection_rect):
|
def render_selection_rectangle(self, selection_rect, sim_view_rect=None):
|
||||||
"""Render the selection rectangle."""
|
"""Render the selection rectangle, offset for sim_view if sim_view_rect is provided."""
|
||||||
if not selection_rect:
|
if not selection_rect:
|
||||||
return
|
return
|
||||||
|
|
||||||
left, top, width, height = selection_rect
|
left, top, width, height = selection_rect
|
||||||
|
|
||||||
|
# Offset for sim_view if sim_view_rect is given
|
||||||
|
if sim_view_rect is not None:
|
||||||
|
left -= sim_view_rect.left
|
||||||
|
top -= sim_view_rect.top
|
||||||
|
|
||||||
# Draw semi-transparent fill
|
# Draw semi-transparent fill
|
||||||
s = pygame.Surface((width, height), pygame.SRCALPHA)
|
s = pygame.Surface((width, height), pygame.SRCALPHA)
|
||||||
s.fill(SELECTION_GRAY)
|
s.fill(SELECTION_GRAY)
|
||||||
self.screen.blit(s, (left, top))
|
self.render_area.blit(s, (left, top))
|
||||||
|
|
||||||
# Draw border
|
# Draw border
|
||||||
pygame.draw.rect(self.screen, SELECTION_BORDER,
|
pygame.draw.rect(self.render_area, SELECTION_BORDER,
|
||||||
pygame.Rect(left, top, width, height), 1)
|
pygame.Rect(left, top, width, height), 1)
|
||||||
|
|
||||||
def render_selected_objects_outline(self, selected_objects, camera):
|
def render_selected_objects_outline(self, selected_objects, camera):
|
||||||
@ -239,4 +249,4 @@ class Renderer:
|
|||||||
screen_x, screen_y = camera.world_to_screen(obj_x, obj_y)
|
screen_x, screen_y = camera.world_to_screen(obj_x, obj_y)
|
||||||
size = camera.get_relative_size(width)
|
size = camera.get_relative_size(width)
|
||||||
rect = pygame.Rect(screen_x - size // 2, screen_y - size // 2, size, size)
|
rect = pygame.Rect(screen_x - size // 2, screen_y - size // 2, size, size)
|
||||||
pygame.draw.rect(self.screen, SELECTION_BLUE, rect, 1)
|
pygame.draw.rect(self.render_area, SELECTION_BLUE, rect, 1)
|
||||||
|
|||||||
@ -20,16 +20,18 @@ class SimulationEngine:
|
|||||||
pygame.init()
|
pygame.init()
|
||||||
|
|
||||||
info = pygame.display.Info()
|
info = pygame.display.Info()
|
||||||
self.window_width, self.window_height = info.current_w, info.current_h
|
self.window_width, self.window_height = info.current_w // 2, info.current_h // 2
|
||||||
self.screen = pygame.display.set_mode((self.window_width, self.window_height),
|
self.screen = pygame.display.set_mode((self.window_width, self.window_height),
|
||||||
pygame.RESIZABLE | pygame.FULLSCREEN)
|
pygame.RESIZABLE, vsync=1)
|
||||||
|
|
||||||
self.ui_manager = UIManager((self.window_width, self.window_height))
|
self.ui_manager = UIManager((self.window_width, self.window_height))
|
||||||
|
|
||||||
|
self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, RENDER_BUFFER)
|
||||||
|
self._update_simulation_view()
|
||||||
|
|
||||||
# self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1)
|
# self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vsync=1)
|
||||||
pygame.display.set_caption("Dynamic Abstraction System Testing")
|
pygame.display.set_caption("Dynamic Abstraction System Testing")
|
||||||
self.clock = pygame.time.Clock()
|
self.clock = pygame.time.Clock()
|
||||||
self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, RENDER_BUFFER)
|
|
||||||
|
|
||||||
self.last_tick_time = time.perf_counter()
|
self.last_tick_time = time.perf_counter()
|
||||||
self.last_tps_time = time.perf_counter()
|
self.last_tps_time = time.perf_counter()
|
||||||
@ -38,9 +40,10 @@ class SimulationEngine:
|
|||||||
self.total_ticks = 0
|
self.total_ticks = 0
|
||||||
|
|
||||||
self.world = self._setup_world()
|
self.world = self._setup_world()
|
||||||
self.input_handler = InputHandler(self.camera, self.world)
|
self.input_handler = InputHandler(self.camera, self.world, self.sim_view_rect)
|
||||||
self.renderer = Renderer(self.screen)
|
self.renderer = Renderer(self.sim_view)
|
||||||
self.hud = HUD()
|
self.hud = HUD(self.ui_manager, self.window_width, self.window_height)
|
||||||
|
self.hud.update_layout(self.window_width, self.window_height)
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
|
|
||||||
@ -50,6 +53,18 @@ class SimulationEngine:
|
|||||||
self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height))
|
self.sim_view = pygame.Surface((self.sim_view_width, self.sim_view_height))
|
||||||
self.sim_view_rect = self.sim_view.get_rect(center=(self.window_width // 2, self.window_height // 2))
|
self.sim_view_rect = self.sim_view.get_rect(center=(self.window_width // 2, self.window_height // 2))
|
||||||
|
|
||||||
|
self.ui_manager.set_window_resolution((self.window_width, self.window_height))
|
||||||
|
self.renderer = Renderer(self.sim_view)
|
||||||
|
|
||||||
|
# Update camera to match new sim_view size
|
||||||
|
if hasattr(self, 'camera'):
|
||||||
|
self.camera.screen_width = self.sim_view_width
|
||||||
|
self.camera.screen_height = self.sim_view_height
|
||||||
|
|
||||||
|
if hasattr(self, 'input_handler'):
|
||||||
|
self.input_handler.update_sim_view_rect(self.sim_view_rect)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _setup_world():
|
def _setup_world():
|
||||||
world = World(CELL_SIZE, (CELL_SIZE * GRID_WIDTH, CELL_SIZE * GRID_HEIGHT))
|
world = World(CELL_SIZE, (CELL_SIZE * GRID_WIDTH, CELL_SIZE * GRID_HEIGHT))
|
||||||
@ -88,6 +103,14 @@ class SimulationEngine:
|
|||||||
events = pygame.event.get()
|
events = pygame.event.get()
|
||||||
self.running = self.input_handler.handle_events(events, self.hud.manager)
|
self.running = self.input_handler.handle_events(events, self.hud.manager)
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if event.type == pygame.VIDEORESIZE:
|
||||||
|
self.window_width, self.window_height = event.w, event.h
|
||||||
|
self.screen = pygame.display.set_mode((self.window_width, self.window_height),
|
||||||
|
pygame.RESIZABLE)
|
||||||
|
self._update_simulation_view()
|
||||||
|
self.hud.update_layout(self.window_width, self.window_height)
|
||||||
|
|
||||||
if self.input_handler.sprint_mode:
|
if self.input_handler.sprint_mode:
|
||||||
# Sprint mode: run as many ticks as possible, skip rendering
|
# Sprint mode: run as many ticks as possible, skip rendering
|
||||||
current_time = time.perf_counter()
|
current_time = time.perf_counter()
|
||||||
@ -140,14 +163,22 @@ class SimulationEngine:
|
|||||||
self.input_handler.update_camera(keys, deltatime)
|
self.input_handler.update_camera(keys, deltatime)
|
||||||
|
|
||||||
def _render(self):
|
def _render(self):
|
||||||
self.renderer.clear_screen()
|
self.renderer.clear_screen(self.screen)
|
||||||
self.renderer.draw_grid(self.camera, self.input_handler.show_grid)
|
self.renderer.draw_grid(self.camera, self.input_handler.show_grid)
|
||||||
self.renderer.render_world(self.world, self.camera)
|
self.renderer.render_world(self.world, self.camera)
|
||||||
self.renderer.render_interaction_radius(self.world, self.camera, self.input_handler.selected_objects, self.input_handler.show_interaction_radius)
|
self.renderer.render_interaction_radius(self.world, self.camera, self.input_handler.selected_objects, self.input_handler.show_interaction_radius)
|
||||||
self.renderer.render_selection_rectangle(self.input_handler.get_selection_rect())
|
self.renderer.render_selection_rectangle(self.input_handler.get_selection_rect(), self.sim_view_rect)
|
||||||
self.renderer.render_selected_objects_outline(self.input_handler.selected_objects, self.camera)
|
self.renderer.render_selected_objects_outline(self.input_handler.selected_objects, self.camera)
|
||||||
|
|
||||||
self.hud.render_mouse_position(self.screen, self.camera)
|
# In core/simulation_engine.py, in _render():
|
||||||
|
self.screen.blit(self.sim_view, (self.sim_view_rect.left, self.sim_view_rect.top))
|
||||||
|
|
||||||
|
# Draw border around sim_view
|
||||||
|
border_color = (255, 255, 255) # White
|
||||||
|
border_width = 3
|
||||||
|
pygame.draw.rect(self.screen, border_color, self.sim_view_rect, border_width)
|
||||||
|
|
||||||
|
self.hud.render_mouse_position(self.screen, self.camera, self.sim_view_rect)
|
||||||
self.hud.render_fps(self.screen, self.clock)
|
self.hud.render_fps(self.screen, self.clock)
|
||||||
self.hud.render_tps(self.screen, self.actual_tps)
|
self.hud.render_tps(self.screen, self.actual_tps)
|
||||||
self.hud.render_tick_count(self.screen, self.total_ticks)
|
self.hud.render_tick_count(self.screen, self.total_ticks)
|
||||||
|
|||||||
50
ui/hud.py
50
ui/hud.py
@ -10,15 +10,22 @@ import math
|
|||||||
|
|
||||||
|
|
||||||
class HUD:
|
class HUD:
|
||||||
def __init__(self, ui_manager):
|
def __init__(self, ui_manager, screen_width=SCREEN_WIDTH, screen_height=SCREEN_HEIGHT):
|
||||||
self.font = pygame.font.Font("freesansbold.ttf", FONT_SIZE)
|
self.font = pygame.font.Font("freesansbold.ttf", FONT_SIZE)
|
||||||
self.legend_font = pygame.font.Font("freesansbold.ttf", LEGEND_FONT_SIZE)
|
self.legend_font = pygame.font.Font("freesansbold.ttf", LEGEND_FONT_SIZE)
|
||||||
|
|
||||||
|
self.screen_width = screen_width
|
||||||
|
self.screen_height = screen_height
|
||||||
|
|
||||||
self.manager = ui_manager
|
self.manager = ui_manager
|
||||||
|
|
||||||
def render_mouse_position(self, screen, camera):
|
def render_mouse_position(self, screen, camera, sim_view_rect):
|
||||||
"""Render mouse position in top left."""
|
"""Render mouse position in top left."""
|
||||||
mouse_x, mouse_y = camera.get_real_coordinates(*pygame.mouse.get_pos())
|
mouse_x, mouse_y = pygame.mouse.get_pos()
|
||||||
|
sim_view_x = mouse_x - sim_view_rect.left
|
||||||
|
sim_view_y = mouse_y - sim_view_rect.top
|
||||||
|
world_x, world_y = camera.get_real_coordinates(sim_view_x, sim_view_y)
|
||||||
|
|
||||||
mouse_text = self.font.render(f"Mouse: ({mouse_x:.2f}, {mouse_y:.2f})", True, WHITE)
|
mouse_text = self.font.render(f"Mouse: ({mouse_x:.2f}, {mouse_y:.2f})", True, WHITE)
|
||||||
text_rect = mouse_text.get_rect()
|
text_rect = mouse_text.get_rect()
|
||||||
text_rect.topleft = (HUD_MARGIN, HUD_MARGIN)
|
text_rect.topleft = (HUD_MARGIN, HUD_MARGIN)
|
||||||
@ -28,21 +35,21 @@ class HUD:
|
|||||||
"""Render FPS in top right."""
|
"""Render FPS in top right."""
|
||||||
fps_text = self.font.render(f"FPS: {int(clock.get_fps())}", True, WHITE)
|
fps_text = self.font.render(f"FPS: {int(clock.get_fps())}", True, WHITE)
|
||||||
fps_rect = fps_text.get_rect()
|
fps_rect = fps_text.get_rect()
|
||||||
fps_rect.topright = (SCREEN_WIDTH - HUD_MARGIN, HUD_MARGIN)
|
fps_rect.topright = (self.screen_width - HUD_MARGIN, HUD_MARGIN)
|
||||||
screen.blit(fps_text, fps_rect)
|
screen.blit(fps_text, fps_rect)
|
||||||
|
|
||||||
def render_tps(self, screen, actual_tps):
|
def render_tps(self, screen, actual_tps):
|
||||||
"""Render TPS in bottom right."""
|
"""Render TPS in bottom right."""
|
||||||
tps_text = self.font.render(f"TPS: {actual_tps}", True, WHITE)
|
tps_text = self.font.render(f"TPS: {actual_tps}", True, WHITE)
|
||||||
tps_rect = tps_text.get_rect()
|
tps_rect = tps_text.get_rect()
|
||||||
tps_rect.bottomright = (SCREEN_WIDTH - HUD_MARGIN, SCREEN_HEIGHT - HUD_MARGIN)
|
tps_rect.bottomright = (self.screen_width - HUD_MARGIN, self.screen_height - HUD_MARGIN)
|
||||||
screen.blit(tps_text, tps_rect)
|
screen.blit(tps_text, tps_rect)
|
||||||
|
|
||||||
def render_tick_count(self, screen, total_ticks):
|
def render_tick_count(self, screen, total_ticks):
|
||||||
"""Render total tick count in bottom left."""
|
"""Render total tick count in bottom left."""
|
||||||
tick_text = self.font.render(f"Ticks: {total_ticks}", True, WHITE)
|
tick_text = self.font.render(f"Ticks: {total_ticks}", True, WHITE)
|
||||||
tick_rect = tick_text.get_rect()
|
tick_rect = tick_text.get_rect()
|
||||||
tick_rect.bottomleft = (HUD_MARGIN, SCREEN_HEIGHT - HUD_MARGIN)
|
tick_rect.bottomleft = (HUD_MARGIN, self.screen_height - HUD_MARGIN)
|
||||||
screen.blit(tick_text, tick_rect)
|
screen.blit(tick_text, tick_rect)
|
||||||
|
|
||||||
def render_pause_indicator(self, screen, is_paused):
|
def render_pause_indicator(self, screen, is_paused):
|
||||||
@ -50,7 +57,7 @@ class HUD:
|
|||||||
if is_paused:
|
if is_paused:
|
||||||
pause_text = self.font.render("Press 'Space' to unpause", True, WHITE)
|
pause_text = self.font.render("Press 'Space' to unpause", True, WHITE)
|
||||||
pause_rect = pause_text.get_rect()
|
pause_rect = pause_text.get_rect()
|
||||||
pause_rect.center = (SCREEN_WIDTH // 2, 20)
|
pause_rect.center = (self.screen_width // 2, 20)
|
||||||
screen.blit(pause_text, pause_rect)
|
screen.blit(pause_text, pause_rect)
|
||||||
|
|
||||||
def render_selected_objects_info(self, screen, selected_objects):
|
def render_selected_objects_info(self, screen, selected_objects):
|
||||||
@ -58,7 +65,7 @@ class HUD:
|
|||||||
if len(selected_objects) < 1:
|
if len(selected_objects) < 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
max_width = SCREEN_WIDTH - 20
|
max_width = self.screen_width - 20
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
for obj in selected_objects:
|
for obj in selected_objects:
|
||||||
@ -94,7 +101,7 @@ class HUD:
|
|||||||
if not showing_legend:
|
if not showing_legend:
|
||||||
legend_text = self.legend_font.render("Press 'L' to show controls", True, WHITE)
|
legend_text = self.legend_font.render("Press 'L' to show controls", True, WHITE)
|
||||||
legend_rect = legend_text.get_rect()
|
legend_rect = legend_text.get_rect()
|
||||||
legend_rect.center = (SCREEN_WIDTH // 2, SCREEN_HEIGHT - 20)
|
legend_rect.center = (self.screen_width // 2, self.screen_height - 20)
|
||||||
screen.blit(legend_text, legend_rect)
|
screen.blit(legend_text, legend_rect)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -112,8 +119,8 @@ class HUD:
|
|||||||
legend_width = left_width + right_width + column_gap
|
legend_width = left_width + right_width + column_gap
|
||||||
legend_height = max(len(left_col), len(right_col)) * legend_font_height + 10
|
legend_height = max(len(left_col), len(right_col)) * legend_font_height + 10
|
||||||
|
|
||||||
legend_x = (SCREEN_WIDTH - legend_width) // 2
|
legend_x = (self.screen_width - legend_width) // 2
|
||||||
legend_y = SCREEN_HEIGHT - legend_height - 10
|
legend_y = self.screen_height - legend_height - 10
|
||||||
|
|
||||||
# Draw left column
|
# Draw left column
|
||||||
for i, (key, desc) in enumerate(left_col):
|
for i, (key, desc) in enumerate(left_col):
|
||||||
@ -200,8 +207,8 @@ class HUD:
|
|||||||
network: FlexibleNeuralNetwork = cell_brain.neural_network
|
network: FlexibleNeuralNetwork = cell_brain.neural_network
|
||||||
|
|
||||||
# Calculate visualization position
|
# Calculate visualization position
|
||||||
viz_x = SCREEN_WIDTH - VIZ_RIGHT_MARGIN # Right side of screen
|
viz_x = self.screen_width - VIZ_RIGHT_MARGIN # Right side of screen
|
||||||
viz_y = (SCREEN_HEIGHT // 2) - (VIZ_HEIGHT // 2) # Centered vertically
|
viz_y = (self.screen_height // 2) - (VIZ_HEIGHT // 2) # Centered vertically
|
||||||
|
|
||||||
layer_spacing = VIZ_WIDTH // max(1, len(network.layers) - 1) if len(network.layers) > 1 else VIZ_WIDTH
|
layer_spacing = VIZ_WIDTH // max(1, len(network.layers) - 1) if len(network.layers) > 1 else VIZ_WIDTH
|
||||||
|
|
||||||
@ -453,10 +460,10 @@ class HUD:
|
|||||||
tooltip_y = mouse_y + TOOLTIP_Y_OFFSET
|
tooltip_y = mouse_y + TOOLTIP_Y_OFFSET
|
||||||
|
|
||||||
# Adjust if off right edge
|
# Adjust if off right edge
|
||||||
if tooltip_x + width > SCREEN_WIDTH:
|
if tooltip_x + width > self.screen_width:
|
||||||
tooltip_x = mouse_x - width - TOOLTIP_X_OFFSET
|
tooltip_x = mouse_x - width - TOOLTIP_X_OFFSET
|
||||||
# Adjust if off bottom edge
|
# Adjust if off bottom edge
|
||||||
if tooltip_y + height > SCREEN_HEIGHT:
|
if tooltip_y + height > self.screen_height:
|
||||||
tooltip_y = mouse_y - height - TOOLTIP_Y_OFFSET
|
tooltip_y = mouse_y - height - TOOLTIP_Y_OFFSET
|
||||||
|
|
||||||
tooltip_rect = pygame.Rect(tooltip_x, tooltip_y, width, height)
|
tooltip_rect = pygame.Rect(tooltip_x, tooltip_y, width, height)
|
||||||
@ -473,11 +480,16 @@ class HUD:
|
|||||||
tps_text = self.font.render(f"TPS: {actual_tps}", True, (255, 255, 255))
|
tps_text = self.font.render(f"TPS: {actual_tps}", True, (255, 255, 255))
|
||||||
ticks_text = self.font.render(f"Ticks: {total_ticks}", True, (255, 255, 255))
|
ticks_text = self.font.render(f"Ticks: {total_ticks}", True, (255, 255, 255))
|
||||||
|
|
||||||
y = SCREEN_HEIGHT // 2 - 40
|
y = self.screen_height // 2 - 40
|
||||||
header_rect = header.get_rect(center=(SCREEN_WIDTH // 2, y))
|
header_rect = header.get_rect(center=(self.screen_width // 2, y))
|
||||||
tps_rect = tps_text.get_rect(center=(SCREEN_WIDTH // 2, y + 40))
|
tps_rect = tps_text.get_rect(center=(self.screen_width // 2, y + 40))
|
||||||
ticks_rect = ticks_text.get_rect(center=(SCREEN_WIDTH // 2, y + 80))
|
ticks_rect = ticks_text.get_rect(center=(self.screen_width // 2, y + 80))
|
||||||
|
|
||||||
screen.blit(header, header_rect)
|
screen.blit(header, header_rect)
|
||||||
screen.blit(tps_text, tps_rect)
|
screen.blit(tps_text, tps_rect)
|
||||||
screen.blit(ticks_text, ticks_rect)
|
screen.blit(ticks_text, ticks_rect)
|
||||||
|
|
||||||
|
def update_layout(self, window_width, window_height):
|
||||||
|
"""Update HUD layout on window resize."""
|
||||||
|
self.screen_width = window_width
|
||||||
|
self.screen_height = window_height
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user