Skip to content

Commit 0918ac9

Browse files
committed
refactor(Settings): clean up comments and optimize ModSettings UI logic
- Removed outdated comments in ModSettingsUiPatches, ModSettingsUiFactory.Chrome, and ModSettingsUiFactory.Entries to streamline the codebase. - Improved variable initialization in ModSettingsUiFactory.Chrome for clarity. - Enhanced page build logic in RitsuModSettingsSubmenu by deferring content layout refresh, optimizing performance during UI updates.
1 parent 5016ccb commit 0918ac9

4 files changed

Lines changed: 11 additions & 64 deletions

File tree

Settings/Integration/Patches/ModSettingsUiPatches.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,6 @@ private static void TrySchedulePrewarm(NSettingsScreen screen)
186186
if (screen.HasMeta(PrewarmScheduledMeta))
187187
return;
188188
screen.SetMeta(PrewarmScheduledMeta, true);
189-
190-
// Idle frame 1: framework page registration (cheap / usually already warm from the postfix above).
191-
// Idle frame 2: reflection-based mirror registration (the heavy data-layer scan).
192-
// Idle frame 3: pre-create the submenu, building chrome + sidebar and kicking off the async first
193-
// page build, so the eventual click is instant.
194189
Callable.From(() => PrewarmStep(screen, 0)).CallDeferred();
195190
}
196191

