Skip to content

modoptions: string 设置项直接给文本框 + 游戏内文本输入层(并修复键位配置界面被盖住) - #5

Open
std-external wants to merge 2 commits into
std-microblock:masterfrom
std-external:feat/string-text-input
Open

std-external wants to merge 2 commits into
std-microblock:masterfrom
std-external:feat/string-text-input

Conversation

@std-external

@std-external std-external commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

动机

群友反馈:模组设置页里的字符串项(MiaoNet 的名称 / 头衔 / 颜色 / 头像链接等)只显示成一个通用「打开」按钮,值被塞在标题里,既不直观也没法像常规文本框那样编辑。

键位配置界面被设置页盖住、Esc 返回泛白的修复已拆到 #6,本 PR 只含 string 输入。两个 PR 都动了 MaterialModOptions.Update / UpdateInteractions,先合哪个都行,另一个我会 rebase。

改动

1. 字符串项变成常规文本框

  • 字符串项直接显示为行内文本框(当前值 / 「点击输入」占位 / 编辑图标),接受后写回属性并刷新标题
  • 键鼠:直接在行内编辑,走 SDL text input(TextInput.OnInput + TextInputEXT.TextEditing),和 vanilla 改名框同一条路径,支持系统输入法 / 非 ASCII
  • 触屏:走 CeleMod 的 TouchKind: "text" 触控目标拉起系统软键盘
  • 手柄:MInput.ControllerHasFocus 为 true 或 Everest UseKeyboardForTextInput 关闭时,进入自绘 QWERTY(方向键导航 + 确认)
  • 识别三类字符串入口:Everest 自动 string 设置(按命名规则反射)、手写菜单 mod(MiaoNet 这类 Func<string>/Action<string> 闭包)、以及兜底拦截 Overworld.Goto<OuiModOptionString>()

2. 光标、选词与剪贴板

  • 行内编辑器维护真实光标:← → 移动、Home/End 跳首尾、Delete 删光标后、Backspace 删光标前,插入在光标处
  • 空格就是空格:去掉原来的 · 占位,值按原样显示;光标宽度仍按 SystemTtfFont.MeasureVisible 实测,中文/全角字符算 2 格
  • Ctrl+A / C / X / V:全选、复制、剪切、粘贴,走 Everest 的 TextInput.Get/SetClipboardText()(SDL),Windows / Linux / Android 同一条路;粘贴进来的换行/制表符按单行字段过滤
  • Shift + ← → / Home / End 扩选,输入、Backspace、Delete、粘贴都会替换选区;普通 ← → 先把选区折叠到一侧
  • 按住 ← → 连续移动:按下先走一格,约 0.4s 后按 0.045s 的节奏重复,手感接近键盘 repeat
  • 鼠标点击文本任意位置按字符边界就近吸附光标(原来就有,改成共用同一套几何计算)

3. 搜索模组 / 搜索当前设置共用同一套交互

两个搜索框原来只是「末尾追加 + 退格」,现在换成同一个 TextEditBuffer:

  • 有光标,点框内任意位置可以定位,输入/Backspace/Delete 在光标处生效
  • Ctrl+A/C/X/V、Shift 扩选、按住方向键重复,和行内编辑器完全一致(共用 TextEditShortcuts / TextKeyRepeat)
  • focused 时按窗口显示(光标不会跑出框),失焦后仍从开头省略显示
  • 触屏软键盘路径(CeleMod TouchKind: "text")不变,提交后整体写回并把光标放到末尾

4. 可复用输入层

