pygametool-rui 0.1.2
Pygame项目的实用程序包
License: MIT
Requires Python: >=3.10

# PygameTool

一个基于 `pygame` 的常用组件合集，覆盖文字渲染、按钮/单选框、输入框/下拉框、滚动画布、图形绘制、精灵管理、音频管理、消息弹窗等。专注“拿来即用”，与原生 `pygame` 事件循环无缝配合。

## 安装

```
pip install pygametool-rui
```

## 环境与导入

- Python ≥ 3.10（类型语法使用 `int | float`）
- `import PygameTool` 不会创建窗口或初始化音频/Tk；按需在你的程序里调用：
  - `pygame.init(); pygame.display.set_mode(...)`
  - 音频按需初始化，由 `MusicManager` 的内部懒加载完成
  - Tk 根窗口按需创建，由 `messageBox.show_message(...)` 首次调用时创建

## 快速开始

```
import pygame
from PygameTool import Text, Canvas

pygame.init()
screen = pygame.display.set_mode((640, 480))

text = Text("Hello PygameTool", x=50, y=50, font_size=28)
canvas = Canvas(screen)
canvas.addSprite(text)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        canvas.handle_event(event)
    screen.fill((30, 30, 30))
    canvas.draw()
    pygame.display.flip()

pygame.quit()
```

## 组件总览

- `Text`：多行文本、自动换行、行距、居中/非居中、字体/大小/加粗/斜体/背景色
- `Button`：可配置文字/边框/圆角/透明背景，点击回调
- `Radio`：单选框，支持队列分组互斥、文字偏移、点击回调
- `InputBox`：占位符、密码模式、数字限制、居中/左对齐、剪贴板、重复按键处理、回车回调
- `DropDown`：下拉选择（与 `Canvas`/事件循环协作）
- `SeekBar`：拖动条，最小/最大值、当前值显示、颜色配置
- `Rectangle`/`Circle`/`Line`：常见图形绘制，支持颜色字符串/HEX 自动转 RGB
- `Canvas`：限定区域绘制与滚动，裁剪绘制、只在激活画布内响应滚轮
- `Sprite`：图片加载与绘制，支持居中/缩放/遮罩/动画（GIF 帧控制等）
- `Scrollbar`/`starfield`：滚动条、星空效果（与 `Canvas`/绘制配合）
- `MusicManager`：音频懒加载、播放/暂停/恢复/停止、唯一键管理、定时播放、播放序列
- `messageBox`：基于 Tk 的消息弹窗，支持单/双按钮与自定义回调
- `PGMessageBox`：纯 Pygame 风格弹窗，支持背景图、底部固定按钮与事件绑定
- `functool`：颜色名/HEX 转 RGB、构造参数过滤等工具函数
- `starfield`：星空效果

## 常用示例

### 按钮与单选框

```
import pygame
from PygameTool import Button, Radio

pygame.init()
screen = pygame.display.set_mode((480, 360))

def on_click():
    print("clicked!")

btn = Button(screen, x=240, y=60, w=150, h=40, text="点我", func=on_click)
radio1 = Radio(screen, x=100, y=140, num_queue=1, txt="选项A")
radio2 = Radio(screen, x=180, y=140, num_queue=1, txt="选项B")

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        # 按钮/单选框通常各自有事件处理方法（如 click/handle），根据你的实现调用
    screen.fill((240, 240, 240))
    btn.draw()
    radio1.draw()
    radio2.draw()
    pygame.display.flip()
```

### 输入框与拖动条

```
import pygame
from PygameTool import InputBox, SeekBar

pygame.init()
screen = pygame.display.set_mode((600, 400))

ibox = InputBox(x=50, y=60, w=260, h=38, placeholder="输入文本...")
bar = SeekBar(x=50, y=140, w=260, h=12, Min=0, Max=100, value=50, color="DeepSkyBlue")

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        ibox.handle_event(event)
        # bar 的拖动在其内部处理，通常只需传递事件或在更新时调用其方法
    screen.fill((255, 255, 255))
    ibox.draw(screen)
    bar.draw(screen)
    pygame.display.flip()
```

### 图形与精灵

