diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:03:22 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-28 19:03:22 +0800 |
| commit | 588560deda6b701a62653dff19079dc0a21fa944 (patch) | |
| tree | 3d734aaf9ad546ec52a42af0a2c2635d2d26d670 /src/LivingVillage.Desktop/HudLayout.fs | |
| parent | 5c67e85688ff1e6150b03501adae1a5d5e67bbdc (diff) | |
| download | living-village-588560deda6b701a62653dff19079dc0a21fa944.tar.gz | |
p43: HUD 星露谷化切片 — 右上日历钟卡/体力条形图/底部10格hotbar(1-0选中+高亮+输入隔离)
Diffstat (limited to 'src/LivingVillage.Desktop/HudLayout.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/HudLayout.fs | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/HudLayout.fs b/src/LivingVillage.Desktop/HudLayout.fs index 05fce33..b19df4b 100644 --- a/src/LivingVillage.Desktop/HudLayout.fs +++ b/src/LivingVillage.Desktop/HudLayout.fs @@ -151,3 +151,131 @@ module HudLayout = Value = Color(228, 238, 246) SegmentBackdrop = Color(4, 8, 16, 160) Outline = Color(3, 6, 12, 255) } + + // ---- P43:右上日历钟卡(纯模型;季节/天数/时段/时钟全部由真实 tick 导出)---- + + type CalendarReading = + { Season: string + DayNumber: int64 + Period: string + Clock: string } + + /// 一季 28 模拟日,四季循环。纯演示层日历,只读 Tick,不写任何模拟状态。 + let daysPerSeason = 28L + let seasonNames = [| "春"; "夏"; "秋"; "冬" |] + + let dayIndexOf (tick: int64) : int64 = tick / Sim.ticksPerDay + let dayNumberOf (tick: int64) : int64 = dayIndexOf tick + 1L + + let seasonOf (tick: int64) : string = + let index = ((dayIndexOf tick / daysPerSeason) % 4L + 4L) % 4L + seasonNames.[int index] + + let calendarReading (tick: int64) : CalendarReading = + { Season = seasonOf tick + DayNumber = dayNumberOf tick + Period = M6Presentation.timePeriodLabel tick + Clock = M6Presentation.clockTimeLabel tick } + + let calendarLine1 (reading: CalendarReading) : string = + sprintf "%s · 第%d天" reading.Season reading.DayNumber + + let calendarLine2 (reading: CalendarReading) : string = + sprintf "%s %s" reading.Period reading.Clock + + let calendarMarginRight = 12 + let calendarMarginTop = 8 + let calendarPadX = 10 + let calendarPadY = 8 + let calendarLineGap = 4 + let calendarTextHeight = 8 * textScale + let calendarCardHeight = 2 * calendarTextHeight + calendarLineGap + 2 * calendarPadY + let calendarIcon = ClockIcon + + /// 右上角卡片矩形:宽度按两行文字实测宽度 + 图标 + 内边距,绝不越界、绝不压住顶部窄条。 + let calendarCardRect (viewportWidth: int) (reading: CalendarReading) : Rectangle = + let textWidth = + max + (FloatingPrompt.measureWidth (calendarLine1 reading) textScale) + (FloatingPrompt.measureWidth (calendarLine2 reading) textScale) + let width = iconWidth + iconGap + textWidth + 2 * calendarPadX + Rectangle( + viewportWidth - width - calendarMarginRight, + barHeight + accentHeight + calendarMarginTop, + width, + calendarCardHeight) + + // ---- P43:体力图形化条形(填充比例取真实 Needs.Energy,0..100)---- + + let energyBarHeight = 10 + + let energyBarFill (energy: float32) : float32 = + if energy < 0.0f then 0.0f + elif energy > 100.0f then 1.0f + else energy / 100.0f + + let energyBarTrack (x: int) (y: int) (width: int) : Rectangle = + Rectangle(x, y, width, energyBarHeight) + + let energyBarFillRect (x: int) (y: int) (width: int) (energy: float32) : Rectangle = + Rectangle(x, y, int (float32 width * energyBarFill energy), energyBarHeight) + + // ---- P43:底部 10 格 hotbar(原创点阵图标 + 键 1-0 选中 + 选中高亮)---- + + let hotbarSlotCount = 10 + let hotbarSlotSize = 40 + let hotbarSlotGap = 6 + let hotbarIconScale = 3 + let hotbarPad = 8 + let hotbarBottomMargin = 12 + let hotbarWidth = hotbarSlotCount * hotbarSlotSize + (hotbarSlotCount - 1) * hotbarSlotGap + + let hotbarBarRect (viewportWidth: int) (viewportHeight: int) : Rectangle = + Rectangle( + (viewportWidth - hotbarWidth) / 2 - hotbarPad, + viewportHeight - hotbarSlotSize - hotbarBottomMargin - hotbarPad, + hotbarWidth + 2 * hotbarPad, + hotbarSlotSize + 2 * hotbarPad) + + let hotbarSlotRect (viewportWidth: int) (viewportHeight: int) (index: int) : Rectangle = + let bar = hotbarBarRect viewportWidth viewportHeight + Rectangle( + bar.X + hotbarPad + index * (hotbarSlotSize + hotbarSlotGap), + bar.Y + hotbarPad, + hotbarSlotSize, + hotbarSlotSize) + + let hotbarIconRect (slot: Rectangle) : Rectangle = + let size = iconGrid * hotbarIconScale + Rectangle(slot.X + (slot.Width - size) / 2, slot.Y + (slot.Height - size) / 2, size, size) + + /// 10 个原创 7x7 点阵占位图标(0..9),仅用现有像素贴图绘制,无外部素材。 + let hotbarIconPatterns : string list list = + [ [ "0011100"; "0100010"; "1000001"; "1000001"; "1000001"; "0100010"; "0011100" ] + [ "0001000"; "0011000"; "0001000"; "0001000"; "0001000"; "0001000"; "0011100" ] + [ "0000000"; "0100010"; "1110111"; "1111111"; "0111110"; "0011100"; "0001000" ] + [ "0001000"; "0001000"; "0011100"; "0111110"; "1111111"; "1111111"; "0111110" ] + [ "0000000"; "0001000"; "0011100"; "1111110"; "0011100"; "0001000"; "0000000" ] + [ "0010100"; "0010100"; "0111110"; "0011100"; "0011100"; "0101010"; "0100010" ] + [ "0011100"; "0111110"; "1101011"; "1101011"; "1101011"; "0111110"; "0011100" ] + [ "0010001"; "0010010"; "0010100"; "0011000"; "0110000"; "0100000"; "1000000" ] + [ "0000100"; "0001100"; "0011110"; "0111100"; "0111000"; "0110000"; "0100000" ] + [ "0001000"; "0011100"; "0111110"; "1111111"; "0011100"; "0100010"; "1111111" ] ] + + let hotbarIconRows (index: int) : string list = + hotbarIconPatterns.[((index % hotbarSlotCount) + hotbarSlotCount) % hotbarSlotCount] + + /// 键位 1..9 -> 槽 0..8,键 0 -> 槽 9;其余键不选格。 + let selectSlotForDigit (digit: int) : int option = + if digit >= 1 && digit <= 9 then Some (digit - 1) + elif digit = 0 then Some 9 + else None + + /// 在 0..9 内循环移动选中格(供方向导航/测试),越界自动回绕。 + let cycleHotbar (current: int) (delta: int) : int = + let n = hotbarSlotCount + ((current + delta) % n + n) % n + + /// hotbar 输入仅在「游戏内(非菜单)」且世界输入允许(无面板/对话)时生效。 + let hotbarInputAllowed (menuPlaying: bool) (worldInputAllowed: bool) : bool = + menuPlaying && worldInputAllowed |
