Skip to content

Latest commit

 

History

History
565 lines (432 loc) · 14.1 KB

File metadata and controls

565 lines (432 loc) · 14.1 KB

⌨️ Input Component - 输入框组件

状态:✅ 已完成
难度:⭐⭐⭐
代码行数:~310 行


📋 组件概述

Input 是一个交互式输入框组件,演示了文本输入、光标管理和焦点控制技术。

核心功能

  • 文本输入:支持字母、数字、符号输入
  • 光标显示:闪烁光标指示输入位置
  • 焦点管理:点击聚焦、点击外部失焦
  • 退格删除:删除字符
  • 占位符:空时显示提示文本
  • 多输入框:同时管理多个独立输入框
  • Tab 切换:键盘切换焦点
  • 字符限制:最大长度控制
  • 视觉反馈:聚焦、悬停状态

🎨 视觉效果

输入框状态

状态 描述 视觉效果
空闲 无焦点、无内容 灰色边框 + 占位符
悬停 鼠标悬停 半透明高亮
聚焦 激活状态 蓝色边框 + 闪烁光标
有内容 已输入文本 显示文本内容

组件布局

┌──────────────────────────────────┐
│  Input Component Demo            │
│  [Tab] Switch   [ESC] Quit       │
│                                  │
│  Name:                           │
│  ┌────────────────────────────┐  │
│  │john_                       │  │  ← 聚焦状态
│  └────────────────────────────┘  │
│                                  │
│  Email:                          │
│  ┌────────────────────────────┐  │
│  │example@email.com           │  │  ← 占位符
│  └────────────────────────────┘  │
│                                  │
│  Password:                       │
│  ┌────────────────────────────┐  │
│  │********                    │  │  ← 占位符
│  └────────────────────────────┘  │
│                                  │
│  Comment:                        │
│  ┌────────────────────────────┐  │
│  │hello world                 │  │  ← 已输入
│  └────────────────────────────┘  │
└──────────────────────────────────┘

⌨️ 交互演示

鼠标交互

// 1. 检测点击
if (isMousePressed && !wasMousePressed) {
    float mouseX, mouseY;
    input->getMousePosition(mouseX, mouseY);
    
    // 检查是否点击输入框
    for (int i = 0; i < 4; i++) {
        if (isPointInInput(mouseX, mouseY, inputs[i])) {
            focusedInput = i;  // 设置焦点
            break;
        }
    }
    
    // 点击外部失去焦点
    if (!clickedInput) {
        focusedInput = -1;
    }
}

键盘控制

按键 功能
A-Z 输入字母(小写)
0-9 输入数字
Space 输入空格
Backspace 删除最后一个字符
Tab 切换到下一个输入框
ESC 退出程序

🎯 核心实现

1. 输入框数据结构

struct InputData {
    float x, y, width, height;  // 位置和大小
    char* text;                 // 文本内容指针
    int maxLength;              // 最大长度
    const char* placeholder;    // 占位符
    const char* label;          // 标签
};

// 文本缓冲区
static const int MAX_TEXT_LENGTH = 32;
char nameText[MAX_TEXT_LENGTH] = "";
char emailText[MAX_TEXT_LENGTH] = "";

// 输入框配置
InputData inputs[4] = {
    {x, y, w, h, nameText, MAX_TEXT_LENGTH, "Enter your name...", "Name:"},
    {x, y, w, h, emailText, MAX_TEXT_LENGTH, "example@email.com", "Email:"},
    // ...
};

2. 焦点管理

// 全局焦点状态
int focusedInput = -1;  // -1 表示无焦点

// 点击输入框获得焦点
if (isPointInInput(mouseX, mouseY, inputs[i])) {
    focusedInput = i;
}

// 点击外部失去焦点
if (!clickedInput) {
    focusedInput = -1;
}

// 只有聚焦时才处理键盘输入
if (focusedInput >= 0) {
    handleKeyboardInput(inputs[focusedInput]);
}

3. 文本输入处理

void handleKeyboardInput(InputData& inputBox) {
    int len = strlen(inputBox.text);
    
    // 退格键
    if (input->isKeyJustPressed(8)) {  // Backspace
        if (len > 0) {
            inputBox.text[len - 1] = '\0';
        }
        return;
    }
    
    // 长度限制
    if (len >= inputBox.maxLength - 1) {
        return;
    }
    
    // 字母 A-Z
    for (int key = 'A'; key <= 'Z'; key++) {
        if (input->isKeyJustPressed(key)) {
            inputBox.text[len] = key + 32;  // 转小写
            inputBox.text[len + 1] = '\0';
            return;
        }
    }
    
    // 数字 0-9
    for (int key = '0'; key <= '9'; key++) {
        if (input->isKeyJustPressed(key)) {
            inputBox.text[len] = key;
            inputBox.text[len + 1] = '\0';
            return;
        }
    }
    
    // 空格和符号...
}

