Skip to content

Commit 11cce79

Browse files
sebgodclaude
andcommitted
Wire SignalBus for text input activation (Phase 1)
Replace OnActivateTextInput callback and PendingCustomActivation hack with ActivateTextInputSignal/DeactivateTextInputSignal posted via the signal bus. Signals are delivered in OnPostFrame, after event handling completes — no more timing conflicts with HandleMouseDown. - Create SignalBus in Program.cs, pass through VkGuiRenderer to all tabs - Subscribe ActivateTextInput/DeactivateTextInput handlers in Program.cs - Add bus.ProcessPending(tracker) to OnPostFrame - Delete OnActivateTextInput callback from VkEquipmentTab - Delete PendingCustomActivation from EquipmentTabState - Remove previousActiveTextInput hack from GuiEventHandlers - Bump DIR.Lib to 1.3.71 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6af68c3 commit 11cce79

File tree

7 files changed

+39
-38
lines changed

7 files changed

+39
-38
lines changed

nupkgs/DIR.Lib.1.3.71.nupkg

47.9 KB
Binary file not shown.

src/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<PackageVersion Include="Pastel" Version="7.0.1" />
4141
<PackageVersion Include="System.CommandLine" Version="2.0.5" />
4242
<!-- SDL3 + Vulkan packages -->
43-
<PackageVersion Include="DIR.Lib" Version="1.3.70" />
43+
<PackageVersion Include="DIR.Lib" Version="1.3.71" />
4444
<PackageVersion Include="Console.Lib" Version="1.4.93" />
4545
<PackageVersion Include="SdlVulkan.Renderer" Version="1.1.67" />
4646
<PackageVersion Include="SDL3-CS" Version="3.5.0-preview.20260213-150035" />

src/TianWen.UI.Gui/EquipmentTabState.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ public void StopEditingFilters()
6767
public int CustomFilterSlotIndex { get; set; } = -1;
6868
public TextInputState CustomFilterNameInput { get; } = new() { Placeholder = "Filter name..." };
6969

70-
/// <summary>When true, the next render frame should activate the custom filter name input.</summary>
71-
public bool PendingCustomActivation { get; set; }
7270

7371
// OTA property editing: which OTA is being edited (-1 = none)
7472
public int EditingOtaIndex { get; set; } = -1;

src/TianWen.UI.Gui/GuiEventHandlers.cs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,6 @@ public GuiEventHandlers(
344344
}
345345
};
346346

347-
guiRenderer.EquipmentTab.OnActivateTextInput = (textInput) =>
348-
{
349-
appState.ActiveTextInput = textInput;
350-
StartTextInput(sdlWindowHandle);
351-
};
352-
353347
// ---------------------------------------------------------------
354348
// Local helpers captured by closures above
355349
// ---------------------------------------------------------------
@@ -410,9 +404,6 @@ public bool HandleMouseDown(float px, float py, byte clicks = 1)
410404
return true;
411405
}
412406

413-
// Remember active text input before dispatch — callbacks may activate a new one
414-
var previousActiveTextInput = _appState.ActiveTextInput;
415-
416407
// If chrome didn't handle it, try the active tab
417408
if (hit is null)
418409
{
@@ -438,16 +429,8 @@ public bool HandleMouseDown(float px, float py, byte clicks = 1)
438429
return true;
439430
}
440431

441-
// If a callback activated a new text input during dispatch, ensure SDL focus
442-
if (_appState.ActiveTextInput is { IsActive: true } newInput
443-
&& newInput != previousActiveTextInput)
444-
{
445-
StartTextInput(_sdlWindowHandle);
446-
}
447-
// Clicking outside text input → deactivate (unless a callback just activated a new one)
448-
else if (_appState.ActiveTextInput is { IsActive: true } activeInput
449-
&& activeInput == previousActiveTextInput
450-
&& hit is not HitResult.TextInputHit)
432+
// Clicking outside text input → deactivate
433+
if (_appState.ActiveTextInput is { IsActive: true } && hit is not HitResult.TextInputHit)
451434
{
452435
DeactivateTextInput();
453436
}

src/TianWen.UI.Gui/Program.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.Logging;
44
using SdlVulkan.Renderer;
5+
using static SDL3.SDL;
56
using TianWen.Lib.Devices;
67
using TianWen.Lib.Extensions;
78
using TianWen.Lib.Sequencing;
@@ -65,7 +66,8 @@
6566
var ctx = VulkanContext.Create(sdlWindow.Instance, sdlWindow.Surface, (uint)pixW, (uint)pixH);
6667
var renderer = new VkRenderer(ctx, (uint)pixW, (uint)pixH);
6768

