maixpy-ui-lib


Namemaixpy-ui-lib JSON
Version 2.4 PyPI version JSON
download
home_pageNone
SummaryA lightweight UI component library for MaixPy.
upload_time2025-07-24 15:45:47
maintainerNone
docs_urlNone
authorAristore, levi_jia, HYKMAX
requires_python>=3.8
licenseNone
keywords embedded gui maixpy micropython ui
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # MaixPy-UI-Lib:一款为 MaixPy 开发的轻量级 UI 组件库

[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/aristorechina/MaixPy-UI-Lib/blob/main/LICENSE) [![Version](https://img.shields.io/badge/version-2.4-brightgreen.svg)](https://github.com/aristorechina/MaixPy-UI-Lib)

本项目是一款为 MaixPy 开发的轻量级 UI 组件库,遵循 `Apache 2.0` 协议。

欢迎给本项目提pr,要是觉得好用的话请给本项目点个star⭐

---

## 🖼️功能展示

以下画面均截取自本项目的 `main.py`

![button](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/button.jpg)

![switch](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/switch.jpg)

![slider](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/slider.jpg)

![radiobutton](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/radiobutton.jpg)

![checkbox](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/checkbox.jpg)

---

## 📦 安装

`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[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/aristorechina/MaixPy-UI-Lib/blob/main/LICENSE) [![Version](https://img.shields.io/badge/version-2.4-brightgreen.svg)](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![button](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/button.jpg)\n\n![switch](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/switch.jpg)\n\n![slider](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/slider.jpg)\n\n![radiobutton](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/radiobutton.jpg)\n\n![checkbox](https://raw.githubusercontent.com/aristorechina/MaixPy-UI-Lib/main/pics/checkbox.jpg)\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"
}
        
Elapsed time: 1.16849s