Settings/ModSettingsUi/Controls/ModSettingsUiFactory.Chrome.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,7 @@ internal static void AttachContextMenu(Control target, ModSettingsActionsButton
208208
if (target.MouseFilter == Control.MouseFilterEnum.Ignore)
209209
target.MouseFilter = Control.MouseFilterEnum.Pass;
210210

211-
// Build-time cost matters here: every control in a setting line gets a context menu, and a page has
212-
// many lines. A persistent Timer child node per control roughly doubled the node count. Instead the
213-
// long-press timer is a transient SceneTreeTimer created only when a touch press actually begins; a
214-
// per-press token guards cancellation (SceneTreeTimer cannot be stopped) on release or drag.
215-
// 这里构建期成本很关键:设置行里每个控件都挂上下文菜单,而一页有很多行。每控件一个常驻 Timer 子节点会让节点数大致
216-
// 翻倍。改为仅在触摸按下真正开始时创建瞬态 SceneTreeTimer;由于 SceneTreeTimer 无法停止,用每次按下的 token 在抬起或
217-
// 拖拽时守卫取消。
218-
object? activeLongPressToken = null;
211+
object? activeLongPressToken;
219212

220213
target.GuiInput += @event =>
221214
{

Settings/ModSettingsUi/Controls/ModSettingsUiFactory.Entries.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,6 @@ internal static VBoxContainer CreatePageContentHost(ModSettingsPage page)
5151
internal static IEnumerable<PageBuildItem> CreatePageBuildItems(ModSettingsUiContext context,
5252
ModSettingsPage page)
5353
{
54-
// A divider only separates two *visible* sections. Sections can be hidden dynamically (host surface
55-
// or VisibleWhen), so the leading divider must follow the same visibility as its section, and must
56-
// also disappear when no earlier section is visible. Otherwise a hidden middle section leaves its two
57-
// adjacent dividers stacked with nothing between them (or a stray divider sits above the first visible
58-
// section). Each section's predicate is reused so the divider stays in sync on every refresh.
59-
// 分割线只在两个“可见”section 之间起分隔作用。section 可被动态隐藏(host surface 或 VisibleWhen),因此其前导分割线
60-
// 必须跟随该 section 的可见性,且当其之前没有任何可见 section 时也要隐藏。否则一个被隐藏的中间 section 会让它两侧的
61-
// 分割线叠在一起、中间空无一物(或在首个可见 section 之上留下一条孤立分割线)。复用每个 section 的谓词,使分割线在
62-
// 每次刷新时保持同步。
6354
var sectionVisible = new Func<bool>[page.Sections.Count];
6455
for (var index = 0; index < page.Sections.Count; index++)
6556
sectionVisible[index] = BuildSectionVisiblePredicate(page.Sections[index]);

Settings/ModSettingsUi/Core/RitsuModSettingsSubmenu.cs

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ protected override void OnSubmenuShown()
297297
PushPaneHotkeys();
298298
UpdatePaneHotkeyHintIcons();
299299
RequestMirrorVisibilitySyncRefreshIfNeeded();
300+
Callable.From(RefreshContentLayout).CallDeferred();
300301
}
301302

302303
/// <inheritdoc />
@@ -1321,6 +1322,8 @@ private void EnsureSelectedPageContentStructure()
13211322
foreach (var cache in _pageContentCaches.Values)
13221323
{
13231324
cache.BuildCancellation?.Cancel();
1325+
if (cache.State == PageBuildState.Building)
1326+
cache.State = PageBuildState.NotBuilt;
13241327
if (IsInstanceValid(cache.Root))
13251328
cache.Root.Visible = false;
13261329
}
@@ -1893,13 +1896,14 @@ private void RefreshSidebarModCache(SidebarModCache cache, IReadOnlyList<ModSett
18931896

18941897
private async Task BuildPageAsync(ModSettingsPage page, PageContentCache cache)
18951898
{
1896-
if (cache.BuildCancellation != null)
1897-
await cache.BuildCancellation.CancelAsync();
1899+
var previousCancellation = cache.BuildCancellation;
18981900
cache.BuildCancellation = new();
18991901
var ct = cache.BuildCancellation.Token;
19001902
var buildVersion = ++cache.BuildVersion;
19011903
cache.State = PageBuildState.Building;
19021904
ShowContentBuildOverlay(ModSettingsLocalization.Get("entry.loading", "Loading settings…"));
1905+
if (previousCancellation != null)
1906+
await previousCancellation.CancelAsync();
19031907

19041908
var nextHeader = new VBoxContainer
19051909
{
@@ -1940,14 +1944,6 @@ private async Task BuildPageAsync(ModSettingsPage page, PageContentCache cache)
19401944
page.Id)));
19411945
}
19421946

1943-
// Yield by a per-frame time budget rather than after every section. The old "yield after each
1944-
// section" cadence forced one full frame wait per section, so a page of several light sections
1945-
// took many frames to populate even though the total work was trivial. Now sections accumulate
1946-
// within a frame and only yield once the budget is exceeded: light pages appear in a single
1947-
// frame, while heavy pages still chunk across frames to avoid a long hitch.
1948-
// 按每帧时间预算让帧,而非每个 section 之后都让帧。旧的"每 section 让帧一次"会为每个 section 强制等待一整帧,
1949-
// 于是由若干轻量 section 组成的页面即使总工作量很小也要好几帧才填满。现在 section 在一帧内累积,仅当超出预算
1950-
// 才让帧:轻量页面单帧呈现,重量页面仍跨帧分块以避免长卡顿。
19511947
var lastYieldMsec = Time.GetTicksMsec();
19521948
foreach (var item in ModSettingsUiFactory.CreatePageBuildItems(context, page))
19531949
{
@@ -1961,6 +1957,8 @@ private async Task BuildPageAsync(ModSettingsPage page, PageContentCache cache)
19611957
lastYieldMsec = Time.GetTicksMsec();
19621958
}
19631959

1960+
await this.AwaitRitsuProcessFrame(ct);
1961+
19641962
if (buildVersion != cache.BuildVersion || !IsInstanceValid(cache.Root))
19651963
return;
19661964

@@ -2002,6 +2000,8 @@ private async Task BuildPageAsync(ModSettingsPage page, PageContentCache cache)
20022000
{
20032001
nextHeader.QueueFree();
20042002
nextContent.QueueFree();
2003+
if (buildVersion == cache.BuildVersion && cache.State == PageBuildState.Building)
2004+
cache.State = PageBuildState.NotBuilt;
20052005
}
20062006
catch (Exception ex)
20072007
{
@@ -2353,16 +2353,6 @@ private static void GrabControlDeferred(Control? target)
23532353

23542354
private static void WireVerticalOnlyChain(IReadOnlyList<Control> chain)
23552355
{
2356-
// Pin every neighbor to self so Godot's native focus traversal never moves on its own. Up/down are
2357-
// driven live by TryHandleDirectionalFocusInput (which recomputes the focusable order each press),
2358-
// and left/right stay blocked so vertical navigation cannot escape sideways into the other pane.
2359-
// The previous design wired absolute NodePaths to the prev/next chain entry; those dangle the moment
2360-
// the content tree mutates (refresh frees/recreates controls, list add/remove, host swaps,
2361-
// expand/collapse), which made focus jump to a freed node and disappear on complex/dynamic pages.
2362-
// 把每个 neighbor 都钉到自身,使 Godot 原生焦点遍历不会自行移动。上/下由 TryHandleDirectionalFocusInput 实时驱动
2363-
// (每次按键重新计算可聚焦顺序),左/右保持封锁,使纵向导航不会横向逃逸到另一个窗格。旧设计把绝对 NodePath 接到
2364-
// 链中前/后一项;一旦内容树变动(刷新释放/重建控件、列表增删、host 替换、展开/折叠),这些路径就悬空,导致焦点跳到已
2365-
// 释放的节点并在复杂/动态页面上消失。
23662356
foreach (var current in chain)
23672357
{
23682358
var selfPath = current.GetPath();
@@ -2381,27 +2371,13 @@ public override void _Input(InputEvent @event)
23812371
base._Input(@event);
23822372
}
23832373

2384-
/// <summary>
2385-
/// Live up/down focus navigation for the active pane. Instead of relying on pre-wired
2386-
/// <see cref="Control.FocusNeighborTop" /> / <see cref="Control.FocusNeighborBottom" /> paths (which go
2387-
/// stale and drop focus the moment the content tree mutates), the focusable order is recomputed from
2388-
/// the live tree on every press, so navigation stays correct through refreshes, list edits, host swaps
2389-
/// and expand/collapse. Order matches the previous preorder chain, so secondary controls (e.g. the
2390-
/// per-entry actions button) remain reachable. Text editors, popups and the open dropdown keep their
2391-
/// own up/down handling.
2392-
/// 当前窗格的实时上/下焦点导航。不再依赖预先连好的 <see cref="Control.FocusNeighborTop" /> /
2393-
/// <see cref="Control.FocusNeighborBottom" /> 路径(它们在内容树变动时会悬空并丢失焦点),而是每次按键都从实时树重新
2394-
/// 计算可聚焦顺序,因此在刷新、列表编辑、host 替换、展开/折叠期间导航始终正确。顺序与旧的前序链一致,故次级控件(如每
2395-
/// 条目的 actions 按钮)仍可到达。文本编辑器、弹窗与展开的下拉框保留各自的上/下处理。
2396-
/// </summary>
23972374
private bool TryHandleDirectionalFocusInput(InputEvent @event)
23982375
{
23992376
if (!Visible || !IsInstanceValid(this))
24002377
return false;
24012378
if (!ActiveScreenContext.Instance.IsCurrent(this))
24022379
return false;
24032380

2404-
// Echo is allowed so holding the key keeps moving the focus, matching the previous behavior.
24052381
int delta;
24062382
if (@event.IsActionPressed("ui_up"))
24072383
delta = -1;
@@ -2414,14 +2390,9 @@ private bool TryHandleDirectionalFocusInput(InputEvent @event)
24142390
if (owner == null || !IsInstanceValid(owner))
24152391
return false;
24162392

2417-
// Multiline editors use up/down to move the caret; native popups handle their own vertical
2418-
// navigation. Leave those to their own input handling.
24192393
if (owner is TextEdit || IsFocusUnderPopupOrTransientWindow(owner))
24202394
return false;
24212395

2422-
// An open dropdown / actions menu, or a key-binding control recording input, owns directional input
2423-
// while active (its overlay lives inside the content pane, so the pane check above does not exclude
2424-
// it). Defer to the control's own handling in that case.
24252396
for (Node? n = owner; n != null && !ReferenceEquals(n, this); n = n.GetParent())
24262397
if (n is IModSettingsDirectionalInputClaimant { ClaimsDirectionalInput: true })
24272398
return false;
@@ -2455,7 +2426,6 @@ private bool TryHandleDirectionalFocusInput(InputEvent @event)
24552426
return false;
24562427

24572428
var nextIndex = currentIndex + delta;
2458-
// At either end, consume the event so focus stays put rather than escaping the pane or being lost.
24592429
if (nextIndex >= 0 && nextIndex < focusables.Count)
24602430
{
24612431
var target = focusables[nextIndex];
@@ -2624,8 +2594,6 @@ private void UnsubscribeLocaleChanges()
26242594
private void OnLocaleChanged()
26252595
{
26262596
FlushDirtyBindings();
2627-
// Mod display names participate in the sidebar sort tie-break, so the cached ordering must be
2628-
// dropped when the locale changes (the registry cannot observe locale changes itself).
26292597
ModSettingsRegistry.InvalidateOrderingCache();
26302598
_sidebarStructureDirty = true;
26312599
_contentStructureDirty = true;

0 commit comments

Comments
 (0)