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