diff options
Diffstat (limited to 'src/LivingVillage.Desktop')
| -rw-r--r-- | src/LivingVillage.Desktop/DialogOverlay.fs | 3 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 44 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/HudLayout.fs | 19 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/Interaction.fs | 53 |
4 files changed, 102 insertions, 17 deletions
diff --git a/src/LivingVillage.Desktop/DialogOverlay.fs b/src/LivingVillage.Desktop/DialogOverlay.fs index 58ce33e..0c402ec 100644 --- a/src/LivingVillage.Desktop/DialogOverlay.fs +++ b/src/LivingVillage.Desktop/DialogOverlay.fs @@ -20,7 +20,8 @@ module DialogOverlay = let pageMarkerHeight = 20 let minFrameWidth = 480 let maxFrameWidth = 900 - let bottomMargin = 24 + // P77:底部预留 hotbar 带(约 60px)+ 间距,保证弹层不遮挡快捷栏。 + let bottomMargin = 72 type Layout = { X: int diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index 77fa314..8c1ea21 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -3311,11 +3311,21 @@ type LivingVillageGame() as this = match m5View.Prompt, m5View.PromptTargetPos with | Some text, Some targetPos -> let scale = 2 - let rect = FloatingPrompt.layout viewport.Width viewport.Height targetPos.X targetPos.Y camera.X camera.Y scale text + let layout = FloatingPrompt.layoutWrapped viewport.Width viewport.Height targetPos.X targetPos.Y camera.X camera.Y scale text + let promptPalette = HudLayout.palette (M6Presentation.profileAtTick world.Tick).Mode spriteBatch.Begin() - spriteBatch.Draw(pixel, Rectangle(rect.X - 6, rect.Y - 5, rect.Width + 12, rect.Height + 8), Color(15, 19, 25, 200)) - spriteBatch.Draw(pixel, Rectangle(rect.X - 6, rect.Y - 5, rect.Width + 12, 2), Color(92, 180, 190)) - PixelText.draw spriteBatch pixel cjkAtlas rect.X rect.Y scale (Color(240, 250, 240)) text + // P77:与顶/底条同语言的药丸——半透明底 + 1px 描边 + 发丝高光,非大黑块。 + spriteBatch.Draw(pixel, Rectangle(layout.X - 6, layout.Y - 5, layout.Width + 12, layout.Height + 8), promptPalette.SegmentBackdrop) + spriteBatch.Draw(pixel, Rectangle(layout.X - 6, layout.Y - 5, layout.Width + 12, 1), promptPalette.Outline) + spriteBatch.Draw(pixel, Rectangle(layout.X - 6, layout.Y + 3, layout.Width + 12, 1), promptPalette.Accent) + spriteBatch.Draw(pixel, Rectangle(layout.X - 6, layout.Y - 5, 1, layout.Height + 8), promptPalette.Outline) + spriteBatch.Draw(pixel, Rectangle(layout.X + layout.Width + 5, layout.Y - 5, 1, layout.Height + 8), promptPalette.Outline) + layout.Lines + |> List.iteri (fun index line -> + let lineY = layout.Y + index * (8 * scale + 4) + for (dx, dy) in HudLayout.textOutlineOffsets do + PixelText.draw spriteBatch pixel cjkAtlas (layout.X + dx) (lineY + dy) scale promptPalette.Outline line + PixelText.draw spriteBatch pixel cjkAtlas layout.X lineY scale promptPalette.Value line) spriteBatch.End() | _ -> () this.DrawM5Overlay() @@ -3488,11 +3498,12 @@ type LivingVillageGame() as this = let line1 = HudLayout.calendarLine1 reading let line2 = HudLayout.calendarLine2 reading spriteBatch.Begin() + // P77:与顶/底条同语言的药丸——半透明底 + 1px 描边 + 发丝高光,卡片微缩。 spriteBatch.Draw(pixel, rect, palette.SegmentBackdrop) - spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Y, rect.Width, 2), palette.Accent) - spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Bottom - 2, rect.Width, 2), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Y, 2, rect.Height), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(rect.Right - 2, rect.Y, 2, rect.Height), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Y, rect.Width, 1), palette.Accent) + spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Bottom - 1, rect.Width, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(rect.X, rect.Y, 1, rect.Height), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(rect.Right - 1, rect.Y, 1, rect.Height), palette.Outline) let iconX = rect.X + HudLayout.calendarPadX let iconY = rect.Y + (rect.Height - HudLayout.iconHeight) / 2 this.DrawPattern (HudLayout.iconRows HudLayout.calendarIcon) iconX iconY HudLayout.iconScale palette.Value @@ -3586,16 +3597,19 @@ type LivingVillageGame() as this = let textScale = 2 let lineHeight = 18 let lines = M5Interaction.panelLines world m5View - let panelWidth = min 380 (max 1 (viewport.Width - 32)) - let panelX = 16 - let innerX = panelX + 12 - let innerWidth = max 1 (panelWidth - 24) + let widthGuess = min 380 (max 1 (viewport.Width - 32)) + let innerWidthGuess = max 1 (widthGuess - 24) // P76:按实测像素宽度折行(中文逐字),换行而非整行截断;不再 Substring 截断。 - let wrapped = HudLayout.wrapLines innerWidth textScale lines + let wrapped = HudLayout.wrapLines innerWidthGuess textScale lines let maxLines = max 1 ((viewport.Height - HudLayout.barHeight - 48) / lineHeight) let visibleLines = wrapped |> List.truncate maxLines - let panelHeight = visibleLines.Length * lineHeight + 26 - let panelY = max (HudLayout.barHeight + 12) (viewport.Height - panelHeight - 16) + // P77:面板几何由 HudLayout 统一给出,底边抬到 hotbar 顶上方,绝不遮挡快捷栏。 + let rect = HudLayout.panelRect viewport.Width viewport.Height visibleLines.Length + let panelX = rect.X + let panelY = rect.Y + let panelWidth = rect.Width + let panelHeight = rect.Height + let innerX = panelX + HudLayout.panelPadX spriteBatch.Begin() // 半透明圆角感面板:细描边 + 1px 发丝线(与 P75/P76 同语言),非大色块/纯黑。 spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, panelHeight / 2), Color(38, 52, 62, 175)) diff --git a/src/LivingVillage.Desktop/HudLayout.fs b/src/LivingVillage.Desktop/HudLayout.fs index 4310586..63d4ed5 100644 --- a/src/LivingVillage.Desktop/HudLayout.fs +++ b/src/LivingVillage.Desktop/HudLayout.fs @@ -197,7 +197,7 @@ module HudLayout = let calendarMarginRight = 12 let calendarMarginTop = 8 let calendarPadX = 10 - let calendarPadY = 8 + let calendarPadY = 6 let calendarLineGap = 4 let calendarTextHeight = 8 * textScale let calendarCardHeight = 2 * calendarTextHeight + calendarLineGap + 2 * calendarPadY @@ -324,6 +324,23 @@ module HudLayout = let hotbarInputAllowed (menuPlaying: bool) (worldInputAllowed: bool) : bool = menuPlaying && worldInputAllowed + // ---- P77:底部左侧互动/任务面板几何(与 hotbar 错开,不遮挡)---- + + let panelLineHeight = 18 + let panelMaxWidth = 380 + let panelPadX = 12 + let panelTopExtra = 26 + + /// 面板几何:左下角、宽度自适应视口,且底边始终抬到 hotbar 顶上方 8px(不遮挡快捷栏)。 + let panelRect (viewportWidth: int) (viewportHeight: int) (lineCount: int) : Rectangle = + let width = min panelMaxWidth (max 1 (viewportWidth - 32)) + let height = max 1 lineCount * panelLineHeight + panelTopExtra + let bar = hotbarBarRect viewportWidth viewportHeight + let maxBottom = bar.Y - 8 + let y = + max (barHeight + 12) (min (viewportHeight - height - 16) (max 0 (maxBottom - height))) + Rectangle(16, y, width, height) + // ---- P76:面板文本按实测像素宽度折行(换行而非整行截断,中文逐字、拉丁逐字)---- let wrapText (maxWidth: int) (scale: int) (text: string) : string list = 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 = |