4. 光标闪烁

// 光标状态
float cursorBlinkTime = 0.0f;
bool cursorVisible = true;

// 更新光标闪烁(每 0.5 秒切换)
void onUpdate(float deltaTime) {
    cursorBlinkTime += deltaTime;
    if (cursorBlinkTime >= 0.5f) {
        cursorBlinkTime = 0.0f;
        cursorVisible = !cursorVisible;
    }
}

// 绘制光标(仅聚焦时)
if (isFocused && cursorVisible) {
    float cursorX = textX + textLength * 8.0f;  // 文本末尾
    renderer->drawRectangle(cursorX, cursorY, 2.0f, cursorHeight, colorCursor);
}

5. 输入框渲染

void drawInput(const InputData& inputBox, bool isFocused, bool isHovered) {
    // 1. 绘制标签
    renderer->drawText(inputBox.label, x, y - 20, colorLabel, 14.0f);
    
    // 2. 绘制背景
    renderer->drawRectangle(x, y, width, height, colorBoxBg);
    
    // 3. 绘制边框(聚焦时使用不同颜色和宽度)
    float* borderColor = isFocused ? colorBoxFocus : colorBox;
    float borderWidth = isFocused ? 2.5f : 2.0f;
    // ... 绘制四条边
    
    // 4. 绘制文本或占位符
    if (strlen(inputBox.text) > 0) {
        renderer->drawText(inputBox.text, textX, textY, colorText, 14.0f);
    } else if (!isFocused) {
        renderer->drawText(inputBox.placeholder, textX, textY, colorPlaceholder, 14.0f);
    }
    
    // 5. 绘制光标(仅聚焦时)
    if (isFocused && cursorVisible) {
        renderer->drawRectangle(cursorX, cursorY, 2.0f, cursorHeight, colorCursor);
    }
    
    // 6. 绘制悬停效果
    if (isHovered && !isFocused) {
        renderer->drawRectangle(x, y, width, height, highlightColor);
    }
}

🔬 技术要点

1. 字符串管理

C 风格字符串

char text[32] = "";              // 固定大小缓冲区
int len = strlen(text);          // 获取长度
text[len] = 'a';                 // 添加字符
text[len + 1] = '\0';            // null 终止符
text[len - 1] = '\0';            // 删除字符

注意事项

  • 始终保留空间给 \0 终止符
  • 检查长度避免溢出
  • maxLength - 1 是最多可输入的字符数

2. 键盘输入处理

限制

  • API 只提供 isKeyJustPressed(keyCode)
  • 需要手动遍历所有可能的按键
  • 没有大小写状态检测(简化为小写)

方案

// 遍历所有字母
for (int key = 'A'; key <= 'Z'; key++) {
    if (input->isKeyJustPressed(key)) {
        // 处理输入
    }
}

// 遍历所有数字
for (int key = '0'; key <= '9'; key++) {
    if (input->isKeyJustPressed(key)) {
        // 处理输入
    }
}

3. 光标位置计算

简化方案

// 假设固定宽度字体,每个字符约 8 像素
float cursorX = textStartX + textLength * 8.0f;

精确方案(需要 API 支持):

// 使用文本测量 API
float textWidth = renderer->measureText(text, fontSize);
float cursorX = textStartX + textWidth;

4. 焦点状态机

         点击输入框
    ┌─────────────────┐
    │                 ▼
空闲 ────────────────> 聚焦
    ◄────────────────
     点击外部/Tab切换

📊 性能分析

渲染调用统计

每帧渲染调用(4 个输入框):

  • 背景矩形:4 次
  • 边框矩形:16 次(每个输入框 4 条边)
  • 文本/占位符:4 次
  • 光标:0-1 次(仅聚焦时)
  • 标签文本:4 次
  • 悬停高亮:0-1 次
  • 说明文本:2 次

总计:约 30-35 次渲染调用/帧


🎓 学习要点

初学者

  1. C 字符串操作

    • strlenstrcpy 等函数
    • null 终止符的重要性
    • 缓冲区溢出防护
  2. 焦点管理

    • 全局焦点状态
    • 点击获得/失去焦点
  3. 简单动画

    • 光标闪烁的实现
    • 时间累加和状态切换

进阶开发者

  1. 输入处理

    • 键盘事件的逐帧检测
    • 字符映射和转换
    • 输入限制和验证
  2. 状态同步

    • 输入时重置光标闪烁
    • 焦点改变时的状态更新
  3. 组件设计

    • 数据与逻辑分离
    • 统一的渲染接口

