devilutionX/Source/gmenu.cpp
Anders Jenbo 68923c6c33 Generic game text render function
This should gradually replace all the direct rendering of game texts
throughout the code. The interface is made to closly mirror that of the
art fonts as that is what will eventually be used for rendering Unicode
fonts both in the menus and ingame.

fixup!  Generic game text render function
2021-05-08 18:48:19 +02:00

415 lines
9.3 KiB
C++

/**
* @file gmenu.cpp
*
* Implementation of the in-game navigation and interaction.
*/
#include "gmenu.h"
#include "control.h"
#include "controls/axis_direction.h"
#include "controls/controller_motion.h"
#include "engine.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/text_render.hpp"
#include "stores.h"
#include "utils/language.h"
#include "utils/stdcompat/optional.hpp"
#include "utils/ui_fwd.h"
namespace devilution {
namespace {
std::optional<CelSprite> optbar_cel;
std::optional<CelSprite> PentSpin_cel;
std::optional<CelSprite> option_cel;
std::optional<CelSprite> sgpLogo;
} // namespace
bool mouseNavigation;
TMenuItem *sgpCurrItem;
int LogoAnim_tick;
BYTE LogoAnim_frame;
int PentSpin_tick;
void (*gmenu_current_option)();
TMenuItem *sgpCurrentMenu;
int sgCurrentMenuIdx;
static void gmenu_print_text(const CelOutputBuffer &out, int x, int y, const char *pszStr)
{
BYTE c;
while (*pszStr) {
c = gbFontTransTbl[(BYTE)*pszStr++];
c = fontframe[GameFontBig][c];
if (c != 0)
CelDrawLightTo(out, x, y, *BigTGold_cel, c, nullptr);
x += fontkern[GameFontBig][c] + 2;
}
}
void gmenu_draw_pause(const CelOutputBuffer &out)
{
if (currlevel != 0)
RedBack(out);
if (sgpCurrentMenu == nullptr) {
light_table_index = 0;
gmenu_print_text(out, 252 + PANEL_LEFT, 176, _("Pause"));
}
}
void FreeGMenu()
{
sgpLogo = std::nullopt;
PentSpin_cel = std::nullopt;
option_cel = std::nullopt;
optbar_cel = std::nullopt;
}
void gmenu_init_menu()
{
LogoAnim_frame = 1;
sgpCurrentMenu = nullptr;
sgpCurrItem = nullptr;
gmenu_current_option = nullptr;
sgCurrentMenuIdx = 0;
mouseNavigation = false;
if (gbIsHellfire)
sgpLogo = LoadCel("Data\\hf_logo3.CEL", 430);
else
sgpLogo = LoadCel("Data\\Diabsmal.CEL", 296);
PentSpin_cel = LoadCel("Data\\PentSpin.CEL", 48);
option_cel = LoadCel("Data\\option.CEL", 27);
optbar_cel = LoadCel("Data\\optbar.CEL", 287);
}
bool gmenu_is_active()
{
return sgpCurrentMenu != nullptr;
}
static void gmenu_up_down(bool isDown)
{
int i;
if (sgpCurrItem == nullptr) {
return;
}
mouseNavigation = false;
i = sgCurrentMenuIdx;
if (sgCurrentMenuIdx) {
while (i) {
i--;
if (isDown) {
sgpCurrItem++;
if (!sgpCurrItem->fnMenu)
sgpCurrItem = &sgpCurrentMenu[0];
} else {
if (sgpCurrItem == sgpCurrentMenu)
sgpCurrItem = &sgpCurrentMenu[sgCurrentMenuIdx];
sgpCurrItem--;
}
if ((sgpCurrItem->dwFlags & GMENU_ENABLED) != 0) {
if (i)
PlaySFX(IS_TITLEMOV);
return;
}
}
}
}
static void gmenu_left_right(bool isRight)
{
int step, steps;
if ((sgpCurrItem->dwFlags & GMENU_SLIDER) == 0)
return;
step = sgpCurrItem->dwFlags & 0xFFF;
steps = (int)(sgpCurrItem->dwFlags & 0xFFF000) >> 12;
if (isRight) {
if (step == steps)
return;
step++;
} else {
if (step == 0)
return;
step--;
}
sgpCurrItem->dwFlags &= 0xFFFFF000;
sgpCurrItem->dwFlags |= step;
sgpCurrItem->fnMenu(false);
}
void gmenu_set_items(TMenuItem *pItem, void (*gmFunc)())
{
int i;
PauseMode = 0;
mouseNavigation = false;
sgpCurrentMenu = pItem;
gmenu_current_option = gmFunc;
if (gmFunc != nullptr) {
gmenu_current_option();
pItem = sgpCurrentMenu;
}
sgCurrentMenuIdx = 0;
if (sgpCurrentMenu != nullptr) {
for (i = 0; sgpCurrentMenu[i].fnMenu != nullptr; i++) {
sgCurrentMenuIdx++;
}
}
// BUGFIX: OOB access when sgCurrentMenuIdx is 0; should be set to NULL instead. (fixed)
sgpCurrItem = sgCurrentMenuIdx > 0 ? &sgpCurrentMenu[sgCurrentMenuIdx - 1] : nullptr;
gmenu_up_down(true);
}
static void gmenu_clear_buffer(const CelOutputBuffer &out, int x, int y, int width, int height)
{
BYTE *i = out.at(x, y);
while (height--) {
memset(i, 205, width);
i -= out.pitch();
}
}
static int gmenu_get_lfont(TMenuItem *pItem)
{
const char *text;
int i;
BYTE c;
if ((pItem->dwFlags & GMENU_SLIDER) != 0)
return 490;
text = _(pItem->pszStr);
i = 0;
while (*text) {
c = gbFontTransTbl[(BYTE)*text++];
i += fontkern[GameFontBig][fontframe[GameFontBig][c]] + 2;
}
return i - 2;
}
static void gmenu_draw_menu_item(const CelOutputBuffer &out, TMenuItem *pItem, int y)
{
DWORD w, x, nSteps, step, pos;
w = gmenu_get_lfont(pItem);
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
x = 16 + w / 2;
CelDrawTo(out, x + PANEL_LEFT, y - 10, *optbar_cel, 1);
step = pItem->dwFlags & 0xFFF;
nSteps = (pItem->dwFlags & 0xFFF000) >> 12;
if (nSteps < 2)
nSteps = 2;
pos = step * 256 / nSteps;
gmenu_clear_buffer(out, x + 2 + PANEL_LEFT, y - 12, pos + 13, 28);
CelDrawTo(out, x + 2 + pos + PANEL_LEFT, y - 12, *option_cel, 1);
}
x = gnScreenWidth / 2 - w / 2;
light_table_index = (pItem->dwFlags & GMENU_ENABLED) ? 0 : 15;
gmenu_print_text(out, x, y, _(pItem->pszStr));
if (pItem == sgpCurrItem) {
CelDrawTo(out, x - 54, y + 1, *PentSpin_cel, PentSpn2Spin());
CelDrawTo(out, x + 4 + w, y + 1, *PentSpin_cel, PentSpn2Spin());
}
}
static void GameMenuMove()
{
static AxisDirectionRepeater repeater;
const AxisDirection move_dir = repeater.Get(GetLeftStickOrDpadDirection());
if (move_dir.x != AxisDirectionX_NONE)
gmenu_left_right(move_dir.x == AxisDirectionX_RIGHT);
if (move_dir.y != AxisDirectionY_NONE)
gmenu_up_down(move_dir.y == AxisDirectionY_DOWN);
}
void gmenu_draw(const CelOutputBuffer &out)
{
int y;
TMenuItem *i;
if (sgpCurrentMenu != nullptr) {
GameMenuMove();
if (gmenu_current_option != nullptr)
gmenu_current_option();
if (gbIsHellfire) {
const DWORD ticks = SDL_GetTicks();
if ((int)(ticks - LogoAnim_tick) > 25) {
LogoAnim_frame++;
if (LogoAnim_frame > 16)
LogoAnim_frame = 1;
LogoAnim_tick = ticks;
}
}
CelDrawTo(out, (gnScreenWidth - sgpLogo->Width()) / 2, 102 + UI_OFFSET_Y, *sgpLogo, LogoAnim_frame);
y = 160 + UI_OFFSET_Y;
i = sgpCurrentMenu;
if (sgpCurrentMenu->fnMenu != nullptr) {
while (i->fnMenu != nullptr) {
gmenu_draw_menu_item(out, i, y);
i++;
y += 45;
}
}
}
}
bool gmenu_presskeys(int vkey)
{
if (sgpCurrentMenu == nullptr)
return false;
switch (vkey) {
case DVL_VK_RETURN:
if ((sgpCurrItem->dwFlags & GMENU_ENABLED) != 0) {
PlaySFX(IS_TITLEMOV);
sgpCurrItem->fnMenu(true);
}
break;
case DVL_VK_ESCAPE:
PlaySFX(IS_TITLEMOV);
gmenu_set_items(nullptr, nullptr);
break;
case DVL_VK_SPACE:
return false;
case DVL_VK_LEFT:
gmenu_left_right(false);
break;
case DVL_VK_RIGHT:
gmenu_left_right(true);
break;
case DVL_VK_UP:
gmenu_up_down(false);
break;
case DVL_VK_DOWN:
gmenu_up_down(true);
break;
}
return true;
}
static bool gmenu_get_mouse_slider(int *plOffset)
{
*plOffset = 282;
if (MouseX < 282 + PANEL_LEFT) {
*plOffset = 0;
return false;
}
if (MouseX > 538 + PANEL_LEFT) {
*plOffset = 256;
return false;
}
*plOffset = MouseX - 282 - PANEL_LEFT;
return true;
}
bool gmenu_on_mouse_move()
{
int step, nSteps;
if (!mouseNavigation)
return false;
gmenu_get_mouse_slider(&step);
nSteps = (int)(sgpCurrItem->dwFlags & 0xFFF000) >> 12;
step *= nSteps;
step /= 256;
sgpCurrItem->dwFlags &= 0xFFFFF000;
sgpCurrItem->dwFlags |= step;
sgpCurrItem->fnMenu(false);
return true;
}
bool gmenu_left_mouse(bool isDown)
{
TMenuItem *pItem;
int i, w, dummy;
if (!isDown) {
if (mouseNavigation) {
mouseNavigation = false;
return true;
}
return false;
}
if (sgpCurrentMenu == nullptr) {
return false;
}
if (MouseY >= PANEL_TOP) {
return false;
}
if (MouseY - (117 + UI_OFFSET_Y) < 0) {
return true;
}
i = (MouseY - (117 + UI_OFFSET_Y)) / 45;
if (i >= sgCurrentMenuIdx) {
return true;
}
pItem = &sgpCurrentMenu[i];
if ((sgpCurrentMenu[i].dwFlags & GMENU_ENABLED) == 0) {
return true;
}
w = gmenu_get_lfont(pItem);
if (MouseX < gnScreenWidth / 2 - w / 2) {
return true;
}
if (MouseX > gnScreenWidth / 2 + w / 2) {
return true;
}
sgpCurrItem = pItem;
PlaySFX(IS_TITLEMOV);
if ((pItem->dwFlags & GMENU_SLIDER) != 0) {
mouseNavigation = gmenu_get_mouse_slider(&dummy);
gmenu_on_mouse_move();
} else {
sgpCurrItem->fnMenu(true);
}
return true;
}
void gmenu_enable(TMenuItem *pMenuItem, bool enable)
{
if (enable)
pMenuItem->dwFlags |= GMENU_ENABLED;
else
pMenuItem->dwFlags &= ~GMENU_ENABLED;
}
/**
* @brief Set the TMenuItem slider position based on the given value
*/
void gmenu_slider_set(TMenuItem *pItem, int min, int max, int value)
{
int nSteps;
assert(pItem);
nSteps = (int)(pItem->dwFlags & 0xFFF000) >> 12;
if (nSteps < 2)
nSteps = 2;
pItem->dwFlags &= 0xFFFFF000;
pItem->dwFlags |= ((max - min - 1) / 2 + (value - min) * nSteps) / (max - min);
}
/**
* @brief Get the current value for the slider
*/
int gmenu_slider_get(TMenuItem *pItem, int min, int max)
{
int nSteps, step;
step = pItem->dwFlags & 0xFFF;
nSteps = (int)(pItem->dwFlags & 0xFFF000) >> 12;
if (nSteps < 2)
nSteps = 2;
return min + (step * (max - min) + (nSteps - 1) / 2) / nSteps;
}
/**
* @brief Set the number of steps for the slider
*/
void gmenu_slider_steps(TMenuItem *pItem, int steps)
{
pItem->dwFlags &= 0xFF000FFF;
pItem->dwFlags |= (steps << 12) & 0xFFF000;
}
} // namespace devilution