From dce50b835235a103af1d8e6ec836fe12d841f135 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 29 Sep 2026 15:44:35 +0800 Subject: p77: 日历卡/浮动提示药丸化并修复面板遮挡快捷栏 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DrawCalendarCard 改半透明底+1px 描边+accent 发丝线,日历卡微缩(52→48) - FloatingPrompt 新增 layoutWrapped/wrapText,锚定长文本逐字折行(不丢字、居中锚点、避让 HUD 带) - 新增 HudLayout.panelRect,DrawPanel 用其几何;DialogOverlay.bottomMargin 24→72 1280x720 下对话 rect 底 648、任务面板底 652,均 < hotbar 顶 660,不再压 3-8 号槽 - 新增 P77LayoutTests(4 尺寸不破版/不遮挡 + 真实长提示折行) - 证据 LV_P43/P44/P67_SHOT + scripts/analyze-p77.py(verdict PASS) 落 docs/evidence/p77-* - Desktop 361/361, Kernel 120/120, digest 953775FA...F7F6C 不变 --- src/LivingVillage.Desktop/Interaction.fs | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'src/LivingVillage.Desktop/Interaction.fs') diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs index 3a65e8e..5281180 100644 --- a/src/LivingVillage.Desktop/Interaction.fs +++ b/src/LivingVillage.Desktop/Interaction.fs @@ -211,6 +211,59 @@ module FloatingPrompt = let y = max topSafe (min bottomSafe screenY) { X = x; Y = y; Width = width; Height = height } + /// P77:锚定提示支持长文本折行(逐字、不丢字),供真实用例(如「E 与河边的货郎交谈并查看货品」)。 + let maxPromptWidth = 360 + + type PromptLayout = + { Lines: string list + X: int + Y: int + Width: int + Height: int } + + let wrapText (maxWidth: int) (scale: int) (text: string) : string list = + if text = "" then + [ "" ] + else + let builder = System.Text.StringBuilder() + let mutable lines = [] + for ch in text.ToCharArray() do + let candidate = builder.ToString() + string ch + if builder.Length > 0 && measureWidth candidate scale > maxWidth then + lines <- builder.ToString() :: lines + builder.Clear() |> ignore + builder.Append(ch) |> ignore + else + builder.Append(ch) |> ignore + lines <- builder.ToString() :: lines + List.rev lines + + /// 锚定点的多行提示版式:按需折行、水平居中于目标、垂直钳制在两条 HUD 带之间。 + let layoutWrapped + (viewportWidth: int) + (viewportHeight: int) + (anchorWorldX: float32) + (anchorWorldY: float32) + (cameraX: float32) + (cameraY: float32) + (scale: int) + (text: string) : PromptLayout = + let margin = 8 + let wrapWidth = min maxPromptWidth (max 80 (viewportWidth - 2 * margin)) + let lines = wrapText wrapWidth scale text + let width = lines |> List.map (fun line -> measureWidth line scale) |> List.max + let lineHeight = 8 * scale + 4 + let height = lines.Length * lineHeight + 10 + let screenX = int anchorWorldX - int cameraX - width / 2 + let screenY = int anchorWorldY - int cameraY - 24 - height + let x = + if width + margin * 2 > viewportWidth then margin + else max margin (min (viewportWidth - margin - width) screenX) + let topSafe = hudTopBarHeight + margin + let bottomSafe = max topSafe (viewportHeight - hudBottomBandHeight - height - margin) + let y = max topSafe (min bottomSafe screenY) + { Lines = lines; X = x; Y = y; Width = width; Height = height } + module M5Interaction = let initial : M5View = -- cgit v1.2.3