68-
var guiRenderer = new VkGuiRenderer(renderer, (uint)pixW, (uint)pixH)
69+
var bus = new SignalBus();
70+
var guiRenderer = new VkGuiRenderer(renderer, (uint)pixW, (uint)pixH, bus)
6971
{
7072
DpiScale = sdlWindow.DisplayScale
7173
};
@@ -74,6 +76,30 @@
7476
var cts = new CancellationTokenSource();
7577
var tracker = new BackgroundTaskTracker();
7678
var handlers = new GuiEventHandlers(sp, appState, plannerState, guiRenderer, sdlWindow.Handle, cts, external, tracker);
79+
80+
// Signal subscriptions — text input activation/deactivation via SDL
81+
bus.Subscribe<ActivateTextInputSignal>(sig =>
82+
{
83+
if (appState.ActiveTextInput is { } prev && prev != sig.Input)
84+
{
85+
prev.Deactivate();
86+
}
87+
sig.Input.Activate();
88+
appState.ActiveTextInput = sig.Input;
89+
StartTextInput(sdlWindow.Handle);
90+
appState.NeedsRedraw = true;
91+
});
92+
93+
bus.Subscribe<DeactivateTextInputSignal>(_ =>
94+
{
95+
if (appState.ActiveTextInput is { IsActive: true } active)
96+
{
97+
active.Deactivate();
98+
appState.ActiveTextInput = null;
99+
StopTextInput(sdlWindow.Handle);
100+
appState.NeedsRedraw = true;
101+
}
102+
});
77103
Task? plannerTask = null;
78104

79105
// Wire tab callbacks that need DI/profile access
@@ -239,6 +265,7 @@ await PlannerActions.ComputeTonightsBestAsync(
239265

240266
OnPostFrame = () =>
241267
{
268+
bus.ProcessPending(tracker);
242269
tracker.ProcessCompletions(logger);
243270
appState.NeedsRedraw = false;
244271
plannerState.NeedsRedraw = false;

src/TianWen.UI.Gui/VkEquipmentTab.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ public sealed class VkEquipmentTab : VkTabBase
7777
/// <summary>Callback for updating profile data (filter config, OTA props). Set by the host.</summary>
7878
public Func<ProfileData, Task>? OnUpdateProfile { get; set; }
7979

80-
/// <summary>Callback to activate a text input with SDL focus. Set by the host.</summary>
81-
public Action<TextInputState>? OnActivateTextInput { get; set; }
80+
// OnActivateTextInput removed — use PostSignal(new ActivateTextInputSignal(...)) instead
8281

8382
public VkEquipmentTab(VkRenderer renderer) : base(renderer)
8483
{
@@ -834,13 +833,6 @@ private float RenderFilterTable(
834833
if (State.CustomFilterSlotIndex == f)
835834
{
836835
RenderTextInput(State.CustomFilterNameInput, (int)nameCellX, (int)cursor, (int)nameColW, (int)rowH, fontPath, fontSize * 0.8f);
837-
// Deferred activation — happens on the first render frame after Custom... was clicked
838-
if (State.PendingCustomActivation)
839-
{
840-
State.PendingCustomActivation = false;
841-
State.CustomFilterNameInput.Activate();
842-
OnActivateTextInput?.Invoke(State.CustomFilterNameInput);
843-
}
844836
}
845837
else
846838
{
@@ -867,8 +859,8 @@ private float RenderFilterTable(
867859
var preservedName = capturedF < filters.Count && filters[capturedF].CustomName is { } cn ? cn : "";
868860
State.CustomFilterNameInput.Text = preservedName;
869861
State.CustomFilterNameInput.CursorPos = preservedName.Length;
870-
// Defer activation to next render frame to avoid HandleMouseDown interference
871-
State.PendingCustomActivation = true;
862+
// Signal deferred text input activation (processed in OnPostFrame)
863+
PostSignal(new ActivateTextInputSignal(State.CustomFilterNameInput));
872864
},
873865
customEntryLabel: existingCustom is { Length: > 0 } ? $"Custom: {existingCustom}" : null);
874866
});

src/TianWen.UI.Gui/VkGuiRenderer.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,15 @@ private static readonly (string Icon, GuiTab Tab)[] SidebarTabs =
7272
private static readonly RGBAColor32 ContentBg = new RGBAColor32(0x16, 0x16, 0x1e, 0xff);
7373
private static readonly RGBAColor32 PlaceholderText = new RGBAColor32(0x55, 0x55, 0x66, 0xff);
7474

75-
public VkGuiRenderer(VkRenderer renderer, uint width, uint height) : base(renderer)
75+
public VkGuiRenderer(VkRenderer renderer, uint width, uint height, SignalBus? bus = null) : base(renderer)
7676
{
77+
Bus = bus;
7778
_renderer = renderer;
7879
_width = width;
7980
_height = height;
80-
_plannerTab = new VkPlannerTab(renderer);
81-
_equipmentTab = new VkEquipmentTab(renderer);
82-
_sessionTab = new VkSessionTab(renderer);
81+
_plannerTab = new VkPlannerTab(renderer) { Bus = bus };
82+
_equipmentTab = new VkEquipmentTab(renderer) { Bus = bus };
83+
_sessionTab = new VkSessionTab(renderer) { Bus = bus };
8384
ResolveFontPath();
8485
}
8586

0 commit comments

Comments
 (0)