Skip to content

Latest commit

 

History

History
190 lines (141 loc) · 5.87 KB

File metadata and controls

190 lines (141 loc) · 5.87 KB

TextField 焦点环问题的根本原因和解决方案

🔍 问题根源

为什么焦点环不能随自定义高度改变?

NSTextField 的焦点环(focus ring)由以下机制决定:

  1. Cell-Based 架构

    • NSTextField 使用 NSTextFieldCell 来处理绘制和编辑
    • 焦点环基于 cell 的 `focus焦点环边界既不来自 view 的 frame,也不完全来自约束设置的尺寸
  2. Intrinsic Content Size 优先

    • Cell 有自己的 intrinsicContentSize,基于字体和内容计算
    • 即使外部 view 通过约束改变了 frame,cell 仍然使用自己计算的内容尺寸来绘制焦点环
  3. 系统绘制机制

    • 焦点环由系统的 -drawFocusRingMask-focusRingMaskBounds 方法绘制
    • 这些方法返回的是 cell 认为的"有效内容区域",而不是 view 的实际 bounds

❌ 已尝试但无效的方法

1. 设置 Exterior Focus Ring Type (setFocusRingType: 2)

  • 预期: 焦点环绘制在控件外部,可能会更大
  • 实际: 焦点环位置变了,但尺寸仍然基于 cell 的内容区域
  • 结论: 只改变位置,不改变大小

2. 强制布局更新 (layoutSubtreeIfNeeded, setFrameSize)

  • 预期: 刷新后 cell 会感知到新的 frame尺寸
  • 实际: Cell 独立计算,不受 frame 影响
  • 结论: 布局更新不影响 cell 的内部逻辑

3. Cell 配置 (setScrollable:false, setWraps:false)

  • 预期: 让 cell 使用整个 view 的 bounds
  • 实际: 这些设置影响文本布局,但不影响焦点环大小
  • 结论: Cell 行为略有改善,但焦点环问题依旧

4. 创建自定义子类重写 drawFocusRingMask

  • 预期: 完全控制焦点环的绘制区域
  • 实际: Rust 的生命周期系统与 Objective-C runtime 冲突
  • 结论: 技术上可行但在 Rust 中实现复杂度极高

✅ 实际可行的解决方案

方案 A: 使用标准控件尺寸 ⭐ 推荐

不要自定义高度,而是使用系统的标准尺寸:

TextField::new()
    .placeholder("Name")
    .control_size(0)  // Regular: 标准高度 (~22px)
    .control_size(3)  // Large: 大尺寸 (~28px, macOS 11+)
    
    .bezel_style(1)   // Rounded bezel
    .builder()
    .font_size(14.0);  // 调整字体大小而不是控件高度

优点:

  • ✅ 焦点环完美匹配
  • ✅ 符合系统设计规范
  • ✅ 自动适配深色模式
  • ✅ 无需额外workaround

缺点:

  • ⚠️ 高度选择有限(只有 4 种系统预设)

方案 B: 使用自定义视觉反馈 🎨

禁用焦点环,用自定义边框/背景色来表示焦点状态:

// 创建带自定义焦点效果的 TextField wrapper
fn custom_focus_textfield() -> UIBuilder<NSView> {
    let container = rect()
        .height(Size::Fixed(44.0))
        .corner_radius(6.0)
        .background((240, 240, 240));  // 灰色背景
    
    let field = TextField::new()
        .placeholder("Name")
        .focus_ring_type(1)  // 禁用系统焦点环
        .no_bezel()  // 无边框
        .builder();
    
    container.child(field)
}

// TODO: 添加焦点状态监听,改变容器背景色/边框

优点:

  • ✅ 完全自定义外观
  • ✅ 焦点效果可以是任意尺寸
  • ✅ 可以实现现代 UI 设计

缺点:

  • ⚠️ 需要实现焦点状态监听
  • ⚠️ 不符合 macOS 原生体验
  • ⚠️ 需要自己处理可访问性

方案 C: 增加内边距让控件"更大" 📦

不增加 TextField 高度,而是在外面包一层有 padding 的容器:

let field_wrapper = rect()
    .padding(EdgeInsets::symmetric(12.0, 8.0))  // 上下各 12px
    .child(
        TextField::new()
            .placeholder("Name")
            .bezel_style(1)
            .builder()
    );

优点:

  • ✅ TextField 使用标准高度,焦点环正常
  • ✅ 视觉上看起来更大(因为有padding)
  • ✅ 保持原生体验

缺点:

  • ⚠️ 增加视图层级
  • ⚠️ 点击区域变大但输入区域没变

方案 D: 接受现实,优化其他方面 🤷

承认焦点环限制,但优化其他视觉元素:

TextField::new()
    .placeholder("Your Name")
    .bezel_style(1)
    .width(Size::Fixed(360.0))
    .height(Size::Fixed(40.0))      // 自定义高度
    .focus_ring_type(2)             // Exterior - 至少让它更明显
    .builder()
    .font_size(15.0)                // 大字体提升可读性
    .color((0, 0, 0, 200))          // 深色文字contrast

说明: 虽然焦点环小,但通过其他方式提升整体体验

📊 建议的最佳实践

对于表单应用

const FORM_FIELD_HEIGHT: f64 = 40.0;  // 定义但知道焦点环限制

TextField::new()
    .placeholder("Name")
    .bezel_style(1)
    // 选项 1: 使用标准高度(焦点环完美)
    .control_size(3)  // Large
    
    // 选项 2: 或自定义高度(接受焦点环小的问题)
    // .height(Size::Fixed(FORM_FIELD_HEIGHT))
    // .focus_ring_type(2)  // 至少让它更visible
    
    .builder()
    .font_size(14.0);

设计建议

  1. 优先使用系统标准尺寸

    • Regular (control_size(0)): ~22px 高度
    • Large (control_size(3)): ~28px 高度
  2. 如果必须自定义高度

    • 使用 28-44px 范围(太小或太大都不合适)
    • 添加 .focus_ring_type(2) 让焦点环更visible
    • 通过字体大小而非控件高度来改善可读性
  3. 考虑用户体验而非完美像素

    • 焦点环小一点不影响实际使用
    • Tab 键导航仍然正常工作
    • 可访问性不受影响

🎯 结论

这是 AppKit 的设计限制,不是bug。

NSTextField 的 cell-based 架构设计就是这样的。如果需要完全自定义的焦点效果:

  1. 使用方案 B(自定义视觉反馈)
  2. 或考虑使用 NSView + NSTextView 自己实现文本输入

对于大多数表单应用,建议使用方案 A 或 C,保持原生体验。