🚀 扩展方向

简单扩展

  • 数字输入框:只允许输入数字
  • 密码模式:显示 * 号代替实际字符
  • 字符计数:显示已输入/最大字符数

中等扩展

  • 光标移动:方向键移动光标到任意位置
  • 文本选择:Shift+方向键选择文本
  • 复制粘贴:Ctrl+C/V 支持
  • 撤销重做:Ctrl+Z/Y 支持

高级扩展

  • IME 输入:支持中文输入法
  • 多行输入:文本域(textarea)
  • 自动完成:建议列表
  • 输入验证:正则表达式验证
  • 格式化输入:电话号码、日期等

🏗️ 构建与运行

构建步骤

# 1. 配置项目
cd examples/cpp/input
cmake -S . -B build -G "Visual Studio 17 2022" -A x64

# 2. 编译
cmake --build build --config Release

# 3. 复制 DLL 到运行时目录
copy build\Release\Release\input.dll ..\..\..\..\native\build\Release\

# 4. 编译着色器
cd assets\shaders
compile_shaders.bat

运行组件

cd ..\..\..\..\native
.\build\Release\bitui_native.exe .\build\Release\input.dll

📁 文件结构

input/
├── input.cpp                         # 组件实现(~310 行)
├── CMakeLists.txt                    # 构建配置
├── README.md                         # 本文档
├── assets/
│   └── shaders/                      # 着色器目录
│       ├── triangle.vert             # 顶点着色器(GLSL)
│       ├── triangle.frag             # 片段着色器(GLSL)
│       ├── compile_shaders.bat       # 着色器编译脚本
│       └── spv/                      # 编译后的 SPIR-V
│           ├── triangle_vert.spv
│           └── triangle_frag.spv
└── build/                            # 构建输出
    └── Release/
        └── Release/
            └── input.dll             # 组件库

🔗 相关组件

组件 关系 说明
Button 兄弟组件 类似的点击交互
Slider 兄弟组件 类似的焦点管理
Dropdown 未实现 需要输入框作为基础

📚 参考资料

API 使用

  • IInput::isMouseButtonPressed(int button) - 检测鼠标按键状态
  • IInput::getMousePosition(float& x, float& y) - 获取鼠标位置
  • IInput::isKeyJustPressed(int keyCode) - 检测键盘按键
  • IRenderer::drawRectangle() - 绘制矩形
  • IRenderer::drawText() - 绘制文本

C 字符串函数

  • strlen(const char* str) - 获取字符串长度
  • strcpy(char* dest, const char* src) - 复制字符串
  • strcat(char* dest, const char* src) - 连接字符串
  • strcmp(const char* s1, const char* s2) - 比较字符串

💡 最佳实践

  1. 字符串安全

    • 始终检查长度避免溢出
    • 确保 null 终止符存在
    • 使用 maxLength - 1 作为最大输入
  2. 焦点管理

    • 明确的焦点状态
    • 一次只有一个输入框聚焦
    • 点击外部失去焦点
  3. 用户体验

    • 光标闪烁提示输入位置
    • 占位符提示输入内容
    • 清晰的视觉反馈
  4. 输入处理

    • 重置光标闪烁增强响应感
    • 立即显示输入字符
    • 防止重复输入(使用 JustPressed)

🐛 调试建议

常见问题

  1. 字符不显示

    • 检查文本颜色是否与背景对比明显
    • 验证文本位置计算
    • 确认字体大小合适
  2. 光标不闪烁

    • 检查时间累加逻辑
    • 验证 cursorVisible 状态切换
    • 确认聚焦状态正确
  3. 无法输入

    • 检查焦点状态
    • 验证键码是否正确
    • 确认长度未超限
  4. 缓冲区溢出

    • 检查 maxLength 限制
    • 验证 null 终止符位置
    • 确认数组大小足够

📈 版本历史

  • v1.0 (2025-10-12) - 初始版本
    • 实现基础输入框功能
    • 支持 4 个独立输入框
    • 添加文本输入和删除
    • 实现光标闪烁显示
    • 添加焦点管理
    • 支持 Tab 键切换

📞 技术支持

  • 项目主页:Bit HCI
  • 文档:docs/guides/
  • 示例:examples/cpp/

🎨 设计说明

本组件使用简化的输入处理方案:

  • 只支持基本字符:字母、数字、空格、常用符号
  • 固定小写:简化大小写处理
  • 光标固定在末尾:简化光标管理
  • 无文本选择:简化交互逻辑

这些简化使组件更容易理解和实现,同时保留了输入框的核心功能。在实际应用中,可以根据需求逐步添加更多功能。


Happy Coding! 🎉