summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/LivingVillage.Desktop/Game.fs27
-rw-r--r--src/LivingVillage.Desktop/HudLayout.fs85
2 files changed, 100 insertions, 12 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index cb28e58..8e58727 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -1581,14 +1581,33 @@ type LivingVillageGame() as this =
spriteBatch.Draw(pixel, Rectangle(0, 0, viewport.Width, barHeight / 2), palette.BarTop)
spriteBatch.Draw(pixel, Rectangle(0, barHeight / 2, viewport.Width, barHeight - barHeight / 2), palette.BarBottom)
spriteBatch.Draw(pixel, Rectangle(0, barHeight, viewport.Width, HudLayout.accentHeight), palette.Accent)
+ // 图标用 pixel 贴图按 7x7 点阵绘制(无外部素材),先描边后填充,保证昼夜可读。
+ let drawIcon (iconX: int) (iconY: int) (color: Color) (icon: HudLayout.HudIcon) =
+ HudLayout.iconRows icon
+ |> List.iteri (fun row bits ->
+ bits
+ |> Seq.iteri (fun column bit ->
+ if bit = '1' then
+ spriteBatch.Draw(
+ pixel,
+ Rectangle(
+ iconX + column * HudLayout.iconScale,
+ iconY + row * HudLayout.iconScale,
+ HudLayout.iconScale,
+ HudLayout.iconScale),
+ color)))
+ let iconY = (barHeight - HudLayout.iconHeight) / 2
for (x, width, segment) in placed do
spriteBatch.Draw(pixel, HudLayout.segmentBackdrop x width, palette.SegmentBackdrop)
+ let textX = HudLayout.textOrigin x
let labelAdvanced = FloatingPrompt.measureWidth (segment.Label + " ") HudLayout.textScale
for (dx, dy) in HudLayout.textOutlineOffsets do
- PixelText.draw spriteBatch pixel cjkAtlas (x + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Label
- PixelText.draw spriteBatch pixel cjkAtlas (x + labelAdvanced + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Value
- PixelText.draw spriteBatch pixel cjkAtlas x textY HudLayout.textScale palette.Label segment.Label
- PixelText.draw spriteBatch pixel cjkAtlas (x + labelAdvanced) textY HudLayout.textScale palette.Value segment.Value
+ drawIcon (x + dx) (iconY + dy) palette.Outline segment.Icon
+ PixelText.draw spriteBatch pixel cjkAtlas (textX + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Label
+ PixelText.draw spriteBatch pixel cjkAtlas (textX + labelAdvanced + dx) (textY + dy) HudLayout.textScale palette.Outline segment.Value
+ drawIcon x iconY palette.Value segment.Icon
+ PixelText.draw spriteBatch pixel cjkAtlas textX textY HudLayout.textScale palette.Label segment.Label
+ PixelText.draw spriteBatch pixel cjkAtlas (textX + labelAdvanced) textY HudLayout.textScale palette.Value segment.Value
spriteBatch.End()
/// 常驻情境状态:顶部条下方的小药丸,仅 WorldPanel 且瞬态未过期时出现。
diff --git a/src/LivingVillage.Desktop/HudLayout.fs b/src/LivingVillage.Desktop/HudLayout.fs
index f3e4c12..05fce33 100644
--- a/src/LivingVillage.Desktop/HudLayout.fs
+++ b/src/LivingVillage.Desktop/HudLayout.fs
@@ -14,32 +14,101 @@ module HudLayout =
let segmentGap = 20
let textScale = 2
+ /// 常驻段的像素图标(7x7 点阵,按 iconScale 放大),全部用 pixel 贴图绘制、无外部素材。
+ type HudIcon =
+ | PeriodIcon
+ | ClockIcon
+ | SpeedIcon
+ | EnergyIcon
+ | MoneyIcon
+
type Segment =
{ Label: string
- Value: string }
+ Value: string
+ Icon: HudIcon }
let segmentText (segment: Segment) : string = sprintf "%s %s" segment.Label segment.Value
/// 常驻状态片段,数值全部取自世界真实状态,绝不编造。
let segments (world: World) (control: SimulationControl) : Segment list =
let needs = world.Avatar.Mind.Needs
- [ { Label = "时段"; Value = M6Presentation.timePeriodLabel world.Tick }
- { Label = "时间"; Value = M6Presentation.clockTimeLabel world.Tick }
- { Label = "倍速"; Value = M6Presentation.speedLabel control }
- { Label = "体力"; Value = sprintf "%.0f" needs.Energy }
- { Label = "钱币"; Value = sprintf "%.0f" needs.Money } ]
+ [ { Label = "时段"; Value = M6Presentation.timePeriodLabel world.Tick; Icon = PeriodIcon }
+ { Label = "时间"; Value = M6Presentation.clockTimeLabel world.Tick; Icon = ClockIcon }
+ { Label = "倍速"; Value = M6Presentation.speedLabel control; Icon = SpeedIcon }
+ { Label = "体力"; Value = sprintf "%.0f" needs.Energy; Icon = EnergyIcon }
+ { Label = "钱币"; Value = sprintf "%.0f" needs.Money; Icon = MoneyIcon } ]
+
+ // ---- 图标几何与点阵(纯数据/纯函数,Game 与回归测试共用)----
+
+ let iconGrid = 7
+ let iconScale = textScale
+ let iconWidth = iconGrid * iconScale
+ let iconHeight = iconGrid * iconScale
+ let iconGap = 6
+
+ /// 7x7 点阵,'1' 为实心。全部原创点阵,仅用现有像素贴图绘制。
+ let iconRows (icon: HudIcon) : string list =
+ match icon with
+ | PeriodIcon ->
+ [ "0001000"
+ "1010101"
+ "0011100"
+ "0111110"
+ "0011100"
+ "1010101"
+ "0001000" ]
+ | ClockIcon ->
+ [ "0011100"
+ "0100010"
+ "1001001"
+ "1001000"
+ "1001000"
+ "0100010"
+ "0011100" ]
+ | SpeedIcon ->
+ [ "0100010"
+ "0110011"
+ "0011011"
+ "0001110"
+ "0011011"
+ "0110011"
+ "0100010" ]
+ | EnergyIcon ->
+ [ "0000000"
+ "0101010"
+ "1111111"
+ "1111111"
+ "0111110"
+ "0011100"
+ "0001000" ]
+ | MoneyIcon ->
+ [ "0011100"
+ "0111110"
+ "1110111"
+ "1100011"
+ "1110111"
+ "0111110"
+ "0011100" ]
+
+ /// 文本起始 x(图标占 iconWidth + iconGap)。
+ let textOrigin (segmentX: int) : int = segmentX + iconWidth + iconGap
/// 段文本(含标签)的像素宽度。
let segmentWidth (segment: Segment) : int =
FloatingPrompt.measureWidth (segmentText segment) textScale
- /// 左对齐排布,返回 (起点 x, 宽度, 分段);纯函数,便于布局回归断言。
+ /// 段总宽(图标 + 间隙 + 文本)。
+ let segmentSpan (segment: Segment) : int =
+ iconWidth + iconGap + segmentWidth segment
+
+ /// 左对齐排布,返回 (起点 x, 总宽, 分段);段内 x 起为图标,textOrigin 起为文本。
+ /// 纯函数,便于布局回归断言。
let layout (segments: Segment list) : (int * int * Segment) list =
let _, placed =
segments
|> List.fold
(fun (x, acc) segment ->
- let width = segmentWidth segment
+ let width = segmentSpan segment
x + width + segmentGap, (x, width, segment) :: acc)
(paddingX, [])
List.rev placed