You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
5.4 KiB
208 lines
5.4 KiB
#include <algorithm> |
|
#include <cstddef> |
|
#include <cstdint> |
|
#include <optional> |
|
#include <string> |
|
#include <string_view> |
|
#include <vector> |
|
|
|
#ifdef USE_SDL3 |
|
#include <SDL3/SDL_events.h> |
|
#include <SDL3/SDL_rect.h> |
|
#include <SDL3/SDL_surface.h> |
|
#include <SDL3/SDL_timer.h> |
|
#else |
|
#include <SDL.h> |
|
#endif |
|
|
|
#include "DiabloUI/credits_lines.h" |
|
#include "DiabloUI/diabloui.h" |
|
#include "DiabloUI/support_lines.h" |
|
#include "DiabloUI/ui_flags.hpp" |
|
#include "controls/input.h" |
|
#include "controls/menu_controls.h" |
|
#include "engine/load_clx.hpp" |
|
#include "engine/point.hpp" |
|
#include "engine/render/clx_render.hpp" |
|
#include "engine/render/text_render.hpp" |
|
#include "engine/surface.hpp" |
|
#include "hwcursor.hpp" |
|
#include "utils/display.h" |
|
#include "utils/is_of.hpp" |
|
#include "utils/language.h" |
|
#include "utils/sdl_compat.h" |
|
#include "utils/sdl_geometry.h" |
|
#include "utils/ui_fwd.h" |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
const SDL_Rect VIEWPORT = { 0, 114, 640, 251 }; |
|
const int LINE_H = 22; |
|
|
|
// The maximum number of visible lines is the number of whole lines |
|
// (VIEWPORT.h / LINE_H) rounded up, plus one extra line for when |
|
// a line is leaving the screen while another one is entering. |
|
#define MAX_VISIBLE_LINES ((VIEWPORT.h - 1) / LINE_H + 2) |
|
|
|
class CreditsRenderer { |
|
|
|
public: |
|
CreditsRenderer(const char *const *text, std::size_t textLines) |
|
{ |
|
for (size_t i = 0; i < textLines; i++) { |
|
const std::string_view orgText = _(text[i]); |
|
|
|
uint16_t offset = 0; |
|
size_t indexFirstNotTab = 0; |
|
while (indexFirstNotTab < orgText.size() && orgText[indexFirstNotTab] == '\t') { |
|
offset += 40; |
|
indexFirstNotTab++; |
|
} |
|
|
|
const std::string paragraphs = WordWrapString(orgText.substr(indexFirstNotTab), 580 - offset, FontSizeDialog); |
|
|
|
size_t previous = 0; |
|
while (true) { |
|
const size_t next = paragraphs.find('\n', previous); |
|
linesToRender.emplace_back(LineContent { offset, paragraphs.substr(previous, next - previous) }); |
|
if (next == std::string::npos) |
|
break; |
|
previous = next + 1; |
|
} |
|
} |
|
|
|
ticks_begin_ = SDL_GetTicks(); |
|
prev_offset_y_ = 0; |
|
finished_ = false; |
|
} |
|
|
|
~CreditsRenderer() |
|
{ |
|
ArtBackgroundWidescreen = std::nullopt; |
|
ArtBackground = std::nullopt; |
|
} |
|
|
|
void Render(); |
|
|
|
[[nodiscard]] bool Finished() const |
|
{ |
|
return finished_; |
|
} |
|
|
|
private: |
|
struct LineContent { |
|
uint16_t offset; |
|
std::string text; |
|
}; |
|
|
|
std::vector<LineContent> linesToRender; |
|
bool finished_; |
|
Uint32 ticks_begin_; |
|
int prev_offset_y_; |
|
}; |
|
|
|
void CreditsRenderer::Render() |
|
{ |
|
const int offsetY = -VIEWPORT.h + ((SDL_GetTicks() - ticks_begin_) / 40); |
|
if (offsetY == prev_offset_y_) |
|
return; |
|
prev_offset_y_ = offsetY; |
|
|
|
SDL_FillSurfaceRect(DiabloUiSurface(), nullptr, 0); |
|
const Point uiPosition = GetUIRectangle().position; |
|
if (ArtBackgroundWidescreen) |
|
RenderClxSprite(Surface(DiabloUiSurface()), (*ArtBackgroundWidescreen)[0], uiPosition - Displacement { 320, 0 }); |
|
RenderClxSprite(Surface(DiabloUiSurface()), (*ArtBackground)[0], uiPosition); |
|
|
|
const std::size_t linesBegin = std::max(offsetY / LINE_H, 0); |
|
const std::size_t linesEnd = std::min(linesBegin + MAX_VISIBLE_LINES, linesToRender.size()); |
|
|
|
if (linesBegin >= linesEnd) { |
|
if (linesEnd == linesToRender.size()) |
|
finished_ = true; |
|
return; |
|
} |
|
|
|
SDL_Rect viewport = VIEWPORT; |
|
viewport.x += uiPosition.x; |
|
viewport.y += uiPosition.y; |
|
ScaleOutputRect(&viewport); |
|
|
|
// We use unscaled coordinates for calculation throughout. |
|
Sint16 destY = static_cast<Sint16>(uiPosition.y + VIEWPORT.y - (offsetY - linesBegin * LINE_H)); |
|
for (std::size_t i = linesBegin; i < linesEnd; ++i, destY += LINE_H) { |
|
const Sint16 destX = uiPosition.x + VIEWPORT.x + 31; |
|
|
|
auto &lineContent = linesToRender[i]; |
|
|
|
SDL_Rect dstRect = MakeSdlRect(destX + lineContent.offset, destY, 0, 0); |
|
ScaleOutputRect(&dstRect); |
|
dstRect.x -= viewport.x; |
|
dstRect.y -= viewport.y; |
|
|
|
const Surface &out = Surface(DiabloUiSurface(), viewport); |
|
DrawString(out, lineContent.text, Point { dstRect.x, dstRect.y }, |
|
{ .flags = UiFlags::FontSizeDialog | UiFlags::ColorDialogWhite, .spacing = -1 }); |
|
} |
|
} |
|
|
|
bool TextDialog(const char *const *text, std::size_t textLines) |
|
{ |
|
CreditsRenderer creditsRenderer(text, textLines); |
|
bool endMenu = false; |
|
|
|
if (IsHardwareCursor()) |
|
SetHardwareCursorVisible(false); |
|
|
|
SDL_Event event; |
|
do { |
|
creditsRenderer.Render(); |
|
UiFadeIn(); |
|
while (PollEvent(&event)) { |
|
switch (event.type) { |
|
case SDL_EVENT_KEY_DOWN: |
|
case SDL_EVENT_MOUSE_BUTTON_UP: |
|
endMenu = true; |
|
break; |
|
default: |
|
for (const MenuAction menuAction : GetMenuActions(event)) { |
|
if (IsNoneOf(menuAction, MenuAction_BACK, MenuAction_SELECT)) |
|
continue; |
|
endMenu = true; |
|
break; |
|
} |
|
break; |
|
} |
|
UiHandleEvents(&event); |
|
} |
|
} while (!endMenu && !creditsRenderer.Finished()); |
|
|
|
return true; |
|
} |
|
|
|
} // namespace |
|
|
|
bool UiCreditsDialog() |
|
{ |
|
ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); |
|
LoadBackgroundArt("ui_art\\credits"); |
|
|
|
return TextDialog(CreditLines, CreditLinesSize); |
|
} |
|
|
|
bool UiSupportDialog() |
|
{ |
|
ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\supportw.clx"); |
|
if (ArtBackgroundWidescreen.has_value()) { |
|
LoadBackgroundArt("ui_art\\support"); |
|
} else { |
|
ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); |
|
LoadBackgroundArt("ui_art\\credits"); |
|
} |
|
|
|
return TextDialog(SupportLines, SupportLinesSize); |
|
} |
|
|
|
} // namespace devilution
|
|
|