Source/TextEntryOverlay.cs:手机式 QWERTY(qwertyuiop / asdfghjkl / zxcvbnm,shift 切大写,123 / #+= / ABC 明确切页,底部 取消/空格/退格/接受,长按空格连打,角标只是提示)。Source/TextEditBuffer.cs 只保留与游戏无关的文本/光标/选区状态,宽度窗口拆到 Source/TextEditGeometry.cs,剪贴板快捷键在 Source/TextEditShortcuts.cs,长按重复在 Source/TextKeyRepeat.cs。测试工程可以直接链接 TextEditBuffer / TextKeyRepeat 做纯逻辑测试。

改了哪些文件

文件 说明
Source/TextEditBuffer.cs 光标 + 选区 + 替换/粘贴语义(无游戏依赖)
Source/TextEditGeometry.cs 新增:按字体宽度取窗口、点击定位光标
Source/TextEditShortcuts.cs 新增:Ctrl+A/C/X/V 共用实现
Source/TextKeyRepeat.cs 新增:按住方向键的重复节奏
Source/TextEntryOverlay.cs 游戏内 QWERTY 输入层(手柄用),跟随新的几何接口
Source/MaterialModOptions.cs 行内文本框、搜索框、光标/选区渲染、输入源分流
Tests/ModOptions/Program.cs 新增 TextEditBuffer / TextKeyRepeat 回归测试

验证

  • dotnet build Source/MicroblocksQolUtils.csproj:0 error / 0 warning(Everest stable lib-stripped)
  • dotnet run --project Tests/ModOptions/ModOptions.csproj -c Release:OptionRules 36 + 文本编辑 60 assertions 通过
  • dotnet run --project Tests/Capture/Capture.csproj -c Release:PASS
  • 本机 Linux 无法跑游戏实机截图;请按下面步骤在 Windows/Android 上验证。

手工验证步骤

  1. 空格:输入 a (前后各两个空格),显示就是空格本身,不再出现 ·;提交后重新打开值仍保留这些空格。
  2. 光标/选词:点开 string 项,← → / Home / End 移动;Shift+← → 选中一段后直接输入或按 Backspace 应替换选中内容;中文/全角字符前后的光标不偏半格。
  3. 剪贴板:Ctrl+A 全选,Ctrl+C 复制,Ctrl+X 剪切,Ctrl+V 粘贴;粘贴带换行的内容只保留单行文本。
  4. 长按:按住 ← 或 → 不放,约 0.4s 后开始连续移动。
  5. 搜索框:在「搜索模组」「搜索当前设置」里重复 2-4 步,点击框内任意位置光标应落到位。
  6. 输入法:fcitx5 等系统输入法组合输入中文时,正在组合的串仍显示在光标处,确认后落进值里;Esc 取消不改值、超长只截停新输入、空值占位保持原样。

本轮没做

鼠标/触屏拖动选择文本留到下一轮(需要处理按住拖动、跨窗口滚动和触屏手势冲突),本次先做真空格 + Ctrl+A/C/X/V + Shift 选词 + 长按移动这几项。

… fields

String settings in the material mod options page are now edited like a
regular text box:

- keyboard mouse users edit inline through SDL text input (IME included);
  the self-drawn QWERTY prompt is kept for gamepad input
- both editors keep a caret, so left/right, Home/End and Delete work, and
  backspace removes the character before the caret
- mouse/touch clicks place the caret at the nearest character boundary;
  widths come from the TTF font, so CJK/full-width glyphs count twice
- spaces are drawn as a middle dot so leading/trailing spaces and the
  caret position stay visible

The old key/button config layering fix has moved to its own PR.
@std-external
std-external force-pushed the feat/string-text-input branch from 800d977 to b2b7cea Compare October 26, 2026 15:24
@std-external

Copy link
Copy Markdown
Contributor Author

已 rebase 到最新 master(7c55b36,含 #6 的配置界面层级修复和 #7 的 option 控件重构),force-with-lease 推回 fork,新 head:b2b7cea(历史仍是一个 commit)。

冲突与解决

7 处冲突全在 Source/MaterialModOptions.cs,都是「两边各加一块」或结构体/类型差异,没有需要取舍的逻辑:

位置 master 侧 本 PR 侧 处理
静态字段 optionMetadata、IntSlider min/max 反射字段 OuiModOptionString.OnValueChange / OnExit 反射字段 两段都保留
页面字段 configOverlayOpen、externalOverlays、hiddenOverlays stringSettings、textEntry、行内编辑状态 两段都保留
Update 早期返回 menu 为空 / configOverlayOpen 时 CancelSliderDrag 并交出输入 文本提示层 / 行内编辑时交出输入 保留 master 两个 guard,再接本 PR 两个 guard(config overlay → text entry → inline)
Update 确认键 bool rangeHandled = HandleRangeKeyboard(); string 项按确认键开编辑器 string 分支在前,之后仍是 master 的 range 处理
UpdateInteractions 开头 configOverlayOpen 时 MaterialTouch.Suppress text entry 时 publish 自己的触控目标 suppress 优先,其次 text entry 目标
行级触控目标 只有 Kind == Range 才挂滑条手势(enum 也是 TextMenu.Slider,但走下拉) string 项额外挂 TouchKind: "text" 目标 保留 master 的 range 判定,另加 string 文本目标
内部类型 OptionSnapshot 改为 readonly record struct 新增 StringSettingEntry 类 保留 master 的 struct,同时保留 StringSettingEntry

没有重叠的依据:master 这几次改的是 option 分类、滑条/下拉渲染、配置覆盖层和结构体类型;string 输入走的是 TextMenu.Button + OuiModOptionString 拦截,TryGetOption 只认带 Index/Values 字段的项,string 设置项不会被误判成 switch / dropdown / range。

验证

  • dotnet build Source/MicroblocksQolUtils.csproj -c Release(Everest stable lib-stripped):0 warning / 0 error
  • dotnet run --project Tests/ModOptions/ModOptions.csproj -c Release:OptionRules tests passed (36 assertions)
  • CI:Tests 通过(47s),Windows / Linux / macOS 平台构建进行中
  • 本机跑不了游戏实机,光标 / 空格 / 输入法的手工验证还得按 PR 描述里的步骤在 Windows / Android 上过一遍

顺带清理了 fork

@std-external

Copy link
Copy Markdown
Contributor Author

CI 在这个 head 上全绿:Tests 47s、Windows x64 3m10s、macOS x64 3m55s、Linux x64 8m10s、Android ARM64 2m1s。

The inline string editor and both mod options search boxes now behave like a
regular text box:

- spaces are drawn as spaces; the middle-dot placeholder is gone
- Ctrl+A selects everything, Ctrl+C/X/V copy, cut and paste through Everest's
  clipboard wrapper (SDL), so all platforms share the same path
- Shift+Left/Right/Home/End extend a selection; typing, backspace, delete and
  paste replace it
- holding Left/Right repeats after a short delay, matching keyboard repeat
- clicking inside a search box focuses it and drops the caret at the nearest
  character, same as the inline editor
- the search boxes now keep a real caret instead of appending at the end

The editing logic moved into the game-independent TextEditBuffer so the
ModOptions test project can cover selection, cut/paste semantics, max length and
the repeat timing. Width measurement and windowing stay in TextEditGeometry.

IME handling is untouched: TextInputEXT is never started/stopped here, the
existing TextInput.OnInput / TextEditing subscriptions still own character
input, and composition previews are still drawn at the caret.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant