# MaixPy-UI-Lib:一款为 MaixPy 开发的轻量级 UI 组件库
[](https://github.com/aristorechina/MaixPy-UI-Lib/blob/main/LICENSE) [](https://github.com/aristorechina/MaixPy-UI-Lib)
本项目是一款为 MaixPy 开发的轻量级 UI 组件库,遵循 `Apache 2.0` 协议。
欢迎给本项目提pr,要是觉得好用的话请给本项目点个star⭐
---
## 🖼️功能展示
以下画面均截取自本项目的 `main.py`





---
## 📦 安装
`pip install maixpy-ui-lib`
---
## 🚀快速上手
您可以通过运行仓库中的示例程序 `examples/demo.py` 来快速熟悉本项目的基本功能和使用方法。
---
## 📖组件详解
### 1. 按钮 (Button)
按钮是最基本的交互组件,用于触发一个操作。
#### 使用方式
1. 创建一个 `ButtonManager` 实例。
2. 创建 `Button` 实例,定义其矩形区域、标签文本和回调函数。
3. 使用 `manager.add_button()` 将按钮实例添加到管理器中。
4. 在主循环中,调用 `manager.handle_events(img)` 来处理触摸事件并绘制按钮。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import Button, ButtonManager
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
cam = camera.Camera()
# 2. 定义回调函数
# 当按钮被点击时,这个函数会被调用
def on_button_click():
print("Hello, World! The button was clicked.")
# 你可以在这里执行任何操作,比如切换页面、拍照等
# 3. 初始化UI
# 创建一个按钮管理器
btn_manager = ButtonManager(ts, disp)
# 创建一个按钮实例
# rect: [x, y, 宽度, 高度]
# label: 按钮上显示的文字
# callback: 点击时调用的函数
hello_button = Button(
rect=[240, 200, 160, 80],
label="Click Me",
text_scale=2.0,
callback=on_button_click,
bg_color=(0, 120, 220), # 蓝色背景
pressed_color=(0, 80, 180), # 按下时深蓝色
text_color=(255, 255, 255) # 白色文字
)
# 将按钮添加到管理器
btn_manager.add_button(hello_button)
# 4. 主循环
print("Button example running. Press the button on the screen.")
while not app.need_exit():
img = cam.read()
# 在每一帧中,让管理器处理事件并绘制按钮
btn_manager.handle_events(img)
disp.show(img)
time.sleep(0.02) # 降低CPU使用率
```
#### `Button` 类
创建一个可交互的按钮组件。该组件可以响应触摸事件,并在按下时改变外观,释放时执行回调函数。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :----------------: | :--------------------: | :-----------------------------------------: | :---------------: |
| `rect` | `Sequence[int]` | 按钮的位置和尺寸 `[x, y, w, h]`。**必需**。 | - |
| `label` | `str` | 按钮上显示的文本。**必需**。 | - |
| `callback` | `Callable \| None` | 当按钮被点击时调用的函数。**必需**。 | - |
| `bg_color` | `Sequence[int] \| None` | 背景颜色 (R, G, B)。 | `(50, 50, 50)` |
| `pressed_color` | `Sequence[int] \| None` | 按下状态的背景颜色 (R, G, B)。 | `(0, 120, 220)` |
| `text_color` | `Sequence[int]` | 文本颜色 (R, G, B)。 | `(255, 255, 255)` |
| `border_color` | `Sequence[int]` | 边框颜色 (R, G, B)。 | `(200, 200, 200)` |
| `border_thickness` | `int` | 边框厚度(像素)。 | `2` |
| `text_scale` | `float` | 文本的缩放比例。 | `1.5` |
| `font` | `str \| None` | 使用的字体文件路径。 | `None` |
| `align_h` | `str` | 水平对齐方式 ('left', 'center', 'right')。 | `'center'` |
| `align_v` | `str` | 垂直对齐方式 ('top', 'center', 'bottom')。 | `'center'` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :-----------------: | :----------------------------------------------------------: | :--------------------------: |
| `draw(img)` | `img` (`maix.image.Image`): 将要绘制按钮的目标图像。 | 在指定的图像上绘制按钮。 |
| `handle_event(...)` | `x` (`int`): 触摸点的 X 坐标。<br>`y` (`int`): 触摸点的 Y 坐标。<br>`pressed` (`bool\|int`): 触摸屏是否被按下。<br>`img_w` (`int`): 图像缓冲区的宽度。<br>`img_h` (`int`): 图像缓冲区的高度。<br>`disp_w` (`int`): 显示屏的宽度。<br>`disp_h` (`int`): 显示屏的高度。 | 处理触摸事件并更新按钮状态。 |
#### `ButtonManager` 类
管理一组按钮的事件处理和绘制。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 |
| :----: | :-----------------------: | :--------------: |
| `ts` | `touchscreen.TouchScreen` | 触摸屏设备实例。**必需**。 |
| `disp` | `display.Display` | 显示设备实例。**必需**。 |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :------------------: | :----------------------------------------------: | :--------------------------------: |
| `add_button(button)` | `button` (`Button`): 要添加的 Button 实例。 | 向管理器中添加一个按钮。 |
| `handle_events(img)` | `img` (`maix.image.Image`): 绘制按钮的目标图像。 | 处理所有受管按钮的事件并进行绘制。 |
---
### 2. 滑块 (Slider)
滑块允许用户在一个连续的范围内选择一个值。
#### 使用方式
1. 创建一个 `SliderManager` 实例。
2. 创建 `Slider` 实例,定义其区域、数值范围和回调函数。
3. 使用 `manager.add_slider()` 添加滑块。
4. 在主循环中,调用 `manager.handle_events(img)`。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import Slider, SliderManager
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
cam = camera.Camera()
# 全局变量,用于存储滑块的值
current_brightness = 128
# 2. 定义回调函数
# 当滑块的值改变时,这个函数会被调用
def on_slider_update(value):
global current_brightness
current_brightness = value
print(f"Slider value updated to: {value}")
# 3. 初始化UI
slider_manager = SliderManager(ts, disp)
brightness_slider = Slider(
rect=[50, 230, 540, 20],
scale=2.0,
min_val=0,
max_val=255,
default_val=current_brightness,
callback=on_slider_update,
label="Slider"
)
slider_manager.add_slider(brightness_slider)
# 4. 主循环
print("Slider example running. Drag the slider.")
title_color = image.Color.from_rgb(255, 255, 255)
while not app.need_exit():
img = cam.read()
# 实时显示滑块的值
img.draw_string(20, 20, f"Value: {current_brightness}", scale=2.0, color=title_color)
# 处理滑块事件并绘制
slider_manager.handle_events(img)
disp.show(img)
time.sleep(0.02)
```
#### `Slider` 类
创建一个可拖动的滑块组件,用于在一定范围内选择一个值。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :--------------------: | :---------------: | :-----------------------------------------: | :---------------: |
| `rect` | `Sequence[int]` | 滑块的位置和尺寸 `[x, y, w, h]`。**必需**。 | - |
| `scale` | `float` | 滑块的整体缩放比例。 | `1.0` |
| `min_val` | `int` | 滑块的最小值。 | `0` |
| `max_val` | `int` | 滑块的最大值。 | `100` |
| `default_val` | `int` | 滑块的默认值。 | `50` |
| `callback` | `Callable \| None` | 值改变时调用的函数,接收新值作为参数。 | `None` |
| `label` | `str` | 滑块上方的标签文本。 | `""` |
| `track_color` | `Sequence[int]` | 滑轨背景颜色 (R, G, B)。 | `(60, 60, 60)` |
| `progress_color` | `Sequence[int]` | 滑轨进度条颜色 (R, G, B)。 | `(0, 120, 220)` |
| `handle_color` | `Sequence[int]` | 滑块手柄颜色 (R, G, B)。 | `(255, 255, 255)` |
| `handle_border_color` | `Sequence[int]` | 滑块手柄边框颜色 (R, G, B)。 | `(100, 100, 100)` |
| `handle_pressed_color` | `Sequence[int]` | 按下时手柄颜色 (R, G, B)。 | `(220, 220, 255)` |
| `label_color` | `Sequence[int]` | 标签文本颜色 (R, G, B)。 | `(200, 200, 200)` |
| `tooltip_bg_color` | `Sequence[int]` | 拖动时提示框背景色 (R, G, B)。 | `(0, 0, 0)` |
| `tooltip_text_color` | `Sequence[int]` | 拖动时提示框文本颜色 (R, G, B)。 | `(255, 255, 255)` |
| `show_tooltip_on_drag` | `bool \| int` | 是否在拖动时显示数值提示框。 | `True` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :-----------------: | :----------------------------------------------------------: | :--------------------------: |
| `draw(img)` | `img` (`maix.image.Image`): 将要绘制滑块的目标图像。 | 在指定的图像上绘制滑块。 |
| `handle_event(...)` | `x` (`int`): 触摸点的 X 坐标。<br>`y` (`int`): 触摸点的 Y 坐标。<br>`pressed` (`bool\|int`): 触摸屏是否被按下。<br>`img_w` (`int`): 图像缓冲区的宽度。<br>`img_h` (`int`): 图像缓冲区的高度。<br>`disp_w` (`int`): 显示屏的宽度。<br>`disp_h` (`int`): 显示屏的高度。 | 处理触摸事件并更新滑块状态。 |
#### `SliderManager` 类
管理一组滑块的事件处理和绘制。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 |
| :----: | :-----------------------: | :--------------: |
| `ts` | `touchscreen.TouchScreen` | 触摸屏设备实例。**必需**。 |
| `disp` | `display.Display` | 显示设备实例。**必需**。 |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :------------------: | :----------------------------------------------: | :--------------------------------: |
| `add_slider(slider)` | `slider` (`Slider`): 要添加的 Slider 实例。 | 向管理器中添加一个滑块。 |
| `handle_events(img)` | `img` (`maix.image.Image`): 绘制滑块的目标图像。 | 处理所有受管滑块的事件并进行绘制。 |
---
### 3. 开关 (Switch)
一个具有“开”和“关”两种状态的切换控件。
#### 使用方式
1. 创建一个 `SwitchManager` 实例。
2. 创建 `Switch` 实例,定义其位置、初始状态和回调函数。
3. 使用 `manager.add_switch()` 添加开关。
4. 在主循环中,调用 `manager.handle_events(img)`。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import Switch, SwitchManager
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
cam = camera.Camera()
# 全局变量,用于存储开关状态
is_light_on = False
# 2. 定义回调函数
def on_switch_toggle(is_on):
global is_light_on
is_light_on = is_on
status = "ON" if is_on else "OFF"
print(f"Switch toggled. Light is now {status}.")
# 3. 初始化UI
switch_manager = SwitchManager(ts, disp)
light_switch = Switch(
position=[280, 190],
scale=2.0,
is_on=is_light_on,
callback=on_switch_toggle
)
switch_manager.add_switch(light_switch)
# 4. 主循环
print("Switch example running. Tap the switch.")
title_color = image.Color.from_rgb(255, 255, 255)
status_on_color = image.Color.from_rgb(30, 200, 30)
status_off_color = image.Color.from_rgb(80, 80, 80)
while not app.need_exit():
img = cam.read()
# 根据开关状态显示一个状态指示灯
status_text = "Light: ON" if is_light_on else "Light: OFF"
status_color = status_on_color if is_light_on else status_off_color
img.draw_string(20, 20, status_text, scale=1.5, color=title_color)
img.draw_rect(310, 280, 50, 50, color=status_color, thickness=-1)
switch_manager.handle_events(img)
disp.show(img)
time.sleep(0.02)
```
#### `Switch` 类
创建一个开关(Switch)组件,用于在开/关两种状态之间切换。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :----------------------: | :---------------: | :--------------------------------------------------: | :---------------: |
| `position` | `Sequence[int]` | 开关的左上角坐标 `[x, y]`。**必需**。 | - |
| `scale` | `float` | 开关的整体缩放比例。 | `1.0` |
| `is_on` | `bool \| int` | 开关的初始状态,True 为开。 | `False` |
| `callback` | `Callable \| None` | 状态切换时调用的函数,接收一个布尔值参数表示新状态。 | `None` |
| `on_color` | `Sequence[int]` | 开启状态下的背景颜色 (R, G, B)。 | `(30, 200, 30)` |
| `off_color` | `Sequence[int]` | 关闭状态下的背景颜色 (R, G, B)。 | `(100, 100, 100)` |
| `handle_color` | `Sequence[int]` | 手柄的颜色 (R, G, B)。 | `(255, 255, 255)` |
| `handle_pressed_color` | `Sequence[int]` | 按下时手柄的颜色 (R, G, B)。 | `(220, 220, 255)` |
| `handle_radius_increase` | `int` | 按下时手柄半径增加量。 | `2` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :-----------------: | :----------------------------------------------------------: | :------------------------------: |
| `toggle()` | - | 切换开关的状态,并执行回调函数。 |
| `draw(img)` | `img` (`maix.image.Image`): 将要绘制开关的目标图像。 | 在指定的图像上绘制开关。 |
| `handle_event(...)` | `x` (`int`): 触摸点的 X 坐标。<br>`y` (`int`): 触摸点的 Y 坐标。<br>`pressed` (`bool\|int`): 触摸屏是否被按下。<br>`img_w` (`int`): 图像缓冲区的宽度。<br>`img_h` (`int`): 图像缓冲区的高度。<br>`disp_w` (`int`): 显示屏的宽度。<br>`disp_h` (`int`): 显示屏的高度。 | 处理触摸事件并更新开关状态。 |
#### `SwitchManager` 类
管理一组开关的事件处理和绘制。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 |
| :----: | :-----------------------: | :--------------: |
| `ts` | `touchscreen.TouchScreen` | 触摸屏设备实例。**必需**。 |
| `disp` | `display.Display` | 显示设备实例。**必需**。 |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :------------------: | :----------------------------------------------: | :--------------------------------: |
| `add_switch(switch)` | `switch` (`Switch`): 要添加的 Switch 实例。 | 向管理器中添加一个开关。 |
| `handle_events(img)` | `img` (`maix.image.Image`): 绘制开关的目标图像。 | 处理所有受管开关的事件并进行绘制。 |
---
### 4. 复选框 (Checkbox)
允许用户从一组选项中进行多项选择。
#### 使用方式
1. 创建一个 `CheckboxManager` 实例。
2. 创建多个 `Checkbox` 实例,每个都有独立的回调和状态。
3. 使用 `manager.add_checkbox()` 添加它们。
4. 在主循环中,调用 `manager.handle_events(img)`。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import Checkbox, CheckboxManager
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
cam = camera.Camera()
# 全局字典,用于存储每个复选框的状态
options = {'Checkbox A': True, 'Checkbox B': False}
# 2. 定义回调函数 (使用闭包来区分是哪个复选框被点击)
def create_checkbox_callback(key):
def on_check_change(is_checked):
options[key] = is_checked
print(f"Option '{key}' is now {'checked' if is_checked else 'unchecked'}.")
return on_check_change
# 3. 初始化UI
checkbox_manager = CheckboxManager(ts, disp)
checkbox_a = Checkbox(
position=[80, 150],
label="Checkbox A",
is_checked=options['Checkbox A'],
callback=create_checkbox_callback('Checkbox A'),
scale=2.0
)
checkbox_b = Checkbox(
position=[80, 300],
label="Checkbox B",
is_checked=options['Checkbox B'],
callback=create_checkbox_callback('Checkbox B'),
scale=2.0
)
checkbox_manager.add_checkbox(checkbox_a)
checkbox_manager.add_checkbox(checkbox_b)
# 4. 主循环
print("Checkbox example running. Tap the checkboxes.")
title_color = image.Color.from_rgb(255, 255, 255)
while not app.need_exit():
img = cam.read()
# 显示当前状态
a_status = "ON" if options['Checkbox A'] else "OFF"
b_status = "ON" if options['Checkbox B'] else "OFF"
img.draw_string(20, 20, f"Checkbox A: {a_status}, Checkbox B: {b_status}", scale=1.5, color=title_color)
checkbox_manager.handle_events(img)
disp.show(img)
time.sleep(0.02)
```
#### `Checkbox` 类
创建一个复选框(Checkbox)组件,可独立选中或取消。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :-----------------: | :---------------: | :--------------------------------------------------: | :---------------: |
| `position` | `Sequence[int]` | 复选框的左上角坐标 `[x, y]`。**必需**。 | - |
| `label` | `str` | 复选框旁边的标签文本。**必需**。 | - |
| `scale` | `float` | 复选框的整体缩放比例。 | `1.0` |
| `is_checked` | `bool \| int` | 复选框的初始状态,True 为选中。 | `False` |
| `callback` | `Callable \| None` | 状态切换时调用的函数,接收一个布尔值参数表示新状态。 | `None` |
| `box_color` | `Sequence[int]` | 未选中时方框的颜色 (R, G, B)。 | `(200, 200, 200)` |
| `box_checked_color` | `Sequence[int]` | 选中时方框的颜色 (R, G, B)。 | `(0, 120, 220)` |
| `check_color` | `Sequence[int]` | 选中标记(对勾)的颜色 (R, G, B)。 | `(255, 255, 255)` |
| `text_color` | `Sequence[int]` | 标签文本的颜色 (R, G, B)。 | `(200, 200, 200)` |
| `box_thickness` | `int` | 方框边框的厚度。 | `2` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :-----------------: | :----------------------------------------------------------: | :--------------------------------: |
| `toggle()` | - | 切换复选框的选中状态,并执行回调。 |
| `draw(img)` | `img` (`maix.image.Image`): 将要绘制复选框的目标图像。 | 在指定的图像上绘制复选框。 |
| `handle_event(...)` | `x` (`int`): 触摸点的 X 坐标。<br>`y` (`int`): 触摸点的 Y 坐标。<br>`pressed` (`bool\|int`): 触摸屏是否被按下。<br>`img_w` (`int`): 图像缓冲区的宽度。<br>`img_h` (`int`): 图像缓冲区的高度。<br>`disp_w` (`int`): 显示屏的宽度。<br>`disp_h` (`int`): 显示屏的高度。 | 处理触摸事件并更新复选框状态。 |
#### `CheckboxManager` 类
管理一组复选框的事件处理和绘制。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 |
| :----: | :-----------------------: | :--------------: |
| `ts` | `touchscreen.TouchScreen` | 触摸屏设备实例。**必需**。 |
| `disp` | `display.Display` | 显示设备实例。**必需**。 |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :----------------------: | :------------------------------------------------: | :----------------------------------: |
| `add_checkbox(checkbox)` | `checkbox` (`Checkbox`): 要添加的 Checkbox 实例。 | 向管理器中添加一个复选框。 |
| `handle_events(img)` | `img` (`maix.image.Image`): 绘制复选框的目标图像。 | 处理所有受管复选框的事件并进行绘制。 |
---
### 5. 单选框 (RadioButton)
允许用户从一组互斥的选项中只选择一项。
#### 使用方式
1. 创建一个 `RadioManager` 实例。**注意**:`RadioManager` 构造时需要接收 `default_value` 和一个全局 `callback`。
2. 创建 `RadioButton` 实例,每个按钮必须有唯一的 `value`。
3. 使用 `manager.add_radio()` 添加它们。
4. 在主循环中,调用 `manager.handle_events(img)`。管理器会自动处理互斥逻辑。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import RadioButton, RadioManager
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
cam = camera.Camera()
# 全局变量,存储当前选择的模式
current_mode = None
# 2. 定义回调函数
# 这个回调由 RadioManager 调用,传入被选中项的 value
def on_mode_change(selected_value):
global current_mode
current_mode = selected_value
print(f"Mode changed to: {selected_value}")
# 3. 初始化UI
# 创建 RadioManager,并传入默认值和回调
radio_manager = RadioManager(ts, disp,
default_value=current_mode,
callback=on_mode_change)
# 创建三个 RadioButton 实例,注意它们的 value 是唯一的
radio_a = RadioButton(position=[80, 100], label="Mode A", value="Mode A", scale=2.0)
radio_b = RadioButton(position=[80, 200], label="Mode B", value="Mode B", scale=2.0)
radio_c = RadioButton(position=[80, 300], label="Mode C", value="Mode C", scale=2.0)
# 将它们都添加到管理器中
radio_manager.add_radio(radio_a)
radio_manager.add_radio(radio_b)
radio_manager.add_radio(radio_c)
# 4. 主循环
print("Radio button example running. Select a mode.")
title_color = image.Color.from_rgb(255, 255, 255)
while not app.need_exit():
img = cam.read()
img.draw_string(20, 20, f"Current: {current_mode}", scale=1.8, color=title_color)
radio_manager.handle_events(img)
disp.show(img)
time.sleep(0.02)
```
#### `RadioButton` 类
创建一个单选框(RadioButton)项。通常与 `RadioManager` 结合使用。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :---------------------: | :-------------: | :-------------------------------: | :---------------: |
| `position` | `Sequence[int]` | 单选框圆圈的左上角坐标 `[x, y]`。**必需**。 | - |
| `label` | `str` | 按钮旁边的标签文本。**必需**。 | - |
| `value` | `any` | 与此单选框关联的唯一值。**必需**。 | - |
| `scale` | `float` | 组件的整体缩放比例。 | `1.0` |
| `circle_color` | `Sequence[int]` | 未选中时圆圈的颜色 (R, G, B)。 | `(200, 200, 200)` |
| `circle_selected_color` | `Sequence[int]` | 选中时圆圈的颜色 (R, G, B)。 | `(0, 120, 220)` |
| `dot_color` | `Sequence[int]` | 选中时中心圆点的颜色 (R, G, B)。 | `(255, 255, 255)` |
| `text_color` | `Sequence[int]` | 标签文本的颜色 (R, G, B)。 | `(200, 200, 200)` |
| `circle_thickness` | `int` | 圆圈边框的厚度。 | `2` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :---------: | :----------------------------------------------------: | :------------------------: |
| `draw(img)` | `img` (`maix.image.Image`): 将要绘制单选框的目标图像。 | 在指定的图像上绘制单选框。 |
#### `RadioManager` 类
管理一个单选框组,确保只有一个按钮能被选中。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :-------------: | :-----------------------: | :------------------------------------------------: | :----: |
| `ts` | `touchscreen.TouchScreen` | 触摸屏设备实例。**必需**。 | - |
| `disp` | `display.Display` | 显示设备实例。**必需**。 | - |
| `default_value` | `any` | 默认选中的按钮的值。 | `None` |
| `callback` | `Callable \| None` | 选中项改变时调用的函数,接收新选中项的值作为参数。 | `None` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 |
| :------------------: | :--------------------------------------------------: | :------------------------------: |
| `add_radio(radio)` | `radio` (`RadioButton`): 要添加的 RadioButton 实例。 | 向管理器中添加一个单选框。 |
| `handle_events(img)` | `img` (`maix.image.Image`): 绘制单选框的目标图像。 | 处理所有单选框的事件并进行绘制。 |
---
### 6. 分辨率适配器 (ResolutionAdapter)
一个辅助工具类,用于自动适配不同分辨率的屏幕,以保持UI布局的一致性。
#### 使用方式
1. 创建一个 `ResolutionAdapter` 实例,并指定目标屏幕尺寸和可选的设计基础分辨率。
2. 基于您的设计基础分辨率,定义组件的原始 `rect`、`position` 等参数。
3. 调用 `adapter.scale_rect()` 等方法,将原始参数转换为适配后的值。
4. 使用转换后的值来创建您的UI组件。
#### 示例
```python
from maix import display, camera, app, touchscreen
from maixpy_ui import Button, ButtonManager, ResolutionAdapter
import time
# 1. 初始化硬件
disp = display.Display()
ts = touchscreen.TouchScreen()
# cam = camera.Camera(640,480)
cam = camera.Camera(320,240)
# 2. 创建分辨率适配器,并明确指定我们的设计是基于 640x480 的
adapter = ResolutionAdapter(
display_width=cam.width(),
display_height=cam.height(),
base_width=640,
base_height=480
)
# 3. 基于 640x480 的画布来定义组件参数
original_rect = [160, 200, 320, 80]
original_font_scale = 3.0
# 4. 使用适配器转换参数
scaled_rect = adapter.scale_rect(original_rect)
scaled_font_size = adapter.scale_value(original_font_scale)
# 5. 使用缩放后的值创建组件
btn_manager = ButtonManager(ts, disp)
adapted_button = Button(
rect=scaled_rect,
label="Big Button",
text_scale=scaled_font_size,
callback=lambda: print("Adapted button clicked!")
)
btn_manager.add_button(adapted_button)
# 6. 主循环
print("ResolutionAdapter example running (640x480 base).")
while not app.need_exit():
img = cam.read()
btn_manager.handle_events(img)
disp.show(img)
time.sleep(0.02)
```
#### `ResolutionAdapter` 类
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :--------------: | :---: | :--------------------------: | :----: |
| `display_width` | `int` | 目标显示屏的宽度。**必需**。 | - |
| `display_height` | `int` | 目标显示屏的高度。**必需**。 | - |
| `base_width` | `int` | UI设计的基准宽度。 | `320` |
| `base_height` | `int` | UI设计的基准高度。 | `240` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 | 返回值 |
| :-------------------------: | :---------------------------------------------------------: | :--------------------------------: | :-------------: |
| `scale_position(x, y)` | `x` (`int`): 原始 X 坐标。<br>`y` (`int`): 原始 Y 坐标。 | 缩放一个坐标点 (x, y)。 | `Sequence[int]` |
| `scale_size(width, height)` | `width` (`int`): 原始宽度。<br>`height` (`int`): 原始高度。 | 缩放一个尺寸 (width, height)。 | `Sequence[int]` |
| `scale_rect(rect)` | `rect` (`list[int]`): 原始矩形 `[x, y, w, h]`。 | 缩放一个矩形。 | `Sequence[int]` |
| `scale_value(value)` | `value` (`int\|float`): 原始数值。 | 缩放一个通用数值,如半径、厚度等。 | `float` |
### 7. 页面与 UI 管理器 (Page and UIManager)
用于构建多页面应用,并管理页面间的树型导航。
#### 使用方式
1. 创建一个全局的 `UIManager` 实例。
2. 定义继承自 `Page` 的自定义页面类,并在构造函数中为页面命名。
3. 在父页面中,创建子页面的实例,并使用 `parent.add_child()` 方法来构建页面树。
4. 使用 `ui_manager.set_root_page()` 设置应用的根页面。
5. 在页面内部,通过 `self.ui_manager` 调用导航方法,如 `navigate_to_child()`、`navigate_to_parent()`、`navigate_to_root()` 等。
6. 在主循环中,持续调用 `ui_manager.update(img)` 来驱动当前活动页面的更新和绘制。
#### 示例
```python
from maix import display, camera, app, touchscreen, image
from maixpy_ui import Page, UIManager, Button, ButtonManager
import time
# --------------------------------------------------------------------------
# 1. 初始化硬件 & 全局资源
# --------------------------------------------------------------------------
disp = display.Display()
ts = touchscreen.TouchScreen()
screen_w, screen_h = disp.width(), disp.height()
cam = camera.Camera(screen_w, screen_h)
# 预创建颜色对象
COLOR_WHITE = image.Color(255, 255, 255)
COLOR_GREY = image.Color(150, 150, 150)
COLOR_GREEN = image.Color(30, 200, 30)
COLOR_BLUE = image.Color(0, 120, 220)
def get_background():
if cam:
img = cam.read()
if img: return img
return image.new(size=(screen_w, screen_h), color=(10, 20, 30))
# --------------------------------------------------------------------------
# 2. 定义页面类
# --------------------------------------------------------------------------
class BasePage(Page):
"""一个包含通用功能的页面基类,例如绘制调试信息"""
def draw_path_info(self, img: image.Image):
"""在屏幕右下角绘制当前的导航路径"""
info = self.ui_manager.get_navigation_info()
path_str = " > ".join(info['current_path'])
# 计算文本尺寸
text_scale = 1.0
text_size = image.string_size(path_str, scale=text_scale)
# 计算绘制位置(右下角,留出一些边距)
padding = 10
text_x = screen_w - text_size.width() - padding
text_y = screen_h - text_size.height() - padding
# 绘制文本
img.draw_string(text_x, text_y, path_str, scale=text_scale, color=COLOR_GREY)
def update(self, img: image.Image):
"""子类应该重写此方法,并在末尾调用 super().update(img) 来绘制调试信息"""
self.draw_path_info(img)
class PageA1(BasePage):
"""最深层的页面"""
def __init__(self, ui_manager):
super().__init__(ui_manager, name="page_a1")
self.btn_manager = ButtonManager(ts, disp)
self.btn_manager.add_button(Button([40, 150, 400, 80], "Back to Parent (-> Page A)", lambda: self.ui_manager.navigate_to_parent()))
self.btn_manager.add_button(Button([40, 250, 400, 80], "Go Back in History", lambda: self.ui_manager.go_back()))
self.btn_manager.add_button(Button([40, 350, 400, 80], "Go to Root (Home)", lambda: self.ui_manager.navigate_to_root(), bg_color=COLOR_GREEN))
def update(self, img):
img.draw_string(20, 20, "Page A.1 (Deepest)", scale=2.0, color=COLOR_WHITE)
history = self.ui_manager.navigation_history
prev_page_name = history[-1].name if history else "None"
img.draw_string(20, 80, f"'Go Back' will return to '{prev_page_name}'.", scale=1.2, color=COLOR_GREY)
self.btn_manager.handle_events(img)
super().update(img) # 调用基类的方法来绘制路径信息
class PageA(BasePage):
"""中间层页面 A"""
def __init__(self, ui_manager):
super().__init__(ui_manager, name="page_a")
self.btn_manager = ButtonManager(ts, disp)
self.btn_manager.add_button(Button([80, 150, 350, 80], "Go to Page A.1", lambda: self.ui_manager.navigate_to_child("page_a1")))
self.btn_manager.add_button(Button([20, 400, 250, 80], "Back to Parent", lambda: self.ui_manager.navigate_to_parent()))
self.add_child(PageA1(self.ui_manager))
def update(self, img):
img.draw_string(20, 20, "Page A", scale=2.5, color=COLOR_WHITE)
self.btn_manager.handle_events(img)
super().update(img)
class PageB(BasePage):
"""中间层页面 B"""
def __init__(self, ui_manager):
super().__init__(ui_manager, name="page_b")
self.btn_manager = ButtonManager(ts, disp)
self.btn_manager.add_button(Button([80, 150, 350, 80], "Jump to Page A.1 by Path", lambda: self.ui_manager.navigate_to_path(["page_a", "page_a1"])))
self.btn_manager.add_button(Button([20, 400, 250, 80], "Back to Parent", lambda: self.ui_manager.navigate_to_parent()))
def update(self, img):
img.draw_string(20, 20, "Page B", scale=2.5, color=COLOR_WHITE)
img.draw_string(20, 80, "From here, we'll jump to A.1.", scale=1.2, color=COLOR_GREY)
img.draw_string(20, 110, "This will make 'Go Back' and 'Back to Parent' different on the next page.", scale=1.2, color=COLOR_GREY)
self.btn_manager.handle_events(img)
super().update(img)
class RootPage(BasePage):
"""根页面"""
def __init__(self, ui_manager):
super().__init__(ui_manager, name="root")
self.btn_manager = ButtonManager(ts, disp)
self.btn_manager.add_button(Button([80, 150, 350, 80], "Path 1: Go to Page A", lambda: self.ui_manager.navigate_to_child("page_a")))
self.btn_manager.add_button(Button([80, 300, 350, 80], "Path 2: Go to Page B", lambda: self.ui_manager.navigate_to_child("page_b")))
self.add_child(PageA(self.ui_manager))
self.add_child(PageB(self.ui_manager))
def update(self, img):
img.draw_string(20, 20, "Root Page (Home)", scale=2.5, color=COLOR_WHITE)
img.draw_string(20, 80, "Try both paths to see how 'Go Back' behaves differently.", scale=1.2, color=COLOR_GREY)
self.btn_manager.handle_events(img)
super().update(img) # 调用基类的方法来绘制路径信息
# --------------------------------------------------------------------------
# 3. 主程序逻辑
# --------------------------------------------------------------------------
if __name__ == "__main__":
ui_manager = UIManager()
root_page = RootPage(ui_manager)
ui_manager.set_root_page(root_page)
print("Navigation demo with persistent path display running.")
while not app.need_exit():
img = get_background()
ui_manager.update(img)
disp.show(img)
time.sleep(0.02)
```
#### `Page` 类
页面(Page)的基类,支持树型父子节点结构。所有具体的UI页面都应继承此类。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :--------: | :---------: | :-------------------------------------------: | :----: |
| `ui_manager` | `UIManager` | 用于页面导航的 UIManager 实例。**必需**。 | - |
| `name` | `str` | 页面的唯一名称标识符,用于在父页面中查找。 | `""` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 | 返回值 |
| :-----------------: | :--------------------------------------------: | :------------------------------------------------------: | :--------------: |
| `add_child(page)` | `page` (`Page`): 要添加的子页面实例。 | 将一个页面添加为当前页面的子节点,以构建页面树。 | - |
| `remove_child(page)` | `page` (`Page`): 要移除的子页面实例。 | 从当前页面移除一个子节点。 | `bool` |
| `get_child(name)` | `name` (`str`): 子页面的名称。 | 根据名称获取子页面,用于自定义导航逻辑。 | `Page \| None` |
| `on_enter()` | - | 当页面进入视图时调用。子类可重写以实现初始化逻辑。 | - |
| `on_exit()` | - | 当页面离开视图时调用。子类可重写以实现清理逻辑。 | - |
| `on_child_enter()` | `child` (`Page`): 进入视图的子页面。 | 当此页面的一个子页面进入视图时调用。父页面可重写。 | - |
| `on_child_exit()` | `child` (`Page`): 离开视图的子页面。 | 当此页面的一个子页面离开视图时调用。父页面可重写。 | - |
| `update(img)` | `img` (`maix.image.Image`): 用于绘制的图像缓冲区。 | 每帧调用的更新和绘制方法。**子类必须重写此方法**。 | - |
#### `UIManager` 类
UI 管理器,基于树型页面结构提供灵活的导航功能。
##### 构造函数: `__init__`
| 参数 | 类型 | 描述 | 默认值 |
| :-------: | :---------: | :--------------------------: | :----: |
| `root_page` | `Page \| None` | 根页面实例,如果为None则需要后续设置。 | `None` |
##### 方法 (Methods)
| 方法 | 参数 | 描述 | 返回值 |
|:-----------------------------:|:------------------------------------------:|:------------------------------------------------------------:|:--------------------:|
| `set_root_page(page)` | `page` (`Page`): 新的根页面实例。 | 设置或重置UI管理器的根页面,并清空历史。 | `None` |
| `get_current_page()` | - | 获取当前活动的页面。 | `Page \| None` |
| `navigate_to_child(name)` | `name` (`str`): 子页面的名称。 | 导航到当前页面的指定名称的子页面。 | `bool` |
| `navigate_to_parent()` | - | 导航到当前页面的父页面。 | `bool` |
| `navigate_to_root()` | - | 直接导航到树的根页面。 | `bool` |
| `navigate_to_path(path)` | `path` (`List[str]`): 从根页面开始的绝对路径。 | 根据绝对路径导航到指定页面。 | `bool` |
| `navigate_to_relative_path(path)` | `path` (`List[str]`): 从当前页面开始的相对路径。 | 根据相对路径导航到指定页面。 | `bool` |
| `navigate_to_page(target_page)` | `target_page` (`Page`): 目标页面实例。 | 直接导航到指定页面。 | `bool` |
| `go_back()` | - | 返回到导航历史记录中的前一个页面。 | `bool` |
| `remove_page(page)` | `page` (`Page`): 要移除的页面实例。 | 移除指定的页面,并从父页面子页面列表中删除。 | `bool` |
| `clear_history()` | - | 清空导航历史记录。 | `None` |
| `get_current_path()` | - | 获取当前页面的完整路径。 | `List[str]` |
| `get_navigation_info()` | - | 获取包含当前路径、历史深度等信息的字典,用于调试或显示。 | `dict` |
| `update(img)` | `img` (`maix.image.Image`): 用于绘制的图像缓冲区。 | 更新当前活动页面的状态。此方法应在主循环中每帧调用。 | `None` |
---
## ⚖️许可协议
本项目基于 **Apache License, Version 2.0** 许可。详细信息请参阅代码文件中的许可证说明。
Raw data
{
"_id": null,
"home_page": null,
"name": "maixpy-ui-lib",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "embedded, gui, maixpy, micropython, ui",
"author": "Aristore, levi_jia, HYKMAX",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/5b/64/b587dbed43c80f735dd6de2cd0748102eb7f22271d2b68df708707a27f03/maixpy_ui_lib-2.4.tar.gz",
"platform": null,
"description": "# MaixPy-UI-Lib\uff1a\u4e00\u6b3e\u4e3a MaixPy \u5f00\u53d1\u7684\u8f7b\u91cf\u7ea7 UI \u7ec4\u4ef6\u5e93\n\n[](https://github.com/aristorechina/MaixPy-UI-Lib/blob/main/LICENSE) [](https://github.com/aristorechina/MaixPy-UI-Lib)\n\n\u672c\u9879\u76ee\u662f\u4e00\u6b3e\u4e3a MaixPy \u5f00\u53d1\u7684\u8f7b\u91cf\u7ea7 UI \u7ec4\u4ef6\u5e93\uff0c\u9075\u5faa `Apache 2.0` \u534f\u8bae\u3002\n\n\u6b22\u8fce\u7ed9\u672c\u9879\u76ee\u63d0pr\uff0c\u8981\u662f\u89c9\u5f97\u597d\u7528\u7684\u8bdd\u8bf7\u7ed9\u672c\u9879\u76ee\u70b9\u4e2astar\u2b50\n\n---\n\n## \ud83d\uddbc\ufe0f\u529f\u80fd\u5c55\u793a\n\n\u4ee5\u4e0b\u753b\u9762\u5747\u622a\u53d6\u81ea\u672c\u9879\u76ee\u7684 `main.py`\n\n\n\n\n\n\n\n\n\n\n\n---\n\n## \ud83d\udce6 \u5b89\u88c5\n\n`pip install maixpy-ui-lib`\n\n---\n\n## \ud83d\ude80\u5feb\u901f\u4e0a\u624b\n\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u8fd0\u884c\u4ed3\u5e93\u4e2d\u7684\u793a\u4f8b\u7a0b\u5e8f `examples/demo.py` \u6765\u5feb\u901f\u719f\u6089\u672c\u9879\u76ee\u7684\u57fa\u672c\u529f\u80fd\u548c\u4f7f\u7528\u65b9\u6cd5\u3002\n\n---\n\n## \ud83d\udcd6\u7ec4\u4ef6\u8be6\u89e3\n\n### 1. \u6309\u94ae (Button)\n\n\u6309\u94ae\u662f\u6700\u57fa\u672c\u7684\u4ea4\u4e92\u7ec4\u4ef6\uff0c\u7528\u4e8e\u89e6\u53d1\u4e00\u4e2a\u64cd\u4f5c\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a `ButtonManager` \u5b9e\u4f8b\u3002\n2. \u521b\u5efa `Button` \u5b9e\u4f8b\uff0c\u5b9a\u4e49\u5176\u77e9\u5f62\u533a\u57df\u3001\u6807\u7b7e\u6587\u672c\u548c\u56de\u8c03\u51fd\u6570\u3002\n3. \u4f7f\u7528 `manager.add_button()` \u5c06\u6309\u94ae\u5b9e\u4f8b\u6dfb\u52a0\u5230\u7ba1\u7406\u5668\u4e2d\u3002\n4. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u8c03\u7528 `manager.handle_events(img)` \u6765\u5904\u7406\u89e6\u6478\u4e8b\u4ef6\u5e76\u7ed8\u5236\u6309\u94ae\u3002\n\n#### \u793a\u4f8b\n\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import Button, ButtonManager\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\ncam = camera.Camera()\n\n# 2. \u5b9a\u4e49\u56de\u8c03\u51fd\u6570\n# \u5f53\u6309\u94ae\u88ab\u70b9\u51fb\u65f6\uff0c\u8fd9\u4e2a\u51fd\u6570\u4f1a\u88ab\u8c03\u7528\ndef on_button_click():\n print(\"Hello, World! The button was clicked.\")\n # \u4f60\u53ef\u4ee5\u5728\u8fd9\u91cc\u6267\u884c\u4efb\u4f55\u64cd\u4f5c\uff0c\u6bd4\u5982\u5207\u6362\u9875\u9762\u3001\u62cd\u7167\u7b49\n\n# 3. \u521d\u59cb\u5316UI\n# \u521b\u5efa\u4e00\u4e2a\u6309\u94ae\u7ba1\u7406\u5668\nbtn_manager = ButtonManager(ts, disp)\n\n# \u521b\u5efa\u4e00\u4e2a\u6309\u94ae\u5b9e\u4f8b\n# rect: [x, y, \u5bbd\u5ea6, \u9ad8\u5ea6]\n# label: \u6309\u94ae\u4e0a\u663e\u793a\u7684\u6587\u5b57\n# callback: \u70b9\u51fb\u65f6\u8c03\u7528\u7684\u51fd\u6570\nhello_button = Button(\n rect=[240, 200, 160, 80],\n label=\"Click Me\",\n text_scale=2.0,\n callback=on_button_click,\n bg_color=(0, 120, 220), # \u84dd\u8272\u80cc\u666f\n pressed_color=(0, 80, 180), # \u6309\u4e0b\u65f6\u6df1\u84dd\u8272\n text_color=(255, 255, 255) # \u767d\u8272\u6587\u5b57\n)\n\n# \u5c06\u6309\u94ae\u6dfb\u52a0\u5230\u7ba1\u7406\u5668\nbtn_manager.add_button(hello_button)\n\n# 4. \u4e3b\u5faa\u73af\nprint(\"Button example running. Press the button on the screen.\")\nwhile not app.need_exit():\n img = cam.read()\n \n # \u5728\u6bcf\u4e00\u5e27\u4e2d\uff0c\u8ba9\u7ba1\u7406\u5668\u5904\u7406\u4e8b\u4ef6\u5e76\u7ed8\u5236\u6309\u94ae\n btn_manager.handle_events(img)\n \n disp.show(img)\n time.sleep(0.02) # \u964d\u4f4eCPU\u4f7f\u7528\u7387\n```\n\n#### `Button` \u7c7b\n\u521b\u5efa\u4e00\u4e2a\u53ef\u4ea4\u4e92\u7684\u6309\u94ae\u7ec4\u4ef6\u3002\u8be5\u7ec4\u4ef6\u53ef\u4ee5\u54cd\u5e94\u89e6\u6478\u4e8b\u4ef6\uff0c\u5e76\u5728\u6309\u4e0b\u65f6\u6539\u53d8\u5916\u89c2\uff0c\u91ca\u653e\u65f6\u6267\u884c\u56de\u8c03\u51fd\u6570\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :----------------: | :--------------------: | :-----------------------------------------: | :---------------: |\n| `rect` | `Sequence[int]` | \u6309\u94ae\u7684\u4f4d\u7f6e\u548c\u5c3a\u5bf8 `[x, y, w, h]`\u3002**\u5fc5\u9700**\u3002 | - |\n| `label` | `str` | \u6309\u94ae\u4e0a\u663e\u793a\u7684\u6587\u672c\u3002**\u5fc5\u9700**\u3002 | - |\n| `callback` | `Callable \\| None` | \u5f53\u6309\u94ae\u88ab\u70b9\u51fb\u65f6\u8c03\u7528\u7684\u51fd\u6570\u3002**\u5fc5\u9700**\u3002 | - |\n| `bg_color` | `Sequence[int] \\| None` | \u80cc\u666f\u989c\u8272 (R, G, B)\u3002 | `(50, 50, 50)` |\n| `pressed_color` | `Sequence[int] \\| None` | \u6309\u4e0b\u72b6\u6001\u7684\u80cc\u666f\u989c\u8272 (R, G, B)\u3002 | `(0, 120, 220)` |\n| `text_color` | `Sequence[int]` | \u6587\u672c\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `border_color` | `Sequence[int]` | \u8fb9\u6846\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `border_thickness` | `int` | \u8fb9\u6846\u539a\u5ea6\uff08\u50cf\u7d20\uff09\u3002 | `2` |\n| `text_scale` | `float` | \u6587\u672c\u7684\u7f29\u653e\u6bd4\u4f8b\u3002 | `1.5` |\n| `font` | `str \\| None` | \u4f7f\u7528\u7684\u5b57\u4f53\u6587\u4ef6\u8def\u5f84\u3002 | `None` |\n| `align_h` | `str` | \u6c34\u5e73\u5bf9\u9f50\u65b9\u5f0f ('left', 'center', 'right')\u3002 | `'center'` |\n| `align_v` | `str` | \u5782\u76f4\u5bf9\u9f50\u65b9\u5f0f ('top', 'center', 'bottom')\u3002 | `'center'` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :-----------------: | :----------------------------------------------------------: | :--------------------------: |\n| `draw(img)` | `img` (`maix.image.Image`): \u5c06\u8981\u7ed8\u5236\u6309\u94ae\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5728\u6307\u5b9a\u7684\u56fe\u50cf\u4e0a\u7ed8\u5236\u6309\u94ae\u3002 |\n| `handle_event(...)` | `x` (`int`): \u89e6\u6478\u70b9\u7684 X \u5750\u6807\u3002<br>`y` (`int`): \u89e6\u6478\u70b9\u7684 Y \u5750\u6807\u3002<br>`pressed` (`bool\\|int`): \u89e6\u6478\u5c4f\u662f\u5426\u88ab\u6309\u4e0b\u3002<br>`img_w` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u5bbd\u5ea6\u3002<br>`img_h` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u9ad8\u5ea6\u3002<br>`disp_w` (`int`): \u663e\u793a\u5c4f\u7684\u5bbd\u5ea6\u3002<br>`disp_h` (`int`): \u663e\u793a\u5c4f\u7684\u9ad8\u5ea6\u3002 | \u5904\u7406\u89e6\u6478\u4e8b\u4ef6\u5e76\u66f4\u65b0\u6309\u94ae\u72b6\u6001\u3002 |\n\n#### `ButtonManager` \u7c7b\n\u7ba1\u7406\u4e00\u7ec4\u6309\u94ae\u7684\u4e8b\u4ef6\u5904\u7406\u548c\u7ed8\u5236\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 |\n| :----: | :-----------------------: | :--------------: |\n| `ts` | `touchscreen.TouchScreen` | \u89e6\u6478\u5c4f\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n| `disp` | `display.Display` | \u663e\u793a\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :------------------: | :----------------------------------------------: | :--------------------------------: |\n| `add_button(button)` | `button` (`Button`): \u8981\u6dfb\u52a0\u7684 Button \u5b9e\u4f8b\u3002 | \u5411\u7ba1\u7406\u5668\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u6309\u94ae\u3002 |\n| `handle_events(img)` | `img` (`maix.image.Image`): \u7ed8\u5236\u6309\u94ae\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5904\u7406\u6240\u6709\u53d7\u7ba1\u6309\u94ae\u7684\u4e8b\u4ef6\u5e76\u8fdb\u884c\u7ed8\u5236\u3002 |\n\n---\n\n### 2. \u6ed1\u5757 (Slider)\n\n\u6ed1\u5757\u5141\u8bb8\u7528\u6237\u5728\u4e00\u4e2a\u8fde\u7eed\u7684\u8303\u56f4\u5185\u9009\u62e9\u4e00\u4e2a\u503c\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a `SliderManager` \u5b9e\u4f8b\u3002\n2. \u521b\u5efa `Slider` \u5b9e\u4f8b\uff0c\u5b9a\u4e49\u5176\u533a\u57df\u3001\u6570\u503c\u8303\u56f4\u548c\u56de\u8c03\u51fd\u6570\u3002\n3. \u4f7f\u7528 `manager.add_slider()` \u6dfb\u52a0\u6ed1\u5757\u3002\n4. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u8c03\u7528 `manager.handle_events(img)`\u3002\n\n#### \u793a\u4f8b\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import Slider, SliderManager\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\ncam = camera.Camera()\n\n# \u5168\u5c40\u53d8\u91cf\uff0c\u7528\u4e8e\u5b58\u50a8\u6ed1\u5757\u7684\u503c\ncurrent_brightness = 128 \n\n# 2. \u5b9a\u4e49\u56de\u8c03\u51fd\u6570\n# \u5f53\u6ed1\u5757\u7684\u503c\u6539\u53d8\u65f6\uff0c\u8fd9\u4e2a\u51fd\u6570\u4f1a\u88ab\u8c03\u7528\ndef on_slider_update(value):\n global current_brightness\n current_brightness = value\n print(f\"Slider value updated to: {value}\")\n\n# 3. \u521d\u59cb\u5316UI\nslider_manager = SliderManager(ts, disp)\n\nbrightness_slider = Slider(\n rect=[50, 230, 540, 20],\n scale=2.0,\n min_val=0,\n max_val=255,\n default_val=current_brightness,\n callback=on_slider_update,\n label=\"Slider\"\n)\n\nslider_manager.add_slider(brightness_slider)\n\n# 4. \u4e3b\u5faa\u73af\nprint(\"Slider example running. Drag the slider.\")\ntitle_color = image.Color.from_rgb(255, 255, 255)\n\nwhile not app.need_exit():\n img = cam.read()\n \n # \u5b9e\u65f6\u663e\u793a\u6ed1\u5757\u7684\u503c\n img.draw_string(20, 20, f\"Value: {current_brightness}\", scale=2.0, color=title_color)\n \n # \u5904\u7406\u6ed1\u5757\u4e8b\u4ef6\u5e76\u7ed8\u5236\n slider_manager.handle_events(img)\n \n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `Slider` \u7c7b\n\u521b\u5efa\u4e00\u4e2a\u53ef\u62d6\u52a8\u7684\u6ed1\u5757\u7ec4\u4ef6\uff0c\u7528\u4e8e\u5728\u4e00\u5b9a\u8303\u56f4\u5185\u9009\u62e9\u4e00\u4e2a\u503c\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :--------------------: | :---------------: | :-----------------------------------------: | :---------------: |\n| `rect` | `Sequence[int]` | \u6ed1\u5757\u7684\u4f4d\u7f6e\u548c\u5c3a\u5bf8 `[x, y, w, h]`\u3002**\u5fc5\u9700**\u3002 | - |\n| `scale` | `float` | \u6ed1\u5757\u7684\u6574\u4f53\u7f29\u653e\u6bd4\u4f8b\u3002 | `1.0` |\n| `min_val` | `int` | \u6ed1\u5757\u7684\u6700\u5c0f\u503c\u3002 | `0` |\n| `max_val` | `int` | \u6ed1\u5757\u7684\u6700\u5927\u503c\u3002 | `100` |\n| `default_val` | `int` | \u6ed1\u5757\u7684\u9ed8\u8ba4\u503c\u3002 | `50` |\n| `callback` | `Callable \\| None` | \u503c\u6539\u53d8\u65f6\u8c03\u7528\u7684\u51fd\u6570\uff0c\u63a5\u6536\u65b0\u503c\u4f5c\u4e3a\u53c2\u6570\u3002 | `None` |\n| `label` | `str` | \u6ed1\u5757\u4e0a\u65b9\u7684\u6807\u7b7e\u6587\u672c\u3002 | `\"\"` |\n| `track_color` | `Sequence[int]` | \u6ed1\u8f68\u80cc\u666f\u989c\u8272 (R, G, B)\u3002 | `(60, 60, 60)` |\n| `progress_color` | `Sequence[int]` | \u6ed1\u8f68\u8fdb\u5ea6\u6761\u989c\u8272 (R, G, B)\u3002 | `(0, 120, 220)` |\n| `handle_color` | `Sequence[int]` | \u6ed1\u5757\u624b\u67c4\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `handle_border_color` | `Sequence[int]` | \u6ed1\u5757\u624b\u67c4\u8fb9\u6846\u989c\u8272 (R, G, B)\u3002 | `(100, 100, 100)` |\n| `handle_pressed_color` | `Sequence[int]` | \u6309\u4e0b\u65f6\u624b\u67c4\u989c\u8272 (R, G, B)\u3002 | `(220, 220, 255)` |\n| `label_color` | `Sequence[int]` | \u6807\u7b7e\u6587\u672c\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `tooltip_bg_color` | `Sequence[int]` | \u62d6\u52a8\u65f6\u63d0\u793a\u6846\u80cc\u666f\u8272 (R, G, B)\u3002 | `(0, 0, 0)` |\n| `tooltip_text_color` | `Sequence[int]` | \u62d6\u52a8\u65f6\u63d0\u793a\u6846\u6587\u672c\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `show_tooltip_on_drag` | `bool \\| int` | \u662f\u5426\u5728\u62d6\u52a8\u65f6\u663e\u793a\u6570\u503c\u63d0\u793a\u6846\u3002 | `True` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :-----------------: | :----------------------------------------------------------: | :--------------------------: |\n| `draw(img)` | `img` (`maix.image.Image`): \u5c06\u8981\u7ed8\u5236\u6ed1\u5757\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5728\u6307\u5b9a\u7684\u56fe\u50cf\u4e0a\u7ed8\u5236\u6ed1\u5757\u3002 |\n| `handle_event(...)` | `x` (`int`): \u89e6\u6478\u70b9\u7684 X \u5750\u6807\u3002<br>`y` (`int`): \u89e6\u6478\u70b9\u7684 Y \u5750\u6807\u3002<br>`pressed` (`bool\\|int`): \u89e6\u6478\u5c4f\u662f\u5426\u88ab\u6309\u4e0b\u3002<br>`img_w` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u5bbd\u5ea6\u3002<br>`img_h` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u9ad8\u5ea6\u3002<br>`disp_w` (`int`): \u663e\u793a\u5c4f\u7684\u5bbd\u5ea6\u3002<br>`disp_h` (`int`): \u663e\u793a\u5c4f\u7684\u9ad8\u5ea6\u3002 | \u5904\u7406\u89e6\u6478\u4e8b\u4ef6\u5e76\u66f4\u65b0\u6ed1\u5757\u72b6\u6001\u3002 |\n\n#### `SliderManager` \u7c7b\n\u7ba1\u7406\u4e00\u7ec4\u6ed1\u5757\u7684\u4e8b\u4ef6\u5904\u7406\u548c\u7ed8\u5236\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 |\n| :----: | :-----------------------: | :--------------: |\n| `ts` | `touchscreen.TouchScreen` | \u89e6\u6478\u5c4f\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n| `disp` | `display.Display` | \u663e\u793a\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :------------------: | :----------------------------------------------: | :--------------------------------: |\n| `add_slider(slider)` | `slider` (`Slider`): \u8981\u6dfb\u52a0\u7684 Slider \u5b9e\u4f8b\u3002 | \u5411\u7ba1\u7406\u5668\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u6ed1\u5757\u3002 |\n| `handle_events(img)` | `img` (`maix.image.Image`): \u7ed8\u5236\u6ed1\u5757\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5904\u7406\u6240\u6709\u53d7\u7ba1\u6ed1\u5757\u7684\u4e8b\u4ef6\u5e76\u8fdb\u884c\u7ed8\u5236\u3002 |\n\n---\n\n### 3. \u5f00\u5173 (Switch)\n\n\u4e00\u4e2a\u5177\u6709\u201c\u5f00\u201d\u548c\u201c\u5173\u201d\u4e24\u79cd\u72b6\u6001\u7684\u5207\u6362\u63a7\u4ef6\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a `SwitchManager` \u5b9e\u4f8b\u3002\n2. \u521b\u5efa `Switch` \u5b9e\u4f8b\uff0c\u5b9a\u4e49\u5176\u4f4d\u7f6e\u3001\u521d\u59cb\u72b6\u6001\u548c\u56de\u8c03\u51fd\u6570\u3002\n3. \u4f7f\u7528 `manager.add_switch()` \u6dfb\u52a0\u5f00\u5173\u3002\n4. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u8c03\u7528 `manager.handle_events(img)`\u3002\n\n#### \u793a\u4f8b\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import Switch, SwitchManager\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\ncam = camera.Camera()\n\n# \u5168\u5c40\u53d8\u91cf\uff0c\u7528\u4e8e\u5b58\u50a8\u5f00\u5173\u72b6\u6001\nis_light_on = False\n\n# 2. \u5b9a\u4e49\u56de\u8c03\u51fd\u6570\ndef on_switch_toggle(is_on):\n global is_light_on\n is_light_on = is_on\n status = \"ON\" if is_on else \"OFF\"\n print(f\"Switch toggled. Light is now {status}.\")\n\n# 3. \u521d\u59cb\u5316UI\nswitch_manager = SwitchManager(ts, disp)\n\nlight_switch = Switch(\n position=[280, 190],\n scale=2.0,\n is_on=is_light_on,\n callback=on_switch_toggle\n)\n\nswitch_manager.add_switch(light_switch)\n\n# 4. \u4e3b\u5faa\u73af\nprint(\"Switch example running. Tap the switch.\")\ntitle_color = image.Color.from_rgb(255, 255, 255)\nstatus_on_color = image.Color.from_rgb(30, 200, 30)\nstatus_off_color = image.Color.from_rgb(80, 80, 80)\n\nwhile not app.need_exit():\n img = cam.read()\n \n # \u6839\u636e\u5f00\u5173\u72b6\u6001\u663e\u793a\u4e00\u4e2a\u72b6\u6001\u6307\u793a\u706f\n status_text = \"Light: ON\" if is_light_on else \"Light: OFF\"\n status_color = status_on_color if is_light_on else status_off_color\n img.draw_string(20, 20, status_text, scale=1.5, color=title_color)\n img.draw_rect(310, 280, 50, 50, color=status_color, thickness=-1)\n \n switch_manager.handle_events(img)\n \n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `Switch` \u7c7b\n\u521b\u5efa\u4e00\u4e2a\u5f00\u5173\uff08Switch\uff09\u7ec4\u4ef6\uff0c\u7528\u4e8e\u5728\u5f00/\u5173\u4e24\u79cd\u72b6\u6001\u4e4b\u95f4\u5207\u6362\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :----------------------: | :---------------: | :--------------------------------------------------: | :---------------: |\n| `position` | `Sequence[int]` | \u5f00\u5173\u7684\u5de6\u4e0a\u89d2\u5750\u6807 `[x, y]`\u3002**\u5fc5\u9700**\u3002 | - |\n| `scale` | `float` | \u5f00\u5173\u7684\u6574\u4f53\u7f29\u653e\u6bd4\u4f8b\u3002 | `1.0` |\n| `is_on` | `bool \\| int` | \u5f00\u5173\u7684\u521d\u59cb\u72b6\u6001\uff0cTrue \u4e3a\u5f00\u3002 | `False` |\n| `callback` | `Callable \\| None` | \u72b6\u6001\u5207\u6362\u65f6\u8c03\u7528\u7684\u51fd\u6570\uff0c\u63a5\u6536\u4e00\u4e2a\u5e03\u5c14\u503c\u53c2\u6570\u8868\u793a\u65b0\u72b6\u6001\u3002 | `None` |\n| `on_color` | `Sequence[int]` | \u5f00\u542f\u72b6\u6001\u4e0b\u7684\u80cc\u666f\u989c\u8272 (R, G, B)\u3002 | `(30, 200, 30)` |\n| `off_color` | `Sequence[int]` | \u5173\u95ed\u72b6\u6001\u4e0b\u7684\u80cc\u666f\u989c\u8272 (R, G, B)\u3002 | `(100, 100, 100)` |\n| `handle_color` | `Sequence[int]` | \u624b\u67c4\u7684\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `handle_pressed_color` | `Sequence[int]` | \u6309\u4e0b\u65f6\u624b\u67c4\u7684\u989c\u8272 (R, G, B)\u3002 | `(220, 220, 255)` |\n| `handle_radius_increase` | `int` | \u6309\u4e0b\u65f6\u624b\u67c4\u534a\u5f84\u589e\u52a0\u91cf\u3002 | `2` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :-----------------: | :----------------------------------------------------------: | :------------------------------: |\n| `toggle()` | - | \u5207\u6362\u5f00\u5173\u7684\u72b6\u6001\uff0c\u5e76\u6267\u884c\u56de\u8c03\u51fd\u6570\u3002 |\n| `draw(img)` | `img` (`maix.image.Image`): \u5c06\u8981\u7ed8\u5236\u5f00\u5173\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5728\u6307\u5b9a\u7684\u56fe\u50cf\u4e0a\u7ed8\u5236\u5f00\u5173\u3002 |\n| `handle_event(...)` | `x` (`int`): \u89e6\u6478\u70b9\u7684 X \u5750\u6807\u3002<br>`y` (`int`): \u89e6\u6478\u70b9\u7684 Y \u5750\u6807\u3002<br>`pressed` (`bool\\|int`): \u89e6\u6478\u5c4f\u662f\u5426\u88ab\u6309\u4e0b\u3002<br>`img_w` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u5bbd\u5ea6\u3002<br>`img_h` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u9ad8\u5ea6\u3002<br>`disp_w` (`int`): \u663e\u793a\u5c4f\u7684\u5bbd\u5ea6\u3002<br>`disp_h` (`int`): \u663e\u793a\u5c4f\u7684\u9ad8\u5ea6\u3002 | \u5904\u7406\u89e6\u6478\u4e8b\u4ef6\u5e76\u66f4\u65b0\u5f00\u5173\u72b6\u6001\u3002 |\n\n#### `SwitchManager` \u7c7b\n\u7ba1\u7406\u4e00\u7ec4\u5f00\u5173\u7684\u4e8b\u4ef6\u5904\u7406\u548c\u7ed8\u5236\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 |\n| :----: | :-----------------------: | :--------------: |\n| `ts` | `touchscreen.TouchScreen` | \u89e6\u6478\u5c4f\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n| `disp` | `display.Display` | \u663e\u793a\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :------------------: | :----------------------------------------------: | :--------------------------------: |\n| `add_switch(switch)` | `switch` (`Switch`): \u8981\u6dfb\u52a0\u7684 Switch \u5b9e\u4f8b\u3002 | \u5411\u7ba1\u7406\u5668\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u5f00\u5173\u3002 |\n| `handle_events(img)` | `img` (`maix.image.Image`): \u7ed8\u5236\u5f00\u5173\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5904\u7406\u6240\u6709\u53d7\u7ba1\u5f00\u5173\u7684\u4e8b\u4ef6\u5e76\u8fdb\u884c\u7ed8\u5236\u3002 |\n\n---\n\n### 4. \u590d\u9009\u6846 (Checkbox)\n\n\u5141\u8bb8\u7528\u6237\u4ece\u4e00\u7ec4\u9009\u9879\u4e2d\u8fdb\u884c\u591a\u9879\u9009\u62e9\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a `CheckboxManager` \u5b9e\u4f8b\u3002\n2. \u521b\u5efa\u591a\u4e2a `Checkbox` \u5b9e\u4f8b\uff0c\u6bcf\u4e2a\u90fd\u6709\u72ec\u7acb\u7684\u56de\u8c03\u548c\u72b6\u6001\u3002\n3. \u4f7f\u7528 `manager.add_checkbox()` \u6dfb\u52a0\u5b83\u4eec\u3002\n4. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u8c03\u7528 `manager.handle_events(img)`\u3002\n\n#### \u793a\u4f8b\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import Checkbox, CheckboxManager\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\ncam = camera.Camera()\n\n# \u5168\u5c40\u5b57\u5178\uff0c\u7528\u4e8e\u5b58\u50a8\u6bcf\u4e2a\u590d\u9009\u6846\u7684\u72b6\u6001\noptions = {'Checkbox A': True, 'Checkbox B': False}\n\n# 2. \u5b9a\u4e49\u56de\u8c03\u51fd\u6570 (\u4f7f\u7528\u95ed\u5305\u6765\u533a\u5206\u662f\u54ea\u4e2a\u590d\u9009\u6846\u88ab\u70b9\u51fb)\ndef create_checkbox_callback(key):\n def on_check_change(is_checked):\n options[key] = is_checked\n print(f\"Option '{key}' is now {'checked' if is_checked else 'unchecked'}.\")\n return on_check_change\n\n# 3. \u521d\u59cb\u5316UI\ncheckbox_manager = CheckboxManager(ts, disp)\n\ncheckbox_a = Checkbox(\n position=[80, 150],\n label=\"Checkbox A\",\n is_checked=options['Checkbox A'],\n callback=create_checkbox_callback('Checkbox A'),\n scale=2.0\n)\ncheckbox_b = Checkbox(\n position=[80, 300],\n label=\"Checkbox B\",\n is_checked=options['Checkbox B'],\n callback=create_checkbox_callback('Checkbox B'),\n scale=2.0\n)\n\ncheckbox_manager.add_checkbox(checkbox_a)\ncheckbox_manager.add_checkbox(checkbox_b)\n\n# 4. \u4e3b\u5faa\u73af\nprint(\"Checkbox example running. Tap the checkboxes.\")\ntitle_color = image.Color.from_rgb(255, 255, 255)\nwhile not app.need_exit():\n img = cam.read()\n \n # \u663e\u793a\u5f53\u524d\u72b6\u6001\n a_status = \"ON\" if options['Checkbox A'] else \"OFF\"\n b_status = \"ON\" if options['Checkbox B'] else \"OFF\"\n img.draw_string(20, 20, f\"Checkbox A: {a_status}, Checkbox B: {b_status}\", scale=1.5, color=title_color)\n \n checkbox_manager.handle_events(img)\n \n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `Checkbox` \u7c7b\n\u521b\u5efa\u4e00\u4e2a\u590d\u9009\u6846\uff08Checkbox\uff09\u7ec4\u4ef6\uff0c\u53ef\u72ec\u7acb\u9009\u4e2d\u6216\u53d6\u6d88\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :-----------------: | :---------------: | :--------------------------------------------------: | :---------------: |\n| `position` | `Sequence[int]` | \u590d\u9009\u6846\u7684\u5de6\u4e0a\u89d2\u5750\u6807 `[x, y]`\u3002**\u5fc5\u9700**\u3002 | - |\n| `label` | `str` | \u590d\u9009\u6846\u65c1\u8fb9\u7684\u6807\u7b7e\u6587\u672c\u3002**\u5fc5\u9700**\u3002 | - |\n| `scale` | `float` | \u590d\u9009\u6846\u7684\u6574\u4f53\u7f29\u653e\u6bd4\u4f8b\u3002 | `1.0` |\n| `is_checked` | `bool \\| int` | \u590d\u9009\u6846\u7684\u521d\u59cb\u72b6\u6001\uff0cTrue \u4e3a\u9009\u4e2d\u3002 | `False` |\n| `callback` | `Callable \\| None` | \u72b6\u6001\u5207\u6362\u65f6\u8c03\u7528\u7684\u51fd\u6570\uff0c\u63a5\u6536\u4e00\u4e2a\u5e03\u5c14\u503c\u53c2\u6570\u8868\u793a\u65b0\u72b6\u6001\u3002 | `None` |\n| `box_color` | `Sequence[int]` | \u672a\u9009\u4e2d\u65f6\u65b9\u6846\u7684\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `box_checked_color` | `Sequence[int]` | \u9009\u4e2d\u65f6\u65b9\u6846\u7684\u989c\u8272 (R, G, B)\u3002 | `(0, 120, 220)` |\n| `check_color` | `Sequence[int]` | \u9009\u4e2d\u6807\u8bb0\uff08\u5bf9\u52fe\uff09\u7684\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `text_color` | `Sequence[int]` | \u6807\u7b7e\u6587\u672c\u7684\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `box_thickness` | `int` | \u65b9\u6846\u8fb9\u6846\u7684\u539a\u5ea6\u3002 | `2` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :-----------------: | :----------------------------------------------------------: | :--------------------------------: |\n| `toggle()` | - | \u5207\u6362\u590d\u9009\u6846\u7684\u9009\u4e2d\u72b6\u6001\uff0c\u5e76\u6267\u884c\u56de\u8c03\u3002 |\n| `draw(img)` | `img` (`maix.image.Image`): \u5c06\u8981\u7ed8\u5236\u590d\u9009\u6846\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5728\u6307\u5b9a\u7684\u56fe\u50cf\u4e0a\u7ed8\u5236\u590d\u9009\u6846\u3002 |\n| `handle_event(...)` | `x` (`int`): \u89e6\u6478\u70b9\u7684 X \u5750\u6807\u3002<br>`y` (`int`): \u89e6\u6478\u70b9\u7684 Y \u5750\u6807\u3002<br>`pressed` (`bool\\|int`): \u89e6\u6478\u5c4f\u662f\u5426\u88ab\u6309\u4e0b\u3002<br>`img_w` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u5bbd\u5ea6\u3002<br>`img_h` (`int`): \u56fe\u50cf\u7f13\u51b2\u533a\u7684\u9ad8\u5ea6\u3002<br>`disp_w` (`int`): \u663e\u793a\u5c4f\u7684\u5bbd\u5ea6\u3002<br>`disp_h` (`int`): \u663e\u793a\u5c4f\u7684\u9ad8\u5ea6\u3002 | \u5904\u7406\u89e6\u6478\u4e8b\u4ef6\u5e76\u66f4\u65b0\u590d\u9009\u6846\u72b6\u6001\u3002 |\n\n#### `CheckboxManager` \u7c7b\n\u7ba1\u7406\u4e00\u7ec4\u590d\u9009\u6846\u7684\u4e8b\u4ef6\u5904\u7406\u548c\u7ed8\u5236\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 |\n| :----: | :-----------------------: | :--------------: |\n| `ts` | `touchscreen.TouchScreen` | \u89e6\u6478\u5c4f\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n| `disp` | `display.Display` | \u663e\u793a\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :----------------------: | :------------------------------------------------: | :----------------------------------: |\n| `add_checkbox(checkbox)` | `checkbox` (`Checkbox`): \u8981\u6dfb\u52a0\u7684 Checkbox \u5b9e\u4f8b\u3002 | \u5411\u7ba1\u7406\u5668\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u590d\u9009\u6846\u3002 |\n| `handle_events(img)` | `img` (`maix.image.Image`): \u7ed8\u5236\u590d\u9009\u6846\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5904\u7406\u6240\u6709\u53d7\u7ba1\u590d\u9009\u6846\u7684\u4e8b\u4ef6\u5e76\u8fdb\u884c\u7ed8\u5236\u3002 |\n\n---\n\n### 5. \u5355\u9009\u6846 (RadioButton)\n\n\u5141\u8bb8\u7528\u6237\u4ece\u4e00\u7ec4\u4e92\u65a5\u7684\u9009\u9879\u4e2d\u53ea\u9009\u62e9\u4e00\u9879\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a `RadioManager` \u5b9e\u4f8b\u3002**\u6ce8\u610f**\uff1a`RadioManager` \u6784\u9020\u65f6\u9700\u8981\u63a5\u6536 `default_value` \u548c\u4e00\u4e2a\u5168\u5c40 `callback`\u3002\n2. \u521b\u5efa `RadioButton` \u5b9e\u4f8b\uff0c\u6bcf\u4e2a\u6309\u94ae\u5fc5\u987b\u6709\u552f\u4e00\u7684 `value`\u3002\n3. \u4f7f\u7528 `manager.add_radio()` \u6dfb\u52a0\u5b83\u4eec\u3002\n4. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u8c03\u7528 `manager.handle_events(img)`\u3002\u7ba1\u7406\u5668\u4f1a\u81ea\u52a8\u5904\u7406\u4e92\u65a5\u903b\u8f91\u3002\n\n#### \u793a\u4f8b\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import RadioButton, RadioManager\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\ncam = camera.Camera()\n\n# \u5168\u5c40\u53d8\u91cf\uff0c\u5b58\u50a8\u5f53\u524d\u9009\u62e9\u7684\u6a21\u5f0f\ncurrent_mode = None\n\n# 2. \u5b9a\u4e49\u56de\u8c03\u51fd\u6570\n# \u8fd9\u4e2a\u56de\u8c03\u7531 RadioManager \u8c03\u7528\uff0c\u4f20\u5165\u88ab\u9009\u4e2d\u9879\u7684 value\ndef on_mode_change(selected_value):\n global current_mode\n current_mode = selected_value\n print(f\"Mode changed to: {selected_value}\")\n\n# 3. \u521d\u59cb\u5316UI\n# \u521b\u5efa RadioManager\uff0c\u5e76\u4f20\u5165\u9ed8\u8ba4\u503c\u548c\u56de\u8c03\nradio_manager = RadioManager(ts, disp, \n default_value=current_mode, \n callback=on_mode_change)\n\n# \u521b\u5efa\u4e09\u4e2a RadioButton \u5b9e\u4f8b\uff0c\u6ce8\u610f\u5b83\u4eec\u7684 value \u662f\u552f\u4e00\u7684\nradio_a = RadioButton(position=[80, 100], label=\"Mode A\", value=\"Mode A\", scale=2.0)\nradio_b = RadioButton(position=[80, 200], label=\"Mode B\", value=\"Mode B\", scale=2.0)\nradio_c = RadioButton(position=[80, 300], label=\"Mode C\", value=\"Mode C\", scale=2.0)\n\n# \u5c06\u5b83\u4eec\u90fd\u6dfb\u52a0\u5230\u7ba1\u7406\u5668\u4e2d\nradio_manager.add_radio(radio_a)\nradio_manager.add_radio(radio_b)\nradio_manager.add_radio(radio_c)\n\n# 4. \u4e3b\u5faa\u73af\nprint(\"Radio button example running. Select a mode.\")\ntitle_color = image.Color.from_rgb(255, 255, 255)\nwhile not app.need_exit():\n img = cam.read()\n \n img.draw_string(20, 20, f\"Current: {current_mode}\", scale=1.8, color=title_color)\n \n radio_manager.handle_events(img)\n \n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `RadioButton` \u7c7b\n\u521b\u5efa\u4e00\u4e2a\u5355\u9009\u6846\uff08RadioButton\uff09\u9879\u3002\u901a\u5e38\u4e0e `RadioManager` \u7ed3\u5408\u4f7f\u7528\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :---------------------: | :-------------: | :-------------------------------: | :---------------: |\n| `position` | `Sequence[int]` | \u5355\u9009\u6846\u5706\u5708\u7684\u5de6\u4e0a\u89d2\u5750\u6807 `[x, y]`\u3002**\u5fc5\u9700**\u3002 | - |\n| `label` | `str` | \u6309\u94ae\u65c1\u8fb9\u7684\u6807\u7b7e\u6587\u672c\u3002**\u5fc5\u9700**\u3002 | - |\n| `value` | `any` | \u4e0e\u6b64\u5355\u9009\u6846\u5173\u8054\u7684\u552f\u4e00\u503c\u3002**\u5fc5\u9700**\u3002 | - |\n| `scale` | `float` | \u7ec4\u4ef6\u7684\u6574\u4f53\u7f29\u653e\u6bd4\u4f8b\u3002 | `1.0` |\n| `circle_color` | `Sequence[int]` | \u672a\u9009\u4e2d\u65f6\u5706\u5708\u7684\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `circle_selected_color` | `Sequence[int]` | \u9009\u4e2d\u65f6\u5706\u5708\u7684\u989c\u8272 (R, G, B)\u3002 | `(0, 120, 220)` |\n| `dot_color` | `Sequence[int]` | \u9009\u4e2d\u65f6\u4e2d\u5fc3\u5706\u70b9\u7684\u989c\u8272 (R, G, B)\u3002 | `(255, 255, 255)` |\n| `text_color` | `Sequence[int]` | \u6807\u7b7e\u6587\u672c\u7684\u989c\u8272 (R, G, B)\u3002 | `(200, 200, 200)` |\n| `circle_thickness` | `int` | \u5706\u5708\u8fb9\u6846\u7684\u539a\u5ea6\u3002 | `2` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :---------: | :----------------------------------------------------: | :------------------------: |\n| `draw(img)` | `img` (`maix.image.Image`): \u5c06\u8981\u7ed8\u5236\u5355\u9009\u6846\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5728\u6307\u5b9a\u7684\u56fe\u50cf\u4e0a\u7ed8\u5236\u5355\u9009\u6846\u3002 |\n\n#### `RadioManager` \u7c7b\n\u7ba1\u7406\u4e00\u4e2a\u5355\u9009\u6846\u7ec4\uff0c\u786e\u4fdd\u53ea\u6709\u4e00\u4e2a\u6309\u94ae\u80fd\u88ab\u9009\u4e2d\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :-------------: | :-----------------------: | :------------------------------------------------: | :----: |\n| `ts` | `touchscreen.TouchScreen` | \u89e6\u6478\u5c4f\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 | - |\n| `disp` | `display.Display` | \u663e\u793a\u8bbe\u5907\u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 | - |\n| `default_value` | `any` | \u9ed8\u8ba4\u9009\u4e2d\u7684\u6309\u94ae\u7684\u503c\u3002 | `None` |\n| `callback` | `Callable \\| None` | \u9009\u4e2d\u9879\u6539\u53d8\u65f6\u8c03\u7528\u7684\u51fd\u6570\uff0c\u63a5\u6536\u65b0\u9009\u4e2d\u9879\u7684\u503c\u4f5c\u4e3a\u53c2\u6570\u3002 | `None` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 |\n| :------------------: | :--------------------------------------------------: | :------------------------------: |\n| `add_radio(radio)` | `radio` (`RadioButton`): \u8981\u6dfb\u52a0\u7684 RadioButton \u5b9e\u4f8b\u3002 | \u5411\u7ba1\u7406\u5668\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u5355\u9009\u6846\u3002 |\n| `handle_events(img)` | `img` (`maix.image.Image`): \u7ed8\u5236\u5355\u9009\u6846\u7684\u76ee\u6807\u56fe\u50cf\u3002 | \u5904\u7406\u6240\u6709\u5355\u9009\u6846\u7684\u4e8b\u4ef6\u5e76\u8fdb\u884c\u7ed8\u5236\u3002 |\n\n---\n\n### 6. \u5206\u8fa8\u7387\u9002\u914d\u5668 (ResolutionAdapter)\n\n\u4e00\u4e2a\u8f85\u52a9\u5de5\u5177\u7c7b\uff0c\u7528\u4e8e\u81ea\u52a8\u9002\u914d\u4e0d\u540c\u5206\u8fa8\u7387\u7684\u5c4f\u5e55\uff0c\u4ee5\u4fdd\u6301UI\u5e03\u5c40\u7684\u4e00\u81f4\u6027\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n\n1. \u521b\u5efa\u4e00\u4e2a `ResolutionAdapter` \u5b9e\u4f8b\uff0c\u5e76\u6307\u5b9a\u76ee\u6807\u5c4f\u5e55\u5c3a\u5bf8\u548c\u53ef\u9009\u7684\u8bbe\u8ba1\u57fa\u7840\u5206\u8fa8\u7387\u3002\n2. \u57fa\u4e8e\u60a8\u7684\u8bbe\u8ba1\u57fa\u7840\u5206\u8fa8\u7387\uff0c\u5b9a\u4e49\u7ec4\u4ef6\u7684\u539f\u59cb `rect`\u3001`position` \u7b49\u53c2\u6570\u3002\n3. \u8c03\u7528 `adapter.scale_rect()` \u7b49\u65b9\u6cd5\uff0c\u5c06\u539f\u59cb\u53c2\u6570\u8f6c\u6362\u4e3a\u9002\u914d\u540e\u7684\u503c\u3002\n4. \u4f7f\u7528\u8f6c\u6362\u540e\u7684\u503c\u6765\u521b\u5efa\u60a8\u7684UI\u7ec4\u4ef6\u3002\n\n#### \u793a\u4f8b\n\n```python\nfrom maix import display, camera, app, touchscreen\nfrom maixpy_ui import Button, ButtonManager, ResolutionAdapter\nimport time\n\n# 1. \u521d\u59cb\u5316\u786c\u4ef6\ndisp = display.Display()\nts = touchscreen.TouchScreen()\n# cam = camera.Camera(640,480)\ncam = camera.Camera(320,240)\n\n# 2. \u521b\u5efa\u5206\u8fa8\u7387\u9002\u914d\u5668\uff0c\u5e76\u660e\u786e\u6307\u5b9a\u6211\u4eec\u7684\u8bbe\u8ba1\u662f\u57fa\u4e8e 640x480 \u7684\nadapter = ResolutionAdapter(\n display_width=cam.width(), \n display_height=cam.height(),\n base_width=640,\n base_height=480\n)\n\n# 3. \u57fa\u4e8e 640x480 \u7684\u753b\u5e03\u6765\u5b9a\u4e49\u7ec4\u4ef6\u53c2\u6570\noriginal_rect = [160, 200, 320, 80]\noriginal_font_scale = 3.0 \n\n# 4. \u4f7f\u7528\u9002\u914d\u5668\u8f6c\u6362\u53c2\u6570\nscaled_rect = adapter.scale_rect(original_rect)\nscaled_font_size = adapter.scale_value(original_font_scale) \n\n# 5. \u4f7f\u7528\u7f29\u653e\u540e\u7684\u503c\u521b\u5efa\u7ec4\u4ef6\nbtn_manager = ButtonManager(ts, disp)\nadapted_button = Button(\n rect=scaled_rect,\n label=\"Big Button\",\n text_scale=scaled_font_size,\n callback=lambda: print(\"Adapted button clicked!\")\n)\nbtn_manager.add_button(adapted_button)\n\n# 6. \u4e3b\u5faa\u73af\nprint(\"ResolutionAdapter example running (640x480 base).\")\nwhile not app.need_exit():\n img = cam.read()\n btn_manager.handle_events(img)\n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `ResolutionAdapter` \u7c7b\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :--------------: | :---: | :--------------------------: | :----: |\n| `display_width` | `int` | \u76ee\u6807\u663e\u793a\u5c4f\u7684\u5bbd\u5ea6\u3002**\u5fc5\u9700**\u3002 | - |\n| `display_height` | `int` | \u76ee\u6807\u663e\u793a\u5c4f\u7684\u9ad8\u5ea6\u3002**\u5fc5\u9700**\u3002 | - |\n| `base_width` | `int` | UI\u8bbe\u8ba1\u7684\u57fa\u51c6\u5bbd\u5ea6\u3002 | `320` |\n| `base_height` | `int` | UI\u8bbe\u8ba1\u7684\u57fa\u51c6\u9ad8\u5ea6\u3002 | `240` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 | \u8fd4\u56de\u503c |\n| :-------------------------: | :---------------------------------------------------------: | :--------------------------------: | :-------------: |\n| `scale_position(x, y)` | `x` (`int`): \u539f\u59cb X \u5750\u6807\u3002<br>`y` (`int`): \u539f\u59cb Y \u5750\u6807\u3002 | \u7f29\u653e\u4e00\u4e2a\u5750\u6807\u70b9 (x, y)\u3002 | `Sequence[int]` |\n| `scale_size(width, height)` | `width` (`int`): \u539f\u59cb\u5bbd\u5ea6\u3002<br>`height` (`int`): \u539f\u59cb\u9ad8\u5ea6\u3002 | \u7f29\u653e\u4e00\u4e2a\u5c3a\u5bf8 (width, height)\u3002 | `Sequence[int]` |\n| `scale_rect(rect)` | `rect` (`list[int]`): \u539f\u59cb\u77e9\u5f62 `[x, y, w, h]`\u3002 | \u7f29\u653e\u4e00\u4e2a\u77e9\u5f62\u3002 | `Sequence[int]` |\n| `scale_value(value)` | `value` (`int\\|float`): \u539f\u59cb\u6570\u503c\u3002 | \u7f29\u653e\u4e00\u4e2a\u901a\u7528\u6570\u503c\uff0c\u5982\u534a\u5f84\u3001\u539a\u5ea6\u7b49\u3002 | `float` |\n\n### 7. \u9875\u9762\u4e0e UI \u7ba1\u7406\u5668 (Page and UIManager)\n\n\u7528\u4e8e\u6784\u5efa\u591a\u9875\u9762\u5e94\u7528\uff0c\u5e76\u7ba1\u7406\u9875\u9762\u95f4\u7684\u6811\u578b\u5bfc\u822a\u3002\n\n#### \u4f7f\u7528\u65b9\u5f0f\n1. \u521b\u5efa\u4e00\u4e2a\u5168\u5c40\u7684 `UIManager` \u5b9e\u4f8b\u3002\n2. \u5b9a\u4e49\u7ee7\u627f\u81ea `Page` \u7684\u81ea\u5b9a\u4e49\u9875\u9762\u7c7b\uff0c\u5e76\u5728\u6784\u9020\u51fd\u6570\u4e2d\u4e3a\u9875\u9762\u547d\u540d\u3002\n3. \u5728\u7236\u9875\u9762\u4e2d\uff0c\u521b\u5efa\u5b50\u9875\u9762\u7684\u5b9e\u4f8b\uff0c\u5e76\u4f7f\u7528 `parent.add_child()` \u65b9\u6cd5\u6765\u6784\u5efa\u9875\u9762\u6811\u3002\n4. \u4f7f\u7528 `ui_manager.set_root_page()` \u8bbe\u7f6e\u5e94\u7528\u7684\u6839\u9875\u9762\u3002\n5. \u5728\u9875\u9762\u5185\u90e8\uff0c\u901a\u8fc7 `self.ui_manager` \u8c03\u7528\u5bfc\u822a\u65b9\u6cd5\uff0c\u5982 `navigate_to_child()`\u3001`navigate_to_parent()`\u3001`navigate_to_root()` \u7b49\u3002\n6. \u5728\u4e3b\u5faa\u73af\u4e2d\uff0c\u6301\u7eed\u8c03\u7528 `ui_manager.update(img)` \u6765\u9a71\u52a8\u5f53\u524d\u6d3b\u52a8\u9875\u9762\u7684\u66f4\u65b0\u548c\u7ed8\u5236\u3002\n\n#### \u793a\u4f8b\n\n```python\nfrom maix import display, camera, app, touchscreen, image\nfrom maixpy_ui import Page, UIManager, Button, ButtonManager\nimport time\n\n# --------------------------------------------------------------------------\n# 1. \u521d\u59cb\u5316\u786c\u4ef6 & \u5168\u5c40\u8d44\u6e90\n# --------------------------------------------------------------------------\ndisp = display.Display()\nts = touchscreen.TouchScreen()\nscreen_w, screen_h = disp.width(), disp.height()\ncam = camera.Camera(screen_w, screen_h)\n\n# \u9884\u521b\u5efa\u989c\u8272\u5bf9\u8c61\nCOLOR_WHITE = image.Color(255, 255, 255)\nCOLOR_GREY = image.Color(150, 150, 150)\nCOLOR_GREEN = image.Color(30, 200, 30)\nCOLOR_BLUE = image.Color(0, 120, 220)\n\ndef get_background():\n if cam:\n img = cam.read()\n if img: return img\n return image.new(size=(screen_w, screen_h), color=(10, 20, 30))\n\n# --------------------------------------------------------------------------\n# 2. \u5b9a\u4e49\u9875\u9762\u7c7b\n# --------------------------------------------------------------------------\n\nclass BasePage(Page):\n \"\"\"\u4e00\u4e2a\u5305\u542b\u901a\u7528\u529f\u80fd\u7684\u9875\u9762\u57fa\u7c7b\uff0c\u4f8b\u5982\u7ed8\u5236\u8c03\u8bd5\u4fe1\u606f\"\"\"\n def draw_path_info(self, img: image.Image):\n \"\"\"\u5728\u5c4f\u5e55\u53f3\u4e0b\u89d2\u7ed8\u5236\u5f53\u524d\u7684\u5bfc\u822a\u8def\u5f84\"\"\"\n info = self.ui_manager.get_navigation_info()\n path_str = \" > \".join(info['current_path'])\n \n # \u8ba1\u7b97\u6587\u672c\u5c3a\u5bf8\n text_scale = 1.0\n text_size = image.string_size(path_str, scale=text_scale)\n \n # \u8ba1\u7b97\u7ed8\u5236\u4f4d\u7f6e\uff08\u53f3\u4e0b\u89d2\uff0c\u7559\u51fa\u4e00\u4e9b\u8fb9\u8ddd\uff09\n padding = 10\n text_x = screen_w - text_size.width() - padding\n text_y = screen_h - text_size.height() - padding\n \n # \u7ed8\u5236\u6587\u672c\n img.draw_string(text_x, text_y, path_str, scale=text_scale, color=COLOR_GREY)\n \n def update(self, img: image.Image):\n \"\"\"\u5b50\u7c7b\u5e94\u8be5\u91cd\u5199\u6b64\u65b9\u6cd5\uff0c\u5e76\u5728\u672b\u5c3e\u8c03\u7528 super().update(img) \u6765\u7ed8\u5236\u8c03\u8bd5\u4fe1\u606f\"\"\"\n self.draw_path_info(img)\n\nclass PageA1(BasePage):\n \"\"\"\u6700\u6df1\u5c42\u7684\u9875\u9762\"\"\"\n def __init__(self, ui_manager):\n super().__init__(ui_manager, name=\"page_a1\")\n self.btn_manager = ButtonManager(ts, disp)\n self.btn_manager.add_button(Button([40, 150, 400, 80], \"Back to Parent (-> Page A)\", lambda: self.ui_manager.navigate_to_parent()))\n self.btn_manager.add_button(Button([40, 250, 400, 80], \"Go Back in History\", lambda: self.ui_manager.go_back()))\n self.btn_manager.add_button(Button([40, 350, 400, 80], \"Go to Root (Home)\", lambda: self.ui_manager.navigate_to_root(), bg_color=COLOR_GREEN))\n\n def update(self, img):\n img.draw_string(20, 20, \"Page A.1 (Deepest)\", scale=2.0, color=COLOR_WHITE)\n history = self.ui_manager.navigation_history\n prev_page_name = history[-1].name if history else \"None\"\n img.draw_string(20, 80, f\"'Go Back' will return to '{prev_page_name}'.\", scale=1.2, color=COLOR_GREY)\n self.btn_manager.handle_events(img)\n super().update(img) # \u8c03\u7528\u57fa\u7c7b\u7684\u65b9\u6cd5\u6765\u7ed8\u5236\u8def\u5f84\u4fe1\u606f\n\nclass PageA(BasePage):\n \"\"\"\u4e2d\u95f4\u5c42\u9875\u9762 A\"\"\"\n def __init__(self, ui_manager):\n super().__init__(ui_manager, name=\"page_a\")\n self.btn_manager = ButtonManager(ts, disp)\n self.btn_manager.add_button(Button([80, 150, 350, 80], \"Go to Page A.1\", lambda: self.ui_manager.navigate_to_child(\"page_a1\")))\n self.btn_manager.add_button(Button([20, 400, 250, 80], \"Back to Parent\", lambda: self.ui_manager.navigate_to_parent()))\n self.add_child(PageA1(self.ui_manager))\n\n def update(self, img):\n img.draw_string(20, 20, \"Page A\", scale=2.5, color=COLOR_WHITE)\n self.btn_manager.handle_events(img)\n super().update(img)\n\nclass PageB(BasePage):\n \"\"\"\u4e2d\u95f4\u5c42\u9875\u9762 B\"\"\"\n def __init__(self, ui_manager):\n super().__init__(ui_manager, name=\"page_b\")\n self.btn_manager = ButtonManager(ts, disp)\n self.btn_manager.add_button(Button([80, 150, 350, 80], \"Jump to Page A.1 by Path\", lambda: self.ui_manager.navigate_to_path([\"page_a\", \"page_a1\"])))\n self.btn_manager.add_button(Button([20, 400, 250, 80], \"Back to Parent\", lambda: self.ui_manager.navigate_to_parent()))\n\n def update(self, img):\n img.draw_string(20, 20, \"Page B\", scale=2.5, color=COLOR_WHITE)\n img.draw_string(20, 80, \"From here, we'll jump to A.1.\", scale=1.2, color=COLOR_GREY)\n img.draw_string(20, 110, \"This will make 'Go Back' and 'Back to Parent' different on the next page.\", scale=1.2, color=COLOR_GREY)\n self.btn_manager.handle_events(img)\n super().update(img)\n\nclass RootPage(BasePage):\n \"\"\"\u6839\u9875\u9762\"\"\"\n def __init__(self, ui_manager):\n super().__init__(ui_manager, name=\"root\")\n self.btn_manager = ButtonManager(ts, disp)\n self.btn_manager.add_button(Button([80, 150, 350, 80], \"Path 1: Go to Page A\", lambda: self.ui_manager.navigate_to_child(\"page_a\")))\n self.btn_manager.add_button(Button([80, 300, 350, 80], \"Path 2: Go to Page B\", lambda: self.ui_manager.navigate_to_child(\"page_b\")))\n self.add_child(PageA(self.ui_manager))\n self.add_child(PageB(self.ui_manager))\n\n def update(self, img):\n img.draw_string(20, 20, \"Root Page (Home)\", scale=2.5, color=COLOR_WHITE)\n img.draw_string(20, 80, \"Try both paths to see how 'Go Back' behaves differently.\", scale=1.2, color=COLOR_GREY)\n self.btn_manager.handle_events(img)\n super().update(img) # \u8c03\u7528\u57fa\u7c7b\u7684\u65b9\u6cd5\u6765\u7ed8\u5236\u8def\u5f84\u4fe1\u606f\n\n# --------------------------------------------------------------------------\n# 3. \u4e3b\u7a0b\u5e8f\u903b\u8f91\n# --------------------------------------------------------------------------\nif __name__ == \"__main__\":\n ui_manager = UIManager()\n root_page = RootPage(ui_manager)\n ui_manager.set_root_page(root_page)\n\n print(\"Navigation demo with persistent path display running.\")\n\n while not app.need_exit():\n img = get_background()\n ui_manager.update(img)\n disp.show(img)\n time.sleep(0.02)\n```\n\n#### `Page` \u7c7b\n\u9875\u9762\uff08Page\uff09\u7684\u57fa\u7c7b\uff0c\u652f\u6301\u6811\u578b\u7236\u5b50\u8282\u70b9\u7ed3\u6784\u3002\u6240\u6709\u5177\u4f53\u7684UI\u9875\u9762\u90fd\u5e94\u7ee7\u627f\u6b64\u7c7b\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :--------: | :---------: | :-------------------------------------------: | :----: |\n| `ui_manager` | `UIManager` | \u7528\u4e8e\u9875\u9762\u5bfc\u822a\u7684 UIManager \u5b9e\u4f8b\u3002**\u5fc5\u9700**\u3002 | - |\n| `name` | `str` | \u9875\u9762\u7684\u552f\u4e00\u540d\u79f0\u6807\u8bc6\u7b26\uff0c\u7528\u4e8e\u5728\u7236\u9875\u9762\u4e2d\u67e5\u627e\u3002 | `\"\"` |\n\n##### \u65b9\u6cd5 (Methods)\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 | \u8fd4\u56de\u503c |\n| :-----------------: | :--------------------------------------------: | :------------------------------------------------------: | :--------------: |\n| `add_child(page)` | `page` (`Page`): \u8981\u6dfb\u52a0\u7684\u5b50\u9875\u9762\u5b9e\u4f8b\u3002 | \u5c06\u4e00\u4e2a\u9875\u9762\u6dfb\u52a0\u4e3a\u5f53\u524d\u9875\u9762\u7684\u5b50\u8282\u70b9\uff0c\u4ee5\u6784\u5efa\u9875\u9762\u6811\u3002 | - |\n| `remove_child(page)` | `page` (`Page`): \u8981\u79fb\u9664\u7684\u5b50\u9875\u9762\u5b9e\u4f8b\u3002 | \u4ece\u5f53\u524d\u9875\u9762\u79fb\u9664\u4e00\u4e2a\u5b50\u8282\u70b9\u3002 | `bool` |\n| `get_child(name)` | `name` (`str`): \u5b50\u9875\u9762\u7684\u540d\u79f0\u3002 | \u6839\u636e\u540d\u79f0\u83b7\u53d6\u5b50\u9875\u9762\uff0c\u7528\u4e8e\u81ea\u5b9a\u4e49\u5bfc\u822a\u903b\u8f91\u3002 | `Page \\| None` |\n| `on_enter()` | - | \u5f53\u9875\u9762\u8fdb\u5165\u89c6\u56fe\u65f6\u8c03\u7528\u3002\u5b50\u7c7b\u53ef\u91cd\u5199\u4ee5\u5b9e\u73b0\u521d\u59cb\u5316\u903b\u8f91\u3002 | - |\n| `on_exit()` | - | \u5f53\u9875\u9762\u79bb\u5f00\u89c6\u56fe\u65f6\u8c03\u7528\u3002\u5b50\u7c7b\u53ef\u91cd\u5199\u4ee5\u5b9e\u73b0\u6e05\u7406\u903b\u8f91\u3002 | - |\n| `on_child_enter()` | `child` (`Page`): \u8fdb\u5165\u89c6\u56fe\u7684\u5b50\u9875\u9762\u3002 | \u5f53\u6b64\u9875\u9762\u7684\u4e00\u4e2a\u5b50\u9875\u9762\u8fdb\u5165\u89c6\u56fe\u65f6\u8c03\u7528\u3002\u7236\u9875\u9762\u53ef\u91cd\u5199\u3002 | - |\n| `on_child_exit()` | `child` (`Page`): \u79bb\u5f00\u89c6\u56fe\u7684\u5b50\u9875\u9762\u3002 | \u5f53\u6b64\u9875\u9762\u7684\u4e00\u4e2a\u5b50\u9875\u9762\u79bb\u5f00\u89c6\u56fe\u65f6\u8c03\u7528\u3002\u7236\u9875\u9762\u53ef\u91cd\u5199\u3002 | - |\n| `update(img)` | `img` (`maix.image.Image`): \u7528\u4e8e\u7ed8\u5236\u7684\u56fe\u50cf\u7f13\u51b2\u533a\u3002 | \u6bcf\u5e27\u8c03\u7528\u7684\u66f4\u65b0\u548c\u7ed8\u5236\u65b9\u6cd5\u3002**\u5b50\u7c7b\u5fc5\u987b\u91cd\u5199\u6b64\u65b9\u6cd5**\u3002 | - |\n\n#### `UIManager` \u7c7b\nUI \u7ba1\u7406\u5668\uff0c\u57fa\u4e8e\u6811\u578b\u9875\u9762\u7ed3\u6784\u63d0\u4f9b\u7075\u6d3b\u7684\u5bfc\u822a\u529f\u80fd\u3002\n\n##### \u6784\u9020\u51fd\u6570: `__init__`\n| \u53c2\u6570 | \u7c7b\u578b | \u63cf\u8ff0 | \u9ed8\u8ba4\u503c |\n| :-------: | :---------: | :--------------------------: | :----: |\n| `root_page` | `Page \\| None` | \u6839\u9875\u9762\u5b9e\u4f8b\uff0c\u5982\u679c\u4e3aNone\u5219\u9700\u8981\u540e\u7eed\u8bbe\u7f6e\u3002 | `None` |\n\n##### \u65b9\u6cd5 (Methods)\n\n| \u65b9\u6cd5 | \u53c2\u6570 | \u63cf\u8ff0 | \u8fd4\u56de\u503c |\n|:-----------------------------:|:------------------------------------------:|:------------------------------------------------------------:|:--------------------:|\n| `set_root_page(page)` | `page` (`Page`): \u65b0\u7684\u6839\u9875\u9762\u5b9e\u4f8b\u3002 | \u8bbe\u7f6e\u6216\u91cd\u7f6eUI\u7ba1\u7406\u5668\u7684\u6839\u9875\u9762\uff0c\u5e76\u6e05\u7a7a\u5386\u53f2\u3002 | `None` |\n| `get_current_page()` | - | \u83b7\u53d6\u5f53\u524d\u6d3b\u52a8\u7684\u9875\u9762\u3002 | `Page \\| None` |\n| `navigate_to_child(name)` | `name` (`str`): \u5b50\u9875\u9762\u7684\u540d\u79f0\u3002 | \u5bfc\u822a\u5230\u5f53\u524d\u9875\u9762\u7684\u6307\u5b9a\u540d\u79f0\u7684\u5b50\u9875\u9762\u3002 | `bool` |\n| `navigate_to_parent()` | - | \u5bfc\u822a\u5230\u5f53\u524d\u9875\u9762\u7684\u7236\u9875\u9762\u3002 | `bool` |\n| `navigate_to_root()` | - | \u76f4\u63a5\u5bfc\u822a\u5230\u6811\u7684\u6839\u9875\u9762\u3002 | `bool` |\n| `navigate_to_path(path)` | `path` (`List[str]`): \u4ece\u6839\u9875\u9762\u5f00\u59cb\u7684\u7edd\u5bf9\u8def\u5f84\u3002 | \u6839\u636e\u7edd\u5bf9\u8def\u5f84\u5bfc\u822a\u5230\u6307\u5b9a\u9875\u9762\u3002 | `bool` |\n| `navigate_to_relative_path(path)` | `path` (`List[str]`): \u4ece\u5f53\u524d\u9875\u9762\u5f00\u59cb\u7684\u76f8\u5bf9\u8def\u5f84\u3002 | \u6839\u636e\u76f8\u5bf9\u8def\u5f84\u5bfc\u822a\u5230\u6307\u5b9a\u9875\u9762\u3002 | `bool` |\n| `navigate_to_page(target_page)` | `target_page` (`Page`): \u76ee\u6807\u9875\u9762\u5b9e\u4f8b\u3002 | \u76f4\u63a5\u5bfc\u822a\u5230\u6307\u5b9a\u9875\u9762\u3002 | `bool` |\n| `go_back()` | - | \u8fd4\u56de\u5230\u5bfc\u822a\u5386\u53f2\u8bb0\u5f55\u4e2d\u7684\u524d\u4e00\u4e2a\u9875\u9762\u3002 | `bool` |\n| `remove_page(page)` | `page` (`Page`): \u8981\u79fb\u9664\u7684\u9875\u9762\u5b9e\u4f8b\u3002 | \u79fb\u9664\u6307\u5b9a\u7684\u9875\u9762\uff0c\u5e76\u4ece\u7236\u9875\u9762\u5b50\u9875\u9762\u5217\u8868\u4e2d\u5220\u9664\u3002 | `bool` |\n| `clear_history()` | - | \u6e05\u7a7a\u5bfc\u822a\u5386\u53f2\u8bb0\u5f55\u3002 | `None` |\n| `get_current_path()` | - | \u83b7\u53d6\u5f53\u524d\u9875\u9762\u7684\u5b8c\u6574\u8def\u5f84\u3002 | `List[str]` |\n| `get_navigation_info()` | - | \u83b7\u53d6\u5305\u542b\u5f53\u524d\u8def\u5f84\u3001\u5386\u53f2\u6df1\u5ea6\u7b49\u4fe1\u606f\u7684\u5b57\u5178\uff0c\u7528\u4e8e\u8c03\u8bd5\u6216\u663e\u793a\u3002 | `dict` |\n| `update(img)` | `img` (`maix.image.Image`): \u7528\u4e8e\u7ed8\u5236\u7684\u56fe\u50cf\u7f13\u51b2\u533a\u3002 | \u66f4\u65b0\u5f53\u524d\u6d3b\u52a8\u9875\u9762\u7684\u72b6\u6001\u3002\u6b64\u65b9\u6cd5\u5e94\u5728\u4e3b\u5faa\u73af\u4e2d\u6bcf\u5e27\u8c03\u7528\u3002 | `None` |\n\n---\n\n## \u2696\ufe0f\u8bb8\u53ef\u534f\u8bae\n\n\u672c\u9879\u76ee\u57fa\u4e8e **Apache License, Version 2.0** \u8bb8\u53ef\u3002\u8be6\u7ec6\u4fe1\u606f\u8bf7\u53c2\u9605\u4ee3\u7801\u6587\u4ef6\u4e2d\u7684\u8bb8\u53ef\u8bc1\u8bf4\u660e\u3002\n",
"bugtrack_url": null,
"license": null,
"summary": "A lightweight UI component library for MaixPy.",
"version": "2.4",
"project_urls": {
"Bug Tracker": "https://github.com/aristorechina/MaixPy-UI-Lib/issues",
"Homepage": "https://github.com/aristorechina/MaixPy-UI-Lib/",
"Repository": "https://github.com/aristorechina/MaixPy-UI-Lib/"
},
"split_keywords": [
"embedded",
" gui",
" maixpy",
" micropython",
" ui"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "839fb38cf72e8163ee1023e41c7ff92b0b1646e869299c313fabd34d3a16d29f",
"md5": "7ac42dd7853f5696c55ee5b8d0ee26d9",
"sha256": "28354ab7c9cf2e3f26ca198cb2933f1acfc20489af9ca9f0ab6b70652e5db86c"
},
"downloads": -1,
"filename": "maixpy_ui_lib-2.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7ac42dd7853f5696c55ee5b8d0ee26d9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 34535,
"upload_time": "2025-07-24T15:45:43",
"upload_time_iso_8601": "2025-07-24T15:45:43.938883Z",
"url": "https://files.pythonhosted.org/packages/83/9f/b38cf72e8163ee1023e41c7ff92b0b1646e869299c313fabd34d3a16d29f/maixpy_ui_lib-2.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5b64b587dbed43c80f735dd6de2cd0748102eb7f22271d2b68df708707a27f03",
"md5": "929845893484a5bcc5deb00d29b0d339",
"sha256": "c90769319333f04b926dcbf5f60e2519ca0c0131f8d834ad96f0b810d12f2a81"
},
"downloads": -1,
"filename": "maixpy_ui_lib-2.4.tar.gz",
"has_sig": false,
"md5_digest": "929845893484a5bcc5deb00d29b0d339",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 446669,
"upload_time": "2025-07-24T15:45:47",
"upload_time_iso_8601": "2025-07-24T15:45:47.833314Z",
"url": "https://files.pythonhosted.org/packages/5b/64/b587dbed43c80f735dd6de2cd0748102eb7f22271d2b68df708707a27f03/maixpy_ui_lib-2.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-24 15:45:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "aristorechina",
"github_project": "MaixPy-UI-Lib",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "maixpy-ui-lib"
}