add menu
This commit is contained in:
@@ -21,6 +21,7 @@ from .rendering import (
|
||||
draw_sect_headquarters,
|
||||
)
|
||||
from .events_panel import draw_sidebar
|
||||
from .menu import PauseMenu
|
||||
|
||||
|
||||
class Front:
|
||||
@@ -44,7 +45,6 @@ class Front:
|
||||
self.font_path = font_path
|
||||
self.sidebar_width = sidebar_width
|
||||
|
||||
self._auto_step = True
|
||||
self._last_step_ms = 0
|
||||
self.events: List[Event] = []
|
||||
|
||||
@@ -76,6 +76,9 @@ class Front:
|
||||
self._assign_avatar_images()
|
||||
|
||||
self.clock = pygame.time.Clock()
|
||||
|
||||
# 暂停菜单
|
||||
self.pause_menu = PauseMenu(pygame)
|
||||
|
||||
# 渲染插值状态:avatar_id -> {start_px, start_py, target_px, target_py, start_ms, duration_ms}
|
||||
self._avatar_display_states: Dict[str, Dict[str, float]] = {}
|
||||
@@ -116,37 +119,48 @@ class Front:
|
||||
current_step_task = None
|
||||
while running:
|
||||
dt_ms = self.clock.tick(60)
|
||||
self._last_step_ms += dt_ms
|
||||
|
||||
# 游戏未暂停时才累积时间
|
||||
if not self.pause_menu.is_visible:
|
||||
self._last_step_ms += dt_ms
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
running = False
|
||||
elif event.key == pygame.K_a:
|
||||
self._auto_step = not self._auto_step
|
||||
elif event.key == pygame.K_SPACE:
|
||||
if current_step_task is None or current_step_task.done():
|
||||
current_step_task = asyncio.create_task(self._step_once_async())
|
||||
self.pause_menu.toggle()
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
||||
self._handle_mouse_click()
|
||||
# 处理菜单点击
|
||||
if self.pause_menu.is_visible:
|
||||
action = self._handle_menu_click()
|
||||
if action == "quit":
|
||||
running = False
|
||||
else:
|
||||
self._handle_mouse_click()
|
||||
# 兼容旧版滚轮为 MOUSEBUTTON 4/5
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN and event.button in (4, 5):
|
||||
delta = 1 if event.button == 4 else -1
|
||||
self._on_mouse_wheel(delta)
|
||||
if not self.pause_menu.is_visible:
|
||||
delta = 1 if event.button == 4 else -1
|
||||
self._on_mouse_wheel(delta)
|
||||
# pygame 2 的标准滚轮事件
|
||||
elif getattr(pygame, "MOUSEWHEEL", None) is not None and event.type == pygame.MOUSEWHEEL:
|
||||
# event.y: 上滚为正,下滚为负
|
||||
self._on_mouse_wheel(int(getattr(event, "y", 0)))
|
||||
if self._auto_step and self._last_step_ms >= self.step_interval_ms:
|
||||
if not self.pause_menu.is_visible:
|
||||
# event.y: 上滚为正,下滚为负
|
||||
self._on_mouse_wheel(int(getattr(event, "y", 0)))
|
||||
|
||||
# 游戏未暂停时才自动步进
|
||||
if not self.pause_menu.is_visible and self._last_step_ms >= self.step_interval_ms:
|
||||
if current_step_task is None or current_step_task.done():
|
||||
current_step_task = asyncio.create_task(self._step_once_async())
|
||||
self._last_step_ms = 0
|
||||
|
||||
if current_step_task and current_step_task.done():
|
||||
await current_step_task
|
||||
current_step_task = None
|
||||
# 再次确保目标同步(防止外部触发的状态变更遗漏)
|
||||
self._update_avatar_display_targets()
|
||||
|
||||
self._render()
|
||||
await asyncio.sleep(0.016)
|
||||
pygame.quit()
|
||||
@@ -193,7 +207,7 @@ class Front:
|
||||
)
|
||||
hovered_avatar = self._pick_hover_with_scroll(hovered_default, hover_candidates)
|
||||
# 先绘制状态栏和侧边栏,再绘制 tooltip 保证 tooltip 在最上层
|
||||
draw_status_bar(pygame, self.screen, self.colors, self.status_font, self.margin, self.world, self._auto_step)
|
||||
draw_status_bar(pygame, self.screen, self.colors, self.status_font, self.margin, self.world)
|
||||
|
||||
# 计算筛选后的事件
|
||||
if self._sidebar_filter_avatar_id is None:
|
||||
@@ -236,10 +250,14 @@ class Front:
|
||||
elif hovered_region is not None:
|
||||
mouse_x, mouse_y = pygame.mouse.get_pos()
|
||||
draw_tooltip_for_region(pygame, self.screen, self.colors, self.tooltip_font, hovered_region, mouse_x, mouse_y)
|
||||
|
||||
# 绘制暂停菜单(在最上层)
|
||||
self._menu_option_rects = self.pause_menu.draw(self.screen, self.colors, self.status_font)
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
def _handle_mouse_click(self) -> None:
|
||||
# 仅处理侧栏筛选点击
|
||||
"""处理侧栏筛选点击"""
|
||||
pygame = self.pygame
|
||||
mouse_pos = pygame.mouse.get_pos()
|
||||
ui = getattr(self, "_sidebar_ui", {}) or {}
|
||||
@@ -254,6 +272,12 @@ class Front:
|
||||
self._sidebar_filter_avatar_id = oid if oid is not None else None
|
||||
self._sidebar_filter_open = False
|
||||
return
|
||||
|
||||
def _handle_menu_click(self) -> Optional[str]:
|
||||
"""处理菜单点击,返回动作"""
|
||||
mouse_pos = self.pygame.mouse.get_pos()
|
||||
option_rects = getattr(self, "_menu_option_rects", [])
|
||||
return self.pause_menu.handle_click(mouse_pos, option_rects)
|
||||
|
||||
def _get_region_font(self, size: int):
|
||||
return _get_region_font_cached(self.pygame, self._region_font_cache, size, self.font_path)
|
||||
|
||||
109
src/front/menu.py
Normal file
109
src/front/menu.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""游戏暂停菜单"""
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
class MenuOption:
|
||||
"""菜单选项"""
|
||||
def __init__(self, label: str, action: str):
|
||||
self.label = label
|
||||
self.action = action
|
||||
|
||||
|
||||
class PauseMenu:
|
||||
"""暂停菜单"""
|
||||
def __init__(self, pygame_mod):
|
||||
self.pygame = pygame_mod
|
||||
self.is_visible = False
|
||||
self.options = [
|
||||
MenuOption("退出游戏", "quit")
|
||||
]
|
||||
self.selected_index = 0
|
||||
|
||||
def toggle(self):
|
||||
"""切换菜单显示状态"""
|
||||
self.is_visible = not self.is_visible
|
||||
self.selected_index = 0
|
||||
|
||||
def show(self):
|
||||
"""显示菜单"""
|
||||
self.is_visible = True
|
||||
|
||||
def hide(self):
|
||||
"""隐藏菜单"""
|
||||
self.is_visible = False
|
||||
|
||||
def handle_click(self, mouse_pos: Tuple[int, int], option_rects: list) -> Optional[str]:
|
||||
"""处理鼠标点击,返回被点击的选项动作"""
|
||||
if not self.is_visible:
|
||||
return None
|
||||
|
||||
for i, rect in enumerate(option_rects):
|
||||
if rect.collidepoint(mouse_pos):
|
||||
return self.options[i].action
|
||||
return None
|
||||
|
||||
def draw(self, screen, colors, font):
|
||||
"""绘制菜单"""
|
||||
if not self.is_visible:
|
||||
return []
|
||||
|
||||
pygame = self.pygame
|
||||
screen_w, screen_h = screen.get_size()
|
||||
|
||||
# 绘制半透明黑色背景(模糊效果)
|
||||
overlay = pygame.Surface((screen_w, screen_h), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 160))
|
||||
screen.blit(overlay, (0, 0))
|
||||
|
||||
# 计算菜单尺寸
|
||||
padding = 40
|
||||
option_height = 50
|
||||
option_spacing = 20
|
||||
menu_width = 300
|
||||
menu_height = padding * 2 + len(self.options) * option_height + (len(self.options) - 1) * option_spacing
|
||||
|
||||
# 菜单居中位置
|
||||
menu_x = (screen_w - menu_width) // 2
|
||||
menu_y = (screen_h - menu_height) // 2
|
||||
|
||||
# 绘制菜单背景
|
||||
menu_rect = pygame.Rect(menu_x, menu_y, menu_width, menu_height)
|
||||
pygame.draw.rect(screen, (40, 40, 40), menu_rect, border_radius=12)
|
||||
pygame.draw.rect(screen, (100, 100, 100), menu_rect, 2, border_radius=12)
|
||||
|
||||
# 绘制选项
|
||||
option_rects = []
|
||||
current_y = menu_y + padding
|
||||
|
||||
for i, option in enumerate(self.options):
|
||||
option_rect = pygame.Rect(
|
||||
menu_x + 30,
|
||||
current_y,
|
||||
menu_width - 60,
|
||||
option_height
|
||||
)
|
||||
|
||||
# 检测鼠标悬停
|
||||
mouse_pos = pygame.mouse.get_pos()
|
||||
is_hovered = option_rect.collidepoint(mouse_pos)
|
||||
|
||||
# 绘制选项背景
|
||||
bg_color = (80, 80, 80) if is_hovered else (50, 50, 50)
|
||||
pygame.draw.rect(screen, bg_color, option_rect, border_radius=8)
|
||||
pygame.draw.rect(screen, (120, 120, 120), option_rect, 1, border_radius=8)
|
||||
|
||||
# 绘制选项文本
|
||||
text_color = (255, 255, 255) if is_hovered else (200, 200, 200)
|
||||
text_surf = font.render(option.label, True, text_color)
|
||||
text_x = option_rect.centerx - text_surf.get_width() // 2
|
||||
text_y = option_rect.centery - text_surf.get_height() // 2
|
||||
screen.blit(text_surf, (text_x, text_y))
|
||||
|
||||
option_rects.append(option_rect)
|
||||
current_y += option_height + option_spacing
|
||||
|
||||
return option_rects
|
||||
|
||||
|
||||
__all__ = ["PauseMenu"]
|
||||
|
||||
@@ -377,9 +377,8 @@ def draw_tooltip_for_region(pygame_mod, screen, colors, font, region, mouse_x: i
|
||||
draw_tooltip(pygame_mod, screen, colors, wrapped_lines, mouse_x, mouse_y, font, min_width=TOOLTIP_MIN_WIDTH, top_limit=STATUS_BAR_HEIGHT)
|
||||
|
||||
|
||||
def draw_operation_guide(pygame_mod, screen, colors, font, margin: int, auto_step: bool):
|
||||
auto_status = "开" if auto_step else "关"
|
||||
guide_text = f"A:自动步进({auto_status}) SPACE:单步 ESC:退出"
|
||||
def draw_operation_guide(pygame_mod, screen, colors, font, margin: int):
|
||||
guide_text = "ESC: 呼出菜单"
|
||||
guide_surf = font.render(guide_text, True, colors["status_text"])
|
||||
x_pos = margin + 8
|
||||
screen.blit(guide_surf, (x_pos, 8))
|
||||
@@ -395,14 +394,14 @@ def draw_year_month_info(pygame_mod, screen, colors, font, margin: int, guide_wi
|
||||
screen.blit(ym_surf, (x_pos, 8))
|
||||
|
||||
|
||||
def draw_status_bar(pygame_mod, screen, colors, font, margin: int, world, auto_step: bool):
|
||||
def draw_status_bar(pygame_mod, screen, colors, font, margin: int, world):
|
||||
status_y = 8
|
||||
status_height = STATUS_BAR_HEIGHT
|
||||
status_rect = pygame_mod.Rect(0, 0, screen.get_width(), status_height)
|
||||
pygame_mod.draw.rect(screen, colors["status_bg"], status_rect)
|
||||
pygame_mod.draw.line(screen, colors["status_border"],
|
||||
(0, status_height), (screen.get_width(), status_height), 2)
|
||||
guide_w = draw_operation_guide(pygame_mod, screen, colors, font, margin, auto_step)
|
||||
guide_w = draw_operation_guide(pygame_mod, screen, colors, font, margin)
|
||||
draw_year_month_info(pygame_mod, screen, colors, font, margin, guide_w, world)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user