1 #include <algorithm>
2 #include <memory>
3 #include <vector>
4
5 #include "controls/menu_controls.h"
6 #include "all.h"
7 #include "display.h"
8
9 #include "DiabloUI/diabloui.h"
10 #include "DiabloUI/credits_lines.h"
11 #include "DiabloUI/support_lines.h"
12 #include "DiabloUI/art.h"
13 #include "DiabloUI/art_draw.h"
14 #include "DiabloUI/fonts.h"
15
16 namespace dvl {
17
18 namespace {
19
20 const SDL_Rect VIEWPORT = { 0, 114, 640, 251 };
21 const int SHADOW_OFFSET_X = 2;
22 const int SHADOW_OFFSET_Y = 2;
23 const int LINE_H = 22;
24
25 char const *const *text;
26 std::size_t textLines;
27
28 // The maximum number of visible lines is the number of whole lines
29 // (VIEWPORT.h / LINE_H) rounded up, plus one extra line for when
30 // a line is leaving the screen while another one is entering.
31 #define MAX_VISIBLE_LINES ((VIEWPORT.h - 1) / LINE_H + 2)
32
33 struct CachedLine {
34
CachedLinedvl::__anon3be063c60111::CachedLine35 CachedLine()
36 {
37 m_index = 0;
38 m_surface = NULL;
39 palette_version = pal_surface_palette_version;
40 }
41
CachedLinedvl::__anon3be063c60111::CachedLine42 CachedLine(std::size_t index, SDL_Surface *surface)
43 {
44 m_index = index;
45 m_surface = surface;
46 palette_version = pal_surface_palette_version;
47 }
48
49 std::size_t m_index;
50 SDL_Surface *m_surface;
51 unsigned int palette_version;
52 };
53
RenderText(const char * text,SDL_Color color)54 SDL_Surface *RenderText(const char *text, SDL_Color color)
55 {
56 if (text[0] == '\0')
57 return NULL;
58 SDL_Surface *result = TTF_RenderUTF8_Solid(font, text, color);
59 if (result == NULL)
60 SDL_Log(TTF_GetError());
61 return result;
62 }
63
PrepareLine(std::size_t index)64 CachedLine PrepareLine(std::size_t index)
65 {
66 const char *contents = text[index];
67 while (contents[0] == '\t')
68 ++contents;
69
70 const SDL_Color shadow_color = { 0, 0, 0, 0 };
71 SDL_Surface *text = RenderText(contents, shadow_color);
72
73 // Precompose shadow and text:
74 SDL_Surface *surface = NULL;
75 if (text != NULL) {
76 // Set up the target surface to have 3 colors: mask, text, and shadow.
77 surface = SDL_CreateRGBSurfaceWithFormat(0, text->w + SHADOW_OFFSET_X, text->h + SHADOW_OFFSET_Y, 8, SDL_PIXELFORMAT_INDEX8);
78 const SDL_Color mask_color = { 0, 255, 0, 0 }; // Any color different from both shadow and text
79 const SDL_Color &text_color = palette->colors[224];
80 SDL_Color colors[3] = { mask_color, text_color, shadow_color };
81 if (SDLC_SetSurfaceColors(surface, colors, 0, 3) <= -1)
82 SDL_Log(SDL_GetError());
83 SDLC_SetColorKey(surface, 0);
84
85 // Blit the shadow first:
86 SDL_Rect shadow_rect = { SHADOW_OFFSET_X, SHADOW_OFFSET_Y, 0, 0 };
87 if (SDL_BlitSurface(text, NULL, surface, &shadow_rect) <= -1)
88 ErrSdl();
89
90 // Change the text surface color and blit again:
91 SDL_Color text_colors[2] = { mask_color, text_color };
92 if (SDLC_SetSurfaceColors(text, text_colors, 0, 2) <= -1)
93 ErrSdl();
94 SDLC_SetColorKey(text, 0);
95
96 if (SDL_BlitSurface(text, NULL, surface, NULL) <= -1)
97 ErrSdl();
98
99 SDL_Surface *surface_ptr = surface;
100 ScaleSurfaceToOutput(&surface_ptr);
101 surface = surface_ptr;
102 }
103 SDL_FreeSurface(text);
104 return CachedLine(index, surface);
105 }
106
107 class CreditsRenderer {
108
109 public:
CreditsRenderer()110 CreditsRenderer()
111 {
112 LoadTtfFont();
113 ticks_begin_ = SDL_GetTicks();
114 prev_offset_y_ = 0;
115 finished_ = false;
116 }
117
~CreditsRenderer()118 ~CreditsRenderer()
119 {
120 ArtBackgroundWidescreen.Unload();
121 ArtBackground.Unload();
122 UnloadTtfFont();
123
124 for (size_t x = 0; x < lines_.size(); x++) {
125 if (lines_[x].m_surface)
126 SDL_FreeSurface(lines_[x].m_surface);
127 }
128 }
129
130 void Render();
131
Finished() const132 bool Finished() const
133 {
134 return finished_;
135 }
136
137 private:
138 std::vector<CachedLine> lines_;
139 bool finished_;
140 Uint32 ticks_begin_;
141 int prev_offset_y_;
142 };
143
Render()144 void CreditsRenderer::Render()
145 {
146 const int offset_y = -VIEWPORT.h + (SDL_GetTicks() - ticks_begin_) / 40;
147 if (offset_y == prev_offset_y_)
148 return;
149 prev_offset_y_ = offset_y;
150
151 SDL_FillRect(DiabloUiSurface(), NULL, 0x000000);
152 DrawArt(PANEL_LEFT - 320, UI_OFFSET_Y, &ArtBackgroundWidescreen);
153 DrawArt(PANEL_LEFT, UI_OFFSET_Y, &ArtBackground);
154 if (font == NULL)
155 return;
156
157 const std::size_t lines_begin = std::max(offset_y / LINE_H, 0);
158 const std::size_t lines_end = std::min(lines_begin + MAX_VISIBLE_LINES, textLines);
159
160 if (lines_begin >= lines_end) {
161 if (lines_end == textLines)
162 finished_ = true;
163 return;
164 }
165
166 while (lines_end > lines_.size())
167 lines_.push_back(PrepareLine(lines_.size()));
168
169 SDL_Rect viewport = VIEWPORT;
170 viewport.x += PANEL_LEFT;
171 viewport.y += UI_OFFSET_Y;
172 ScaleOutputRect(&viewport);
173 SDL_SetClipRect(DiabloUiSurface(), &viewport);
174
175 // We use unscaled coordinates for calculation throughout.
176 Sint16 dest_y = UI_OFFSET_Y + VIEWPORT.y - (offset_y - lines_begin * LINE_H);
177 for (std::size_t i = lines_begin; i < lines_end; ++i, dest_y += LINE_H) {
178 CachedLine &line = lines_[i];
179 if (line.m_surface == NULL)
180 continue;
181
182 // Still fading in: the cached line was drawn with a different fade level.
183 if (line.palette_version != pal_surface_palette_version) {
184 SDL_FreeSurface(line.m_surface);
185 line = PrepareLine(line.m_index);
186 }
187
188 Sint16 dest_x = PANEL_LEFT + VIEWPORT.x + 31;
189 int j = 0;
190 while (text[line.m_index][j++] == '\t')
191 dest_x += 40;
192
193 SDL_Rect dst_rect = { dest_x, dest_y, 0, 0 };
194 ScaleOutputRect(&dst_rect);
195 dst_rect.w = line.m_surface->w;
196 dst_rect.h = line.m_surface->h;
197 if (SDL_BlitSurface(line.m_surface, NULL, DiabloUiSurface(), &dst_rect) < 0)
198 ErrSdl();
199 }
200 SDL_SetClipRect(DiabloUiSurface(), NULL);
201 }
202
TextDialog()203 BOOL TextDialog()
204 {
205 CreditsRenderer credits_renderer;
206 bool endMenu = false;
207
208 SDL_Event event;
209 do {
210 credits_renderer.Render();
211 UiFadeIn();
212 while (SDL_PollEvent(&event)) {
213 switch (event.type) {
214 case SDL_KEYDOWN:
215 case SDL_MOUSEBUTTONDOWN:
216 endMenu = true;
217 break;
218 default:
219 switch (GetMenuAction(event)) {
220 case MenuAction_BACK:
221 case MenuAction_SELECT:
222 endMenu = true;
223 break;
224 default:
225 break;
226 }
227 }
228 UiHandleEvents(&event);
229 }
230 } while (!endMenu && !credits_renderer.Finished());
231
232 return true;
233 }
234
235 } // namespace
236
UiCreditsDialog()237 BOOL UiCreditsDialog()
238 {
239 text = CREDITS_LINES;
240 textLines = CREDITS_LINES_SIZE;
241
242 LoadArt("ui_art\\creditsw.pcx", &ArtBackgroundWidescreen);
243 LoadBackgroundArt("ui_art\\credits.pcx");
244
245 return TextDialog();
246 }
247
UiSupportDialog()248 BOOL UiSupportDialog()
249 {
250 text = SUPPORT_LINES;
251 textLines = SUPPORT_LINES_SIZE;
252
253 if (gbIsHellfire) {
254 LoadArt("ui_art\\supportw.pcx", &ArtBackgroundWidescreen);
255 LoadBackgroundArt("ui_art\\support.pcx");
256 } else {
257 LoadArt("ui_art\\creditsw.pcx", &ArtBackgroundWidescreen);
258 LoadBackgroundArt("ui_art\\credits.pcx");
259 }
260
261 return TextDialog();
262 }
263
264 } // namespace dvl
265