ui2: port pause exit dialog
Some checks are pending
Run code linters / Run code linters (push) Waiting to run

This commit is contained in:
Marcin Kurczewski 2025-04-14 21:53:52 +02:00
parent eaf3a2bfc5
commit 03b0b443bb
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
8 changed files with 196 additions and 68 deletions

View file

@ -26,6 +26,7 @@
- fixed sprites missing the fog effect (regression from 4.9)
- improved bubble appearance (#2672)
- improved rendering performance
- improved pause exit dialog - it can now be canceled with escape
- removed the pretty pixels options (it's now always enabled, #2258)
## [4.9](https://github.com/LostArtefacts/TRX/compare/tr1-4.8.3...tr1-4.9) - 2025-03-31

View file

@ -33,6 +33,7 @@
- fixed a crash if an image was missing
- fixed a crash on level load if an animation has no frames (#2746, regression from 0.8)
- fixed a crash in custom levels with large rooms (#2749)
- improved pause exit dialog - it can now be canceled with escape
- removed the need to specify in the game flow levels that have no secrets (secrets will be automatically counted) (#1582)
- removed the hard-coded end-level behaviour of the bird guardian for custom levels (#1583)

View file

@ -11,8 +11,7 @@
#include "game/shell.h"
#include "game/sound.h"
#include "game/text.h"
#include "game/ui/common.h"
#include "game/ui/widgets/requester.h"
#include "game/ui2.h"
#include "memory.h"
#include <stdint.h>
@ -23,14 +22,15 @@ typedef enum {
STATE_FADE_IN,
STATE_WAIT,
STATE_ASK,
STATE_CONFIRM,
STATE_FADE_OUT,
} STATE;
typedef struct {
STATE state;
bool is_ui_ready;
UI_WIDGET *ui;
struct {
bool is_ready;
UI2_PAUSE_STATE state;
} ui;
TEXTSTRING *mode_text;
GF_ACTION action;
FADER back_fader;
@ -43,8 +43,6 @@ static void M_ReturnToGame(M_PRIV *p);
static void M_ExitToTitle(M_PRIV *p);
static void M_CreateText(M_PRIV *p);
static void M_RemoveText(M_PRIV *p);
static int32_t M_DisplayRequester(
M_PRIV *p, const char *header, const char *option1, const char *option2);
static PHASE_CONTROL M_Start(PHASE *phase);
static void M_End(PHASE *phase);
@ -60,10 +58,7 @@ static void M_FadeIn(M_PRIV *const p)
static void M_FadeOut(M_PRIV *const p)
{
M_RemoveText(p);
if (p->ui != nullptr) {
p->ui->free(p->ui);
p->ui = nullptr;
}
p->ui.is_ready = false;
if (p->action == GF_NOOP) {
Fader_Init(&p->back_fader, FADER_ANY, FADER_TRANSPARENT, FADE_TIME);
} else {
@ -108,42 +103,13 @@ static void M_RemoveText(M_PRIV *const p)
p->mode_text = nullptr;
}
static int32_t M_DisplayRequester(
M_PRIV *const p, const char *header, const char *option1,
const char *option2)
{
if (!p->is_ui_ready) {
if (p->ui == nullptr) {
p->ui = UI_Requester_Create((UI_REQUESTER_SETTINGS) {
.is_selectable = true,
.width = 160,
.visible_rows = 2,
});
}
UI_Requester_ClearRows(p->ui);
UI_Requester_SetTitle(p->ui, header);
UI_Requester_AddRowC(p->ui, option1, nullptr);
UI_Requester_AddRowC(p->ui, option2, nullptr);
p->ui->set_position(
p->ui, (UI_GetCanvasWidth() - p->ui->get_width(p->ui)) / 2,
(UI_GetCanvasHeight() - p->ui->get_height(p->ui)) - 50);
p->is_ui_ready = true;
}
const int32_t choice = UI_Requester_GetSelectedRow(p->ui);
if (choice >= 0) {
p->is_ui_ready = false;
}
return choice;
}
static PHASE_CONTROL M_Start(PHASE *const phase)
{
M_PRIV *const p = phase->priv;
p->ui.is_ready = false;
UI2_Pause_Init(&p->ui.state);
M_PauseGame(p);
p->is_ui_ready = false;
return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE };
}
@ -151,10 +117,7 @@ static void M_End(PHASE *const phase)
{
M_PRIV *const p = phase->priv;
M_RemoveText(p);
if (p->ui != nullptr) {
p->ui->free(p->ui);
p->ui = nullptr;
}
UI2_Pause_Free(&p->ui.state);
}
static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames)
@ -164,8 +127,8 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames)
Input_Update();
Shell_ProcessInput();
if (p->ui != nullptr) {
p->ui->control(p->ui);
if (p->ui.is_ready) {
UI2_Pause_Control(&p->ui.state);
}
switch (p->state) {
@ -190,30 +153,23 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames)
break;
case STATE_ASK: {
const int32_t choice = M_DisplayRequester(
p, GS(PAUSE_EXIT_TO_TITLE), GS(PAUSE_CONTINUE), GS(PAUSE_QUIT));
if (choice == 0) {
const UI2_PAUSE_EXIT_CHOICE choice = UI2_Pause_Control(&p->ui.state);
switch (choice) {
case UI2_PAUSE_RESUME_PAUSE:
p->state = STATE_WAIT;
return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT };
case UI2_PAUSE_EXIT_TO_GAME:
M_ReturnToGame(p);
return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT };
} else if (choice == 1) {
p->state = STATE_CONFIRM;
case UI2_PAUSE_EXIT_TO_TITLE:
M_ExitToTitle(p);
return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT };
default:
break;
}
break;
}
case STATE_CONFIRM: {
const int32_t choice = M_DisplayRequester(
p, GS(PAUSE_ARE_YOU_SURE), GS(PAUSE_YES), GS(PAUSE_NO));
if (choice == 0) {
M_ExitToTitle(p);
return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT };
} else if (choice == 1) {
M_ReturnToGame(p);
return (PHASE_CONTROL) { .action = PHASE_ACTION_NO_WAIT };
}
break;
case STATE_FADE_OUT:
if (!Fader_IsActive(&p->back_fader)) {
return (PHASE_CONTROL) {
@ -223,7 +179,6 @@ static PHASE_CONTROL M_Control(PHASE *const phase, int32_t const num_frames)
}
break;
}
}
return (PHASE_CONTROL) { .action = PHASE_ACTION_CONTINUE };
}
@ -237,8 +192,8 @@ static void M_Draw(PHASE *const phase)
Interpolation_Enable();
Fader_Draw(&p->back_fader);
if (p->ui != nullptr) {
p->ui->draw(p->ui);
if (p->state == STATE_ASK) {
UI2_Pause(&p->ui.state);
}
Output_DrawPolyList();
}

View file

@ -0,0 +1,72 @@
#include "game/ui2/dialogs/pause.h"
#include "game/game_string.h"
#include "game/ui2/elements/anchor.h"
#include "game/ui2/elements/frame.h"
#include "game/ui2/elements/label.h"
#include "game/ui2/elements/modal.h"
#include "game/ui2/elements/pad.h"
#include "game/ui2/elements/requester.h"
static const GAME_STRING_ID m_Options[2][2] = {
{ GS_ID(PAUSE_CONTINUE), GS_ID(PAUSE_QUIT) },
{ GS_ID(PAUSE_YES), GS_ID(PAUSE_NO) },
};
void UI2_Pause_Init(UI2_PAUSE_STATE *const s)
{
s->phase = 0;
UI2_Requester_Init(&s->req, 2, 2, true);
}
void UI2_Pause_Free(UI2_PAUSE_STATE *const s)
{
UI2_Requester_Free(&s->req);
}
UI2_PAUSE_EXIT_CHOICE UI2_Pause_Control(UI2_PAUSE_STATE *const s)
{
const int32_t choice = UI2_Requester_Control(&s->req);
if (s->phase == 0) {
if (choice == UI2_REQUESTER_CANCEL) {
return UI2_PAUSE_RESUME_PAUSE;
} else if (choice == 0) {
return UI2_PAUSE_EXIT_TO_GAME;
} else if (choice == 1) {
s->phase = 1;
UI2_Requester_Free(&s->req);
UI2_Requester_Init(&s->req, 2, 2, true);
}
} else {
if (choice == UI2_REQUESTER_CANCEL) {
s->phase = 0;
} else if (choice == 0) {
return UI2_PAUSE_EXIT_TO_TITLE;
} else if (choice == 1) {
return UI2_PAUSE_EXIT_TO_GAME;
}
}
return UI2_PAUSE_NOOP;
}
void UI2_Pause(UI2_PAUSE_STATE *const s)
{
UI2_BeginModal(0.5f, 1.0f);
UI2_BeginPad(50.0f, 50.0f);
UI2_BeginRequester(
&s->req, 0.5f, 1.0f,
s->phase == 0 ? GS(PAUSE_EXIT_TO_TITLE) : GS(PAUSE_ARE_YOU_SURE));
for (int32_t i = UI2_Requester_GetFirstRow(&s->req);
i < UI2_Requester_GetLastRow(&s->req); i++) {
UI2_BeginRequesterRow(&s->req, i);
UI2_BeginAnchor(0.5f, 0.5f);
UI2_Label(GameString_Get(m_Options[s->phase][i]));
UI2_EndAnchor();
UI2_EndRequesterRow(&s->req, i);
}
UI2_EndRequester(&s->req);
UI2_EndPad();
UI2_EndModal();
}

View file

@ -0,0 +1,71 @@
#include "game/game_string.h"
#include "game/ui2/dialogs/pause.h"
#include "game/ui2/elements/anchor.h"
#include "game/ui2/elements/frame.h"
#include "game/ui2/elements/label.h"
#include "game/ui2/elements/modal.h"
#include "game/ui2/elements/pad.h"
#include "game/ui2/elements/requester.h"
static const GAME_STRING_ID m_Options[2][2] = {
{ GS_ID(PAUSE_CONTINUE), GS_ID(PAUSE_QUIT) },
{ GS_ID(PAUSE_YES), GS_ID(PAUSE_NO) },
};
void UI2_Pause_Init(UI2_PAUSE_STATE *const s)
{
s->phase = 0;
UI2_Requester_Init(&s->req, 2, 2, true);
}
void UI2_Pause_Free(UI2_PAUSE_STATE *const s)
{
UI2_Requester_Free(&s->req);
}
UI2_PAUSE_EXIT_CHOICE UI2_Pause_Control(UI2_PAUSE_STATE *const s)
{
const int32_t choice = UI2_Requester_Control(&s->req);
if (s->phase == 0) {
if (choice == UI2_REQUESTER_CANCEL) {
return UI2_PAUSE_RESUME_PAUSE;
} else if (choice == 0) {
return UI2_PAUSE_EXIT_TO_GAME;
} else if (choice == 1) {
s->phase = 1;
UI2_Requester_Free(&s->req);
UI2_Requester_Init(&s->req, 2, 2, true);
}
} else {
if (choice == UI2_REQUESTER_CANCEL) {
s->phase = 0;
} else if (choice == 0) {
return UI2_PAUSE_EXIT_TO_TITLE;
} else if (choice == 1) {
return UI2_PAUSE_EXIT_TO_GAME;
}
}
return UI2_PAUSE_NOOP;
}
void UI2_Pause(UI2_PAUSE_STATE *const s)
{
UI2_BeginModal(0.5f, 1.0f);
UI2_BeginPad(50.0f, 50.0f);
UI2_BeginRequester(
&s->req, 0.5f, 1.0f,
s->phase == 0 ? GS(PAUSE_EXIT_TO_TITLE) : GS(PAUSE_ARE_YOU_SURE));
for (int32_t i = UI2_Requester_GetFirstRow(&s->req);
i < UI2_Requester_GetLastRow(&s->req); i++) {
UI2_BeginRequesterRow(&s->req, i);
UI2_BeginAnchor(0.5f, 0.5f);
UI2_Label(GameString_Get(m_Options[s->phase][i]));
UI2_EndAnchor();
UI2_EndRequesterRow(&s->req, i);
}
UI2_EndRequester(&s->req);
UI2_EndPad();
UI2_EndModal();
}

View file

@ -3,6 +3,7 @@
#include "./ui2/common.h"
#include "./ui2/dialogs/examine_item.h"
#include "./ui2/dialogs/new_game.h"
#include "./ui2/dialogs/pause.h"
#include "./ui2/dialogs/photo_mode.h"
#include "./ui2/elements/anchor.h"
#include "./ui2/elements/flash.h"

View file

@ -0,0 +1,26 @@
#pragma once
// A pause exit confirmation dialog.
#include "../common.h"
#include "../elements/requester.h"
typedef struct {
int32_t phase;
UI2_REQUESTER_STATE req;
} UI2_PAUSE_STATE;
typedef enum {
UI2_PAUSE_NOOP,
UI2_PAUSE_RESUME_PAUSE,
UI2_PAUSE_EXIT_TO_GAME,
UI2_PAUSE_EXIT_TO_TITLE,
} UI2_PAUSE_EXIT_CHOICE;
// state functions
void UI2_Pause_Init(UI2_PAUSE_STATE *s);
UI2_PAUSE_EXIT_CHOICE UI2_Pause_Control(UI2_PAUSE_STATE *s);
void UI2_Pause_Free(UI2_PAUSE_STATE *s);
// draw functions
void UI2_Pause(UI2_PAUSE_STATE *s);

View file

@ -211,6 +211,7 @@ sources = [
'game/ui2/common.c',
'game/ui2/dialogs/examine_item.c',
'game/ui2/dialogs/new_game.c',
'game/ui2/dialogs/pause.c',
'game/ui2/dialogs/photo_mode.c',
'game/ui2/elements/anchor.c',
'game/ui2/elements/flash.c',