diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-29 13:46:49 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-29 13:46:49 +0800 |
| commit | 0439e21a0582ab892c0e4bfa936c4d62ca2bcc61 (patch) | |
| tree | 42cbf64e93c84f9644b2d001b12d4288b42f6a38 /src/LivingVillage.Desktop | |
| parent | e959b1df06f29fe6542cc6cbe2f9005f620d5dc9 (diff) | |
| download | living-village-0439e21a0582ab892c0e4bfa936c4d62ca2bcc61.tar.gz | |
p76: 底部条/槽位药丸化 + 对话/交互面板半透明描边与折行
DrawHotbar 去掉整条大色块与实心槽位,改半透明条+1px 描边+发丝线,槽位内键位小片与图形并排,选中用 accent 描边;新增 HudLayout.actionLegend(取真实 controlRows 的键位+动作名图例,纯展示不改绑定)。DrawDialogOverlay/DrawPanel 背景改半透明描边语言,禁用近纯黑底。新增 HudLayout.wrapText/wrapLines 按实测宽度折行,DrawPanel 弃用 Substring 截断。新增 P76HotbarPanelTests(4 例);LV_P43_SHOT 昼/夜 + LV_P44_SHOT 对话面板证据 + analyze-p76(四区无实心近黑块、亮像素充足,verdict=PASS)。Desktop 358/358、Kernel 120/120、digest 与 p70 一致。
Diffstat (limited to 'src/LivingVillage.Desktop')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 97 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop/HudLayout.fs | 56 |
2 files changed, 120 insertions, 33 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index c6408aa..77fa314 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -3346,16 +3346,16 @@ type LivingVillageGame() as this = let lx = layout.X let ly = layout.Y spriteBatch.Begin() - // 完整背景框:上下两段半透明深色渐变 + 四边细边框 + 顶部强调线。 - spriteBatch.Draw(pixel, Rectangle(lx, ly, layout.Width, layout.Height / 2), Color(30, 44, 52, 224)) + // P76:对话面板改与顶部/底部条同语言——半透明底 + 1px 描边 + 发丝强调线,避免大黑块。 + spriteBatch.Draw(pixel, Rectangle(lx, ly, layout.Width, layout.Height / 2), Color(46, 60, 70, 200)) spriteBatch.Draw( pixel, Rectangle(lx, ly + layout.Height / 2, layout.Width, layout.Height - layout.Height / 2), - Color(14, 22, 30, 232)) - spriteBatch.Draw(pixel, Rectangle(lx, ly, layout.Width, 3), palette.Accent) - spriteBatch.Draw(pixel, Rectangle(lx, ly + layout.Height - 2, layout.Width, 2), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(lx, ly, 2, layout.Height), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(lx + layout.Width - 2, ly, 2, layout.Height), palette.Outline) + Color(30, 42, 52, 210)) + spriteBatch.Draw(pixel, Rectangle(lx, ly, layout.Width, 1), palette.Accent) + spriteBatch.Draw(pixel, Rectangle(lx, ly + layout.Height - 1, layout.Width, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(lx, ly, 1, layout.Height), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(lx + layout.Width - 1, ly, 1, layout.Height), palette.Outline) // 头像:原创 8x16 点阵 @ scale 4 = 32x64,加内框。 if model.ShowAvatar then let avatar = DialogOverlay.avatarRect layout @@ -3513,27 +3513,54 @@ type LivingVillageGame() as this = let palette = HudLayout.palette profile.Mode let bar = HudLayout.hotbarBarRect viewport.Width viewport.Height spriteBatch.Begin() + // P76:底部条改为与顶部条同语言的紧凑半透明药丸——细描边 + 1px 发丝线,不再铺大色块。 spriteBatch.Draw(pixel, bar, palette.SegmentBackdrop) - spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Y, bar.Width, 2), palette.Accent) - spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Bottom - 2, bar.Width, 2), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Y, 2, bar.Height), palette.Outline) - spriteBatch.Draw(pixel, Rectangle(bar.Right - 2, bar.Y, 2, bar.Height), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Y, bar.Width, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Bottom - 1, bar.Width, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(bar.X, bar.Y, 1, bar.Height), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(bar.Right - 1, bar.Y, 1, bar.Height), palette.Outline) + spriteBatch.Draw(pixel, HudLayout.hotbarBarHairline bar, palette.Accent) for index in 0 .. HudLayout.hotbarSlotCount - 1 do let slot = HudLayout.hotbarSlotRect viewport.Width viewport.Height index let selected = index = hotbarSelected - let slotBackdrop = - if selected then Color(48, 66, 74, 210) else palette.SegmentBackdrop - spriteBatch.Draw(pixel, slot, slotBackdrop) + // 槽位药丸:半透明底(选中略亮),1px 描边 + 发丝线,不再是实心大色块。 + let slotFill = + if selected then Color(48, 66, 74, 170) else palette.SegmentBackdrop + spriteBatch.Draw(pixel, slot, slotFill) let border = if selected then palette.Accent else palette.Outline - spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Y, slot.Width, 2), border) - spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Bottom - 2, slot.Width, 2), border) - spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Y, 2, slot.Height), border) - spriteBatch.Draw(pixel, Rectangle(slot.Right - 2, slot.Y, 2, slot.Height), border) - let iconRect = HudLayout.hotbarIconRect slot + spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Y, slot.Width, 1), border) + spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Bottom - 1, slot.Width, 1), border) + spriteBatch.Draw(pixel, Rectangle(slot.X, slot.Y, 1, slot.Height), border) + spriteBatch.Draw(pixel, Rectangle(slot.Right - 1, slot.Y, 1, slot.Height), border) + if selected then + spriteBatch.Draw(pixel, Rectangle(slot.X + 1, slot.Y + slot.Height - 2, max 1 (slot.Width - 2), 1), palette.Accent) + // 键位符号小片 + 图形并排(P76 信息层次)。 + let keyChip = HudLayout.hotbarKeyChipRect slot + spriteBatch.Draw(pixel, keyChip, palette.Outline) + spriteBatch.Draw(pixel, Rectangle(keyChip.X + 1, keyChip.Y + 1, max 1 (keyChip.Width - 2), max 1 (keyChip.Height - 2)), Color(18, 28, 34, 220)) + let label = if index = 9 then "0" else string (index + 1) + PixelText.draw spriteBatch pixel cjkAtlas (keyChip.X + 3) (keyChip.Y + 2) 1 (if selected then palette.Accent else palette.Label) label + let iconRect = HudLayout.hotbarIconRectBesideKey slot let iconColor = if selected then palette.Accent else palette.Value this.DrawPattern (HudLayout.hotbarIconRows index) iconRect.X iconRect.Y HudLayout.hotbarIconScale iconColor - let label = if index = 9 then "0" else string (index + 1) - PixelText.draw spriteBatch pixel cjkAtlas (slot.X + 3) (slot.Y + 2) 1 (if selected then palette.Accent else palette.Label) label + // 图例:真实按键 + 动作名并排(取自 MenuState.controlRows,纯展示,不改任何绑定)。 + let legend = HudLayout.actionLegend MenuState.controlRows + if not legend.IsEmpty then + let legendText = legend |> List.map (fun row -> sprintf "%s %s" row.Keys row.Action) |> String.concat " " + let scale = HudLayout.textScale + let textWidth = FloatingPrompt.measureWidth legendText scale + let legendHeight = 8 * scale + 12 + let legendWidth = textWidth + 20 + let legendX = (viewport.Width - legendWidth) / 2 + let legendY = bar.Y - legendHeight - 6 + spriteBatch.Draw(pixel, Rectangle(legendX, legendY, legendWidth, legendHeight), palette.SegmentBackdrop) + spriteBatch.Draw(pixel, Rectangle(legendX, legendY, legendWidth, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(legendX, legendY + legendHeight - 1, legendWidth, 1), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(legendX, legendY, 1, legendHeight), palette.Outline) + spriteBatch.Draw(pixel, Rectangle(legendX + legendWidth - 1, legendY, 1, legendHeight), palette.Outline) + for (dx, dy) in HudLayout.textOutlineOffsets do + PixelText.draw spriteBatch pixel cjkAtlas (legendX + 10 + dx) (legendY + 6 + dy) scale palette.Outline legendText + PixelText.draw spriteBatch pixel cjkAtlas (legendX + 10) (legendY + 6) scale palette.Value legendText spriteBatch.End() /// 常驻情境状态:顶部条下方的小药丸,仅 WorldPanel 且瞬态未过期时出现。 @@ -3560,23 +3587,27 @@ type LivingVillageGame() as this = let lineHeight = 18 let lines = M5Interaction.panelLines world m5View let panelWidth = min 380 (max 1 (viewport.Width - 32)) - let maxLines = max 1 ((viewport.Height - HudLayout.barHeight - 48) / lineHeight) - let visibleLines = lines |> List.truncate maxLines - let panelHeight = visibleLines.Length * lineHeight + 26 let panelX = 16 - let panelY = max (HudLayout.barHeight + 12) (viewport.Height - panelHeight - 16) let innerX = panelX + 12 let innerWidth = max 1 (panelWidth - 24) - let maxChars = max 1 (innerWidth / (6 * textScale)) + // P76:按实测像素宽度折行(中文逐字),换行而非整行截断;不再 Substring 截断。 + let wrapped = HudLayout.wrapLines innerWidth 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) spriteBatch.Begin() - // 竖向渐变板(半透明,非纯黑),顶部与左侧各一条强调线。 - spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, panelHeight / 2), Color(30, 44, 52, 190)) - spriteBatch.Draw(pixel, Rectangle(panelX, panelY + panelHeight / 2, panelWidth, panelHeight - panelHeight / 2), Color(16, 24, 30, 190)) - spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, 3), Color(92, 180, 190)) - spriteBatch.Draw(pixel, Rectangle(panelX, panelY, 2, panelHeight), Color(92, 180, 190, 160)) + // 半透明圆角感面板:细描边 + 1px 发丝线(与 P75/P76 同语言),非大色块/纯黑。 + spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, panelHeight / 2), Color(38, 52, 62, 175)) + spriteBatch.Draw(pixel, Rectangle(panelX, panelY + panelHeight / 2, panelWidth, panelHeight - panelHeight / 2), Color(26, 38, 48, 180)) + spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, 1), Color(92, 180, 190)) + spriteBatch.Draw(pixel, Rectangle(panelX, panelY, 1, panelHeight), Color(92, 180, 190, 180)) + spriteBatch.Draw(pixel, Rectangle(panelX, panelY + panelHeight - 1, panelWidth, 1), Color(8, 14, 20, 200)) + spriteBatch.Draw(pixel, Rectangle(panelX + panelWidth - 1, panelY, 1, panelHeight), Color(8, 14, 20, 200)) visibleLines |> List.iteri (fun index line -> - let visible = if line.Length > maxChars then line.Substring(0, maxChars) else line let color = if index = 0 then Color(226, 238, 226) else Color(192, 208, 212) - PixelText.draw spriteBatch pixel cjkAtlas innerX (panelY + 13 + index * lineHeight) textScale color visible) + for (dx, dy) in HudLayout.textOutlineOffsets do + PixelText.draw spriteBatch pixel cjkAtlas (innerX + dx) (panelY + 13 + index * lineHeight + dy) textScale (Color(6, 10, 14)) line + PixelText.draw spriteBatch pixel cjkAtlas innerX (panelY + 13 + index * lineHeight) textScale color line) spriteBatch.End() diff --git a/src/LivingVillage.Desktop/HudLayout.fs b/src/LivingVillage.Desktop/HudLayout.fs index cd06224..4310586 100644 --- a/src/LivingVillage.Desktop/HudLayout.fs +++ b/src/LivingVillage.Desktop/HudLayout.fs @@ -260,6 +260,39 @@ module HudLayout = let size = iconGrid * hotbarIconScale Rectangle(slot.X + (slot.Width - size) / 2, slot.Y + (slot.Height - size) / 2, size, size) + // ---- P76:底部条/槽位药丸化(键位符号 + 图形并排,去大色块)---- + + /// 键位符号小片(数字 1..0)尺寸:放在槽位左侧,与图标并排。 + let hotbarKeyChipWidth = 12 + let hotbarKeyChipHeight = 12 + + /// 槽位内的键位小片矩形(左对齐、垂直居中)。 + let hotbarKeyChipRect (slot: Rectangle) : Rectangle = + Rectangle(slot.X + 3, slot.Y + (slot.Height - hotbarKeyChipHeight) / 2, hotbarKeyChipWidth, hotbarKeyChipHeight) + + /// 槽位内图形矩形(键位小片右侧,与图标并排)。 + let hotbarIconRectBesideKey (slot: Rectangle) : Rectangle = + let size = iconGrid * hotbarIconScale + let x = (hotbarKeyChipRect slot).Right + 4 + Rectangle(x, slot.Y + (slot.Height - size) / 2, size, size) + + /// 底部条顶部 1px 强调发丝线(与 P75 顶部条同语言)。 + let hotbarBarHairline (bar: Rectangle) : Rectangle = Rectangle(bar.X + 1, bar.Y + 1, max 1 (bar.Width - 2), 1) + + // ---- P76:真实按键 + 动作名图例(纯展示,取自 MenuState.controlRows,不改任何绑定)---- + + /// 底部图例:真实存在的按键/动作对(subset of MenuState.controlRows 的顺序)。 + /// 仅做信息层次展示,不新增/改动任何快捷键;hotbar 的 1-0 选中语义保持不变。 + let actionLegendKeys : string list = [ "E"; "Q"; "TAB"; "C"; "L"; "F6"; "F7" ] + + let actionLegend (rows: ControlRow list) : ControlRow list = + actionLegendKeys + |> List.choose (fun key -> rows |> List.tryFind (fun row -> row.Keys = key)) + + /// 图例单条文本宽度(键位 + 空格 + 动作名)。 + let legendEntryWidth (row: ControlRow) : int = + FloatingPrompt.measureWidth (sprintf "%s %s" row.Keys row.Action) textScale + /// 10 个原创 7x7 点阵占位图标(0..9),仅用现有像素贴图绘制,无外部素材。 let hotbarIconPatterns : string list list = [ [ "0011100"; "0100010"; "1000001"; "1000001"; "1000001"; "0100010"; "0011100" ] @@ -290,3 +323,26 @@ module HudLayout = /// hotbar 输入仅在「游戏内(非菜单)」且世界输入允许(无面板/对话)时生效。 let hotbarInputAllowed (menuPlaying: bool) (worldInputAllowed: bool) : bool = menuPlaying && worldInputAllowed + + // ---- P76:面板文本按实测像素宽度折行(换行而非整行截断,中文逐字、拉丁逐字)---- + + 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 && FloatingPrompt.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 + + /// 把多行按宽度折行后铺平(不丢字符、不截断)。 + let wrapLines (maxWidth: int) (scale: int) (lines: string list) : string list = + lines |> List.collect (wrapText maxWidth scale) |