```
import pygame
from PygameTool import Rectangle, Circle, Line, Sprite

pygame.init()
screen = pygame.display.set_mode((640, 480))

rect = Rectangle(200, 100, 120, 60, fillet=10, color="#00AAFF", is_center=True)
circle = Circle(420, 120, 40, width=3, color="purple", fillColor="#FFEE88")
line = Line(60, 200, 580, 200, width=2, color="orange")
sprite = Sprite("./example.png", x=320, y=340, is_center=True, scale=0.5)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    screen.fill((28, 28, 28))
    rect.draw(screen)
    circle.draw(screen)
    line.draw(screen)
    sprite.draw(screen)
    pygame.display.flip()
```

### 音频与消息弹窗

```
from PygameTool import MusicManager, messageBox

key = MusicManager.play_sound("click.wav", voice=0.6, times=1)
is_yes = messageBox.show_message("继续操作吗？", title="提示", ok_button="确定", cancel_button="取消")
if is_yes:
    print("用户选择：确定")
```

## 事件循环与滚动画布

- 在 `Canvas` 中添加的元素（精灵/文本/下拉等），通过 `canvas.handle_event(event)` 传递事件；
- 只有点击激活的画布会响应滚轮滚动；绘制时自动裁剪到画布区域；
- 下拉元素绘制在最顶层，避免被裁剪；超出画布区域也可显示。

## 颜色输入规则

- 支持颜色名（如 `"red"`、`"orange"`）、HEX（如 `"#00AAFF"`）、RGB 元组（如 `(0, 170, 255)`）；
- 所有颜色输入会通过 `functool.hex_to_rgb(...)` 标准化为 RGB。

## 纯 Pygame 弹窗（PGMessageBox）

- 功能概述
  - 使用纯 `pygame` 绘制弹窗；支持背景图片（PNG/JPG/BMP/GIF 静态帧）
  - 底部固定三按钮：左`确定`、中`更新`（当提供 `update_text` 时显示）、右`取消`；右上角 `X` 等同取消
  - 每个按钮可绑定回调；按钮文字可自定义；`x,y` 为弹窗中心坐标

- 接口示例
  
  ```python
  import pygame
  from PygameTool import PGMessageBox
  
  pygame.init()
  screen = pygame.display.set_mode((800, 600))
  box = PGMessageBox(x=400, y=300, size=(480, 300), title="提示", message="是否继续？",
                     background_image=None, confirm_text="确定", cancel_text="取消")
  box.bind_confirm(lambda: print("confirm"))
  box.bind_cancel(lambda: print("cancel"))
  box.show()
  
  running = True
  while running:
      for event in pygame.event.get():
          if event.type == pygame.QUIT:
              running = False
          box.handle_event(event)
      screen.fill((30, 30, 30))
      box.draw(screen)
      pygame.display.flip()
  ```

- 关键参数
  - `x,y`：弹窗中心坐标；`size`：尺寸（`(w, h)` 或 `float` 相对屏幕宽度比例）
  - `title`、`message`：标题与正文；`Title_Center`、`Message_Center`：标题/正文居中（默认否）
  - `background_image`：背景图路径（可选）；`confirm_text/update_text/cancel_text`：按钮文字（`update_text=None` 时隐藏）

- 事件绑定
  - `bind_confirm(func)`、`bind_update(func)`、`bind_cancel(func)`；右上角 `X` 触发取消事件

- 限制与注意
  - 组件不阻塞事件循环；按钮固定在弹窗底部
  - 背景图按弹窗内部区域自适应缩放并居中裁剪；GIF 仅加载静态帧
  - 字体可用性依赖系统，必要时调整 `font_name`

## 许可

MIT License

## 更新说明（2025-11-16）

- Text 自动换行与排版优化
  - 使用 `pygame.font.Font.metrics` 进行逐字符度量并线性累计，复杂度 O(n)，显著提升长文本性能。
  - 行首标点避免：换行处遇标点会将前一个非空白字符与标点一起移动到下一行，避免行首出现标点；当两者宽度仍超出有效宽度时，放弃该策略以保证前进。
  - 最小宽度保障：有效宽度为 `max_width` 与“最小字符宽度 × 2”中的较大者，避免极端设置导致卡住或不前进。
  - 零宽/组合字符兼容：对 `advance <= 0` 的字符不计入宽度累计，避免度量异常影响换行。
  - API 不变：继续通过 `Text(text, x, y, max_width=..., line_spacing=...)` 使用；内部自动换行与分页逻辑兼容中文/英文混排。

- 使用建议
  - 在内容变更或区域尺寸改变时重新创建 `Text` 或调用 `change_text/change_font_size` 以刷新内部行缓存。
  - 为中文段落设置合适的 `max_width` 与 `line_spacing`，即可获得自然的换行与段落显示效果。