ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Bevy Feathers NumberInput 数字输入控件实战:scrubbing 拖拽、限制区间与受控数值绑定的完整指南

Bevy Feathers NumberInput 数字输入控件实战:scrubbing 拖拽、限制区间与受控数值绑定的完整指南 Bevy Feathers NumberInput 数字输入控件实战scrubbing 拖拽、限制区间与受控数值绑定的完整指南【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy导读FeathersNumberInput是 Bevy Feathers UI 组件库中的数字输入控件本文基于其新增的 scrubbing按住鼠标拖拽改值能力展开讲清该控件的支持类型、HardLimit/SoftLimit/NumberInputPrecision/Step等可选配置组件各自的语义以及“受控组件”模式下与ValueChange事件的双向同步写法。读完本文你可以直接用场景 DSL 组装一个具备 Blender 式交互、可限制区间、可带单位换算的数字输入框并理解其底层状态机与拖拽速度启发式算法。一、这次更新改了什么向 Blender 的数字输入交互对齐在本次重构之前FeathersNumberInput更像一个能输入数字的文本框。更新后该控件从 Blender 的数字输入字段numeric input中借鉴了最佳交互元素核心变化是引入了多种编辑模式并存Scrubbing拖拽改值按住鼠标左右拖动即可连续修改数值直接键盘输入direct keyboard entry点击后进入编辑态直接键入数字与此同时保留了对f32、f64、i32、i64四种数值类型的完整支持由NumberInputValue的枚举变体决定见 crates/bevy_feathers/src/controls/number_input.rs。这意味着 Feathers 的数字输入控件在交互能力上大大贴近 Blender 的数字字段适用于属性面板、变换工具位置/旋转/缩放、参数化编辑器等需要快速、连续、精确同时具备的场景。二、受控组件模型数值真相保存在应用侧FeathersNumberInput是标准 Feathers 风格的受控控件controlled widget。所谓受控指控件内部不会自动维护当前数值数值的真实来源始终是应用持有的某个数据结构控件与数据之间通过两条通道保持双向同步此设计在 number_input.rs 的文档注释中描述得很清楚用户在控件上拖拽或输入 → 控件发出ValueChangeT事件 → 应用事件处理器把新值写回应用数据应用数据变化无论来自该事件还是别处→ 应用向控件实体插入NumberInputValue组件→ 控件监听组件插入、刷新文本框内容与滑块位置。ValueChangeT事件定义在 crates/bevy_ui_widgets/src/lib.rs除value外还携带一个is_final布尔标志拖拽过程中is_final为false表示值在预览/连续变化状态拖拽结束、回车提交或失焦时is_final为true表示这是一个最终提交值。源码注释特别提醒即使is_final false应用也应该回写控件值插入新的NumberInputValue否则用户拖拽时看不到数字实时变化见 number_input.rs。事件处理器的最小写法feathers_number_input示例展示了这种数值回写自身的最小处理器见 examples/ui/widgets/feathers_number_input.rson( |value_change: OnValueChangef32, mut commands: Commands| { commands .entity(value_change.event_target()) .insert(NumberInputValue::F32(value_change.value)); }, )从源码看控件通过OnInsertNumberInputValue观察者number_input_on_insert_value监听组件插入若新值与当前文本不同就用queue_edit(TextEdit::SelectAll)queue_edit(TextEdit::Insert(...))替换文本框内容同时调用update_slider_pos把背景滑块条移动到对应位置见 number_input.rs。为减少无谓刷新官方建议只在值真正变化时插入NumberInputValue见 number_input.rs。三、行为配置组件四件套 环绕模式控件行为通过若干可选组件直接随场景 DSL 挂到FeathersNumberInput实体上来配置。它们的语义是本次发布说明的核心内容下面逐一展开。HardLimit —— 绝对硬边界HardLimit指定数值的绝对最小/最大范围对应NumberInputRange的一个区间。任何超出该范围的取值都会被clamp截断回边界内无论来自拖拽、步进还是键入。若该组件缺省则使用该数据类型自身的自然范围如f32即整个 32 位浮点范围。按类型提供便捷构造HardLimit::f32(range)、HardLimit::f64(range)、HardLimit::i32(range)、HardLimit::i64(range)见 number_input.rs。clamp 行为本身定义在NumberInputRange::clamp见 number_input.rs。注意若区间类型与值类型不匹配会warn_once并原样返回值。SoftLimit —— 仅约束拖拽的“软边界”SoftLimit指定仅通过拖拽可到达的范围通过键入输入的值可以超出这个范围。这正是它叫“软”的原因——它不是上限而是拖拽舒适区。例如SoftLimit(NumberInputRange::F32(0.0..10.0))意味着拖拽最多改到 0~10但键盘可以输入 25.5。应用场景既希望鼠标操作限定在合理区间防止误拖又保留高级用户直接输入越界值的灵活性。NumberInputPrecision —— 拖拽精度量化NumberInputPrecision(pub i32)表示拖拽时保留的小数位数值为2→ 四舍五入到最近的 1/100值为0→ 取整值为负数如-3→ 四舍五入到最近的千位见 number_input.rs。它的作用仅限于拖拽过程中量化数值避免小数点后面一串数字乱跳不影响键盘键入也不阻止应用通过其他途径如直接插入NumberInputValue设置非量化值见 number_input.rs 注释。实现上NumberInputPrecision::round对F32/F64计算10^n因子做乘后取整再除对整数类型保持恒等映射见 number_input.rs。NumberInputStep —— 步进增量NumberInputStep(pub f64)表示递增/递减时单次改变的增量默认1.0。当光标悬停在无软边界的输入框上时会出现左右两个 chevron 箭头NumberInputDecrement/NumberInputIncrement点击即按该步长加减拖拽速度计算也会参考该值见下文。构造与默认值见 number_input.rs。发布说明中描述的是Step实际实现中的组件名为NumberInputStep二者指向同一配置语义。NumberInputWrap —— 硬边界环绕除四件套外实现中还提供了NumberInputWrapNoWrap/Wrap组件当存在HardLimit且开启Wrap时越界值会wrap环绕回区间内而非被 clamp典型用例是角度/朝向这类周期性数值。环绕实现在NumberInputRange::wrap基于rem_euclid见 number_input.rs若配置了Wrap却没有HardLimit会告警并忽略环绕见 number_input.rs。各约束的合成顺序当一个拖拽/提交同时携带多个约束时应用顺序是有讲究的。emit_drag_value_change中可以看到完整流水线见 number_input.rs以拖拽起始值base_value为基准累加偏移得到候选值每次拖拽是相对拖拽开始值的增量而不是相对上一帧值若有SoftLimit→ clamp 进软范围若有NumberInputPrecision→ 按小数位量化最后处理硬边界HardLimitWrap则环绕否则 clamp。硬边界最后应用确保任何路径产出的值都不可能越出硬边界。四、两种交互外观Slider 模式与 Scrubber 模式控件的外观与拖拽手感取决于是否配置SoftLimit这是本次发布说明区分出的两种模式。有 SoftLimit看起来像一个滑块当SoftLimit存在时控件观感与手感都更接近 FeathersSlider输入框背景会绘制一条滑轨slide bar数值所在位置有一条与滑块 thumb 等宽的“已填充”条拖拽速度按公式(范围长度 / 滑轨像素宽度)计算见 number_input.rs也就是鼠标移动多少像素值就按比例覆盖多少范围鼠标位移与滑轨尺寸严格同步——这正是发布说明中“changes in the bars size will be synchronized with movement of the mouse”的含义滑轨位置由update_slider_pos依据NumberInputRange::thumb_position计算出的 0~1 比例实时更新到BackgroundGradient的渐变色标上见 number_input.rs这种用渐变画滑块的做法可以让滑轨也保持圆角。无 SoftLimit更像一个“擦子”Scrubber当SoftLimit不存在时控件不画滑轨表现为典型的“scrubber”拖拽速度不再由滑轨决定而是依据一套启发式算法综合当前可用信息推断见 number_input.rs优先级从高到低为有NumberInputStep→ 速度 step × BASE_DRAG_SPEEDBASE_DRAG_SPEED 0.01见 number_input.rs是整数类型I32/I64→ 视为 step1使用基础速度有NumberInputPrecision→ 由精度反推速度 10^(-precision)以上都没有 → 使用自适应算法取当前值绝对值的数量级距 1 最近的 10 的幂作为量级缩放BASE_DRAG_SPEED × 10^decade保证大数拖得快、小数拖得慢。增强细节Shift 慢速微调scrubber_on_drag中还有一个实用设计拖拽时按住Shift位移增量乘以0.1实现慢速精细调节见 number_input.rs。五、点击 vs 拖拽内部状态机如何区分两种手势两种模式共同的前提是需要区分用户是想拖拽改值还是想点一下进入输入模式。控件用一个DragState组件 EditMode枚举Idle/Scrubbing/Editing见 number_input.rs管理状态迁移并定义一个DRAG_THRESHOLD_DISTANCE 0.5像素阈值见 number_input.rs。从事件处理器可以梳理出完整的判定流程输入框内部有一个绝对定位、覆盖整个输入区、用于拦截指针事件的透明子实体scrubber承载 press/release/drag_start/drag/drag_end/drag_cancel 一系列观察者见 number_input.rsscrubber_on_press非编辑态下按下 → 进入Scrubbing模式并把TextReadWriteMode切为Static隐藏光标同时propagate(false)阻止事件继续传给底层文本编辑见 number_input.rsscrubber_on_drag实时记录累计最大位移max_distance只有当位移超过 0.5px 阈值才开始改值防止点击时的抖动误触发——这是“click vs drag”判定的核心见 number_input.rsscrubber_on_release若整次按下释放过程中max_distance从未超过阈值 → 视为一次点击切换进Editing模式恢复TextReadWriteMode::Editable、光标变成 I-Beam并根据点击位置把文本光标MoveToPoint定位过去见 number_input.rs输入过程中若发生PointerCancel如窗口失焦打断scrubber_on_drag_cancel会把状态还原为Idle见 number_input.rs。拖拽/悬停时控件的滑轨与配色由set_slidebar_styles统一刷新它根据 disabled / pressed / hovered / focused 状态在SLIDER_BAR_*、SLIDER_BG_*、TEXT_INPUT_TEXT_*等主题 token 之间切换颜色并同步设置系统光标形状禁用时NotAllowed否则ColResize见 number_input.rs。点击进入编辑模式后的行为非拖拽点击会激活typing 模式用户可以输入数字直接改值回车Enter提交number_input_on_enter_key把当前文本按控件声明的格式解析应用硬边界与环绕约束后触发一次is_final true的ValueChange并把模式切回Idle见 number_input.rs失焦提交number_input_on_focus_lost走同样的解析提交路径并恢复光标与只读模式见 number_input.rs解析失败时只warn!(number input parsing failed, invalid format)并放弃该次提交见 number_input.rs若文本被清空为空串则不触发事件见 number_input.rs。六、类型支持与格式解析NumberInputValue 是如何分派的NumberInputValue是承载数值的组件也是一个带类型的枚举pub enum NumberInputValue { F32(f32), F64(f64), I32(i32), I64(i64), }默认值为F32(0.0)见 number_input.rs。由于FeathersNumberInput通过#[require(NumberInputValue)]要求该组件创建控件时只需直接插入一个带值的NumberInputValue变体即可设定初始值并决定控件的数据格式见 number_input.rs。NumberFormat枚举默认F32记录当前编辑格式它决定发出的ValueChangeT泛型类型trigger_value_change会按枚举变体把commands.trigger(ValueChange { source, value, is_final })分派为ValueChangef32/ValueChangef64/ValueChangei32/ValueChangei64四者之一见 number_input.rs。事件中携带的source实体即FeathersNumberInput根实体应用可以据此区分是哪个输入框发出的变化。整数变体的加减采用saturating_add防止溢出见 number_input.rs。七、带单位的数字输入Units 注册表与展示换算除了裸数值实现还提供了成熟的单位系统让同一个控件既显示45°又能以45d编辑NumberInputUnits挂在控件上的引用组件内容是一个字符串 id如length_meters、angle_degrees指向注册表中某个UnitsFormat见 number_input.rsUnitsFormattrait定义id()、format(value, editing)按展示/编辑两种模式格式化字符串、parse(...)把带后缀字符串解析回规范单位数值见 number_input.rsStandardUnitKind以后缀表 换算比例表的形式便捷实现UnitsFormat并支持canonical_index内部存储单位如弧度/display_index展示单位如度/editing_index编辑单位如 d三套索引分离见 number_input.rs。内置了三组标准单位见 number_input.rs类型id典型后缀内部规范单位Dimensionlessnone无—LengthMeterslength_metersm / km / cm / mm / ft / in / mi米TimeSecondstime_secondss / ks / cs / ms秒AngleDegreesangle_degreesrad / ° / d / deg弧度显示为度有意思的细节角度单位内部规范量是弧度但展示时输出45°由于°不方便键入编辑模式会自动把内容替换为45ddisplay_index与editing_index分离正是为此见 number_input.rs。scrubber_on_release在进入编辑态前也会先把显示文本替换为可编辑格式并在结束时换回展示格式见 number_input.rs。这些单位对象由UnitsRegistry资源集中管理NumberInputPlugin在Plugin::build时默认注册了上述四者并挂接两个系统在PickingSystems::Last、InputFocusSystems::Dispatch之后同步滑轨配色见 number_input.rs。注册表支持通过insert注入自定义单位见 number_input.rs。解析/格式化的正确性有单元测试兜底例如test_length_meters_convert_km_to_m1km → 1000m、test_angle_degrees_formatπ/4 rad → 45° / 编辑时 45d、test_parse_negative_numbers-50.5cm → -0.505m等见 number_input.rs。八、从零搭一个输入框完整示例控件本身是一个可继承的 Scene 组件#[derive(SceneComponent)]见 number_input.rs在 bsn 场景 DSL 中用FeathersNumberInput生成。仓库中的feathers_number_input示例examples/ui/widgets/feathers_number_input.rs把各种配置组合平铺展示节选如下fn demo_root() - impl Scene { bsn! { ... Children[ demo_field_f32(soft limit, 2.0, bsn!( SoftLimit(NumberInputRange::F32(0.0..10.0)) )), demo_field_f32(hard limit, 3.0, bsn!( HardLimit(NumberInputRange::F32(-100.0..100.0)) )), demo_field_f32(soft hard, 4.0, bsn!( SoftLimit(NumberInputRange::F32(0.0..10.0)) HardLimit(NumberInputRange::F32(-100.0..100.0)) )), demo_field_f32(precision(2), 6.0, bsn!( NumberInputPrecision(2) )), demo_field_f32(hard limit wrap, 0.0, bsn!( HardLimit(NumberInputRange::F32(-180.0..180.0)) NumberInputWrap::Wrap )), demo_field_f32(in meters, 2.0, bsn!( NumberInputUnits::new(LengthMeters) )), demo_field_f32(in degrees, PI, bsn!( NumberInputUnits::new(AngleDegrees) )), ] } } fn demo_field_f32(label_text: str, value: f32, options: impl Scene) - impl Scene { bsn! { ... Children [ ( FeathersNumberInput NumberInputValue::F32(value) {options} Node { flex_grow: 1.0, max_width: px(120) } on( |value_change: OnValueChangef32, mut commands: Commands| { commands.entity(value_change.event_target()) .insert(NumberInputValue::F32(value_change.value)); }) ), ] } }要点拆解FeathersNumberInput生成控件骨架其内部结构标签段、可编辑文本、透明 scrubber 层、左右 chevron由FeathersNumberInput::scene预置见 number_input.rsNumberInputValue::F32(value)直接设置初始值与数值格式可选行为组件SoftLimit、HardLimit、NumberInputPrecision、NumberInputStep、NumberInputWrap、NumberInputUnits、InteractionDisabled用{options}展开注入每个输入框必须配套OnValueChangef32或对应泛型处理器做受控回写InteractionDisabled组件可让控件进入禁用态文本只读、光标变为NotAllowed、滑轨与文字改用 disabled token相关逻辑见 number_input.rs。需要 f32 / i32 标准字段时也可以直接复用仓库封装好的 helper注意它们需要bevy_feathersfeature 开启examples/helpers/number_input_f32.rs 提供number_input_f32(name, identifier, value, precision, limits)内部组合了HardLimit::f32(limits)与可选标识组件examples/helpers/number_input_i32.rs 提供number_input_i32(...)。标识组件用来在多输入框场景中区分事件来源——查询哪个带number_input_identifier的FeathersNumberInput是ValueChange的source实体即可。视觉定制sigil 与 label创建控件时还可通过FeathersNumberInputProps定制两处外观见 number_input.rssigil_color输入框左侧的一条彩色竖条默认透明常用于区分不同坐标轴如 X/Y/Z 分别用不同 token 着色示例中用的是tokens::TEXT_INPUT_X_AXISlabel_textsigil 右侧的静态说明文字惯例填 X / Y / Z带 label 时 sigil 会加宽为 4px 的左边框。在 bsn 中使用 props 的写法是属性语法FeathersNumberInput { sigil_color: ..., label_text: X }见 examples/ui/widgets/feathers_number_input.rs。运行示例在启用bevy_feathersfeature 的前提下可用 cargo 直接运行该示例cargo run --example feathers_number_input --features bevy/bevy_feathers具体 feature 名称以仓库 Cargo.toml 中的配置为准。九、升级迁移用插入组件取代触发事件如果你是从旧 API 迁移到本次重构版本需要特别注意程序化更新数值的通道变了旧版本通过触发UpdateNumberInput事件来更新值新版本改为插入NumberInputValue组件详见 迁移指南。后者在创建时指定初始值也更容易// BEFORE旧 API已废弃 commands.trigger(UpdateNumberInput { entity: input_ent, value: NumberInputValue::F32(new_value), }); // AFTER新 API commands .entity(input_ent) .insert(NumberInputValue::F32(new_value));其余不变事件处理器仍然监听ValueChangeTValueChange中is_final false拖拽中与 true提交的语义配合受控回写可以实现拖拽实时预览、松开才落库的编辑器级体验。结语把本次发布说明与 number_input.rs 的实现对照来看FeathersNumberInput的交互模型可以概括为三句话可选组件决定能力限制/精度/步进/单位/环绕有无SoftLimit决定观感滑块还是擦子受控回写决定数据流事件外发 组件插入回流。借助0.5px的手势阈值与EditMode状态机点击与拖拽被可靠区分借助启发式拖拽速度与 Shift 微调从千分位小值到十万级大值都能用一只手顺滑调整。这套能力对需要高密度数值编辑的工具型 UI 非常实用值得在属性面板、变换 gizmo 或参数化设置界面中优先采用。【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表