diff --git a/CMakeLists.txt b/CMakeLists.txt index c4a24fb49..27a47956f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,7 @@ set(devilutionx_SRCS SourceX/dvlnet/frame_queue.cpp SourceX/DiabloUI/art_draw.cpp SourceX/DiabloUI/art.cpp + SourceX/DiabloUI/button.cpp SourceX/DiabloUI/credits.cpp SourceX/DiabloUI/diabloui.cpp SourceX/DiabloUI/dialogs.cpp diff --git a/SourceX/DiabloUI/button.cpp b/SourceX/DiabloUI/button.cpp new file mode 100644 index 000000000..29d919e60 --- /dev/null +++ b/SourceX/DiabloUI/button.cpp @@ -0,0 +1,53 @@ +#include "DiabloUI/button.h" +#include "DiabloUI/art_draw.h" +#include "DiabloUI/text_draw.h" + +namespace dvl { + +Art SmlButton; + +void LoadSmlButtonArt() +{ + LoadArt("ui_art\\but_sml.pcx", &SmlButton, 15); +} + +void RenderButton(UiButton *button) +{ + int frame; + if (button->has_flag(UIS_DISABLED)) { + frame = button->frame_map[UiButton::DISABLED]; + } else if (button->pressed) { + frame = button->frame_map[UiButton::PRESSED]; + } else { + frame = button->frame_map[UiButton::DEFAULT]; + } + DrawArt(button->rect.x, button->rect.y, button->art, frame, button->rect.w, button->rect.h); + + SDL_Rect text_rect = button->rect; + if (!button->pressed) --text_rect.y; + DrawTTF(button->text, text_rect, UIS_CENTER, + SDL_Color{ 243, 243, 243, 0 }, SDL_Color{ 0, 0, 0, 0 }, &button->render_cache); +} + +bool HandleMouseEventButton(const SDL_Event &event, UiButton *button) +{ + if (event.button.button != SDL_BUTTON_LEFT) + return false; + switch (event.type) { + case SDL_MOUSEBUTTONUP: + button->action(); + return true; + case SDL_MOUSEBUTTONDOWN: + button->pressed = true; + return true; + default: + return false; + } +} + +void HandleGlobalMouseUpButton(UiButton *button) +{ + button->pressed = false; +} + +} // namespace dvl diff --git a/SourceX/DiabloUI/button.h b/SourceX/DiabloUI/button.h new file mode 100644 index 000000000..2969e51af --- /dev/null +++ b/SourceX/DiabloUI/button.h @@ -0,0 +1,52 @@ +#pragma once + +#include "DiabloUI/art.h" +#include "DiabloUI/ui_item.h" + +namespace dvl { + +extern Art SmlButton; +void LoadSmlButtonArt(); +inline void UnloadSmlButtonArt() +{ + SmlButton.Unload(); +} +constexpr decltype(SDL_Rect().w) SML_BUTTON_WIDTH = 110; +constexpr decltype(SDL_Rect().h) SML_BUTTON_HEIGHT = 27; + +enum class ButtonFrame { + BG_GOLD = 0, + BG_GOLD_PRESSED, + BG_YELLOW, + BG_YELLOW_PRESSED, + DISABLED, +}; + +constexpr UiButton::FrameMap ButtonBgYellowFrameMap = { + static_cast(ButtonFrame::BG_YELLOW), + static_cast(ButtonFrame::BG_YELLOW_PRESSED), + static_cast(ButtonFrame::DISABLED), +}; +constexpr UiButton::FrameMap ButtonBgGoldFrameMap = { + static_cast(ButtonFrame::BG_GOLD), + static_cast(ButtonFrame::BG_GOLD_PRESSED), + static_cast(ButtonFrame::DISABLED), +}; + +constexpr UiButton MakeSmlButton( + const char *text, void (*action)(), decltype(SDL_Rect().x) x, decltype(SDL_Rect().y) y, int flags = 0) +{ + return UiButton( + &SmlButton, + (flags & UIS_GOLD) ? ButtonBgGoldFrameMap : ButtonBgYellowFrameMap, + text, + action, + SDL_Rect{ x, y, SML_BUTTON_WIDTH, SML_BUTTON_HEIGHT }, + flags); +} + +void RenderButton(UiButton *button); +bool HandleMouseEventButton(const SDL_Event &event, UiButton *button); +void HandleGlobalMouseUpButton(UiButton *button); + +} // namespace dvl diff --git a/SourceX/DiabloUI/diabloui.cpp b/SourceX/DiabloUI/diabloui.cpp index 1c67e3b98..6a0382040 100644 --- a/SourceX/DiabloUI/diabloui.cpp +++ b/SourceX/DiabloUI/diabloui.cpp @@ -11,6 +11,7 @@ #include "DiabloUI/art_draw.h" #include "DiabloUI/text_draw.h" #include "DiabloUI/fonts.h" +#include "DiabloUI/button.h" namespace dvl { @@ -379,10 +380,7 @@ bool UiFocusNavigation(SDL_Event *event) } } - const bool mouse_handled = gUiItems && gUiItemCnt && UiItemMouseEvents(event, gUiItems, gUiItemCnt); - if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_LEFT) - scrollBarState.downArrowPressed = scrollBarState.upArrowPressed = false; - if (mouse_handled) + if (UiItemMouseEvents(event, gUiItems, gUiItemCnt)) return true; if (gfnListEsc && event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_ESCAPE) { @@ -634,23 +632,37 @@ void DrawSelector(const SDL_Rect &rect) DrawArt(rect.x + rect.w - art->w(), y, art, frame); } -void UiRender() -{ +void UiPollAndRender() { SDL_Event event; while (SDL_PollEvent(&event)) { UiFocusNavigation(&event); } - UiRenderItems(gUiItems, gUiItemCnt); + UiRender(gUiItems, gUiItemCnt); + UiFadeIn(); +} + +void UiRender(UiItem *items, std::size_t size) +{ + UiRenderItems(items, size); DrawLogo(); DrawMouse(); - UiFadeIn(); } namespace { -void Render(const UiText &ui_text) +void Render(UiText *ui_text) { - DrawArtStr(ui_text.text, ui_text.rect, ui_text.flags); + DrawTTF(ui_text->text, + ui_text->rect, + ui_text->flags, + ui_text->color, + ui_text->shadow_color, + &ui_text->render_cache); +} + +void Render(const UiArtText &ui_art_text) +{ + DrawArtStr(ui_art_text.text, ui_art_text.rect, ui_art_text.flags); } void Render(const UiImage &ui_image) @@ -716,28 +728,34 @@ void Render(const UiEdit &ui_edit) DrawArtStr(ui_edit.value, rect, ui_edit.flags, /*drawTextCursor=*/true); } -void RenderItem(const UiItem &item) +void RenderItem(UiItem *item) { - if (item.has_flag(UIS_HIDDEN)) + if (item->has_flag(UIS_HIDDEN)) return; - switch (item.type) { + switch (item->type) { case UI_TEXT: - Render(item.text); + Render(&item->text); + break; + case UI_ART_TEXT: + Render(item->art_text); break; case UI_IMAGE: - Render(item.image); + Render(item->image); break; case UI_ART_TEXT_BUTTON: - Render(item.art_text_button); + Render(item->art_text_button); + break; + case UI_BUTTON: + RenderButton(&item->button); break; case UI_LIST: - Render(item.list); + Render(item->list); break; case UI_SCROLLBAR: - Render(item.scrollbar); + Render(item->scrollbar); break; case UI_EDIT: - Render(item.edit); + Render(item->edit); break; } } @@ -801,17 +819,19 @@ bool HandleMouseEventScrollBar(const SDL_Event &event, const UiScrollBar &ui_sb) return false; } -bool HandleMouseEvent(const SDL_Event &event, const UiItem &item) +bool HandleMouseEvent(const SDL_Event &event, UiItem *item) { - if (item.has_flag(UIS_HIDDEN) || !IsInsideRect(event, item.rect())) + if (item->has_any_flag(UIS_HIDDEN | UIS_DISABLED) || !IsInsideRect(event, item->rect())) return false; - switch (item.type) { + switch (item->type) { case UI_ART_TEXT_BUTTON: - return HandleMouseEventArtTextButton(event, item.art_text_button); + return HandleMouseEventArtTextButton(event, item->art_text_button); + case UI_BUTTON: + return HandleMouseEventButton(event, &item->button); case UI_LIST: - return HandleMouseEventList(event, item.list); + return HandleMouseEventList(event, item->list); case UI_SCROLLBAR: - return HandleMouseEventScrollBar(event, item.scrollbar); + return HandleMouseEventScrollBar(event, item->scrollbar); default: return false; } @@ -819,20 +839,34 @@ bool HandleMouseEvent(const SDL_Event &event, const UiItem &item) } // namespace -void UiRenderItems(UiItem *items, int size) +void UiRenderItems(UiItem *items, std::size_t size) { - for (int i = 0; i < size; i++) - RenderItem(items[i]); + for (std::size_t i = 0; i < size; i++) + RenderItem(&items[i]); } -bool UiItemMouseEvents(SDL_Event *event, UiItem *items, int size) +bool UiItemMouseEvents(SDL_Event *event, UiItem *items, std::size_t size) { - for (int i = 0; i < size; i++) { - if (HandleMouseEvent(*event, items[i])) - return true; + if (!items || size == 0) return false; + + bool handled = false; + for (std::size_t i = 0; i < size; i++) { + if (HandleMouseEvent(*event, &items[i])) { + handled = true; + break; + } } - return false; + if (event->type == SDL_MOUSEBUTTONUP && event->button.button == SDL_BUTTON_LEFT) { + scrollBarState.downArrowPressed = scrollBarState.upArrowPressed = false; + for (std::size_t i = 0; i < size; ++i) { + UiItem &item = items[i]; + if (item.type == UI_BUTTON) + HandleGlobalMouseUpButton(&item.button); + } + } + + return handled; } void DrawLogo(int t, int size) diff --git a/SourceX/DiabloUI/diabloui.h b/SourceX/DiabloUI/diabloui.h index 83c530adb..0b7efa49c 100644 --- a/SourceX/DiabloUI/diabloui.h +++ b/SourceX/DiabloUI/diabloui.h @@ -38,7 +38,7 @@ extern void (*gfnSoundFunction)(char *file); bool IsInsideRect(const SDL_Event &event, const SDL_Rect &rect); void UiFadeIn(int steps = 16); bool UiFocusNavigation(SDL_Event *event); -bool UiItemMouseEvents(SDL_Event *event, UiItem *items, int size); +bool UiItemMouseEvents(SDL_Event *event, UiItem *items, std::size_t size); int GetCenterOffset(int w, int bw = 0); void DrawLogo(int t = 0, int size = LOGO_MED); void DrawMouse(); @@ -49,8 +49,9 @@ void UiFocusNavigationEsc(); void UiFocusNavigationYesNo(); void UiInitList(int min, int max, void (*fnFocus)(int value), void (*fnSelect)(int value), void (*fnEsc)(), UiItem *items, int size, bool wraps = false, bool (*fnYesNo)() = NULL); void UiInitScrollBar(UiScrollBar *ui_sb, std::size_t viewport_size, const std::size_t *current_offset); -void UiRender(); -void UiRenderItems(UiItem *items, int size); +void UiPollAndRender(); +void UiRender(UiItem *items, std::size_t size); +void UiRenderItems(UiItem *items, std::size_t size); void DvlIntSetting(const char *valuename, int *value); void DvlStringSetting(const char *valuename, char *string, int len); diff --git a/SourceX/DiabloUI/dialogs.cpp b/SourceX/DiabloUI/dialogs.cpp index 1958dbd80..3dfc15bfd 100644 --- a/SourceX/DiabloUI/dialogs.cpp +++ b/SourceX/DiabloUI/dialogs.cpp @@ -1,46 +1,79 @@ +#include "DiabloUI/dialogs.h" + #include "devilution.h" #include "DiabloUI/diabloui.h" +#include "DiabloUI/button.h" +#include "DiabloUI/fonts.h" namespace dvl { -Art dialogArt; // "ui_art\\spopup.pcx" -char dialogMessage[256]; +extern SDL_Surface *pal_surface; + +namespace { -Art progressArt; // "ui_art\\prog_bg.pcx" +Art dialogArt; +Art progressArt; +char dialogText[256]; +char dialogCaption[256]; +bool fontWasLoaded; +bool textInputWasActive; + +UiItem *dialogItems; +std::size_t dialogItemsSize; + +enum class State { + DEFAULT = 0, + OK, + CANCEL, +}; + +State state; void DialogActionOK() { + state = State::OK; } void DialogActionCancel() { + state = State::CANCEL; } +constexpr auto DIALOG_ART_S = UiImage(&dialogArt, { 180, 168, 280, 144 }); +constexpr auto DIALOG_ART_L = UiImage(&dialogArt, { 128, 168, 385, 280 }); + UiItem OKCANCEL_DIALOG[] = { - UiImage(&dialogArt, { 180, 168, 280, 144 }), - UiText(dialogMessage, { 200, 180, 240, 80 }, UIS_CENTER), - UiArtTextButton("OK", &DialogActionOK, { 200, 265, 110, 28 }, UIS_SML1), - UiArtTextButton("Cancel", &DialogActionCancel, { 330, 265, 110, 28 }, UIS_SML2), + DIALOG_ART_S, + UiText(dialogText, { 200, 200, 240, 80 }, UIS_CENTER), + MakeSmlButton("OK", &DialogActionOK, 200, 265), + MakeSmlButton("Cancel", &DialogActionCancel, 330, 265), }; UiItem OK_DIALOG[] = { - UiImage(&dialogArt, { 180, 168, 280, 144 }), - UiText(dialogMessage, { 200, 180, 240, 80 }, UIS_CENTER), - UiArtTextButton("OK", &DialogActionOK, { 200, 265, 110, 28 }, UIS_SML1), + DIALOG_ART_S, + UiText(dialogText, { 200, 200, 240, 80 }, UIS_CENTER), + MakeSmlButton("OK", &DialogActionOK, 266, 265), +}; + +UiItem OK_DIALOG_WITH_CAPTION[] = { + DIALOG_ART_L, + UiText(dialogText, { 200, 200, 240, 80 }, UIS_CENTER), + UiText(dialogCaption, { 200, 280, 240, 80 }, UIS_CENTER), + MakeSmlButton("OK", &DialogActionOK, 266, 401), }; UiItem PROGRESS_DIALOG[] = { - UiImage(&dialogArt, { 180, 168, 280, 144 }), - UiText(dialogMessage, { 180, 177, 280, 43 }, UIS_CENTER), + DIALOG_ART_S, + UiText(dialogText, { 180, 177, 280, 43 }, UIS_CENTER), UiImage(&progressArt, { 205, 220, 228, 38 }), - UiArtTextButton("Cancel", &DialogActionCancel, { 265, 267, 110, 28 }, UIS_SML1), + MakeSmlButton("Cancel", &DialogActionCancel, 330, 265), }; UiListItem SELOK_DIALOG_ITEMS[] = { { "OK", 0 } }; UiItem SELOK_DIALOG[] = { - UiText(dialogMessage, { 140, 210, 400, 168 }, UIS_CENTER), + UiText(dialogText, { 140, 210, 400, 168 }, UIS_CENTER), UiList(SELOK_DIALOG_ITEMS, 230, 390, 180, 35, UIS_CENTER), }; @@ -49,4 +82,88 @@ UiItem SPAWNERR_DIALOG[] = { UiArtTextButton("OK", &DialogActionOK, { 230, 407, 180, 43 }), }; +void Init(const char *text, const char *caption, bool error) +{ + strcpy(dialogText, text); + if (caption == nullptr) { + LoadMaskedArt(error ? "ui_art\\srpopup.pcx" : "ui_art\\spopup.pcx", &dialogArt); + dialogItems = OK_DIALOG; + dialogItemsSize = size(OK_DIALOG); + } else { + LoadMaskedArt(error ? "ui_art\\lrpopup.pcx" : "ui_art\\lpopup.pcx", &dialogArt); + strcpy(dialogCaption, caption); + dialogItems = OK_DIALOG_WITH_CAPTION; + dialogItemsSize = size(OK_DIALOG_WITH_CAPTION); + } + LoadSmlButtonArt(); + + fontWasLoaded = font != nullptr; + if (!fontWasLoaded) + LoadTtfFont(); + textInputWasActive = SDL_IsTextInputActive(); + SDL_StopTextInput(); } + +void Deinit() +{ + dialogArt.Unload(); + UnloadSmlButtonArt(); + if (!fontWasLoaded) + UnloadTtfFont(); + if (textInputWasActive) + SDL_StartTextInput(); + for (std::size_t i = 0; i < dialogItemsSize; ++i) { + dialogItems[i].FreeCache(); + } +} + +void DialogLoop(UiItem *items, std::size_t num_items, UiItem *render_behind, std::size_t render_behind_size) +{ + SDL_Event event; + state = State::DEFAULT; + if (render_behind_size == 0) + SDL_FillRect(pal_surface, nullptr, 0); + do { + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + case SDLK_RETURN: + case SDLK_KP_ENTER: + case SDLK_SPACE: + state = State::OK; + break; + default: + break; + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + UiItemMouseEvents(&event, items, num_items); + break; + case SDL_QUIT: + exit(0); + } + } + UiRenderItems(render_behind, render_behind_size); + UiRender(items, num_items); + UiFadeIn(); + } while (state == State::DEFAULT); +} + +} // namespace + +void UiErrorOkDialog(const char *text, const char *caption, UiItem *render_behind, std::size_t render_behind_size) +{ + Init(text, caption, /*error=*/true); + DialogLoop(dialogItems, dialogItemsSize, render_behind, render_behind_size); + Deinit(); +} + +void UiErrorOkDialog(const char *text, UiItem *render_behind, std::size_t render_behind_size) +{ + UiErrorOkDialog(text, nullptr, render_behind, render_behind_size); +} + +} // namespace dvl diff --git a/SourceX/DiabloUI/dialogs.h b/SourceX/DiabloUI/dialogs.h new file mode 100644 index 000000000..ca1bfd7e6 --- /dev/null +++ b/SourceX/DiabloUI/dialogs.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "DiabloUI/ui_item.h" + +namespace dvl { + +void UiErrorOkDialog(const char *text, UiItem *renderBehind, std::size_t renderBehindSize); +void UiErrorOkDialog(const char *text, const char *caption, UiItem *render_behind, std::size_t render_behind_size); + +} // namespace dvl diff --git a/SourceX/DiabloUI/mainmenu.cpp b/SourceX/DiabloUI/mainmenu.cpp index 16d8228d7..9bd84c932 100644 --- a/SourceX/DiabloUI/mainmenu.cpp +++ b/SourceX/DiabloUI/mainmenu.cpp @@ -17,7 +17,7 @@ UiListItem MAINMENU_DIALOG_ITEMS[] = { UiItem MAINMENU_DIALOG[] = { UiImage(&ArtBackground, { 0, 0, 640, 480 }), UiList(MAINMENU_DIALOG_ITEMS, 64, 192, 510, 43, UIS_HUGE | UIS_GOLD | UIS_CENTER), - UiText(nullptr, { 17, 444, 605, 21 }, UIS_SMALL) + UiArtText(nullptr, { 17, 444, 605, 21 }, UIS_SMALL) }; void UiMainMenuSelect(int value) @@ -38,7 +38,7 @@ void mainmenu_restart_repintro() void mainmenu_Load(char *name, void (*fnSound)(char *file)) { gfnSoundFunction = fnSound; - MAINMENU_DIALOG[size(MAINMENU_DIALOG) - 1].text.text = name; + MAINMENU_DIALOG[size(MAINMENU_DIALOG) - 1].art_text.text = name; MainMenuResult = 0; @@ -63,7 +63,7 @@ BOOL UiMainMenuDialog(char *name, int *pdwResult, void (*fnSound)(char *file), i mainmenu_restart_repintro(); // for automatic starts while (MainMenuResult == 0) { - UiRender(); + UiPollAndRender(); if (GetTickCount() >= dwAttractTicks) { MainMenuResult = MAINMENU_ATTRACT_MODE; } diff --git a/SourceX/DiabloUI/progress.cpp b/SourceX/DiabloUI/progress.cpp index 5b7a25172..798bd04d7 100644 --- a/SourceX/DiabloUI/progress.cpp +++ b/SourceX/DiabloUI/progress.cpp @@ -1,6 +1,7 @@ #include "devilution.h" #include "miniwin/ddraw.h" +#include "DiabloUI/button.h" #include "DiabloUI/diabloui.h" #include "DiabloUI/art_draw.h" #include "DiabloUI/fonts.h" @@ -10,7 +11,6 @@ namespace dvl { Art ArtPopupSm; Art ArtProgBG; Art ProgFil; -Art ButImage; SDL_Surface *msgSurface; SDL_Surface *cancleSurface; int textWidth; @@ -21,7 +21,7 @@ void progress_Load(char *msg) LoadArt("ui_art\\spopup.pcx", &ArtPopupSm); LoadArt("ui_art\\prog_bg.pcx", &ArtProgBG); LoadArt("ui_art\\prog_fil.pcx", &ProgFil); - LoadArt("ui_art\\but_sml.pcx", &ButImage, 15); + LoadSmlButtonArt(); LoadTtfFont(); if (font != NULL) { @@ -39,7 +39,7 @@ void progress_Free() ArtPopupSm.Unload(); ArtProgBG.Unload(); ProgFil.Unload(); - ButImage.Unload(); + UnloadSmlButtonArt(); SDL_FreeSurface(msgSurface); msgSurface = NULL; SDL_FreeSurface(cancleSurface); @@ -59,7 +59,7 @@ void progress_Render(BYTE progress) if (progress) { DrawArt(GetCenterOffset(227), y + 52, &ProgFil, 0, 227 * progress / 100); } - DrawArt(GetCenterOffset(110), y + 99, &ButImage, 2, 110); + DrawArt(GetCenterOffset(110), y + 99, &SmlButton, 2, 110); if (msgSurface) { SDL_Rect dsc_rect = { diff --git a/SourceX/DiabloUI/selconn.cpp b/SourceX/DiabloUI/selconn.cpp index 8f8f6e1fb..0abe0cd87 100644 --- a/SourceX/DiabloUI/selconn.cpp +++ b/SourceX/DiabloUI/selconn.cpp @@ -18,7 +18,7 @@ _SNETVERSIONDATA *selconn_FileInfo; DWORD provider; -UiText SELCONNECT_DIALOG_DESCRIPTION(selconn_Description, { 35, 275, 205, 66 }); +UiArtText SELCONNECT_DIALOG_DESCRIPTION(selconn_Description, { 35, 275, 205, 66 }); UiListItem SELCONN_DIALOG_ITEMS[] = { #ifndef NONET { "Client-Server (TCP)", 0 }, @@ -30,13 +30,13 @@ UiListItem SELCONN_DIALOG_ITEMS[] = { }; UiItem SELCONNECT_DIALOG[] = { UiImage(&ArtBackground, { 0, 0, 640, 480 }), - UiText("Multi Player Game", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), - UiText(selconn_MaxPlayers, { 35, 218, 205, 21 }), - UiText("Requirements:", { 35, 256, 205, 21 }), + UiArtText("Multi Player Game", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), + UiArtText(selconn_MaxPlayers, { 35, 218, 205, 21 }), + UiArtText("Requirements:", { 35, 256, 205, 21 }), SELCONNECT_DIALOG_DESCRIPTION, - UiText("no gateway needed", { 30, 356, 220, 31 }, UIS_CENTER | UIS_MED), - UiText(selconn_Gateway, { 35, 393, 205, 21 }, UIS_CENTER), - UiText("Select Connection", { 300, 211, 295, 33 }, UIS_CENTER | UIS_BIG), + UiArtText("no gateway needed", { 30, 356, 220, 31 }, UIS_CENTER | UIS_MED), + UiArtText(selconn_Gateway, { 35, 393, 205, 21 }, UIS_CENTER), + UiArtText("Select Connection", { 300, 211, 295, 33 }, UIS_CENTER | UIS_BIG), UiArtTextButton("Change Gateway", nullptr, { 16, 427, 250, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD | UIS_HIDDEN), UiList(SELCONN_DIALOG_ITEMS, 305, 256, 285, 26, UIS_CENTER | UIS_VCENTER | UIS_GOLD), UiArtTextButton("OK", &UiFocusNavigationSelect, { 299, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD), @@ -79,7 +79,7 @@ void selconn_Focus(int value) } sprintf(selconn_MaxPlayers, "Players Supported: %d", players); - WordWrap(selconn_Description, SELCONNECT_DIALOG_DESCRIPTION.rect.w); + WordWrapArtStr(selconn_Description, SELCONNECT_DIALOG_DESCRIPTION.rect.w); } void selconn_Select(int value) @@ -118,7 +118,7 @@ int UiSelectProvider( selconn_ReturnValue = true; selconn_EndMenu = false; while (!selconn_EndMenu) { - UiRender(); + UiPollAndRender(); } BlackPalette(); selconn_Free(); diff --git a/SourceX/DiabloUI/selgame.cpp b/SourceX/DiabloUI/selgame.cpp index 42becde0f..40dbbfa15 100644 --- a/SourceX/DiabloUI/selgame.cpp +++ b/SourceX/DiabloUI/selgame.cpp @@ -1,8 +1,10 @@ #include "selgame.h" #include "devilution.h" +#include "config.h" #include "DiabloUI/diabloui.h" #include "DiabloUI/text.h" +#include "DiabloUI/dialogs.h" namespace dvl { @@ -23,7 +25,7 @@ constexpr UiImage SELGAME_BACKGROUND = UiImage(&ArtBackground, { 0, 0, 640, 480 constexpr UiArtTextButton SELGAME_OK = UiArtTextButton("OK", &UiFocusNavigationSelect, { 299, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD); constexpr UiArtTextButton SELGAME_CANCEL = UiArtTextButton("CANCEL", &UiFocusNavigationEsc, { 449, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD); -UiText SELGAME_DESCRIPTION(selgame_Description, { 35, 256, 205, 192 }); +UiArtText SELGAME_DESCRIPTION(selgame_Description, { 35, 256, 205, 192 }); UiListItem SELDIFF_DIALOG_ITEMS[] = { { "Normal", DIFF_NORMAL }, @@ -32,17 +34,17 @@ UiListItem SELDIFF_DIALOG_ITEMS[] = { }; UiItem SELDIFF_DIALOG[] = { SELGAME_BACKGROUND, - UiText("Create Game", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), - UiText(selgame_Label, { 34, 211, 205, 33 }, UIS_CENTER | UIS_BIG), // DIFF + UiArtText("Create Game", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), + UiArtText(selgame_Label, { 34, 211, 205, 33 }, UIS_CENTER | UIS_BIG), // DIFF SELGAME_DESCRIPTION, - UiText("Select Difficulty", { 299, 211, 295, 35 }, UIS_CENTER | UIS_BIG), + UiArtText("Select Difficulty", { 299, 211, 295, 35 }, UIS_CENTER | UIS_BIG), UiList(SELDIFF_DIALOG_ITEMS, 300, 282, 295, 26, UIS_CENTER | UIS_MED | UIS_GOLD), SELGAME_OK, SELGAME_CANCEL, }; -constexpr UiText SELUDPGAME_TITLE = UiText("Join TCP/UDP Games", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG); -constexpr UiText SELUDPGAME_DESCRIPTION_LABEL = UiText("Description:", { 35, 211, 205, 192 }, UIS_MED); +constexpr UiArtText SELUDPGAME_TITLE = UiArtText("Join TCP/UDP Games", { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG); +constexpr UiArtText SELUDPGAME_DESCRIPTION_LABEL = UiArtText("Description:", { 35, 211, 205, 192 }, UIS_MED); UiListItem SELUDPGAME_DIALOG_ITEMS[] = { { "Create Game", 0 }, @@ -53,7 +55,7 @@ UiItem SELUDPGAME_DIALOG[] = { SELUDPGAME_TITLE, SELUDPGAME_DESCRIPTION_LABEL, SELGAME_DESCRIPTION, - UiText("Select Action", { 300, 211, 295, 33 }, UIS_CENTER | UIS_BIG), + UiArtText("Select Action", { 300, 211, 295, 33 }, UIS_CENTER | UIS_BIG), UiList(SELUDPGAME_DIALOG_ITEMS, 305, 255, 285, 26, UIS_CENTER | UIS_MED | UIS_GOLD), SELGAME_OK, SELGAME_CANCEL, @@ -64,8 +66,8 @@ UiItem ENTERIP_DIALOG[] = { SELUDPGAME_TITLE, SELUDPGAME_DESCRIPTION_LABEL, SELGAME_DESCRIPTION, - UiText("Enter IP", { 305, 211, 285, 33 }, UIS_CENTER | UIS_BIG), - UiEdit(selgame_Ip, 128, { 305, 314, 285, 33 }, UIS_LIST | UIS_MED | UIS_GOLD), + UiArtText("Enter IP", { 305, 211, 285, 33 }, UIS_CENTER | UIS_BIG), + UiEdit(selgame_Ip, 128, { 305, 314, 285, 33 }, UIS_MED | UIS_GOLD), SELGAME_OK, SELGAME_CANCEL, }; @@ -75,8 +77,8 @@ UiItem ENTERPASSWORD_DIALOG[] = { SELUDPGAME_TITLE, SELUDPGAME_DESCRIPTION_LABEL, SELGAME_DESCRIPTION, - UiText("Enter Password", { 305, 211, 285, 33 }, UIS_CENTER | UIS_BIG), - UiEdit(selgame_Password, 15, { 305, 314, 285, 33 }, UIS_LIST | UIS_MED | UIS_GOLD), + UiArtText("Enter Password", { 305, 211, 285, 33 }, UIS_CENTER | UIS_BIG), + UiEdit(selgame_Password, 15, { 305, 314, 285, 33 }, UIS_MED | UIS_GOLD), SELGAME_OK, SELGAME_CANCEL, }; @@ -111,7 +113,7 @@ void selgame_GameSelection_Focus(int value) strcpy(selgame_Description, "Enter an IP and join a game already in progress at that address."); break; } - WordWrap(selgame_Description, SELGAME_DESCRIPTION.rect.w); + WordWrapArtStr(selgame_Description, SELGAME_DESCRIPTION.rect.w); } void selgame_GameSelection_Select(int value) @@ -152,7 +154,7 @@ void selgame_Diff_Focus(int value) strcpy(selgame_Description, "Hell Difficulty\nThe most powerful of the underworld's creatures lurk at the gateway into Hell. Only the most experienced characters should venture in this realm."); break; } - WordWrap(selgame_Description, SELGAME_DESCRIPTION.rect.w); + WordWrapArtStr(selgame_Description, SELGAME_DESCRIPTION.rect.w); } void selgame_Diff_Select(int value) @@ -185,13 +187,16 @@ void selgame_Password_Init(int value) void selgame_Password_Select(int value) { - UiInitList(0, 0, NULL, NULL, NULL, NULL, 0); - selgame_endMenu = true; - if (selgame_selectedGame) { SRegSaveString("Phone Book", "Entry1", 0, selgame_Ip); - if (!SNetJoinGame(selgame_selectedGame, selgame_Ip, selgame_Password, NULL, NULL, gdwPlayerId)) { - DrawDlg("Unable to establish a connection. A game of Devilution 0.2.0 was not detected at the specified IP address."); + if (SNetJoinGame(selgame_selectedGame, selgame_Ip, selgame_Password, NULL, NULL, gdwPlayerId)) { + UiInitList(0, 0, NULL, NULL, NULL, NULL, 0); + selgame_endMenu = true; + } else { + UiErrorOkDialog( + "Unable to establish a connection.", + PROJECT_NAME " v" PROJECT_VERSION " game not found or password invalid.", + ENTERPASSWORD_DIALOG, size(ENTERPASSWORD_DIALOG)); selgame_Password_Init(selgame_selectedGame); } return; @@ -200,8 +205,11 @@ void selgame_Password_Select(int value) _gamedata *info = m_client_info->initdata; info->bDiff = gbDifficulty; - if (!SNetCreateGame(NULL, selgame_Password, NULL, 0, (char *)info, sizeof(_gamedata), MAX_PLRS, NULL, NULL, gdwPlayerId)) { - DrawDlg("Unable to create game."); + if (SNetCreateGame(NULL, selgame_Password, NULL, 0, (char *)info, sizeof(_gamedata), MAX_PLRS, NULL, NULL, gdwPlayerId)) { + UiInitList(0, 0, NULL, NULL, NULL, NULL, 0); + selgame_endMenu = true; + } else { + UiErrorOkDialog("Unable to create game.", ENTERPASSWORD_DIALOG, size(ENTERPASSWORD_DIALOG)); selgame_Password_Init(0); } } @@ -221,7 +229,7 @@ int UiSelectGame(int a1, _SNETPROGRAMDATA *client_info, _SNETPLAYERDATA *user_in selgame_endMenu = false; while (!selgame_endMenu) { - UiRender(); + UiPollAndRender(); } BlackPalette(); selgame_Free(); diff --git a/SourceX/DiabloUI/selhero.cpp b/SourceX/DiabloUI/selhero.cpp index 1cb8ab1f0..7d050faa3 100644 --- a/SourceX/DiabloUI/selhero.cpp +++ b/SourceX/DiabloUI/selhero.cpp @@ -5,6 +5,7 @@ #include "scrollbar.h" #include "selyesno.h" #include "DiabloUI/diabloui.h" +#include "DiabloUI/dialogs.h" #include "devilution.h" namespace dvl { @@ -35,24 +36,24 @@ namespace { UiItem SELHERO_DIALOG[] = { UiImage(&ArtBackground, { 0, 0, 640, 480 }), - UiText(title, { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), + UiArtText(title, { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), UiImage(&ArtHero, UI_NUM_CLASSES, { 30, 211, 180, 76 }), - UiText("Level:", { 39, 323, 110, 21 }, UIS_RIGHT), - UiText(textStats[0], { 159, 323, 40, 21 }, UIS_CENTER), - UiText("Strength:", { 39, 358, 110, 21 }, UIS_RIGHT), - UiText(textStats[1], { 159, 358, 40, 21 }, UIS_CENTER), - UiText("Magic:", { 39, 380, 110, 21 }, UIS_RIGHT), - UiText(textStats[2], { 159, 380, 40, 21 }, UIS_CENTER), - UiText("Dexterity:", { 39, 401, 110, 21 }, UIS_RIGHT), - UiText(textStats[3], { 159, 401, 40, 21 }, UIS_CENTER), - UiText("Vitality:", { 39, 422, 110, 21 }, UIS_RIGHT), - UiText(textStats[4], { 159, 422, 40, 21 }, UIS_CENTER), + UiArtText("Level:", { 39, 323, 110, 21 }, UIS_RIGHT), + UiArtText(textStats[0], { 159, 323, 40, 21 }, UIS_CENTER), + UiArtText("Strength:", { 39, 358, 110, 21 }, UIS_RIGHT), + UiArtText(textStats[1], { 159, 358, 40, 21 }, UIS_CENTER), + UiArtText("Magic:", { 39, 380, 110, 21 }, UIS_RIGHT), + UiArtText(textStats[2], { 159, 380, 40, 21 }, UIS_CENTER), + UiArtText("Dexterity:", { 39, 401, 110, 21 }, UIS_RIGHT), + UiArtText(textStats[3], { 159, 401, 40, 21 }, UIS_CENTER), + UiArtText("Vitality:", { 39, 422, 110, 21 }, UIS_RIGHT), + UiArtText(textStats[4], { 159, 422, 40, 21 }, UIS_CENTER), }; UiImage *SELHERO_DIALOG_HERO_IMG = &SELHERO_DIALOG[2].image; UiListItem SELLIST_DIALOG_ITEMS[kMaxViewportItems]; UiItem SELLIST_DIALOG[] = { - UiText("Select Hero", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), + UiArtText("Select Hero", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), UiList(SELLIST_DIALOG_ITEMS, 265, 256, 320, 26, UIS_CENTER | UIS_MED | UIS_GOLD), MakeScrollBar({585, 244, 25, 178}), UiArtTextButton("OK", &UiFocusNavigationSelect, { 239, 429, 120, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD), @@ -69,15 +70,15 @@ UiListItem SELCLAS_DIALOG_ITEMS[] = { { "Sorcerer", UI_SORCERER } }; UiItem SELCLASS_DIALOG[] = { - UiText("Choose Class", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), + UiArtText("Choose Class", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), UiList(SELCLAS_DIALOG_ITEMS, 264, 285, 320, 33, UIS_CENTER | UIS_MED | UIS_GOLD), UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD), UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD) }; UiItem ENTERNAME_DIALOG[] = { - UiText("Enter Name", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), - UiEdit(selhero_heroInfo.name, 15, { 265, 317, 320, 33 }, UIS_LIST | UIS_MED | UIS_GOLD), + UiArtText("Enter Name", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), + UiEdit(selhero_heroInfo.name, 15, { 265, 317, 320, 33 }, UIS_MED | UIS_GOLD), UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD), UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 429, 140, 35 }, UIS_CENTER | UIS_BIG | UIS_GOLD) }; @@ -87,7 +88,7 @@ UiListItem SELLOAD_DIALOG_ITEMS[] = { { "New Game", 1 } }; UiItem SELLOAD_DIALOG[] = { - UiText("Save File Exists", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), + UiArtText("Save File Exists", { 264, 211, 320, 33 }, UIS_CENTER | UIS_BIG), UiList(SELLOAD_DIALOG_ITEMS, 265, 285, 320, 33, UIS_CENTER | UIS_MED | UIS_GOLD), UiArtTextButton("OK", &UiFocusNavigationSelect, { 279, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD), UiArtTextButton("Cancel", &UiFocusNavigationEsc, { 429, 427, 140, 35 }, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD) @@ -254,9 +255,14 @@ void selhero_ClassSelector_Esc() void selhero_Name_Select(int value) { - UiInitList(0, 0, NULL, NULL, NULL, NULL, 0); - gfnHeroCreate(&selhero_heroInfo); - selhero_endMenu = true; + if (gfnHeroCreate(&selhero_heroInfo)) { + UiInitList(0, 0, NULL, NULL, NULL, NULL, 0); + selhero_endMenu = true; + } else { + UiErrorOkDialog("Unable to create character.", SELHERO_DIALOG, size(SELHERO_DIALOG)); + memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name)); + selhero_ClassSelector_Select(selhero_heroInfo.heroclass); + } } void selhero_Name_Esc() @@ -319,7 +325,7 @@ BOOL UiSelHeroDialog( selhero_endMenu = false; while (!selhero_endMenu && !selhero_navigateYesNo) { UiRenderItems(SELHERO_DIALOG, size(SELHERO_DIALOG)); - UiRender(); + UiPollAndRender(); } BlackPalette(); selhero_Free(); diff --git a/SourceX/DiabloUI/selyesno.cpp b/SourceX/DiabloUI/selyesno.cpp index 01b33e82d..c1ab230a8 100644 --- a/SourceX/DiabloUI/selyesno.cpp +++ b/SourceX/DiabloUI/selyesno.cpp @@ -20,11 +20,11 @@ UiListItem SELYESNO_DIALOG_ITEMS[] = { UiItem SELYESNO_DIALOG[] = { UiImage(&ArtBackground, { 0, 0, 640, 480 }), - UiText(selyesno_title, { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), - UiText(selyesno_confirmationMessage, { 120, 236, 280, 168 }, UIS_MED), + UiArtText(selyesno_title, { 24, 161, 590, 35 }, UIS_CENTER | UIS_BIG), + UiArtText(selyesno_confirmationMessage, { 120, 236, 280, 168 }, UIS_MED), UiList(SELYESNO_DIALOG_ITEMS, 230, 390, 180, 35, UIS_CENTER | UIS_BIG | UIS_GOLD) }; -UiText *SELYESNO_DIALOG_CONFIRMATION_MESSAGE = &SELYESNO_DIALOG[2].text; +UiArtText *SELYESNO_DIALOG_CONFIRMATION_MESSAGE = &SELYESNO_DIALOG[2].art_text; void selyesno_Free() { @@ -60,14 +60,14 @@ BOOL UiSelHeroDelYesNoDialog( } sprintf(selyesno_confirmationMessage, "Are you sure you want to delete the character \"%s\"?", selyesno_heroInfo.name); - WordWrap(selyesno_confirmationMessage, SELYESNO_DIALOG_CONFIRMATION_MESSAGE->rect.w); + WordWrapArtStr(selyesno_confirmationMessage, SELYESNO_DIALOG_CONFIRMATION_MESSAGE->rect.w); UiInitList(0, 1, NULL, selyesno_Select, selyesno_Esc, SELYESNO_DIALOG, size(SELYESNO_DIALOG), true, NULL); selyesno_endMenu = false; while (!selyesno_endMenu) { UiRenderItems(SELYESNO_DIALOG, size(SELYESNO_DIALOG)); - UiRender(); + UiPollAndRender(); } BlackPalette(); diff --git a/SourceX/DiabloUI/text.cpp b/SourceX/DiabloUI/text.cpp index 3619e5961..8b0993019 100644 --- a/SourceX/DiabloUI/text.cpp +++ b/SourceX/DiabloUI/text.cpp @@ -2,7 +2,7 @@ namespace dvl { -std::size_t GetStrWidth(const char *str, std::size_t size) +std::size_t GetArtStrWidth(const char *str, std::size_t size) { int strWidth = 0; @@ -17,7 +17,7 @@ std::size_t GetStrWidth(const char *str, std::size_t size) return strWidth; } -void WordWrap(char *text, std::size_t width) +void WordWrapArtStr(char *text, std::size_t width) { const std::size_t len = strlen(text); std::size_t lineStart = 0; @@ -31,7 +31,7 @@ void WordWrap(char *text, std::size_t width) if (i != len) text[i] = '\0'; - if (GetStrWidth(&text[lineStart], AFT_SMALL) <= width) { + if (GetArtStrWidth(&text[lineStart], AFT_SMALL) <= width) { if (i != len) text[i] = ' '; continue; diff --git a/SourceX/DiabloUI/text.h b/SourceX/DiabloUI/text.h index 6b0179f09..85bed0d43 100644 --- a/SourceX/DiabloUI/text.h +++ b/SourceX/DiabloUI/text.h @@ -6,13 +6,7 @@ namespace dvl { -enum TXT_JUST { - JustLeft = 0, - JustCentre = 1, - JustRight = 2, -}; - -std::size_t GetStrWidth(const char *str, std::size_t size); -void WordWrap(char *text, std::size_t width); +std::size_t GetArtStrWidth(const char *str, std::size_t size); +void WordWrapArtStr(char *text, std::size_t width); } // namespace dvl diff --git a/SourceX/DiabloUI/text_draw.cpp b/SourceX/DiabloUI/text_draw.cpp index e6c9320a5..04f2f20b7 100644 --- a/SourceX/DiabloUI/text_draw.cpp +++ b/SourceX/DiabloUI/text_draw.cpp @@ -1,3 +1,5 @@ +#include "DiabloUI/text_draw.h" + #include "DiabloUI/art_draw.h" #include "DiabloUI/fonts.h" #include "DiabloUI/text.h" @@ -5,27 +7,51 @@ namespace dvl { +extern SDL_Surface *pal_surface; + namespace { -int AlignedTextOffsetX(const char *text, std::size_t rect_w, TXT_JUST align, _artFontTables size) +int AlignXOffset(int flags, const SDL_Rect &dest, int w) { - switch (align) { - case JustCentre: - return (rect_w - GetStrWidth(text, size)) / 2; - case JustRight: - return rect_w - GetStrWidth(text, size); - default: - return 0; - } + if (flags & UIS_CENTER) + return (dest.w - w) / 2; + if (flags & UIS_RIGHT) + return dest.w - w; + return 0; } } // namespace -void DrawArtStr(const char *text, const SDL_Rect &rect, int flags, bool drawTextCursor = false) +void DrawTTF(const char *text, const SDL_Rect &rect, int flags, + const SDL_Color &text_color, const SDL_Color &shadow_color, + TtfSurfaceCache **render_cache) +{ + if (*render_cache == nullptr) { + *render_cache = new TtfSurfaceCache(); + (*render_cache)->text = TTF_RenderUTF8_Solid(font, text, text_color); + (*render_cache)->shadow = TTF_RenderUTF8_Solid(font, text, shadow_color); + } + SDL_Surface *text_surface = (*render_cache)->text; + SDL_Surface *shadow_surface = (*render_cache)->shadow; + + SDL_Rect dest_rect = rect; + const int x_offset = AlignXOffset(flags, rect, text_surface->w); + const int y_offset = (flags & UIS_VCENTER) ? (rect.h - text_surface->h) / 2 : 0; + dest_rect.x += static_cast(SCREEN_X + x_offset); + dest_rect.y += static_cast(SCREEN_Y + y_offset); + + SDL_Rect shadow_rect = dest_rect; + shadow_rect.x += 2; + if (SDL_BlitSurface(shadow_surface, nullptr, pal_surface, &shadow_rect) <= -1) + SDL_Log(SDL_GetError()); + if (SDL_BlitSurface(text_surface, nullptr, pal_surface, &dest_rect) <= -1) + SDL_Log(SDL_GetError()); +} + +void DrawArtStr(const char *text, const SDL_Rect &rect, int flags, bool drawTextCursor) { _artFontTables size = AFT_SMALL; _artFontColors color = flags & UIS_GOLD ? AFC_GOLD : AFC_SILVER; - TXT_JUST align = JustLeft; if (flags & UIS_MED) size = AFT_MED; @@ -34,18 +60,10 @@ void DrawArtStr(const char *text, const SDL_Rect &rect, int flags, bool drawText else if (flags & UIS_HUGE) size = AFT_HUGE; - if (flags & UIS_CENTER) - align = JustCentre; - else if (flags & UIS_RIGHT) - align = JustRight; - - int x = rect.x + AlignedTextOffsetX(text, rect.w, align, size); - - int sx = x; - int sy = rect.y; - if (flags & UIS_VCENTER) - sy += (rect.h - ArtFonts[size][color].h()) / 2; + const int x = rect.x + AlignXOffset(flags, rect, GetArtStrWidth(text, size)); + const int y = rect.y + ((flags & UIS_VCENTER) ? (rect.h - ArtFonts[size][color].h()) / 2 : 0); + int sx = x, sy = y; for (size_t i = 0, n = strlen(text); i < n; i++) { if (text[i] == '\n') { sx = x; diff --git a/SourceX/DiabloUI/text_draw.h b/SourceX/DiabloUI/text_draw.h index 19f59e065..3cb9f973a 100644 --- a/SourceX/DiabloUI/text_draw.h +++ b/SourceX/DiabloUI/text_draw.h @@ -4,6 +4,21 @@ namespace dvl { +struct TtfSurfaceCache { + ~TtfSurfaceCache() + { + mem_free_dbg(text); + mem_free_dbg(shadow); + } + + SDL_Surface *text = nullptr; + SDL_Surface *shadow = nullptr; +}; + +void DrawTTF(const char *text, const SDL_Rect &rect, int flags, + const SDL_Color &text_color, const SDL_Color &shadow_color, + TtfSurfaceCache **surface_cache); + void DrawArtStr(const char *text, const SDL_Rect &rect, int flags, bool drawTextCursor = false); } // namespace dvl diff --git a/SourceX/DiabloUI/title.cpp b/SourceX/DiabloUI/title.cpp index 849b84db0..1404380ee 100644 --- a/SourceX/DiabloUI/title.cpp +++ b/SourceX/DiabloUI/title.cpp @@ -19,7 +19,7 @@ BOOL UiTitleDialog(int a1) { UiItem TITLESCREEN_DIALOG[] = { UiImage(&ArtBackground, { 0, 0, 640, 480 }), - UiText("Copyright \xA9 1996-2001 Blizzard Entertainment", { 49, 410, 550, 26 }, UIS_MED | UIS_CENTER) + UiArtText("Copyright \xA9 1996-2001 Blizzard Entertainment", { 49, 410, 550, 26 }, UIS_MED | UIS_CENTER) }; title_Load(); diff --git a/SourceX/DiabloUI/ui_item.h b/SourceX/DiabloUI/ui_item.h index c89616628..b3df112c3 100644 --- a/SourceX/DiabloUI/ui_item.h +++ b/SourceX/DiabloUI/ui_item.h @@ -1,19 +1,24 @@ #pragma once #include +#include #include #include #include "devilution.h" -#include "art.h" #include "stubs.h" +#include "DiabloUI/art.h" +#include "DiabloUI/text_draw.h" + namespace dvl { enum UiType { UI_TEXT, - UI_IMAGE, + UI_ART_TEXT, UI_ART_TEXT_BUTTON, + UI_IMAGE, + UI_BUTTON, UI_LIST, UI_SCROLLBAR, UI_EDIT, @@ -29,11 +34,8 @@ enum UiFlags { UIS_VCENTER = 1 << 6, UIS_SILVER = 1 << 7, UIS_GOLD = 1 << 8, - UIS_SML1 = 1 << 9, - UIS_SML2 = 1 << 10, - UIS_LIST = 1 << 11, - UIS_DISABLED = 1 << 12, - UIS_HIDDEN = 1 << 13, + UIS_DISABLED = 1 << 9, + UIS_HIDDEN = 1 << 10, }; struct UiItemBase { @@ -48,6 +50,11 @@ struct UiItemBase { return flags & flag; } + bool has_any_flag(int flags) const + { + return (this->flags & flags) != 0; + } + void add_flag(UiFlags flag) { flags |= flag; @@ -79,8 +86,34 @@ struct UiImage : public UiItemBase { int frame; }; +// Plain text (TTF). struct UiText : public UiItemBase { constexpr UiText(const char *text, SDL_Rect rect, int flags = 0) + : UiItemBase(rect, flags) + , color{ 243, 243, 243, 0 } + , shadow_color{ 0, 0, 0, 0 } + , text(text) + , render_cache(nullptr) + { + } + + SDL_Color color; + SDL_Color shadow_color; + const char *text; + + // State: + TtfSurfaceCache *render_cache; + + void FreeCache() + { + delete render_cache; + render_cache = nullptr; + } +}; + +// Text drawn with Diablo sprites. +struct UiArtText : public UiItemBase { + constexpr UiArtText(const char *text, SDL_Rect rect, int flags = 0) : UiItemBase(rect, flags) , text(text) { @@ -89,6 +122,7 @@ struct UiText : public UiItemBase { const char *text; }; +// Clickable Diablo sprites text. struct UiArtTextButton : public UiItemBase { constexpr UiArtTextButton(const char *text, void (*action)(), SDL_Rect rect, int flags = 0) : UiItemBase(rect, flags) @@ -101,6 +135,43 @@ struct UiArtTextButton : public UiItemBase { void (*action)(); }; +// A button (uses Diablo sprites). +struct UiButton : public UiItemBase { + enum FrameKey { + DEFAULT = 0, + PRESSED, + DISABLED + }; + using FrameMap = int[3]; + + constexpr UiButton(Art *art, const FrameMap &frame_map, const char *text, void (*action)(), SDL_Rect rect, int flags = 0) + : UiItemBase(rect, flags) + , art(art) + , frame_map{ frame_map[0], frame_map[1], frame_map[2] } + , text(text) + , action(action) + , pressed(false) + , render_cache(nullptr) + { + } + + Art *art; + const FrameMap frame_map; + + const char *text; + void (*action)(); + + // State + bool pressed; + TtfSurfaceCache *render_cache; + + void FreeCache() + { + delete render_cache; + render_cache = nullptr; + } +}; + struct UiListItem { constexpr UiListItem(const char *text = "", int value = 0) : text(text) @@ -185,9 +256,9 @@ struct UiItem { { } - constexpr UiItem(UiImage image) - : type(UI_IMAGE) - , image(image) + constexpr UiItem(UiArtText text) + : type(UI_ART_TEXT) + , art_text(text) { } @@ -197,6 +268,18 @@ struct UiItem { { } + constexpr UiItem(UiImage image) + : type(UI_IMAGE) + , image(image) + { + } + + constexpr UiItem(UiButton button) + : type(UI_BUTTON) + , button(button) + { + } + constexpr UiItem(UiList list) : type(UI_LIST) , list(list) @@ -218,8 +301,10 @@ struct UiItem { UiType type; union { UiText text; + UiArtText art_text; UiImage image; UiArtTextButton art_text_button; + UiButton button; UiList list; UiScrollBar scrollbar; UiEdit edit; @@ -231,10 +316,29 @@ struct UiItem { return common.has_flag(flag); } + bool has_any_flag(int flags) const + { + return common.has_any_flag(flags); + } + const SDL_Rect &rect() const { return common.rect; } + + void FreeCache() + { + switch (type) { + case UI_BUTTON: + button.FreeCache(); + break; + case UI_TEXT: + text.FreeCache(); + break; + default: + break; + } + } }; } // namespace dvl