summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 12:43:20 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 12:43:20 +0800
commit12616f1d67d936a542e1e22926fb0ebcd4108906 (patch)
tree64e99e4eac72815d8aab15c599abb60d02d31c82 /src/LivingVillage.Desktop.Tests
parenteaedd532c7d93dec9cca5d361685ed32c85aeebb (diff)
downloadliving-village-12616f1d67d936a542e1e22926fb0ebcd4108906.tar.gz
feat(desktop): 目标上方浮动情境提示
新增 FloatingPrompt 纯布局模块(含 measureWidth 与 PixelText 等宽规则):短提示锚定在唯一目标头顶上方,水平居中并钳制在视口内,避开左下 HUD 带(height-160)与右上状态条带(宽度 248);沿用现有 CJK 图集字体路径,不新造字形;对话/需求面板与菜单打开时不显示(复用 worldInputAllowed 判定与渲染门)。回归测试覆盖 1280x720/1920x1080 五个极限锚点、宽度测量、面板开启时提示为空且禁用世界输入。
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
-rw-r--r--src/LivingVillage.Desktop.Tests/PrototypeTests.fs40
1 files changed, 40 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
index af34802..7e4ce5f 100644
--- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
+++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
@@ -630,6 +630,46 @@ type PrototypeTests () =
CollectionAssert.AreEqual(snapshot, tieWorld.Npcs)
[<TestMethod>]
+ member _.FloatingPromptStaysInViewportAndClearOfHudBands () =
+ for (width, height) in [ (1280, 720); (1920, 1080) ] do
+ let anchors : (float32 * float32) list =
+ [ (100.0f, 100.0f) // near top-left
+ (float32 width / 2.0f, float32 height / 2.0f)
+ (float32 width - 60.0f, 120.0f) // near top-right strip
+ (200.0f, float32 height - 200.0f) // above bottom-left HUD band
+ (float32 width - 100.0f, float32 height - 120.0f) ]
+ for (anchorX, anchorY) in anchors do
+ for text in [ "E 交谈"; "E 进屋"; "E 出屋" ] do
+ let rect = FloatingPrompt.layout width height anchorX anchorY 0.0f 0.0f 2 text
+ let textWidth = FloatingPrompt.measureWidth text 2
+ Assert.AreEqual<int>(textWidth, rect.Width)
+ Assert.IsTrue(rect.Width > 0, "prompt must not be truncated to zero width")
+ Assert.IsTrue(rect.X >= 0 && rect.X + rect.Width <= width, sprintf "x overflow %A %A" (anchorX, anchorY) rect)
+ Assert.IsTrue(rect.Y >= 0 && rect.Y + rect.Height <= height, sprintf "y overflow %A %A" (anchorX, anchorY) rect)
+ // Never cover the bottom-left HUD band (camera 0: band starts at height - 160).
+ Assert.IsTrue(rect.Y + rect.Height <= height - FloatingPrompt.hudBottomBandHeight + 8, sprintf "bottom HUD overlap %A %A" (anchorX, anchorY) rect)
+ // Never cover the top-right status strip.
+ if rect.X + rect.Width >= width - FloatingPrompt.hudTopRightWidth then
+ Assert.IsTrue(rect.Y >= FloatingPrompt.hudTopRightHeight, sprintf "top-right HUD overlap %A %A" (anchorX, anchorY) rect)
+
+ [<TestMethod>]
+ member _.FloatingPromptMeasureMatchesPixelTextAdvances () =
+ // E(12) space(8) 交(16) 谈(16) at scale 2.
+ Assert.AreEqual<int>(52, FloatingPrompt.measureWidth "E 交谈" 2)
+ Assert.AreEqual<int>(20, FloatingPrompt.measureWidth "E " 2)
+
+ [<TestMethod>]
+ member _.FloatingPromptHiddenWhilePanelsOrMenusAreOpen () =
+ let world = Sim.initialWorldN 42UL 2
+ let dialogueView = { M5Interaction.initial with Panel = DialoguePanel }
+ let cleared = M5Interaction.refreshPrompt world dialogueView
+ Assert.AreEqual<string option>(None, cleared.Prompt)
+ Assert.IsFalse(M5Interaction.worldInputAllowed dialogueView)
+
+ let needsView = { M5Interaction.initial with Panel = NeedsPanel }
+ Assert.AreEqual<string option>(None, M5Interaction.refreshPrompt world needsView |> fun value -> value.Prompt)
+
+ [<TestMethod>]
member _.WorldPanelDoesNotRenderA永久KeyList () =
let world = Sim.initialWorldN 42UL 0
let lines = M5Interaction.panelLines world M5Interaction.initial