summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/TitleScreen.fs
blob: 0dcd6936a29f4c1c14fdd6f356637af6dab36a90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
namespace LivingVillage.Desktop

module TitleScreen =

    type ColorRgb =
        { R: int
          G: int
          B: int }

    type TitleLayout =
        { SkyBands: ColorRgb list
          RoofTileSlots: int list
          RidgeY: int
          EaveY: int
          WaterRows: int list
          Lanterns: (int * int) list
          TitleText: string
          TitleX: int
          TitleY: int
          TitleScale: int
          MenuX: int
          MenuTopY: int
          MenuLineHeight: int
          PanelX: int
          PanelY: int
          PanelWidth: int
          PanelHeight: int
          HeadingY: int
          FirstItemY: int
          CursorX: int
          CursorWidth: int
          TextX: int
          ControlsPanelY: int
          ControlsRowHeight: int
          ControlsKeyX: int
          ControlsKeyWidth: int
          ControlsActionX: int }

    type TextRect =
        { X: int
          Y: int
          Width: int
          Height: int }

    let rectsDisjoint (a: TextRect) (b: TextRect) : bool =
        a.X + a.Width <= b.X
        || b.X + b.Width <= a.X
        || a.Y + a.Height <= b.Y
        || b.Y + b.Height <= a.Y

    /// Key-name and action rectangles for one controls-page row. The key column has a
    /// fixed width shared by every row, so all action texts start on the same x.
    let controlRowRects (layout: TitleLayout) (rowIndex: int) : TextRect * TextRect =
        let y = layout.ControlsPanelY + 60 + rowIndex * layout.ControlsRowHeight
        { X = layout.ControlsKeyX; Y = y; Width = layout.ControlsKeyWidth; Height = 16 },
        { X = layout.ControlsActionX; Y = y; Width = layout.PanelX + layout.PanelWidth - layout.ControlsActionX - 24; Height = 16 }

    /// Cursor and label rectangles for one menu row. The cursor is drawn in its own
    /// column; the label always starts to the right of it, so they can never overprint.
    let selectionRects (layout: TitleLayout) (rowIndex: int) : TextRect * TextRect =
        let y = layout.FirstItemY + rowIndex * layout.MenuLineHeight
        { X = layout.CursorX; Y = y; Width = layout.CursorWidth; Height = 16 },
        { X = layout.TextX; Y = y; Width = layout.PanelX + layout.PanelWidth - layout.TextX - 24; Height = 16 }

    // Palette mirrors the VillageArt atlas: roof (45,49,57)/(30,33,38), water (54,116,145),
    // lantern paper (212,92,74). Purely decorative reuse of existing art colors.
    let private skyBands : ColorRgb list =
        [ { R = 16; G = 26; B = 42 }
          { R = 20; G = 36; B = 52 }
          { R = 24; G = 46; B = 62 }
          { R = 28; G = 56; B = 68 }
          { R = 32; G = 64; B = 72 } ]

    let private roofRidgeSlot = 8
    let private roofEaveSlot = 9
    let private waterTileSlot = 1
    let private lanternColor = { R = 212; G = 92; B = 74 }
    let private lanternHalo = { R = 252; G = 216; B = 148 }

    let lanternColors : ColorRgb * ColorRgb = lanternColor, lanternHalo
    let skyBandCount = skyBands.Length
    let waterSlot = waterTileSlot
    let roofSlots = roofRidgeSlot, roofEaveSlot

    /// Deterministic 2s entrance at the fixed 60Hz step: 1.0 = elements fully parked
    /// off-screen at start, 0.0 = settled. Pure function of the frame counter.
    let entranceProgress (frameCount: int64) : float32 =
        if frameCount >= 120L then 0.0f
        else 1.0f - float32 frameCount / 120.0f

    let layout (viewportWidth: int) (viewportHeight: int) : TitleLayout =
        let width = max 320 viewportWidth
        let height = max 240 viewportHeight
        let tilePixels = 32
        let columns = width / tilePixels
        let roofTileSlots =
            [ 0 .. columns - 1 ]
            |> List.map (fun column ->
                if column % 4 = 0 || column % 7 = 3 then roofRidgeSlot else roofEaveSlot)
        let waterRows =
            [ height - 96
              height - 64 ]
        let lanternCount = max 3 (columns / 10)
        let lanterns =
            [ 1 .. lanternCount ]
            |> List.map (fun index ->
                let x = index * width / (lanternCount + 1)
                let y = (List.head waterRows) - 44 + (if index % 2 = 0 then 12 else 0)
                x, y)
        let panelWidth = min 460 (width - 64)
        { SkyBands = skyBands
          RoofTileSlots = roofTileSlots
          RidgeY = 44
          EaveY = 76
          WaterRows = waterRows
          Lanterns = lanterns
          TitleText = "江南水乡"
          TitleX = (width - 4 * (4 * 16)) / 2
          TitleY = 120
          TitleScale = 4
          MenuX = (width - panelWidth) / 2 + 40
          MenuTopY = height / 2 - 40
          MenuLineHeight = 30
          PanelX = (width - panelWidth) / 2
          PanelY = height / 2 - 64
          PanelWidth = panelWidth
          PanelHeight = min 360 (height - height / 2 - 40)
          HeadingY = height / 2 - 64 + 20
          FirstItemY = height / 2 - 64 + 60
          CursorX = (width - panelWidth) / 2 + 24
          CursorWidth = 20
          TextX = (width - panelWidth) / 2 + 24 + 20 + 12
          ControlsPanelY = max 16 (height - 480)
          ControlsRowHeight = 25
          ControlsKeyX = (width - panelWidth) / 2 + 24
          ControlsKeyWidth = 120
          ControlsActionX = (width - panelWidth) / 2 + 24 + 120 + 12 }