diff options
Diffstat (limited to 'src/gui')
94 files changed, 2026 insertions, 1116 deletions
diff --git a/src/gui/abilitieswindow.cpp b/src/gui/abilitieswindow.cpp index 700fa7ff..d8122bf3 100644 --- a/src/gui/abilitieswindow.cpp +++ b/src/gui/abilitieswindow.cpp @@ -145,13 +145,13 @@ void AbilitiesWindow::draw(gcn::Graphics *graphics) void AbilitiesWindow::rebuild(const std::map<int, Ability> &abilityData) { delete_all(mEntries); - + mEntries.clear(); int vPos = 0; //vertical position of next placed element for (auto &[id, ability] : abilityData) { - logger->log("Updating ability GUI for %d", id); + Log::info("Updating ability GUI for %d", id); AbilityInfo *info = AbilityDB::get(id); if (info) @@ -166,7 +166,7 @@ void AbilitiesWindow::rebuild(const std::map<int, Ability> &abilityData) } else { - logger->log("Warning: No info available of ability %d", id); + Log::warn("No info available of ability %d", id); } } } diff --git a/src/gui/buydialog.cpp b/src/gui/buydialog.cpp index 135c2119..fb316722 100644 --- a/src/gui/buydialog.cpp +++ b/src/gui/buydialog.cpp @@ -54,7 +54,7 @@ BuyDialog::BuyDialog(int npcId): setCloseButton(true); setMinWidth(260); setMinHeight(230); - setDefaultSize(260, 230, ImageRect::CENTER); + setDefaultSize(260, 230, WindowAlignment::Center); mShopItems = new ShopItems; diff --git a/src/gui/buyselldialog.cpp b/src/gui/buyselldialog.cpp index be49ba58..c555cf01 100644 --- a/src/gui/buyselldialog.cpp +++ b/src/gui/buyselldialog.cpp @@ -23,8 +23,6 @@ #include "playerinfo.h" -#include "gui/setup.h" - #include "gui/widgets/button.h" #include "net/net.h" @@ -109,11 +107,6 @@ void BuySellDialog::action(const gcn::ActionEvent &event) void BuySellDialog::closeAll() { - auto it = instances.begin(); - auto it_end = instances.end(); - - for (; it != it_end; it++) - { - (*it)->close(); - } + for (auto &instance : instances) + instance->close(); } diff --git a/src/gui/changeemaildialog.cpp b/src/gui/changeemaildialog.cpp index ce83087b..e10a0d18 100644 --- a/src/gui/changeemaildialog.cpp +++ b/src/gui/changeemaildialog.cpp @@ -112,8 +112,8 @@ void ChangeEmailDialog::action(const gcn::ActionEvent &event) const std::string username = mLoginData->username.c_str(); const std::string newFirstEmail = mFirstEmailField->getText(); const std::string newSecondEmail = mSecondEmailField->getText(); - logger->log("ChangeEmailDialog::Email change, Username is %s", - username.c_str()); + Log::info("ChangeEmailDialog::Email change, Username is %s", + username.c_str()); std::stringstream errorMessage; int error = 0; diff --git a/src/gui/changepassworddialog.cpp b/src/gui/changepassworddialog.cpp index 437a8c90..91d7721d 100644 --- a/src/gui/changepassworddialog.cpp +++ b/src/gui/changepassworddialog.cpp @@ -94,8 +94,8 @@ void ChangePasswordDialog::action(const gcn::ActionEvent &event) const std::string oldPassword = mOldPassField->getText(); const std::string newFirstPass = mFirstPassField->getText(); const std::string newSecondPass = mSecondPassField->getText(); - logger->log("ChangePasswordDialog::Password change, Username is %s", - username.c_str()); + Log::info("ChangePasswordDialog::Password change, Username is %s", + username.c_str()); std::stringstream errorMessage; int error = 0; diff --git a/src/gui/charcreatedialog.cpp b/src/gui/charcreatedialog.cpp index cff7d822..6b46d2ac 100644 --- a/src/gui/charcreatedialog.cpp +++ b/src/gui/charcreatedialog.cpp @@ -191,8 +191,10 @@ void CharCreateDialog::action(const gcn::ActionEvent &event) 0 : mHairStylesIds.at(mHairStyleId); int hairColor = mHairColorsIds.empty() ? 0 : mHairColorsIds.at(mHairColorId); + + Gender gender = mFemale->isSelected() ? Gender::Female : Gender::Male; Net::getCharHandler()->newCharacter(getName(), characterSlot, - mFemale->isSelected(), + gender, hairStyle, hairColor, mAttributes); } diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp index 2485be69..1ed353dd 100644 --- a/src/gui/charselectdialog.cpp +++ b/src/gui/charselectdialog.cpp @@ -271,7 +271,7 @@ void CharSelectDialog::setCharacters(const Net::Characters &characters) if (characterSlot >= (int)mCharacterEntries.size()) { - logger->log("Warning: slot out of range: %d", character->slot); + Log::warn("Slot out of range: %d", character->slot); continue; } diff --git a/src/gui/chatwindow.cpp b/src/gui/chatwindow.cpp index 8a34c961..d19231ab 100644 --- a/src/gui/chatwindow.cpp +++ b/src/gui/chatwindow.cpp @@ -30,6 +30,7 @@ #include "gui/recorder.h" #include "gui/setup.h" +#include "gui/widgets/browserbox.h" #include "gui/widgets/chattab.h" #include "gui/widgets/itemlinkhandler.h" #include "gui/widgets/layout.h" @@ -82,7 +83,7 @@ class ChatAutoComplete : public AutoCompleteLister }; ChatWindow::ChatWindow(): - Window(_("Chat")), + Window(SkinType::Popup, _("Chat")), mItemLinkHandler(new ItemLinkHandler(this)), mChatInput(new ChatInput), mAutoComplete(new ChatAutoComplete), @@ -101,16 +102,20 @@ ChatWindow::ChatWindow(): setResizable(true); setDefaultVisible(true); setSaveVisible(true); - setDefaultSize(600, 123, ImageRect::LOWER_LEFT); + setDefaultSize(600, 123, WindowAlignment::BottomLeft); setMinWidth(150); setMinHeight(90); mChatInput->setActionEventId("chatinput"); mChatInput->addActionListener(this); - getLayout().setPadding(3); + // Override the padding from the theme since we want the content very close + // to the border on this window. + setPadding(std::min<unsigned>(getPadding(), 6)); + getLayout().setPadding(0); + place(0, 0, mChatTabs, 3, 3); - place(0, 3, mChatInput, 3); + place(0, 3, mChatInput, 3).setPadding(mChatInput->getFrameSize()); loadWindowState(); @@ -235,6 +240,7 @@ void ChatWindow::addTab(ChatTab *tab) // Make sure we don't end up with duplicates in the gui // TODO + tab->mTextOutput->setPalette(getSkin().palette); mChatTabs->addTab(tab, tab->mScrollArea); // Update UI diff --git a/src/gui/confirmdialog.cpp b/src/gui/confirmdialog.cpp index c0b471c1..360b399b 100644 --- a/src/gui/confirmdialog.cpp +++ b/src/gui/confirmdialog.cpp @@ -22,49 +22,34 @@ #include "gui/confirmdialog.h" #include "gui/widgets/button.h" +#include "gui/widgets/layout.h" #include "gui/widgets/textbox.h" #include "utils/gettext.h" #include <guichan/font.hpp> -ConfirmDialog::ConfirmDialog(const std::string &title, const std::string &msg, - Window *parent): +ConfirmDialog::ConfirmDialog(const std::string &title, + const std::string &msg, + Window *parent): Window(title, true, parent) { - mTextBox = new TextBox; - mTextBox->setEditable(false); - mTextBox->setOpaque(false); - mTextBox->setTextWrapped(msg, 260); + auto textBox = new TextBox; + textBox->setEditable(false); + textBox->setOpaque(false); + textBox->setTextWrapped(msg, 260); gcn::Button *yesButton = new Button(_("Yes"), "yes", this); gcn::Button *noButton = new Button(_("No"), "no", this); - const int numRows = mTextBox->getNumberOfRows(); - const int inWidth = yesButton->getWidth() + noButton->getWidth() + - (2 * getPadding()); - const int fontHeight = getFont()->getHeight(); - const int height = numRows * fontHeight; - int width = getFont()->getWidth(title); + auto place = getPlacer(0, 0); + place(0, 0, textBox); - if (width < mTextBox->getMinWidth()) - width = mTextBox->getMinWidth(); - if (width < inWidth) - width = inWidth; - - setContentSize(mTextBox->getMinWidth() + fontHeight, height + fontHeight + - noButton->getHeight()); - mTextBox->setPosition(getPadding(), getPadding()); - - // 8 is the padding that GUIChan adds to button widgets - // (top and bottom combined) - yesButton->setPosition((width - inWidth) / 2, height + 8); - noButton->setPosition(yesButton->getX() + inWidth - noButton->getWidth(), - height + 8); - - add(mTextBox); - add(yesButton); - add(noButton); + place = getPlacer(0, 1); + place(0, 0, yesButton); + place(1, 0, noButton); + place.getCell().setHAlign(Layout::CENTER); + reflowLayout(); if (getParent()) { @@ -79,8 +64,5 @@ void ConfirmDialog::action(const gcn::ActionEvent &event) { setActionEventId(event.getId()); distributeActionEvent(); - - // Can we receive anything else anyway? - if (event.getId() == "yes" || event.getId() == "no") - scheduleDelete(); + scheduleDelete(); } diff --git a/src/gui/confirmdialog.h b/src/gui/confirmdialog.h index ba51558e..6098b3f7 100644 --- a/src/gui/confirmdialog.h +++ b/src/gui/confirmdialog.h @@ -25,8 +25,6 @@ #include <guichan/actionlistener.hpp> -class TextBox; - /** * An option dialog. * @@ -42,7 +40,4 @@ class ConfirmDialog : public Window, public gcn::ActionListener * Called when receiving actions from the widgets. */ void action(const gcn::ActionEvent &event) override; - - private: - TextBox *mTextBox; }; diff --git a/src/gui/connectiondialog.cpp b/src/gui/connectiondialog.cpp index 2ff68a15..e6435269 100644 --- a/src/gui/connectiondialog.cpp +++ b/src/gui/connectiondialog.cpp @@ -54,7 +54,7 @@ ConnectionDialog::ConnectionDialog(const std::string &text, void ConnectionDialog::action(const gcn::ActionEvent &) { - logger->log("Cancel pressed"); + Log::info("Cancel pressed"); Client::setState(mCancelState); } diff --git a/src/gui/customserverdialog.cpp b/src/gui/customserverdialog.cpp index 5524e459..9a589671 100644 --- a/src/gui/customserverdialog.cpp +++ b/src/gui/customserverdialog.cpp @@ -154,7 +154,7 @@ void CustomServerDialog::action(const gcn::ActionEvent &event) serverInfo.type = ServerType::Unknown; } #else - serverInfo.type = ServerType::TMWATHENA; + serverInfo.type = ServerType::TmwAthena; #endif if (mPortField->getText().empty()) serverInfo.port = ServerInfo::defaultPortForServerType(serverInfo.type); diff --git a/src/gui/emotepopup.cpp b/src/gui/emotepopup.cpp index e759ab25..4df6b995 100644 --- a/src/gui/emotepopup.cpp +++ b/src/gui/emotepopup.cpp @@ -22,11 +22,10 @@ #include "gui/emotepopup.h" -#include "configuration.h" #include "emoteshortcut.h" #include "graphics.h" -#include "log.h" +#include "gui/gui.h" #include "resources/emotedb.h" #include "resources/image.h" #include "resources/theme.h" @@ -34,19 +33,10 @@ #include <guichan/mouseinput.hpp> #include <guichan/selectionlistener.hpp> -const int EmotePopup::gridWidth = 34; // emote icon width + 4 -const int EmotePopup::gridHeight = 36; // emote icon height + 4 - static const int MAX_COLUMNS = 6; EmotePopup::EmotePopup() { - mSelectionImage = Theme::getImageFromTheme("selection.png"); - if (!mSelectionImage) - logger->error("Unable to load selection.png"); - - mSelectionImage->setAlpha(config.guiAlpha); - addMouseListener(this); recalculateSize(); setVisible(true); @@ -62,30 +52,41 @@ void EmotePopup::draw(gcn::Graphics *graphics) const int emoteCount = EmoteDB::getEmoteCount(); + auto &slotSkin = gui->getTheme()->getSkin(SkinType::EmoteSlot); + WidgetState slotState; + slotState.width = slotSkin.width; + slotState.height = slotSkin.height; + for (int i = 0; i < emoteCount ; i++) { int row = i / mColumnCount; int column = i % mColumnCount; - int emoteX = getPadding() + column * gridWidth; - int emoteY = getPadding() + row * gridHeight; + slotState.x = getPadding() + column * slotSkin.width; + slotState.y = getPadding() + row * slotSkin.height; // Center the last row when there are less emotes than columns if (row == mRowCount - 1) { const int emotesLeft = emoteCount % mColumnCount; - emoteX += (mColumnCount - emotesLeft) * gridWidth / 2; + slotState.x += (mColumnCount - emotesLeft) * slotSkin.width / 2; } + slotState.flags = 0; + // Draw selection image below hovered item if (i == mHoveredEmoteIndex) - g->drawImage(mSelectionImage, emoteX, emoteY + 4); + slotState.flags |= STATE_HOVERED; + + slotSkin.draw(g, slotState); // Draw emote icon if (auto image = EmoteDB::getByIndex(i).image) { image->setAlpha(1.0f); - g->drawImage(image, emoteX, emoteY); + g->drawImage(image, + slotState.x + (slotSkin.width - image->getWidth()) / 2, + slotState.y + (slotSkin.height - image->getHeight()) / 2); } } } @@ -139,22 +140,24 @@ int EmotePopup::getIndexAt(int x, int y) const return -1; // Take into account the border - x -= 2; - y -= 4; + x -= getPadding(); + y -= getPadding(); + + auto &slotSkin = gui->getTheme()->getSkin(SkinType::EmoteSlot); - const int row = y / gridHeight; + const int row = y / slotSkin.height; // Take into account that the last row is centered if (row == mRowCount - 1) { const int emotesLeft = EmoteDB::getEmoteCount() % mColumnCount; const int emotesMissing = mColumnCount - emotesLeft; - x -= emotesMissing * gridWidth / 2; + x -= emotesMissing * slotSkin.width / 2; if (x < 0) return -1; } - const int column = std::min(x / gridWidth, mColumnCount - 1); + const int column = std::min(x / slotSkin.width, mColumnCount - 1); const int index = column + (row * mColumnCount); if (index >= 0 && index < EmoteDB::getEmoteCount()) @@ -178,7 +181,8 @@ void EmotePopup::recalculateSize() mColumnCount = 0; } - setContentSize(mColumnCount * gridWidth, mRowCount * gridHeight); + auto &slotSkin = gui->getTheme()->getSkin(SkinType::EmoteSlot); + setContentSize(mColumnCount * slotSkin.width, mRowCount * slotSkin.height); } void EmotePopup::distributeValueChangedEvent() @@ -186,7 +190,5 @@ void EmotePopup::distributeValueChangedEvent() const gcn::SelectionEvent event(this); for (auto &listener : mListeners) - { listener->valueChanged(event); - } } diff --git a/src/gui/emotepopup.h b/src/gui/emotepopup.h index c95c5723..664eef27 100644 --- a/src/gui/emotepopup.h +++ b/src/gui/emotepopup.h @@ -23,7 +23,6 @@ #pragma once #include "gui/widgets/popup.h" -#include "resources/resource.h" #include <guichan/mouselistener.hpp> @@ -104,7 +103,6 @@ class EmotePopup : public Popup */ void distributeValueChangedEvent(); - ResourceRef<Image> mSelectionImage; int mSelectedEmoteId = -1; int mHoveredEmoteIndex = -1; @@ -112,7 +110,4 @@ class EmotePopup : public Popup int mColumnCount = 1; std::list<gcn::SelectionListener *> mListeners; - - static const int gridWidth; - static const int gridHeight; }; diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp index ff3c6630..8523e7b2 100644 --- a/src/gui/equipmentwindow.cpp +++ b/src/gui/equipmentwindow.cpp @@ -47,9 +47,6 @@ #include <guichan/font.hpp> -static const int BOX_WIDTH = 36; -static const int BOX_HEIGHT = 36; - EquipmentWindow::EquipmentWindow(Equipment *equipment): Window(_("Equipment")), mEquipment(equipment) @@ -66,7 +63,7 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment): setCloseButton(true); setSaveVisible(true); setContentSize(175, 290); - setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); + setDefaultSize(getWidth(), getHeight(), WindowAlignment::Center); loadWindowState(); mUnequip = new Button(_("Unequip"), "unequip", this); @@ -77,28 +74,6 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment): add(playerBox); add(mUnequip); - - loadEquipBoxes(); -} - -void EquipmentWindow::loadEquipBoxes() -{ - mBoxes.resize(mEquipment->getSlotNumber()); - - for (size_t i = 0; i < mBoxes.size(); ++i) - { - auto &box = mBoxes[i]; - - Position boxPosition = Net::getInventoryHandler()->getBoxPosition(i); - box.posX = boxPosition.x + getPadding(); - box.posY = boxPosition.y + getTitleBarHeight(); - - const std::string &backgroundFile = - Net::getInventoryHandler()->getBoxBackground(i); - - if (!backgroundFile.empty()) - box.backgroundImage = Theme::getImageFromTheme(backgroundFile); - } } EquipmentWindow::~EquipmentWindow() @@ -110,58 +85,51 @@ void EquipmentWindow::draw(gcn::Graphics *graphics) { Window::draw(graphics); - // Draw equipment boxes auto *g = static_cast<Graphics*>(graphics); - for (size_t i = 0; i < mBoxes.size(); i++) - { - const auto &box = mBoxes[i]; + auto theme = gui->getTheme(); + auto &boxSkin = theme->getSkin(SkinType::EquipmentBox); - // When there is a background image, draw it centered in the box: - if (box.backgroundImage) - { - int posX = box.posX - + (BOX_WIDTH - box.backgroundImage->getWidth()) / 2; - int posY = box.posY - + (BOX_HEIGHT - box.backgroundImage->getHeight()) / 2; - g->drawImage(box.backgroundImage, posX, posY); - } - - const gcn::Rectangle tRect(box.posX, box.posY, - BOX_WIDTH, BOX_HEIGHT); - - if (static_cast<int>(i) == mSelected) - { - const gcn::Color color = Theme::getThemeColor(Theme::HIGHLIGHT); + // Draw equipment boxes + const int boxCount = mEquipment->getSlotNumber(); + for (int i = 0; i < boxCount; ++i) + { + Position boxPos = Net::getInventoryHandler()->getBoxPosition(i); + boxPos.x += getPadding(); + boxPos.y += getTitleBarHeight(); - // Set color to the highlight color - g->setColor(gcn::Color(color.r, color.g, color.b, getGuiAlpha())); - g->fillRectangle(tRect); - } + WidgetState boxState(gcn::Rectangle(boxPos.x, boxPos.y, boxSkin.width, boxSkin.height)); + if (i == mSelected) + boxState.flags |= STATE_SELECTED; - // Draw black box border - g->setColor(gcn::Color(0, 0, 0)); - g->drawRectangle(tRect); + boxSkin.draw(g, boxState); if (Item *item = mEquipment->getEquipment(i)) { - // Draw Item. - Image *image = item->getImage(); - // Ensure the image is drawn with maximum opacity - image->setAlpha(1.0f); - g->drawImage(image, - box.posX + 2, - box.posY + 2); + if (Image *image = item->getImage()) + { + image->setAlpha(1.0f); + g->drawImage(image, + boxPos.x + boxSkin.padding, + boxPos.y + boxSkin.padding); + } if (i == TmwAthena::EQUIP_PROJECTILE_SLOT) { g->setColor(Theme::getThemeColor(Theme::TEXT)); graphics->drawText(toString(item->getQuantity()), - box.posX + (BOX_WIDTH / 2), - box.posY - getFont()->getHeight(), + boxPos.x + boxSkin.width / 2, + boxPos.y - getFont()->getHeight(), gcn::Graphics::CENTER); } } + else + { + auto &icon = Net::getInventoryHandler()->getBoxIcon(i); + if (!icon.empty()) + if (auto image = theme->getIcon(icon)) + g->drawImage(image, boxPos.x + boxSkin.padding, boxPos.y + boxSkin.padding); + } } } @@ -180,12 +148,18 @@ void EquipmentWindow::action(const gcn::ActionEvent &event) */ int EquipmentWindow::getBoxIndex(int x, int y) const { - for (size_t i = 0; i < mBoxes.size(); ++i) - { - const auto &box = mBoxes[i]; - const gcn::Rectangle tRect(box.posX, box.posY, - BOX_WIDTH, BOX_HEIGHT); + auto &boxSkin = gui->getTheme()->getSkin(SkinType::EquipmentBox); + + // Translate coordinates to content area + const auto childrenArea = const_cast<EquipmentWindow*>(this)->getChildrenArea(); + x -= childrenArea.x; + y -= childrenArea.y; + const int boxCount = mEquipment->getSlotNumber(); + for (int i = 0; i < boxCount; ++i) + { + const Position boxPos = Net::getInventoryHandler()->getBoxPosition(i); + const gcn::Rectangle tRect(boxPos.x, boxPos.y, boxSkin.width, boxSkin.height); if (tRect.isPointInRect(x, y)) return i; } diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h index f46d1175..944ae99e 100644 --- a/src/gui/equipmentwindow.h +++ b/src/gui/equipmentwindow.h @@ -22,14 +22,11 @@ #pragma once #include "equipment.h" -#include "resources/image.h" #include "gui/widgets/window.h" #include <guichan/actionlistener.hpp> -#include <vector> - class Inventory; class Item; class ItemPopup; @@ -58,29 +55,12 @@ class EquipmentWindow : public Window, public gcn::ActionListener void mouseExited(gcn::MouseEvent &event) override; /** - * Loads the correct amount of displayed equip boxes. - */ - void loadEquipBoxes(); - - /** * Returns the current selected slot or -1 if none. */ int getSelected() const { return mSelected; } protected: - /** - * Equipment box. - */ - struct EquipBox - { - int posX = 0; - int posY = 0; - ResourceRef<Image> backgroundImage; - }; - - std::vector<EquipBox> mBoxes; /**< Equipment boxes. */ - int mSelected = -1; /**< Index of selected item. */ Equipment *mEquipment; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d72bd56d..768aeb2b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -42,6 +42,8 @@ #include <guichan/exception.hpp> #include <guichan/image.hpp> +#include <algorithm> + #include <SDL_image.h> // Guichan stuff @@ -57,10 +59,19 @@ gcn::Font *monoFont = nullptr; bool Gui::debugDraw; Gui::Gui(Graphics *graphics, const std::string &themePath) - : mTheme(new Theme(themePath)) + : mAvailableThemes(Theme::getAvailableThemes()) , mCustomCursorScale(Client::getVideo().settings().scale()) { - logger->log("Initializing GUI..."); + // Try to find the requested theme, using the first one as fallback + auto themeIt = std::find_if(mAvailableThemes.begin(), + mAvailableThemes.end(), + [&themePath](const ThemeInfo &theme) { + return theme.getPath() == themePath; + }); + + setTheme(themeIt != mAvailableThemes.end() ? *themeIt : mAvailableThemes.front()); + + Log::info("Initializing GUI..."); // Set graphics setGraphics(graphics); @@ -68,7 +79,7 @@ Gui::Gui(Graphics *graphics, const std::string &themePath) guiInput = new SDLInput; setInput(guiInput); - // Set focus handler + // Replace focus handler delete mFocusHandler; mFocusHandler = new FocusHandler; @@ -95,8 +106,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath) } catch (gcn::Exception e) { - logger->error(std::string("Unable to load '") + fontFile + - std::string("': ") + e.getMessage()); + Log::critical(std::string("Unable to load '") + fontFile + + "': " + e.getMessage()); } // Set bold font @@ -108,8 +119,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath) } catch (gcn::Exception e) { - logger->error(std::string("Unable to load '") + fontFile + - std::string("': ") + e.getMessage()); + Log::critical(std::string("Unable to load '") + fontFile + + "': " + e.getMessage()); } // Set mono font @@ -121,8 +132,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath) } catch (gcn::Exception e) { - logger->error(std::string("Unable to load '") + fontFile + - std::string("': ") + e.getMessage()); + Log::critical(std::string("Unable to load '") + fontFile + + "': " + e.getMessage()); } loadCustomCursors(); @@ -226,6 +237,11 @@ void Gui::setCursorType(Cursor cursor) updateCursor(); } +void Gui::setTheme(const ThemeInfo &theme) +{ + mTheme = std::make_unique<Theme>(theme); +} + void Gui::updateCursor() { if (mCustomCursor && !mCustomMouseCursors.empty()) @@ -272,32 +288,20 @@ void Gui::loadCustomCursors() SDL_Surface *mouseSurface = loadSurface(cursorPath); if (!mouseSurface) { - logger->log("Warning: Unable to load mouse cursor file (%s): %s", - cursorPath.c_str(), SDL_GetError()); + Log::warn("Unable to load mouse cursor file (%s): %s", + cursorPath.c_str(), SDL_GetError()); return; } SDL_SetSurfaceBlendMode(mouseSurface, SDL_BLENDMODE_NONE); -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - const Uint32 rmask = 0xff000000; - const Uint32 gmask = 0x00ff0000; - const Uint32 bmask = 0x0000ff00; - const Uint32 amask = 0x000000ff; -#else - const Uint32 rmask = 0x000000ff; - const Uint32 gmask = 0x0000ff00; - const Uint32 bmask = 0x00ff0000; - const Uint32 amask = 0xff000000; -#endif - constexpr int cursorSize = 40; const int targetCursorSize = cursorSize * mCustomCursorScale; const int columns = mouseSurface->w / cursorSize; - SDL_Surface *cursorSurface = SDL_CreateRGBSurface( + SDL_Surface *cursorSurface = SDL_CreateRGBSurfaceWithFormat( 0, targetCursorSize, targetCursorSize, 32, - rmask, gmask, bmask, amask); + SDL_PIXELFORMAT_RGBA32); for (int i = 0; i < static_cast<int>(Cursor::Count); ++i) { @@ -313,7 +317,7 @@ void Gui::loadCustomCursors() 17 * mCustomCursorScale); if (!cursor) { - logger->log("Warning: Unable to create cursor: %s", SDL_GetError()); + Log::warn("Unable to create cursor: %s", SDL_GetError()); } mCustomMouseCursors.push_back(cursor); diff --git a/src/gui/gui.h b/src/gui/gui.h index 450514f5..2584b780 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -24,6 +24,8 @@ #include "eventlistener.h" #include "guichanfwd.h" +#include "resources/theme.h" + #include "utils/time.h" #include <guichan/gui.hpp> @@ -33,7 +35,6 @@ #include <memory> #include <vector> -class Theme; class TextInput; class Graphics; class SDLInput; @@ -123,6 +124,14 @@ class Gui final : public gcn::Gui, public EventListener */ void setCursorType(Cursor cursor); + const std::vector<ThemeInfo> &getAvailableThemes() const + { return mAvailableThemes; } + + /** + * Sets the global GUI theme. + */ + void setTheme(const ThemeInfo &theme); + /** * The global GUI theme. */ @@ -141,6 +150,7 @@ class Gui final : public gcn::Gui, public EventListener void loadCustomCursors(); void loadSystemCursors(); + std::vector<ThemeInfo> mAvailableThemes; std::unique_ptr<Theme> mTheme; /**< The global GUI theme */ gcn::Font *mGuiFont; /**< The global GUI font */ gcn::Font *mInfoParticleFont; /**< Font for Info Particles*/ diff --git a/src/gui/helpwindow.cpp b/src/gui/helpwindow.cpp index e0e21610..7c7c5d4c 100644 --- a/src/gui/helpwindow.cpp +++ b/src/gui/helpwindow.cpp @@ -45,7 +45,7 @@ HelpWindow::HelpWindow(): setResizable(true); setupWindow->registerWindowForReset(this); - setDefaultSize(500, 400, ImageRect::CENTER); + setDefaultSize(500, 400, WindowAlignment::Center); mBrowserBox = new BrowserBox; mScrollArea = new ScrollArea(mBrowserBox); @@ -98,7 +98,7 @@ void HelpWindow::loadFile(const std::string &file) char *fileContents = (char *) FS::loadFile(fileName, contentsLength); if (!fileContents) { - logger->log("Couldn't load text file: %s", fileName.c_str()); + Log::info("Couldn't load text file: %s", fileName.c_str()); return; } diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp index ab2e9c86..0125700c 100644 --- a/src/gui/inventorywindow.cpp +++ b/src/gui/inventorywindow.cpp @@ -65,7 +65,7 @@ InventoryWindow::InventoryWindow(Inventory *inventory): setCloseButton(true); setSaveVisible(true); - setDefaultSize(387, 307, ImageRect::CENTER); + setDefaultSize(387, 307, WindowAlignment::Center); setMinWidth(316); setMinHeight(179); addKeyListener(this); diff --git a/src/gui/itemamountwindow.cpp b/src/gui/itemamountwindow.cpp index da5dc073..de204da3 100644 --- a/src/gui/itemamountwindow.cpp +++ b/src/gui/itemamountwindow.cpp @@ -21,6 +21,7 @@ #include "gui/itemamountwindow.h" +#include "inventory.h" #include "item.h" #include "keyboardconfig.h" @@ -34,8 +35,8 @@ #include "gui/widgets/slider.h" #include "gui/widgets/icon.h" -#include "net/inventoryhandler.h" #include "net/net.h" +#include "net/tradehandler.h" #include "utils/gettext.h" @@ -44,7 +45,7 @@ void ItemAmountWindow::finish(Item *item, int amount, Usage usage) switch (usage) { case TradeAdd: - tradeWindow->tradeItem(item, amount); + Net::getTradeHandler()->addItem(item, amount); break; case ItemDrop: item->doEvent(Event::DoDrop, amount); @@ -127,7 +128,7 @@ ItemAmountWindow::ItemAmountWindow(Usage usage, Window *parent, Item *item, place(4, 2, cancelButton); place(5, 2, okButton); - reflowLayout(225, 0); + reflowLayout(); resetAmount(); diff --git a/src/gui/itempopup.cpp b/src/gui/itempopup.cpp index 35951331..3e7ba15a 100644 --- a/src/gui/itempopup.cpp +++ b/src/gui/itempopup.cpp @@ -43,38 +43,38 @@ #define ITEMPOPUP_WRAP_WIDTH 196 -static const gcn::Color &getColorFromItemType(ItemType type) +static int getColorIdFromItemType(ItemType type) { switch (type) { case ITEM_UNUSABLE: - return Theme::getThemeColor(Theme::GENERIC); + return Theme::GENERIC; case ITEM_USABLE: - return Theme::getThemeColor(Theme::USABLE); + return Theme::USABLE; case ITEM_EQUIPMENT_ONE_HAND_WEAPON: - return Theme::getThemeColor(Theme::ONEHAND); + return Theme::ONEHAND; case ITEM_EQUIPMENT_TWO_HANDS_WEAPON: - return Theme::getThemeColor(Theme::TWOHAND); + return Theme::TWOHAND; case ITEM_EQUIPMENT_TORSO: - return Theme::getThemeColor(Theme::TORSO); + return Theme::TORSO; case ITEM_EQUIPMENT_ARMS: - return Theme::getThemeColor(Theme::ARMS); + return Theme::ARMS; case ITEM_EQUIPMENT_HEAD: - return Theme::getThemeColor(Theme::HEAD); + return Theme::HEAD; case ITEM_EQUIPMENT_LEGS: - return Theme::getThemeColor(Theme::LEGS); + return Theme::LEGS; case ITEM_EQUIPMENT_SHIELD: - return Theme::getThemeColor(Theme::SHIELD); + return Theme::SHIELD; case ITEM_EQUIPMENT_RING: - return Theme::getThemeColor(Theme::RING); + return Theme::RING; case ITEM_EQUIPMENT_NECKLACE: - return Theme::getThemeColor(Theme::NECKLACE); + return Theme::NECKLACE; case ITEM_EQUIPMENT_FEET: - return Theme::getThemeColor(Theme::FEET); + return Theme::FEET; case ITEM_EQUIPMENT_AMMO: - return Theme::getThemeColor(Theme::AMMO); + return Theme::AMMO; default: - return Theme::getThemeColor(Theme::UNKNOWN_ITEM); + return Theme::UNKNOWN_ITEM; } } @@ -132,7 +132,11 @@ void ItemPopup::setNoItem() mItemName->setCaption(caption); mItemName->adjustSize(); - mItemName->setForegroundColor(Theme::getThemeColor(Theme::GENERIC)); + auto theme = gui->getTheme(); + auto &palette = theme->getPalette(getSkin().palette); + + mItemName->setForegroundColor(palette.getColor(Theme::GENERIC)); + mItemName->setOutlineColor(palette.getOutlineColor(Theme::GENERIC)); mItemName->setPosition(0, 0); mItemDesc->setText(std::string()); @@ -173,9 +177,15 @@ void ItemPopup::setItem(const ItemInfo &item, bool showImage) if (!mItemEquipSlot.empty()) caption += " (" + mItemEquipSlot + ")"; + auto theme = gui->getTheme(); + auto &palette = theme->getPalette(getSkin().palette); + + const auto typeColorId = getColorIdFromItemType(mItemType); + mItemName->setCaption(caption); mItemName->adjustSize(); - mItemName->setForegroundColor(getColorFromItemType(mItemType)); + mItemName->setForegroundColor(palette.getColor(typeColorId)); + mItemName->setOutlineColor(palette.getOutlineColor(typeColorId)); mItemName->setPosition(space, 0); mItemDesc->setTextWrapped(item.description, ITEMPOPUP_WRAP_WIDTH); diff --git a/src/gui/itempopup.h b/src/gui/itempopup.h index 535104cf..c741ed64 100644 --- a/src/gui/itempopup.h +++ b/src/gui/itempopup.h @@ -29,6 +29,7 @@ #include <guichan/mouselistener.hpp> class Icon; +class Label; class TextBox; /** @@ -65,7 +66,7 @@ class ItemPopup : public Popup void mouseMoved(gcn::MouseEvent &mouseEvent) override; private: - gcn::Label *mItemName; + Label *mItemName; TextBox *mItemDesc; TextBox *mItemEffect; TextBox *mItemWeight; diff --git a/src/gui/minimap.cpp b/src/gui/minimap.cpp index 8924bc26..60bfadfa 100644 --- a/src/gui/minimap.cpp +++ b/src/gui/minimap.cpp @@ -41,7 +41,7 @@ #include <guichan/font.hpp> Minimap::Minimap(): - Window(_("Map")) + Window(SkinType::Popup, _("Map")) { setWindowName("Minimap"); setDefaultSize(5, 25, 100, 100); diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp index e2e7b040..033d01cc 100644 --- a/src/gui/npcdialog.cpp +++ b/src/gui/npcdialog.cpp @@ -82,7 +82,7 @@ NpcDialog::NpcDialog(int npcId) setMinWidth(200); setMinHeight(150); - setDefaultSize(260, 200, ImageRect::CENTER); + setDefaultSize(260, 200, WindowAlignment::Center); // Setup output text box mTextBox = new BrowserBox(BrowserBox::AUTO_WRAP); @@ -537,16 +537,11 @@ void NpcEventListener::event(Event::Channel channel, else if (event.getType() == Event::Next) { int id = event.getInt("id"); - NpcDialog *dialog = getDialog(id, false); - if (!dialog) - { - int mNpcId = id; - Net::getNpcHandler()->nextDialog(mNpcId); - return; - } - - dialog->showNextButton(); + if (NpcDialog *dialog = getDialog(id, false)) + dialog->showNextButton(); + else + Net::getNpcHandler()->nextDialog(id); } else if (event.getType() == Event::ClearDialog) { @@ -556,32 +551,19 @@ void NpcEventListener::event(Event::Channel channel, else if (event.getType() == Event::Close) { int id = event.getInt("id"); - NpcDialog *dialog = getDialog(id, false); - if (!dialog) - { - int mNpcId = id; - Net::getNpcHandler()->closeDialog(mNpcId); - return; - } - - dialog->showCloseButton(); - } - else if (event.getType() == Event::CloseDialog) - { - if (NpcDialog *dialog = getDialog(event.getInt("id"), false)) - dialog->setVisible(false); + if (NpcDialog *dialog = getDialog(id, false)) + dialog->showCloseButton(); + else + Net::getNpcHandler()->closeDialog(id); } else if (event.getType() == Event::CloseAll) { NpcDialog::closeAll(); } - else if (event.getType() == Event::End) + else if (event.getType() == Event::CloseDialog) { - int id = event.getInt("id"); - NpcDialog *dialog = getDialog(id, false); - - if (dialog) + if (NpcDialog *dialog = getDialog(event.getInt("id"), false)) dialog->close(); } else if (event.getType() == Event::Post) diff --git a/src/gui/okdialog.cpp b/src/gui/okdialog.cpp index 5a710890..287ee1b8 100644 --- a/src/gui/okdialog.cpp +++ b/src/gui/okdialog.cpp @@ -22,6 +22,7 @@ #include "gui/okdialog.h" #include "gui/widgets/button.h" +#include "gui/widgets/layout.h" #include "gui/widgets/textbox.h" #include "utils/gettext.h" @@ -32,33 +33,16 @@ OkDialog::OkDialog(const std::string &title, const std::string &msg, bool modal, Window *parent): Window(title, modal, parent) { - mTextBox = new TextBox; - mTextBox->setEditable(false); - mTextBox->setOpaque(false); - mTextBox->setTextWrapped(msg, 260); + auto textBox = new TextBox; + textBox->setEditable(false); + textBox->setOpaque(false); + textBox->setTextWrapped(msg, 260); gcn::Button *okButton = new Button(_("OK"), "ok", this); - const int numRows = mTextBox->getNumberOfRows(); - const int fontHeight = getFont()->getHeight(); - const int height = numRows * fontHeight; - int width = getFont()->getWidth(title); - - if (width < mTextBox->getMinWidth()) - width = mTextBox->getMinWidth(); - if (width < okButton->getWidth()) - width = okButton->getWidth(); - - setContentSize(mTextBox->getMinWidth() + fontHeight, height + - fontHeight + okButton->getHeight()); - mTextBox->setPosition(getPadding(), getPadding()); - - // 8 is the padding that GUIChan adds to button widgets - // (top and bottom combined) - okButton->setPosition((width - okButton->getWidth()) / 2, height + 8); - - add(mTextBox); - add(okButton); + place(0, 0, textBox); + place(0, 1, okButton).setHAlign(Layout::CENTER); + reflowLayout(); center(); setVisible(true); @@ -69,8 +53,5 @@ void OkDialog::action(const gcn::ActionEvent &event) { setActionEventId(event.getId()); distributeActionEvent(); - - // Can we receive anything else anyway? - if (event.getId() == "ok") - scheduleDelete(); + scheduleDelete(); } diff --git a/src/gui/okdialog.h b/src/gui/okdialog.h index f56f24e2..18bca454 100644 --- a/src/gui/okdialog.h +++ b/src/gui/okdialog.h @@ -25,8 +25,6 @@ #include <guichan/actionlistener.hpp> -class TextBox; - /** * An 'Ok' button dialog. * @@ -42,7 +40,4 @@ class OkDialog : public Window, public gcn::ActionListener * Called when receiving actions from the widgets. */ void action(const gcn::ActionEvent &event) override; - - private: - TextBox *mTextBox; }; diff --git a/src/gui/palette.cpp b/src/gui/palette.cpp index 6c6e6f06..948660d2 100644 --- a/src/gui/palette.cpp +++ b/src/gui/palette.cpp @@ -25,7 +25,6 @@ #include <cmath> static constexpr double PI = 3.14159265; -const gcn::Color Palette::BLACK = gcn::Color(0, 0, 0); Timer Palette::mRainbowTimer; Palette::Palettes Palette::mInstances; @@ -47,23 +46,37 @@ Palette::Palette(int size) : mInstances.insert(this); } +Palette::Palette(Palette &&pal) + : mColors(std::move(pal.mColors)) + , mGradVector(std::move(pal.mGradVector)) +{ + mInstances.insert(this); +} + Palette::~Palette() { mInstances.erase(this); } -const gcn::Color &Palette::getColor(char c, bool &valid) - { - for (const auto &color : mColors) +Palette &Palette::operator=(Palette &&pal) +{ + if (this != &pal) { - if (color.ch == c) - { - valid = true; - return color.color; - } + mColors = std::move(pal.mColors); + mGradVector = std::move(pal.mGradVector); } - valid = false; - return BLACK; + return *this; +} + +void Palette::setColor(int type, + const gcn::Color &color, + const std::optional<gcn::Color> &outlineColor, + GradientType grad, + int delay) +{ + auto &elem = mColors[type]; + elem.set(type, color, grad, delay); + elem.outlineColor = outlineColor; } void Palette::advanceGradients() @@ -97,17 +110,19 @@ void Palette::advanceGradient(int advance) const int pos = elem->gradientIndex % delay; const int colIndex = elem->gradientIndex / delay; - if (elem->grad == PULSE) - { + switch (elem->grad) { + case STATIC: + break; + case PULSE: { const int colVal = (int) (255.0 * sin(PI * colIndex / numOfColors)); const gcn::Color &col = elem->testColor; elem->color.r = ((colVal * col.r) / 255) % (col.r + 1); elem->color.g = ((colVal * col.g) / 255) % (col.g + 1); elem->color.b = ((colVal * col.b) / 255) % (col.b + 1); + break; } - if (elem->grad == SPECTRUM) - { + case SPECTRUM: { int colVal; if (colIndex % 2) @@ -129,9 +144,9 @@ void Palette::advanceGradient(int advance) elem->color.b = (colIndex == 3 || colIndex == 4) ? 255 : (colIndex == 2 || colIndex == 5) ? colVal : 0; + break; } - else if (elem->grad == RAINBOW) - { + case RAINBOW: { const gcn::Color &startCol = RAINBOW_COLORS[colIndex]; const gcn::Color &destCol = RAINBOW_COLORS[(colIndex + 1) % numOfColors]; @@ -147,6 +162,8 @@ void Palette::advanceGradient(int advance) elem->color.b =(int)(startColVal * startCol.b + destColVal * destCol.b); + break; + } } } } diff --git a/src/gui/palette.h b/src/gui/palette.h index 145a93ac..268b9fc6 100644 --- a/src/gui/palette.h +++ b/src/gui/palette.h @@ -27,8 +27,9 @@ #include <guichan/color.hpp> #include <cstdlib> -#include <string> +#include <optional> #include <set> +#include <string> #include <vector> // Default Gradient Delay @@ -40,8 +41,13 @@ constexpr int GRADIENT_DELAY = 40; class Palette { public: - /** Black Color Constant */ - static const gcn::Color BLACK; + Palette(int size); + Palette(const Palette &) = delete; + Palette(Palette &&); + ~Palette(); + + Palette &operator=(const Palette &) = delete; + Palette &operator=(Palette &&); /** Colors can be static or can alter over time. */ enum GradientType { @@ -51,62 +57,49 @@ class Palette RAINBOW }; - /** - * Returns the color associated with a character, if it exists. Returns - * Palette::BLACK if the character is not found. - * - * @param c character requested - * @param valid indicate whether character is known - * - * @return the requested color or Palette::BLACK - */ - const gcn::Color &getColor(char c, bool &valid); + void setColor(int type, + const gcn::Color &color, + const std::optional<gcn::Color> &outlineColor, + GradientType grad, + int delay); /** - * Gets the color associated with the type. Sets the alpha channel - * before returning. + * Gets the color associated with the type. * * @param type the color type requested - * @param alpha alpha channel to use - * * @return the requested color */ - const gcn::Color &getColor(int type, int alpha = 255) + const gcn::Color &getColor(int type) const { - gcn::Color &col = mColors[type].color; - col.a = alpha; - return col; + return mColors[type].color; } /** - * Gets the GradientType associated with the specified type. + * Gets the optional outline color associated with the type. * - * @param type the color type of the color - * - * @return the gradient type of the color with the given index + * @param type the color type requested + * @return the requested outline color, if any */ - GradientType getGradientType(int type) const + const std::optional<gcn::Color> &getOutlineColor(int type) const { - return mColors[type].grad; + return mColors[type].outlineColor; } /** - * Get the character used by the specified color. + * Gets the GradientType associated with the specified type. * * @param type the color type of the color - * - * @return the color char of the color with the given index + * @return the gradient type of the color with the given index */ - char getColorChar(int type) const + GradientType getGradientType(int type) const { - return mColors[type].ch; + return mColors[type].grad; } /** * Gets the gradient delay for the specified type. * * @param type the color type of the color - * * @return the gradient delay of the color with the given index */ int getGradientDelay(int type) const @@ -128,10 +121,6 @@ class Palette using Palettes = std::set<Palette *>; static Palettes mInstances; - Palette(int size); - - ~Palette(); - void advanceGradient(int advance); struct ColorElem @@ -140,8 +129,8 @@ class Palette gcn::Color color; gcn::Color testColor; gcn::Color committedColor; + std::optional<gcn::Color> outlineColor; std::string text; - char ch; GradientType grad; GradientType committedGrad; int gradientIndex; diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp index 4bafc074..16b16af9 100644 --- a/src/gui/popupmenu.cpp +++ b/src/gui/popupmenu.cpp @@ -318,7 +318,7 @@ void PopupMenu::handleLink(const std::string &link) // Unknown actions else if (link != "cancel") { - logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str()); + Log::info("PopupMenu: Warning, unknown action '%s'", link.c_str()); } setVisible(false); diff --git a/src/gui/questswindow.cpp b/src/gui/questswindow.cpp new file mode 100644 index 00000000..6769815e --- /dev/null +++ b/src/gui/questswindow.cpp @@ -0,0 +1,290 @@ +/* + * The Mana Client + * Copyright (C) 2025 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "questswindow.h" + +#include "configuration.h" + +#include "gui/setup.h" + +#include "gui/widgets/browserbox.h" +#include "gui/widgets/checkbox.h" +#include "gui/widgets/itemlinkhandler.h" +#include "gui/widgets/layout.h" +#include "gui/widgets/listbox.h" +#include "gui/widgets/scrollarea.h" + +#include "net/net.h" +#include "net/playerhandler.h" + +#include "resources/questdb.h" + +#include "utils/gettext.h" + +#include <guichan/font.hpp> + +#include <algorithm> + +class QuestsModel final : public gcn::ListModel +{ +public: + int getNumberOfElements() override + { return mQuests.size(); } + + std::string getElementAt(int i) override + { return mQuests[i].name(); } + + const std::vector<QuestEntry> &getQuests() const + { return mQuests; } + + void setQuests(const std::vector<QuestEntry> &quests) + { mQuests = quests; } + +private: + std::vector<QuestEntry> mQuests; +}; + + +class QuestsListBox final : public ListBox +{ +public: + QuestsListBox(QuestsModel *model) + : ListBox(model) + {} + + unsigned getRowHeight() const override; + + void draw(gcn::Graphics *graphics) override; +}; + +unsigned QuestsListBox::getRowHeight() const +{ + auto rowHeight = ListBox::getRowHeight(); + + if (auto icon = gui->getTheme()->getIcon("complete")) + rowHeight = std::max<unsigned>(rowHeight, icon->getHeight() + 2); + + return rowHeight; +} + +void QuestsListBox::draw(gcn::Graphics *gcnGraphics) +{ + if (!mListModel) + return; + + auto *graphics = static_cast<Graphics *>(gcnGraphics); + auto *model = static_cast<QuestsModel *>(getListModel()); + + const int rowHeight = getRowHeight(); + + auto theme = gui->getTheme(); + auto completeIcon = theme->getIcon("complete"); + auto incompleteIcon = theme->getIcon("incomplete"); + + // Draw filled rectangle around the selected list element + if (mSelected >= 0) + { + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(highlightColor); + graphics->fillRectangle(gcn::Rectangle(0, rowHeight * mSelected, + getWidth(), rowHeight)); + } + + // Draw the list elements + graphics->setFont(getFont()); + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + const int fontHeight = getFont()->getHeight(); + + for (int i = 0, y = 0; i < model->getNumberOfElements(); + ++i, y += rowHeight) + { + if (mSelected == i) + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT)); + else + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + + auto &quest = model->getQuests()[i]; + int x = 1; + + if (const Image *icon = quest.completed ? completeIcon : incompleteIcon) + { + graphics->drawImage(icon, x, y + (rowHeight - icon->getHeight()) / 2); + x += icon->getWidth() + 4; + } + + graphics->drawText(quest.name(), x, y + (rowHeight - fontHeight) / 2); + } +} + + +QuestsWindow::QuestsWindow() + : Window(_("Quests")) + , mQuestsModel(std::make_unique<QuestsModel>()) + , mQuestsListBox(new QuestsListBox(mQuestsModel.get())) + , mHideCompletedCheckBox(new CheckBox(_("Hide completed"), config.hideCompletedQuests)) + , mQuestDetails(new BrowserBox(BrowserBox::AUTO_WRAP)) + , mLinkHandler(std::make_unique<ItemLinkHandler>()) +{ + setWindowName("Quests"); + setupWindow->registerWindowForReset(this); + setResizable(true); + setCloseButton(true); + setSaveVisible(true); + + setDefaultSize(387, 307, WindowAlignment::Center); + setMinWidth(316); + setMinHeight(179); + + mQuestsListBox->addSelectionListener(this); + mHideCompletedCheckBox->setActionEventId("hideCompleted"); + mHideCompletedCheckBox->addActionListener(this); + + auto questListScrollArea = new ScrollArea(mQuestsListBox); + questListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + mQuestDetails->setLinkHandler(mLinkHandler.get()); + mQuestDetailsScrollArea = new ScrollArea(mQuestDetails); + mQuestDetailsScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); + + auto place = getPlacer(0, 0); + place(0, 0, questListScrollArea, 2, 2).setPadding(2); + place(2, 0, mQuestDetailsScrollArea, 3, 2).setPadding(2); + place = getPlacer(0, 1); + place(0, 0, mHideCompletedCheckBox); + + getLayout().setRowHeight(1, 0); // Don't scale up the bottom row + + listen(Event::QuestsChannel); + + refreshQuestList(); + loadWindowState(); +} + +QuestsWindow::~QuestsWindow() = default; + +void QuestsWindow::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "hideCompleted") + { + config.hideCompletedQuests = mHideCompletedCheckBox->isSelected(); + refreshQuestList(); + } +} + +void QuestsWindow::valueChanged(const gcn::SelectionEvent &event) +{ + if (mSelectedQuestIndex != mQuestsListBox->getSelected()) + updateQuestDetails(); +} + +void QuestsWindow::event(Event::Channel channel, const Event &event) +{ + if (channel == Event::QuestsChannel) + { + if (event.getType() == Event::QuestVarsChanged) + refreshQuestList(); + } +} + +void QuestsWindow::refreshQuestList() +{ + // Store the currently selected quest state and varId to preserve selection + const QuestState *selectedQuestState = nullptr; + int selectedVarId = -1; + if (mSelectedQuestIndex >= 0 && mSelectedQuestIndex < mQuestsModel->getNumberOfElements()) + { + const auto &selectedQuest = mQuestsModel->getQuests().at(mSelectedQuestIndex); + selectedQuestState = selectedQuest.state; + selectedVarId = selectedQuest.varId; + } + + auto &questVars = Net::getPlayerHandler()->getQuestVars(); + auto newQuests = QuestDB::getQuestsEntries(questVars, config.hideCompletedQuests); + + // Put completed quests at the top + std::stable_sort(newQuests.begin(), newQuests.end(), [](const QuestEntry &a, const QuestEntry &b) { + return a.completed > b.completed; + }); + + mQuestsModel->setQuests(newQuests); + + if (!selectedQuestState) + return; + + // Try to find and reselect the same quest, preferring exact state match + int newSelectedIndex = -1; + + for (int i = 0; i < static_cast<int>(newQuests.size()); ++i) + { + if (newQuests[i].state == selectedQuestState) + { + newSelectedIndex = i; + break; + } + else if (newSelectedIndex == -1 && newQuests[i].varId == selectedVarId) + { + newSelectedIndex = i; + // Don't break here - continue looking for exact state match + } + } + + if (mSelectedQuestIndex != newSelectedIndex) + mQuestsListBox->setSelected(newSelectedIndex); + else + updateQuestDetails(); +} + +void QuestsWindow::updateQuestDetails() +{ + mQuestDetails->clearRows(); + + mSelectedQuestIndex = mQuestsListBox->getSelected(); + if (mSelectedQuestIndex < 0 || mSelectedQuestIndex >= mQuestsModel->getNumberOfElements()) + return; + + const QuestEntry &quest = mQuestsModel->getQuests().at(mSelectedQuestIndex); + for (const auto &row : quest.rows()) + { + switch (row.type) + { + case QuestRowType::Text: + mQuestDetails->addRow(row.text); + break; + case QuestRowType::Name: + mQuestDetails->addRow("[" + row.text + "]"); + break; + case QuestRowType::Reward: + mQuestDetails->addRow(strprintf(_("Reward: %s"), row.text.c_str())); + break; + case QuestRowType::Giver: + mQuestDetails->addRow(strprintf(_("Quest Giver: %s"), row.text.c_str())); + break; + case QuestRowType::Coordinates: + mQuestDetails->addRow(strprintf(_("Coordinates: %s (%d, %d)"), + row.text.c_str(), row.x, row.y)); + break; + case QuestRowType::NPC: + mQuestDetails->addRow(strprintf(_("NPC: %s"), row.text.c_str())); + break; + } + } +} diff --git a/src/gui/questswindow.h b/src/gui/questswindow.h new file mode 100644 index 00000000..a479f826 --- /dev/null +++ b/src/gui/questswindow.h @@ -0,0 +1,71 @@ +/* + * The Mana Client + * Copyright (C) 2025 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "eventlistener.h" + +#include "gui/widgets/window.h" + +#include <guichan/actionlistener.hpp> +#include <guichan/keylistener.hpp> +#include <guichan/selectionlistener.hpp> + +class BrowserBox; +class CheckBox; +class LinkHandler; +class QuestsListBox; +class QuestsModel; +class ScrollArea; + +/** + * Quests window. + * + * \ingroup Interface + */ +class QuestsWindow final : public Window, + public gcn::ActionListener, + public gcn::SelectionListener, + public EventListener +{ +public: + QuestsWindow(); + ~QuestsWindow(); + + void action(const gcn::ActionEvent &event) override; + + void valueChanged(const gcn::SelectionEvent &event) override; + + void event(Event::Channel channel, const Event &event) override; + +private: + void refreshQuestList(); + void updateQuestDetails(); + + int mSelectedQuestIndex = -1; + std::unique_ptr<QuestsModel> mQuestsModel; + QuestsListBox *mQuestsListBox; + CheckBox *mHideCompletedCheckBox; + BrowserBox *mQuestDetails; + ScrollArea *mQuestDetailsScrollArea; + std::unique_ptr<LinkHandler> mLinkHandler; +}; + +extern QuestsWindow *questsWindow; diff --git a/src/gui/recorder.cpp b/src/gui/recorder.cpp index 894e3631..96e458ff 100644 --- a/src/gui/recorder.cpp +++ b/src/gui/recorder.cpp @@ -47,7 +47,7 @@ Recorder::Recorder(ChatWindow *chat, // 123 is the default chat window height. If you change this in Chat, please // change it here as well setDefaultSize(button->getWidth() + offsetX, button->getHeight() + - offsetY, ImageRect::LOWER_LEFT, 0, 123); + offsetY, WindowAlignment::BottomLeft, 0, 123); place(0, 0, button); diff --git a/src/gui/register.cpp b/src/gui/register.cpp index 62114c10..d7924021 100644 --- a/src/gui/register.cpp +++ b/src/gui/register.cpp @@ -147,7 +147,7 @@ void RegisterDialog::action(const gcn::ActionEvent &event) else if (event.getId() == "register" && canSubmit()) { const std::string user = mUserField->getText(); - logger->log("RegisterDialog::register Username is %s", user.c_str()); + Log::info("RegisterDialog::register Username is %s", user.c_str()); std::string errorMessage; int error = 0; diff --git a/src/gui/selldialog.cpp b/src/gui/selldialog.cpp index 4aeacd6f..4fa12e53 100644 --- a/src/gui/selldialog.cpp +++ b/src/gui/selldialog.cpp @@ -56,7 +56,7 @@ SellDialog::SellDialog(int npcId): setCloseButton(true); setMinWidth(260); setMinHeight(230); - setDefaultSize(260, 230, ImageRect::CENTER); + setDefaultSize(260, 230, WindowAlignment::Center); // Create a ShopItems instance, that is aware of duplicate entries. mShopItems = new ShopItems(true); diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp index d86d751a..e41c0bbe 100644 --- a/src/gui/serverdialog.cpp +++ b/src/gui/serverdialog.cpp @@ -95,20 +95,19 @@ public: auto *model = static_cast<ServersListModel*>(mListModel); - const int alpha = gui->getTheme()->getGuiAlpha(); - - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); const int height = getRowHeight(); - const gcn::Color unsupported = - Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED, - alpha); // Draw filled rectangle around the selected list element if (mSelected >= 0) + { + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(highlightColor); graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, getWidth(), height)); + } // Draw the list elements for (int i = 0, y = 0; i < model->getNumberOfElements(); @@ -116,7 +115,10 @@ public: { const ServerInfo &info = model->getServer(i); - graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + if (mSelected == i) + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT)); + else + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); if (!info.name.empty()) { @@ -132,7 +134,9 @@ public: if (info.version.first > 0) { - graphics->setColor(unsupported); + auto unsupportedColor = Theme::getThemeColor(Theme::SERVER_VERSION_NOT_SUPPORTED); + unsupportedColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(unsupportedColor); graphics->drawText(info.version.second, getWidth() - info.version.first - 2, top); } @@ -200,7 +204,7 @@ ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir): setMinWidth(getWidth()); setMinHeight(getHeight()); - setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER); + setDefaultSize(getWidth(), getHeight(), WindowAlignment::Center); setResizable(true); addKeyListener(this); @@ -232,7 +236,7 @@ void ServerDialog::action(const gcn::ActionEvent &event) // Check login if (index < 0 #ifndef MANASERV_SUPPORT - || mServersListModel->getServer(index).type == ServerType::MANASERV + || mServersListModel->getServer(index).type == ServerType::ManaServ #endif ) { @@ -360,7 +364,7 @@ void ServerDialog::logic() case DownloadStatus::Canceled: case DownloadStatus::Error: mDownloadDone = true; - logger->log("Error retrieving server list: %s", mDownload->getError()); + Log::info("Error retrieving server list: %s", mDownload->getError()); mDownloadText->setCaption(_("Error retrieving server list!")); break; @@ -405,15 +409,14 @@ void ServerDialog::loadServers() if (!rootNode || rootNode.name() != "serverlist") { - logger->log("Error loading server list!"); + Log::info("Error loading server list!"); return; } int version = rootNode.getProperty("version", 0); if (version != 1) { - logger->log("Error: unsupported online server list version: %d", - version); + Log::error("Unsupported online server list version: %d", version); return; } @@ -435,12 +438,12 @@ void ServerDialog::loadServer(XML::Node serverNode) // Ignore unknown server types if (server.type == ServerType::Unknown #ifndef MANASERV_SUPPORT - || server.type == ServerType::MANASERV + || server.type == ServerType::ManaServ #endif ) { - logger->log("Ignoring server entry with unknown type: %s", - type.c_str()); + Log::info("Ignoring server entry with unknown type: %s", + type.c_str()); return; } @@ -510,13 +513,8 @@ void ServerDialog::loadServer(XML::Node serverNode) void ServerDialog::loadCustomServers() { for (auto &server : config.servers) - { if (server.isValid()) - { - server.save = true; mServers.push_back(server); - } - } } void ServerDialog::saveCustomServers(const ServerInfo ¤tServer, int index) diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp index 43b132d8..0031f9bc 100644 --- a/src/gui/setup_audio.cpp +++ b/src/gui/setup_audio.cpp @@ -108,7 +108,7 @@ void Setup_Audio::apply() catch (const char *err) { new OkDialog(_("Sound Engine"), err); - logger->log("Warning: %s", err); + Log::warn("%s", err); } } else diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp index c3d6a8c8..52cb4bf7 100644 --- a/src/gui/setup_colors.cpp +++ b/src/gui/setup_colors.cpp @@ -73,14 +73,21 @@ Setup_Colors::Setup_Colors() : mGradTypeText = new Label; - std::string longText = _("Static"); - - if (getFont()->getWidth(_("Pulse")) > getFont()->getWidth(longText)) - longText = _("Pulse"); - if (getFont()->getWidth(_("Rainbow")) > getFont()->getWidth(longText)) - longText = _("Rainbow"); - if (getFont()->getWidth(_("Spectrum")) > getFont()->getWidth(longText)) - longText = _("Spectrum"); + // Initialize with widest label for layout purposes + const char *longText = _("Static"); + int longWidth = getFont()->getWidth(longText); + + auto maybeLonger = [&] (const char *text) { + const int width = getFont()->getWidth(text); + if (width > longWidth) + { + longText = text; + longWidth = width; + } + }; + maybeLonger(_("Pulse")); + maybeLonger(_("Rainbow")); + maybeLonger(_("Spectrum")); mGradTypeText->setCaption(longText); @@ -180,8 +187,8 @@ void Setup_Colors::action(const gcn::ActionEvent &event) { if (event.getId() == "slider_grad") { - updateGradType(); updateColor(); + updateGradType(); return; } @@ -214,10 +221,10 @@ void Setup_Colors::action(const gcn::ActionEvent &event) } } -void Setup_Colors::valueChanged(const gcn::SelectionEvent &event) +void Setup_Colors::valueChanged(const gcn::SelectionEvent &) { mSelected = mColorBox->getSelected(); - int type = userPalette->getColorTypeAt(mSelected); + const int type = userPalette->getColorTypeAt(mSelected); const gcn::Color *col = &userPalette->getColor(type); Palette::GradientType grad = userPalette->getGradientType(type); const int delay = userPalette->getGradientDelay(type); @@ -226,11 +233,7 @@ void Setup_Colors::valueChanged(const gcn::SelectionEvent &event) mPreviewBox->setContent(mTextPreview); mTextPreview->setFont(boldFont); mTextPreview->setTextColor(col); - mTextPreview->setTextBGColor(nullptr); - mTextPreview->setOpaque(false); - mTextPreview->setShadow(true); mTextPreview->setOutline(true); - mTextPreview->useTextAlpha(false); switch (type) { @@ -242,6 +245,10 @@ void Setup_Colors::valueChanged(const gcn::SelectionEvent &event) case UserPalette::HIT_CRITICAL: case UserPalette::MISS: mTextPreview->setShadow(false); + break; + default: + mTextPreview->setShadow(true); + break; } if (grad != Palette::STATIC && grad != Palette::PULSE) diff --git a/src/gui/setup_interface.cpp b/src/gui/setup_interface.cpp index 7eef974c..ce77d3e6 100644 --- a/src/gui/setup_interface.cpp +++ b/src/gui/setup_interface.cpp @@ -23,12 +23,15 @@ #include "configuration.h" +#include "gui/okdialog.h" #include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/slider.h" #include "gui/widgets/spacer.h" -#include "gui/widgets/dropdown.h" + +#include "resources/theme.h" #include "utils/gettext.h" @@ -37,8 +40,35 @@ #include <SDL.h> +#include <algorithm> #include <string> +class ThemesListModel : public gcn::ListModel +{ +public: + int getNumberOfElements() override + { + return gui->getAvailableThemes().size(); + } + + std::string getElementAt(int i) override + { + return gui->getAvailableThemes().at(i).getName(); + } + + static int getThemeIndex(const std::string &path) + { + auto &themes = gui->getAvailableThemes(); + auto themeIt = std::find_if(themes.begin(), + themes.end(), + [&](const ThemeInfo &theme) { + return theme.getPath() == path; + }); + return themeIt != themes.end() ? std::distance(themes.begin(), themeIt) : 0; + } +}; + + const char *SIZE_NAME[4] = { N_("Tiny"), @@ -97,8 +127,7 @@ Setup_Interface::Setup_Interface(): mPickupParticleEnabled)), mSpeechSlider(new Slider(0, 3)), mSpeechLabel(new Label(std::string())), - mAlphaSlider(new Slider(0.2, 1.0)), - mFontSize(config.fontSize) + mAlphaSlider(new Slider(0.2, 1.0)) { setName(_("Interface")); @@ -108,18 +137,21 @@ Setup_Interface::Setup_Interface(): mShowMonsterDamageCheckBox = new CheckBox(_("Show damage"), mShowMonsterDamageEnabled); - speechLabel = new Label(_("Overhead text:")); - alphaLabel = new Label(_("GUI opacity")); - fontSizeLabel = new Label(_("Font size:")); + gcn::Label *speechLabel = new Label(_("Overhead text:")); + gcn::Label *alphaLabel = new Label(_("GUI opacity")); + gcn::Label *themeLabel = new Label(_("Theme:")); + gcn::Label *fontSizeLabel = new Label(_("Font size:")); + + mThemesListModel = std::make_unique<ThemesListModel>(); + mThemeDropDown = new DropDown(mThemesListModel.get()); - mFontSizeListModel = new FontSizeChoiceListModel; - mFontSizeDropDown = new DropDown(mFontSizeListModel); + mFontSizeListModel = std::make_unique<FontSizeChoiceListModel>(); + mFontSizeDropDown = new DropDown(mFontSizeListModel.get()); mAlphaSlider->setValue(mOpacity); mAlphaSlider->setWidth(90); mAlphaSlider->setEnabled(!config.disableTransparency); - // Set actions mShowMonsterDamageCheckBox->setActionEventId("monsterdamage"); mVisibleNamesCheckBox->setActionEventId("visiblenames"); @@ -127,6 +159,7 @@ Setup_Interface::Setup_Interface(): mPickupParticleCheckBox->setActionEventId("pickupparticle"); mNameCheckBox->setActionEventId("showownname"); mNPCLogCheckBox->setActionEventId("lognpc"); + mThemeDropDown->setActionEventId("theme"); mAlphaSlider->setActionEventId("guialpha"); mSpeechSlider->setActionEventId("speech"); @@ -137,13 +170,16 @@ Setup_Interface::Setup_Interface(): mPickupParticleCheckBox->addActionListener(this); mNameCheckBox->addActionListener(this); mNPCLogCheckBox->addActionListener(this); + mThemeDropDown->addActionListener(this); mAlphaSlider->addActionListener(this); mSpeechSlider->addActionListener(this); mSpeechLabel->setCaption(speechModeToString(mSpeechMode)); mSpeechSlider->setValue(mSpeechMode); - mFontSizeDropDown->setSelected(mFontSize - 10); + mThemeDropDown->setSelected(ThemesListModel::getThemeIndex(config.theme)); + + mFontSizeDropDown->setSelected(config.fontSize - 10); mFontSizeDropDown->adjustHeight(); // Do the layout @@ -162,27 +198,35 @@ Setup_Interface::Setup_Interface(): place(0, 5, space, 1, 1); - place(0, 6, fontSizeLabel, 2); - place(2, 6, mFontSizeDropDown, 2); + place(0, 6, themeLabel, 2); + place(2, 6, mThemeDropDown, 2).setPadding(2); - place(0, 7, space, 1, 1); + place(0, 7, fontSizeLabel, 2); + place(2, 7, mFontSizeDropDown, 2).setPadding(2); - place(0, 8, mAlphaSlider, 2); - place(2, 8, alphaLabel, 2); + place(0, 8, space, 1, 1); - place(0, 9, mSpeechSlider, 2); - place(2, 9, speechLabel, 2); - place(4, 9, mSpeechLabel, 2).setPadding(2); -} + place(0, 9, mAlphaSlider, 2); + place(2, 9, alphaLabel, 2); -Setup_Interface::~Setup_Interface() -{ - delete mFontSizeListModel; + place(0, 10, mSpeechSlider, 2); + place(2, 10, speechLabel, 2); + place(4, 10, mSpeechLabel, 2).setPadding(2); } +Setup_Interface::~Setup_Interface() = default; + void Setup_Interface::apply() { - config.fontSize = mFontSizeDropDown->getSelected() + 10; + auto &theme = gui->getAvailableThemes().at(mThemeDropDown->getSelected()); + auto fontSize = mFontSizeDropDown->getSelected() + 10; + if (config.theme != theme.getPath() || config.fontSize != fontSize) + { + new OkDialog(_("Changing Theme or Font Size"), + _("Theme and font size changes will apply after restart.")); + } + config.theme = theme.getPath(); + config.fontSize = fontSize; mShowMonsterDamageEnabled = config.showMonstersTakedDamage; mVisibleNamesEnabled = config.visibleNames; @@ -201,6 +245,8 @@ void Setup_Interface::cancel() mSpeechSlider->setValue(mSpeechMode); mNameCheckBox->setSelected(mNameEnabled); mNPCLogCheckBox->setSelected(mNPCLogEnabled); + mThemeDropDown->setSelected(ThemesListModel::getThemeIndex(config.theme)); + mFontSizeDropDown->setSelected(config.fontSize - 10); mAlphaSlider->setValue(mOpacity); //mAlphaSlider->setEnabled(!mSDLTransparencyDisabled); diff --git a/src/gui/setup_interface.h b/src/gui/setup_interface.h index 9f5bbf1a..027526a2 100644 --- a/src/gui/setup_interface.h +++ b/src/gui/setup_interface.h @@ -29,8 +29,6 @@ #include <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> -class FontSizeChoiceListModel; - class Setup_Interface : public SetupTab, public gcn::ActionListener, public gcn::KeyListener { @@ -53,11 +51,8 @@ class Setup_Interface : public SetupTab, public gcn::ActionListener, double mOpacity; Being::Speech mSpeechMode; - FontSizeChoiceListModel *mFontSizeListModel; - - gcn::Label *speechLabel; - gcn::Label *alphaLabel; - gcn::Label *fontSizeLabel; + std::unique_ptr<gcn::ListModel> mThemesListModel; + std::unique_ptr<gcn::ListModel> mFontSizeListModel; gcn::CheckBox *mShowMonsterDamageCheckBox; gcn::CheckBox *mVisibleNamesCheckBox; @@ -72,7 +67,7 @@ class Setup_Interface : public SetupTab, public gcn::ActionListener, gcn::Label *mSpeechLabel; gcn::Slider *mAlphaSlider; - int mFontSize; + gcn::DropDown *mThemeDropDown; gcn::DropDown *mFontSizeDropDown; }; diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp index b5322525..313badd8 100644 --- a/src/gui/setup_video.cpp +++ b/src/gui/setup_video.cpp @@ -25,13 +25,13 @@ #include "configuration.h" #include "game.h" #include "graphics.h" -#include "gui/widgets/dropdown.h" #include "localplayer.h" #include "particle.h" #include "gui/okdialog.h" #include "gui/widgets/checkbox.h" +#include "gui/widgets/dropdown.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" #include "gui/widgets/slider.h" diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp index 82c7678e..c33fcf01 100644 --- a/src/gui/shortcutwindow.cpp +++ b/src/gui/shortcutwindow.cpp @@ -31,6 +31,7 @@ static constexpr int GRAB_MARGIN = 4; ShortcutWindow::ShortcutWindow(const std::string &title, ShortcutContainer *content) + : Window(SkinType::ToolWindow, std::string()) { setWindowName(title); // no title presented, title bar gets some extra space so window can be moved. @@ -48,10 +49,13 @@ ShortcutWindow::ShortcutWindow(const std::string &title, const int border = (getPadding() + content->getFrameSize()) * 2; setMinWidth(content->getBoxWidth() + border); setMinHeight(content->getBoxHeight() + border + GRAB_MARGIN); - setMaxWidth(content->getBoxWidth() * content->getMaxItems() + border); - setMaxHeight(content->getBoxHeight() * content->getMaxItems() + border + GRAB_MARGIN); - setDefaultSize(getMinWidth(), getMaxHeight(), ImageRect::LOWER_RIGHT); + const int maxContentWidth = content->getBoxWidth() * content->getMaxItems(); + const int maxContentHeight = content->getBoxHeight() * content->getMaxItems(); + setMaxWidth(std::max(getMinWidth(), maxContentWidth + border)); + setMaxHeight(std::max(getMinHeight(), maxContentHeight + border + GRAB_MARGIN)); + + setDefaultSize(getMinWidth(), getMaxHeight(), WindowAlignment::BottomRight); place(0, 0, scrollArea, 5, 5).setPadding(0); diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp index c1911ac5..49552421 100644 --- a/src/gui/skilldialog.cpp +++ b/src/gui/skilldialog.cpp @@ -134,24 +134,28 @@ public: auto *model = static_cast<SkillModel *>(mListModel); auto *graphics = static_cast<Graphics *>(gcnGraphics); - const int alpha = gui->getTheme()->getGuiAlpha(); - - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); // Draw filled rectangle around the selected list element if (mSelected >= 0) { + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(highlightColor); graphics->fillRectangle(gcn::Rectangle(0, getRowHeight() * mSelected, getWidth(), getRowHeight())); } // Draw the list elements - graphics->setColor(Theme::getThemeColor(Theme::TEXT)); for (int i = 0, y = 1; i < model->getNumberOfElements(); ++i, y += getRowHeight()) { + if (mSelected == i) + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT)); + else + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + if (SkillInfo *e = model->getSkillAt(i)) e->draw(graphics, y, getWidth()); } @@ -290,7 +294,7 @@ void SkillDialog::loadSkills() if (!root || root.name() != "skills") { - logger->log("Error loading skills file: %s", SKILLS_FILE); + Log::info("Error loading skills file: %s", SKILLS_FILE); if (Net::getNetworkType() == ServerType::TmwAthena) { diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp index 072d87bc..265fb166 100644 --- a/src/gui/socialwindow.cpp +++ b/src/gui/socialwindow.cpp @@ -171,7 +171,7 @@ public: { setCaption(party->getName()); - setTabColor(&Theme::getThemeColor(Theme::PARTY_SOCIAL_TAB)); + setTabColor(&Theme::getThemeColor(Theme::PARTY_TAB)); mList = std::make_unique<AvatarListBox>(party); mScroll = std::make_unique<ScrollArea>(mList.get()); diff --git a/src/gui/tradewindow.cpp b/src/gui/tradewindow.cpp index db4b6490..6610bd43 100644 --- a/src/gui/tradewindow.cpp +++ b/src/gui/tradewindow.cpp @@ -24,7 +24,6 @@ #include "event.h" #include "inventory.h" #include "item.h" -#include "localplayer.h" #include "playerinfo.h" #include "units.h" @@ -39,7 +38,6 @@ #include "gui/widgets/textfield.h" #include "gui/widgets/layout.h" -#include "net/inventoryhandler.h" #include "net/net.h" #include "net/tradehandler.h" @@ -48,8 +46,6 @@ #include <guichan/font.hpp> -#include <sstream> - #define CAPTION_PROPOSE _("Propose trade") #define CAPTION_CONFIRMED _("Confirmed. Waiting...") #define CAPTION_ACCEPT _("Agree trade") @@ -64,7 +60,7 @@ TradeWindow::TradeWindow(): setWindowName("Trade"); setResizable(true); setCloseButton(true); - setDefaultSize(386, 180, ImageRect::CENTER); + setDefaultSize(386, 180, WindowAlignment::Center); setMinWidth(386); setMinHeight(180); setupWindow->registerWindowForReset(this); @@ -125,9 +121,7 @@ TradeWindow::TradeWindow(): reset(); } -TradeWindow::~TradeWindow() -{ -} +TradeWindow::~TradeWindow() = default; void TradeWindow::setMoney(int amount) { @@ -138,7 +132,10 @@ void TradeWindow::setMoney(int amount) void TradeWindow::addItem(int id, bool own, int quantity) { - (own ? mMyInventory : mPartnerInventory)->addItem(id, quantity); + if (own) + mMyInventory->addItem(id, quantity); + else + mPartnerInventory->addItem(id, quantity); } void TradeWindow::changeQuantity(int index, bool own, int quantity) @@ -186,22 +183,15 @@ void TradeWindow::receivedOk(bool own) } } -void TradeWindow::tradeItem(Item *item, int quantity) -{ - Net::getTradeHandler()->addItem(item, quantity); -} - void TradeWindow::valueChanged(const gcn::SelectionEvent &event) { - const Item *item; - /* If an item is selected in one container, make sure no item is selected * in the other container. */ if (event.getSource() == mMyItemContainer && - (item = mMyItemContainer->getSelectedItem())) + mMyItemContainer->getSelectedItem()) mPartnerItemContainer->selectNone(); - else if ((item = mPartnerItemContainer->getSelectedItem())) + else if (mPartnerItemContainer->getSelectedItem()) mMyItemContainer->selectNone(); } diff --git a/src/gui/tradewindow.h b/src/gui/tradewindow.h index d54ec35e..85b2f97d 100644 --- a/src/gui/tradewindow.h +++ b/src/gui/tradewindow.h @@ -81,11 +81,6 @@ class TradeWindow : public Window, gcn::ActionListener, gcn::SelectionListener void receivedOk(bool own); /** - * Send trade packet. - */ - void tradeItem(Item *item, int quantity); - - /** * Updates the labels and makes sure only one item is selected in * either my inventory or partner inventory. */ diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp index 444641b5..2dd7b817 100644 --- a/src/gui/truetypefont.cpp +++ b/src/gui/truetypefont.cpp @@ -45,13 +45,6 @@ static const char *getSafeUtf8String(const std::string &text) return buf; } -bool operator==(SDL_Color lhs, SDL_Color rhs) -{ - return (lhs.r == rhs.r && - lhs.g == rhs.g && - lhs.b == rhs.b && - lhs.a == rhs.a); -} class TextChunk { @@ -60,24 +53,49 @@ public: : text(text) {} - void generate(TTF_Font *font) + void render(Graphics *graphics, + int x, int y, + TTF_Font *font, + std::unique_ptr<Image> &img, + float scale); + + const std::string text; + std::unique_ptr<Image> regular; + std::unique_ptr<Image> outlined; +}; + +void TextChunk::render(Graphics *graphics, + int x, int y, + TTF_Font *font, + std::unique_ptr<Image> &img, + float scale) +{ + if (!img) { // Always render in white, we'll use color modulation when rendering + constexpr SDL_Color white = { 255, 255, 255, 255 }; SDL_Surface *surface = TTF_RenderUTF8_Blended(font, getSafeUtf8String(text), - SDL_Color { 255, 255, 255, 255 }); + white); - if (!surface) - return; + if (surface) + { + img.reset(Image::load(surface)); + SDL_FreeSurface(surface); + } + } - img.reset(Image::load(surface)); + if (img) + { + graphics->drawRescaledImageF(img.get(), 0, 0, x, y, + img->getWidth(), + img->getHeight(), + img->getWidth() / scale, + img->getHeight() / scale, true); - SDL_FreeSurface(surface); } +} - std::unique_ptr<Image> img; - const std::string text; -}; std::list<TrueTypeFont*> TrueTypeFont::mFonts; float TrueTypeFont::mScale = 1.0f; @@ -85,6 +103,7 @@ float TrueTypeFont::mScale = 1.0f; TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style) : mFilename(filename) , mPointSize(size) + , mStyle(style) { if (TTF_Init() == -1) { @@ -93,14 +112,18 @@ TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style) } mFont = TTF_OpenFont(filename.c_str(), size * mScale); + mFontOutline = TTF_OpenFont(filename.c_str(), size * mScale); - if (!mFont) + if (!mFont || !mFontOutline) { throw GCN_EXCEPTION("SDLTrueTypeFont::SDLTrueTypeFont: " + std::string(TTF_GetError())); } TTF_SetFontStyle(mFont, style); + TTF_SetFontStyle(mFontOutline, style); + + TTF_SetFontOutline(mFontOutline, static_cast<int>(mScale)); mFonts.push_back(this); } @@ -112,6 +135,9 @@ TrueTypeFont::~TrueTypeFont() if (mFont) TTF_CloseFont(mFont); + if (mFontOutline) + TTF_CloseFont(mFontOutline); + TTF_Quit(); } @@ -123,37 +149,41 @@ void TrueTypeFont::drawString(gcn::Graphics *graphics, return; auto *g = static_cast<Graphics *>(graphics); + TextChunk &chunk = getChunk(text); - bool found = false; + chunk.render(g, x, y, mFont, chunk.regular, mScale); +} - for (auto i = mCache.begin(); i != mCache.end(); ++i) - { - auto &chunk = *i; - if (chunk.text == text) - { - // Raise priority: move it to front - mCache.splice(mCache.begin(), mCache, i); - found = true; - break; - } - } +void TrueTypeFont::drawString(Graphics *graphics, + const std::string &text, + int x, int y, + const std::optional<gcn::Color> &outlineColor, + const std::optional<gcn::Color> &shadowColor) +{ + if (text.empty()) + return; + + auto *g = static_cast<Graphics *>(graphics); + auto color = graphics->getColor(); + TextChunk &chunk = getChunk(text); - if (!found) + if (shadowColor) { - if (mCache.size() >= CACHE_SIZE) - mCache.pop_back(); - mCache.emplace_front(text); - mCache.front().generate(mFont); + g->setColor(*shadowColor); + if (outlineColor) + chunk.render(g, x, y, mFontOutline, chunk.outlined, mScale); + else + chunk.render(g, x + 1, y + 1, mFont, chunk.regular, mScale); } - if (auto img = mCache.front().img.get()) + if (outlineColor) { - g->drawRescaledImageF(img, 0, 0, x, y, - img->getWidth(), - img->getHeight(), - img->getWidth() / mScale, - img->getHeight() / mScale, true); + g->setColor(*outlineColor); + chunk.render(g, x - 1, y - 1, mFontOutline, chunk.outlined, mScale); } + + g->setColor(color); + chunk.render(g, x, y, mFont, chunk.regular, mScale); } void TrueTypeFont::updateFontScale(float scale) @@ -167,9 +197,16 @@ void TrueTypeFont::updateFontScale(float scale) { #if SDL_TTF_VERSION_ATLEAST(2, 0, 18) TTF_SetFontSize(font->mFont, font->mPointSize * mScale); + TTF_SetFontSize(font->mFontOutline, font->mPointSize * mScale); + TTF_SetFontOutline(font->mFontOutline, mScale); #else TTF_CloseFont(font->mFont); + TTF_CloseFont(font->mFontOutline); font->mFont = TTF_OpenFont(font->mFilename.c_str(), font->mPointSize * mScale); + font->mFontOutline = TTF_OpenFont(font->mFilename.c_str(), font->mPointSize * mScale); + TTF_SetFontStyle(font->mFont, font->mStyle); + TTF_SetFontStyle(font->mFontOutline, font->mStyle); + TTF_SetFontOutline(font->mFontOutline, mScale); #endif font->mCache.clear(); @@ -178,19 +215,11 @@ void TrueTypeFont::updateFontScale(float scale) int TrueTypeFont::getWidth(const std::string &text) const { - for (auto i = mCache.begin(); i != mCache.end(); i++) - { - if (i->text == text) - { - // Raise priority: move it to front - // Assumption is that TTF::draw will be called next - mCache.splice(mCache.begin(), mCache, i); - if (i->img) - return std::ceil(i->img->getWidth() / mScale); - return 0; - } - } + TextChunk &chunk = getChunk(text); + if (auto img = chunk.regular.get()) + return std::ceil(img->getWidth() / mScale); + // If the image wasn't created yet, just calculate the width of the text int w, h; TTF_SizeUTF8(mFont, getSafeUtf8String(text), &w, &h); return std::ceil(w / mScale); @@ -205,3 +234,21 @@ int TrueTypeFont::getLineHeight() const { return std::ceil(TTF_FontLineSkip(mFont) / mScale); } + +TextChunk &TrueTypeFont::getChunk(const std::string &text) const +{ + for (auto i = mCache.begin(); i != mCache.end(); i++) + { + if (i->text == text) + { + // Raise priority: move it to front + mCache.splice(mCache.begin(), mCache, i); + return *i; + } + } + + if (mCache.size() >= CACHE_SIZE) + mCache.pop_back(); + + return mCache.emplace_front(text); +} diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h index a479537d..f88938ff 100644 --- a/src/gui/truetypefont.h +++ b/src/gui/truetypefont.h @@ -22,13 +22,16 @@ #pragma once +#include <guichan/color.hpp> #include <guichan/font.hpp> #include <SDL_ttf.h> #include <list> +#include <optional> #include <string> +class Graphics; class TextChunk; /** @@ -46,11 +49,13 @@ class TrueTypeFont : public gcn::Font * @param size Font size. */ TrueTypeFont(const std::string &filename, int size, int style = 0); - ~TrueTypeFont() override; - int getWidth(const std::string &text) const override; + const std::string &filename() const { return mFilename; } + int pointSize() const { return mPointSize; } + int style() const { return mStyle; } + int getWidth(const std::string &text) const override; int getHeight() const override; /** @@ -67,13 +72,27 @@ class TrueTypeFont : public gcn::Font const std::string &text, int x, int y) override; + /** + * Extended version of drawString that allows for rendering text with + * outline and/or shadow. + */ + void drawString(Graphics *graphics, + const std::string &text, + int x, int y, + const std::optional<gcn::Color> &outlineColor, + const std::optional<gcn::Color> &shadowColor); + static void updateFontScale(float scale); private: + TextChunk &getChunk(const std::string &text) const; + const std::string mFilename; - TTF_Font *mFont; + TTF_Font *mFont = nullptr; + TTF_Font *mFontOutline = nullptr; const int mPointSize; + const int mStyle; // Word surfaces cache mutable std::list<TextChunk> mCache; diff --git a/src/gui/unregisterdialog.cpp b/src/gui/unregisterdialog.cpp index 94f6ef62..2abbebf7 100644 --- a/src/gui/unregisterdialog.cpp +++ b/src/gui/unregisterdialog.cpp @@ -98,8 +98,8 @@ void UnRegisterDialog::action(const gcn::ActionEvent &event) else if (event.getId() == "unregister") { const std::string &password = mPasswordField->getText(); - logger->log("UnregisterDialog::unregistered, Username is %s", - mLoginData->username.c_str()); + Log::info("UnregisterDialog::unregistered, Username is %s", + mLoginData->username.c_str()); std::stringstream errorMessage; bool error = false; diff --git a/src/gui/updaterwindow.cpp b/src/gui/updaterwindow.cpp index 5cfb45cd..c16c3e04 100644 --- a/src/gui/updaterwindow.cpp +++ b/src/gui/updaterwindow.cpp @@ -60,7 +60,7 @@ std::vector<UpdateFile> loadXMLFile(const std::string &fileName) if (!rootNode || rootNode.name() != "updates") { - logger->log("Error loading update file: %s", fileName.c_str()); + Log::info("Error loading update file: %s", fileName.c_str()); return files; } @@ -110,7 +110,7 @@ std::vector<UpdateFile> loadTxtFile(const std::string &fileName) } else { - logger->log("Error loading update file: %s", fileName.c_str()); + Log::info("Error loading update file: %s", fileName.c_str()); } fileHandler.close(); @@ -128,7 +128,7 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost, { setWindowName("UpdaterWindow"); setResizable(true); - setDefaultSize(450, 400, ImageRect::CENTER); + setDefaultSize(450, 400, WindowAlignment::Center); setMinWidth(320); setMinHeight(240); @@ -258,9 +258,9 @@ void UpdaterWindow::loadUpdates() mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); if (mUpdateFiles.empty()) { - logger->log("Warning this server does not have a" - " %s file falling back to %s", xmlUpdateFile, - txtUpdateFile); + Log::warn("This server does not have a" + " %s file falling back to %s", xmlUpdateFile, + txtUpdateFile); mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile); } } @@ -336,9 +336,9 @@ void UpdaterWindow::downloadCompleted() mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile); if (mUpdateFiles.empty()) { - logger->log("Warning this server does not have a %s" - " file falling back to %s", - xmlUpdateFile, txtUpdateFile); + Log::warn("This server does not have a %s" + " file falling back to %s", + xmlUpdateFile, txtUpdateFile); // If the resources.xml file fails, fall back onto a older version mDialogState = DialogState::DownloadList; @@ -383,7 +383,7 @@ void UpdaterWindow::downloadCompleted() else { fclose(file); - logger->log("%s already here", thisFile.name.c_str()); + Log::info("%s already here", thisFile.name.c_str()); } mUpdateIndex++; } diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp index 9e529063..3d90846e 100644 --- a/src/gui/viewport.cpp +++ b/src/gui/viewport.cpp @@ -278,7 +278,7 @@ void Viewport::_followMouse() mMouseY = static_cast<int>(logicalY); // If the left button is dragged - if (mPlayerFollowMouse && button & SDL_BUTTON(1)) + if (mPlayerFollowMouse && button & SDL_BUTTON_LMASK) { // We create a mouse event and send it to mouseDragged. const Uint8 *keys = SDL_GetKeyboardState(nullptr); diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp index f7d6e801..a5109267 100644 --- a/src/gui/widgets/avatarlistbox.cpp +++ b/src/gui/widgets/avatarlistbox.cpp @@ -32,33 +32,21 @@ #include <guichan/font.hpp> -int AvatarListBox::instances = 0; -ResourceRef<Image> AvatarListBox::onlineIcon; -ResourceRef<Image> AvatarListBox::offlineIcon; - AvatarListBox::AvatarListBox(AvatarListModel *model): ListBox(model) { - instances++; - - if (instances == 1) - { - onlineIcon = Theme::getImageFromTheme("circle-green.png"); - offlineIcon = Theme::getImageFromTheme("circle-gray.png"); - } - setWidth(200); } -AvatarListBox::~AvatarListBox() +unsigned int AvatarListBox::getRowHeight() const { - instances--; + auto rowHeight = ListBox::getRowHeight(); - if (instances == 0) - { - onlineIcon = nullptr; - offlineIcon = nullptr; - } + auto theme = gui->getTheme(); + if (auto onlineIcon = theme->getIcon("online")) + rowHeight = std::max<unsigned>(rowHeight, onlineIcon->getHeight() + 2); + + return rowHeight; } void AvatarListBox::draw(gcn::Graphics *gcnGraphics) @@ -69,29 +57,43 @@ void AvatarListBox::draw(gcn::Graphics *gcnGraphics) auto *model = static_cast<AvatarListModel *>(mListModel); auto *graphics = static_cast<Graphics *>(gcnGraphics); - const int alpha = gui->getTheme()->getGuiAlpha(); - - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); - const int fontHeight = getFont()->getHeight(); + const int rowHeight = getRowHeight(); // Draw filled rectangle around the selected list element if (mSelected >= 0) - graphics->fillRectangle(gcn::Rectangle(0, fontHeight * mSelected, - getWidth(), fontHeight)); + { + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(highlightColor); + graphics->fillRectangle(gcn::Rectangle(0, rowHeight * mSelected, + getWidth(), rowHeight)); + } + + auto theme = gui->getTheme(); + auto onlineIcon = theme->getIcon("online"); + auto offlineIcon = theme->getIcon("offline"); // Draw the list elements - graphics->setColor(Theme::getThemeColor(Theme::TEXT)); for (int i = 0, y = 0; i < model->getNumberOfElements(); - ++i, y += fontHeight) + ++i, y += rowHeight) { + if (mSelected == i) + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT)); + else + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + Avatar *a = model->getAvatarAt(i); + int x = 1; + // Draw online status - Image *icon = a->getOnline() ? onlineIcon : offlineIcon; - if (icon) - graphics->drawImage(icon, 2, y + 1); + if (const Image *icon = a->getOnline() ? onlineIcon : offlineIcon) + { + graphics->drawImage(icon, x, y + (rowHeight - icon->getHeight()) / 2); + x += icon->getWidth() + 4; + } if (a->getDisplayBold()) graphics->setFont(boldFont); @@ -109,7 +111,7 @@ void AvatarListBox::draw(gcn::Graphics *gcnGraphics) } // Draw Name - graphics->drawText(text, 15, y); + graphics->drawText(text, x, y); if (a->getDisplayBold()) graphics->setFont(getFont()); diff --git a/src/gui/widgets/avatarlistbox.h b/src/gui/widgets/avatarlistbox.h index 9b0588ac..1f51935f 100644 --- a/src/gui/widgets/avatarlistbox.h +++ b/src/gui/widgets/avatarlistbox.h @@ -23,7 +23,6 @@ #include "avatar.h" #include "gui/widgets/listbox.h" -#include "resources/resource.h" #include <string> @@ -43,7 +42,7 @@ class AvatarListBox : public ListBox public: AvatarListBox(AvatarListModel *model); - ~AvatarListBox() override; + unsigned int getRowHeight() const override; /** * Draws the list box. @@ -51,9 +50,4 @@ public: void draw(gcn::Graphics *gcnGraphics) override; void mousePressed(gcn::MouseEvent &event) override; - -private: - static int instances; - static ResourceRef<Image> onlineIcon; - static ResourceRef<Image> offlineIcon; }; diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp index 91366720..bf337a0f 100644 --- a/src/gui/widgets/browserbox.cpp +++ b/src/gui/widgets/browserbox.cpp @@ -23,7 +23,6 @@ #include "gui/widgets/browserbox.h" #include "keyboardconfig.h" -#include "textrenderer.h" #include "gui/gui.h" #include "gui/truetypefont.h" @@ -73,9 +72,12 @@ static void replaceKeys(std::string &text) } } + struct LayoutContext { - LayoutContext(gcn::Font *font); + LayoutContext(gcn::Font *font, const Palette &palette); + + LinePart linePart(int x, std::string text); int y = 0; gcn::Font *font; @@ -83,23 +85,39 @@ struct LayoutContext const int minusWidth; const int tildeWidth; int lineHeight; - gcn::Color selColor; const gcn::Color textColor; + const std::optional<gcn::Color> textOutlineColor; + gcn::Color color; + std::optional<gcn::Color> outlineColor; }; -LayoutContext::LayoutContext(gcn::Font *font) +inline LayoutContext::LayoutContext(gcn::Font *font, const Palette &palette) : font(font) , fontHeight(font->getHeight()) , minusWidth(font->getWidth("-")) , tildeWidth(font->getWidth("~")) , lineHeight(fontHeight) - , selColor(Theme::getThemeColor(Theme::TEXT)) - , textColor(Theme::getThemeColor(Theme::TEXT)) + , textColor(palette.getColor(Theme::TEXT)) + , textOutlineColor(palette.getOutlineColor(Theme::TEXT)) + , color(textColor) + , outlineColor(textOutlineColor) { if (auto *trueTypeFont = dynamic_cast<const TrueTypeFont*>(font)) lineHeight = trueTypeFont->getLineHeight(); } +inline LinePart LayoutContext::linePart(int x, std::string text) +{ + return { + x, + y, + color, + outlineColor, + std::move(text), + font + }; +} + BrowserBox::BrowserBox(Mode mode): mMode(mode) @@ -175,7 +193,7 @@ void BrowserBox::addRow(std::string_view row) replaceKeys(newRow.text); // Layout the newly added row - LayoutContext context(getFont()); + LayoutContext context(getFont(), gui->getTheme()->getPalette(mPalette)); context.y = getHeight(); layoutTextRow(newRow, context); @@ -209,7 +227,7 @@ void BrowserBox::addRow(std::string_view row) void BrowserBox::clearRows() { mTextRows.clear(); - setSize(0, 0); + setSize(mMode == AUTO_SIZE ? 0 : getWidth(), 0); mHoveredLink.reset(); maybeRelayoutText(); } @@ -252,19 +270,20 @@ void BrowserBox::draw(gcn::Graphics *graphics) if (mHoveredLink) { + auto &palette = gui->getTheme()->getPalette(mPalette); auto &link = *mHoveredLink; const gcn::Rectangle &rect = link.rect; if (mHighlightMode & BACKGROUND) { - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT)); + graphics->setColor(palette.getColor(Theme::HIGHLIGHT)); graphics->fillRectangle(rect); } if (mHighlightMode & UNDERLINE) { - graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK)); + graphics->setColor(palette.getColor(Theme::HYPERLINK)); graphics->drawLine(rect.x, rect.y + rect.height, rect.x + rect.width, @@ -272,6 +291,8 @@ void BrowserBox::draw(gcn::Graphics *graphics) } } + auto g = static_cast<Graphics*>(graphics); + for (const auto &row : mTextRows) { for (const auto &part : row.parts) @@ -281,15 +302,15 @@ void BrowserBox::draw(gcn::Graphics *graphics) if (part.y > yEnd) return; - TextRenderer::renderText(graphics, - part.text, - part.x, - part.y, - Graphics::LEFT, - part.color, - part.font, - mOutline, - mShadows); + g->drawText(part.text, + part.x, + part.y, + Graphics::LEFT, + part.color, + part.font, + part.outlineColor.has_value() || mOutline, + mShadows, + part.outlineColor); } } } @@ -299,7 +320,7 @@ void BrowserBox::draw(gcn::Graphics *graphics) */ void BrowserBox::relayoutText() { - LayoutContext context(getFont()); + LayoutContext context(getFont(), gui->getTheme()->getPalette(mPalette)); for (auto &row : mTextRows) layoutTextRow(row, context); @@ -317,7 +338,8 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) { // each line starts with normal font in default color context.font = getFont(); - context.selColor = context.textColor; + context.color = context.textColor; + context.outlineColor = context.textOutlineColor; const int startY = context.y; row.parts.clear(); @@ -330,15 +352,7 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) if (startsWith(row.text, "---")) { for (x = 0; x < getWidth(); x += context.minusWidth - 1) - { - row.parts.push_back(LinePart { - x, - context.y, - context.selColor, - "-", - context.font - }); - } + row.parts.push_back(context.linePart(x, "-")); context.y += row.height; @@ -347,7 +361,9 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) return; } - gcn::Color prevColor = context.selColor; + auto &palette = gui->getTheme()->getPalette(mPalette); + auto prevColor = context.color; + auto prevOutlineColor = context.outlineColor; // TODO: Check if we must take texture size limits into account here // TODO: Check if some of the O(n) calls can be removed @@ -371,44 +387,38 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) const char c = row.text.at(start + 2); start += 3; - bool valid; - const gcn::Color &col = Theme::getThemeColor(c, valid); - - if (c == '>') - { - context.selColor = prevColor; - } - else if (c == '<') - { - prevColor = context.selColor; - context.selColor = col; - } - else if (c == 'B') - { - context.font = boldFont; - } - else if (c == 'b') + switch (c) { - context.font = getFont(); - } - else if (valid) - { - context.selColor = col; - } - else switch (c) - { - case '1': context.selColor = RED; break; - case '2': context.selColor = GREEN; break; - case '3': context.selColor = BLUE; break; - case '4': context.selColor = ORANGE; break; - case '5': context.selColor = YELLOW; break; - case '6': context.selColor = PINK; break; - case '7': context.selColor = PURPLE; break; - case '8': context.selColor = GRAY; break; - case '9': context.selColor = BROWN; break; - case '0': - default: - context.selColor = context.textColor; + case '>': + context.color = prevColor; + context.outlineColor = prevOutlineColor; + break; + case '<': + prevColor = context.color; + prevOutlineColor = context.outlineColor; + context.color = palette.getColor(Theme::HYPERLINK); + context.outlineColor = palette.getOutlineColor(Theme::HYPERLINK); + break; + case 'B': + context.font = boldFont; + break; + case 'b': + context.font = getFont(); + break; + default: { + const auto colorId = Theme::getColorIdForChar(c); + if (colorId) + { + context.color = palette.getColor(*colorId); + context.outlineColor = palette.getOutlineColor(*colorId); + } + else + { + context.color = context.textColor; + context.outlineColor = context.textOutlineColor; + } + break; + } } // Update the position of the links @@ -481,7 +491,8 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) row.parts.push_back(LinePart { getWidth() - context.tildeWidth, context.y, - context.selColor, + context.color, + context.outlineColor, "~", getFont() }); @@ -495,14 +506,7 @@ void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context) wrapped = true; } - row.parts.push_back(LinePart { - x, - context.y, - context.selColor, - std::move(part), - context.font - }); - + row.parts.push_back(context.linePart(x, std::move(part))); row.width = std::max(row.width, x + partWidth); if (mMode == AUTO_WRAP && partWidth == 0) diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h index 7066585d..142bba63 100644 --- a/src/gui/widgets/browserbox.h +++ b/src/gui/widgets/browserbox.h @@ -51,6 +51,7 @@ struct LinePart int x; int y; gcn::Color color; + std::optional<gcn::Color> outlineColor; std::string text; gcn::Font *font; }; @@ -86,6 +87,8 @@ class BrowserBox : public gcn::Widget, */ void setLinkHandler(LinkHandler *handler) { mLinkHandler = handler; } + void setPalette(int palette) { mPalette = palette; } + /** * Sets the Highlight mode for links. */ @@ -154,29 +157,6 @@ class BrowserBox : public gcn::Widget, void drawFrame(gcn::Graphics *) override {} /** - * BrowserBox colors. - * - * NOTES (by Javila): - * - color values is "0x" prefix followed by HTML color style. - * - we can add up to 10 different colors: [0..9]. - * - not all colors will be fine with all backgrounds due transparent - * windows and widgets. So, I think it's better keep BrowserBox - * opaque (white background) by default. - */ - enum - { - RED = 0xff0000, /**< Color 1 */ - GREEN = 0x009000, /**< Color 2 */ - BLUE = 0x0000ff, /**< Color 3 */ - ORANGE = 0xe0980e, /**< Color 4 */ - YELLOW = 0xf1dc27, /**< Color 5 */ - PINK = 0xff00d8, /**< Color 6 */ - PURPLE = 0x8415e2, /**< Color 7 */ - GRAY = 0x919191, /**< Color 8 */ - BROWN = 0x8e4c17 /**< Color 9 */ - }; - - /** * Highlight modes for links. * This can be used for a bitmask. */ @@ -195,6 +175,7 @@ class BrowserBox : public gcn::Widget, std::deque<TextRow> mTextRows; LinkHandler *mLinkHandler = nullptr; + int mPalette = 0; Mode mMode; unsigned int mHighlightMode = UNDERLINE | BACKGROUND; int mWrapIndent = 0; diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp index 31c3a677..604f5dc8 100644 --- a/src/gui/widgets/button.cpp +++ b/src/gui/widgets/button.cpp @@ -28,7 +28,6 @@ #include "resources/image.h" #include "resources/theme.h" -#include "textrenderer.h" #include <guichan/exception.hpp> #include <guichan/font.hpp> @@ -132,8 +131,9 @@ void Button::draw(gcn::Graphics *graphics) if (isPressed()) widgetState.flags |= STATE_SELECTED; + auto g = static_cast<Graphics *>(graphics); auto &skin = gui->getTheme()->getSkin(SkinType::Button); - skin.draw(static_cast<Graphics *>(graphics), widgetState); + skin.draw(g, widgetState); auto skinState = skin.getState(widgetState.flags); auto font = (skinState && skinState->textFormat.bold) ? boldFont : getFont(); @@ -196,18 +196,16 @@ void Button::draw(gcn::Graphics *graphics) } if (btnIconWidth) - static_cast<Graphics *>(graphics)->drawImage(icon, btnIconX, btnIconY); + g->drawImage(icon, btnIconX, btnIconY); if (auto skinState = skin.getState(widgetState.flags)) { - auto &textFormat = skinState->textFormat; - TextRenderer::renderText(static_cast<Graphics *>(graphics), - getCaption(), - textX, - textY, - getAlignment(), - font, - textFormat); + g->drawText(getCaption(), + textX, + textY, + getAlignment(), + font, + skinState->textFormat); } } diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp index 485de566..03c3270a 100644 --- a/src/gui/widgets/chattab.cpp +++ b/src/gui/widgets/chattab.cpp @@ -160,7 +160,7 @@ void ChatTab::chatLog(std::string line, Own own, bool ignoreRecord) tmp.nick = strprintf(_("Global announcement from %s:"), tmp.nick.c_str()); tmp.nick += " "; - lineColor = "##1"; // Equiv. to BrowserBox::RED + lineColor = "##g"; } break; case BY_PLAYER: diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h index dfc07638..5232392f 100644 --- a/src/gui/widgets/chattab.h +++ b/src/gui/widgets/chattab.h @@ -24,7 +24,6 @@ #include "gui/chatwindow.h" #include "gui/widgets/tab.h" -#include "gui/widgets/textfield.h" class BrowserBox; class Recorder; diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp index e6079f2f..4bb0bb72 100644 --- a/src/gui/widgets/checkbox.cpp +++ b/src/gui/widgets/checkbox.cpp @@ -21,8 +21,6 @@ #include "gui/widgets/checkbox.h" -#include "textrenderer.h" - #include "gui/gui.h" #include "resources/theme.h" @@ -44,19 +42,19 @@ void CheckBox::draw(gcn::Graphics* graphics) if (isSelected()) widgetState.flags |= STATE_SELECTED; + auto g = static_cast<Graphics *>(graphics); auto &skin = gui->getTheme()->getSkin(SkinType::CheckBox); - skin.draw(static_cast<Graphics *>(graphics), widgetState); + skin.draw(g, widgetState); if (auto skinState = skin.getState(widgetState.flags)) { auto &textFormat = skinState->textFormat; - TextRenderer::renderText(static_cast<Graphics *>(graphics), - getCaption(), - skin.getMinWidth() + skin.padding + skin.spacing, - skin.padding, - Graphics::LEFT, - textFormat.bold ? boldFont : getFont(), - textFormat); + g->drawText(getCaption(), + skin.getMinWidth() + skin.padding + skin.spacing, + skin.padding, + Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); } } diff --git a/src/gui/widgets/container.h b/src/gui/widgets/container.h index fbdaa1d4..54b7950b 100644 --- a/src/gui/widgets/container.h +++ b/src/gui/widgets/container.h @@ -32,7 +32,7 @@ class LayoutHelper; * A widget container. * * The main difference between the standard Guichan container and this one is - * that childs added to this container are automatically deleted when the + * that children added to this container are automatically deleted when the * container is deleted. * * This container is also non-opaque by default. diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp index e424beec..aae78335 100644 --- a/src/gui/widgets/desktop.cpp +++ b/src/gui/widgets/desktop.cpp @@ -69,24 +69,20 @@ void Desktop::draw(gcn::Graphics *graphics) { auto *g = static_cast<Graphics *>(graphics); - if (!mWallpaper || (getWidth() > mWallpaper->getWidth() || - getHeight() > mWallpaper->getHeight())) - { - // TODO: Color from palette - g->setColor(gcn::Color(64, 64, 64)); - g->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); - } - if (mWallpaper) { g->drawRescaledImage(mWallpaper, 0, 0, 0, 0, mWallpaper->getWidth(), mWallpaper->getHeight(), getWidth(), getHeight(), false); } + else + { + gui->getTheme()->drawSkin(g, SkinType::Desktop, WidgetState(this)); + } - // Draw a thin border under the application version... + // Draw a background beneath the application version for readability... g->setColor(gcn::Color(255, 255, 255, 128)); - g->fillRectangle(gcn::Rectangle(mVersionLabel->getDimension())); + g->fillRectangle(mVersionLabel->getDimension()); Container::draw(graphics); } @@ -113,6 +109,6 @@ void Desktop::setBestFittingWallpaper() } else { - logger->log("Couldn't load %s as wallpaper", wallpaperName.c_str()); + Log::info("Couldn't load %s as wallpaper", wallpaperName.c_str()); } } diff --git a/src/gui/widgets/dropdown.cpp b/src/gui/widgets/dropdown.cpp index 45c8e53f..28308ce6 100644 --- a/src/gui/widgets/dropdown.cpp +++ b/src/gui/widgets/dropdown.cpp @@ -42,6 +42,9 @@ DropDown::DropDown(gcn::ListModel *listModel): setFrameSize(skin.frameSize); mPadding = skin.padding; + // Make sure to call the right setOpaque function + static_cast<ScrollArea*>(mScrollArea)->setOpaque(false); + setHeight(getFont()->getHeight() + 2 * mPadding); } @@ -57,7 +60,8 @@ void DropDown::draw(gcn::Graphics* graphics) const int alpha = gui->getTheme()->getGuiAlpha(); gcn::Color faceColor = getBaseColor(); faceColor.a = alpha; - const gcn::Color *highlightColor = &Theme::getThemeColor(Theme::HIGHLIGHT, alpha); + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = alpha; gcn::Color shadowColor = faceColor - 0x303030; shadowColor.a = alpha; @@ -72,7 +76,7 @@ void DropDown::draw(gcn::Graphics* graphics) if (isFocused()) { - graphics->setColor(*highlightColor); + graphics->setColor(highlightColor); graphics->drawRectangle( gcn::Rectangle(mPadding, mPadding, getWidth() - h - mPadding * 2, h - 2 * mPadding)); } @@ -85,10 +89,10 @@ void DropDown::draw(gcn::Graphics* graphics) // Draw two lines separating the ListBox with selected // element view. - graphics->setColor(*highlightColor); - graphics->drawLine(0, h, getWidth(), h); + graphics->setColor(highlightColor); + graphics->drawLine(0, h, getWidth() - 1, h); graphics->setColor(shadowColor); - graphics->drawLine(0, h + 1, getWidth(), h + 1); + graphics->drawLine(0, h + 1, getWidth() - 1, h + 1); } } @@ -109,21 +113,22 @@ void DropDown::adjustHeight() const int listBoxHeight = mListBox->getHeight(); int height = getFont()->getHeight() + 2 * mPadding; - // The addition/subtraction of 2 compensates for the seperation lines + // The addition/subtraction of 4 compensates for the seperation lines // seperating the selected element view and the scroll area. + const int extraHeight = 4; if (mDroppedDown && getParent()) { - int availableHeight = getParent()->getChildrenArea().height - getY(); + int availableHeight = getParent()->getChildrenArea().height - getY() - getFrameSize(); - if (listBoxHeight > availableHeight - height - 2) + if (listBoxHeight > availableHeight - height - extraHeight) { - mScrollArea->setHeight(availableHeight - height - 2); + mScrollArea->setHeight(availableHeight - height - extraHeight); height = availableHeight; } else { - height += listBoxHeight + 2; + height += listBoxHeight + extraHeight; mScrollArea->setHeight(listBoxHeight); } } @@ -131,11 +136,26 @@ void DropDown::adjustHeight() setHeight(height); mScrollArea->setWidth(getWidth()); - // Resize the ListBox to exactly fit the ScrollArea. - mListBox->setWidth(mScrollArea->getChildrenArea().width); + // Resize the ListBox to exactly fit the ScrollArea, minus the one pixel padding. + mListBox->setWidth(mScrollArea->getChildrenArea().width - 2); mScrollArea->setPosition(0, 0); } +// Overridden to add more space for the separator +gcn::Rectangle DropDown::getChildrenArea() +{ + if (mDroppedDown) + { + // Calculate the children area (with the two pixel border in mind) + return gcn::Rectangle(1, + mFoldedUpHeight + 3, + getWidth() - 2, + getHeight() - mFoldedUpHeight - 4); + } + + return gcn::Rectangle(); +} + void DropDown::drawButton(gcn::Graphics *graphics) { WidgetState state(this); @@ -147,13 +167,12 @@ void DropDown::drawButton(gcn::Graphics *graphics) if (mPushed) state.flags |= STATE_HOVERED; - const auto theme = gui->getTheme(); - const int buttonWidth = theme->getMinWidth(SkinType::DropDownButton); + auto &skin = gui->getTheme()->getSkin(SkinType::DropDownButton); // FIXME: Needs support for setting alignment in the theme. - state.x = state.width - buttonWidth; + state.x = state.width - skin.getMinWidth(); - theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::DropDownButton, state); + skin.draw(static_cast<Graphics *>(graphics), state); } // -- KeyListener notifications diff --git a/src/gui/widgets/dropdown.h b/src/gui/widgets/dropdown.h index a5e2e2f7..d3a6cbbe 100644 --- a/src/gui/widgets/dropdown.h +++ b/src/gui/widgets/dropdown.h @@ -49,6 +49,10 @@ class DropDown : public gcn::DropDown void adjustHeight(); + // Inherited from BasicContainer + + gcn::Rectangle getChildrenArea() override; + // Inherited from FocusListener void focusLost(const gcn::Event& event) override; diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp index d1d00677..63388213 100644 --- a/src/gui/widgets/itemcontainer.cpp +++ b/src/gui/widgets/itemcontainer.cpp @@ -25,7 +25,6 @@ #include "inventory.h" #include "item.h" #include "itemshortcut.h" -#include "log.h" #include "gui/chatwindow.h" #include "gui/itempopup.h" @@ -44,19 +43,12 @@ // TODO: Add support for adding items to the item shortcut window (global // itemShortcut). -static const int BOX_WIDTH = 35; -static const int BOX_HEIGHT = 43; - ItemContainer::ItemContainer(Inventory *inventory): mInventory(inventory) { mItemPopup = new ItemPopup; setFocusable(true); - mSelImg = Theme::getImageFromTheme("selection.png"); - if (!mSelImg) - logger->error("Unable to load selection.png"); - addKeyListener(this); addMouseListener(this); addWidgetListener(this); @@ -112,39 +104,47 @@ void ItemContainer::draw(gcn::Graphics *graphics) } } + auto theme = gui->getTheme(); + auto &slotSkin = theme->getSkin(SkinType::ItemSlot); + WidgetState slotState; + for (int i = 0; i < mGridColumns; i++) { for (int j = 0; j < mGridRows; j++) { - int itemX = i * BOX_WIDTH; - int itemY = j * BOX_HEIGHT; + int itemX = i * slotSkin.width; + int itemY = j * slotSkin.height; int itemIndex = j * mGridColumns + i; + slotState.x = itemX; + slotState.y = itemY; + slotState.flags = 0; + + if (itemIndex == mSelectedIndex) + { + slotState.flags |= STATE_SELECTED; + + if (mSelectionStatus == SEL_DRAGGING) + { + // Reposition the coords to that of the cursor. + itemX = mDragPosX - (slotSkin.width / 2); + itemY = mDragPosY - (slotSkin.height / 2); + } + } + + slotSkin.draw(g, slotState); + Item *item = getItemAt(itemIndex); if (!item || item->getId() == 0) continue; - Image *image = item->getImage(); - if (image) + if (Image *image = item->getImage()) { - if (itemIndex == mSelectedIndex) - { - if (mSelectionStatus == SEL_DRAGGING) - { - // Reposition the coords to that of the cursor. - itemX = mDragPosX - (BOX_WIDTH / 2); - itemY = mDragPosY - (BOX_HEIGHT / 2); - } - else - { - // Draw selection border image. - g->drawImage(mSelImg, itemX, itemY); - } - } - image->setAlpha(1.0f); // ensure the image if fully drawn... - g->drawImage(image, itemX, itemY); + image->setAlpha(1.0f); + g->drawImage(image, itemX + slotSkin.padding, itemY + slotSkin.padding); } + // Draw item caption std::string caption; if (item->getQuantity() > 1) @@ -153,22 +153,22 @@ void ItemContainer::draw(gcn::Graphics *graphics) caption = "Eq."; if (item->isEquipped()) - g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); + g->setColor(theme->getColor(Theme::ITEM_EQUIPPED)); else - g->setColor(gcn::Color(0, 0, 0)); + g->setColor(theme->getColor(Theme::TEXT)); - g->drawText(caption, itemX + BOX_WIDTH / 2, - itemY + BOX_HEIGHT - 14, gcn::Graphics::CENTER); + g->drawText(caption, itemX + slotSkin.width / 2, + itemY + slotSkin.height - 14, gcn::Graphics::CENTER); } } // Draw an orange box around the selected item if (isFocused() && mHighlightedIndex != -1) { - const int itemX = (mHighlightedIndex % mGridColumns) * BOX_WIDTH; - const int itemY = (mHighlightedIndex / mGridColumns) * BOX_HEIGHT; + const int itemX = (mHighlightedIndex % mGridColumns) * slotSkin.width; + const int itemY = (mHighlightedIndex / mGridColumns) * slotSkin.height; g->setColor(gcn::Color(255, 128, 0)); - g->drawRectangle(gcn::Rectangle(itemX, itemY, BOX_WIDTH, BOX_HEIGHT)); + g->drawRectangle(gcn::Rectangle(itemX, itemY, slotSkin.width, slotSkin.height)); } } @@ -354,9 +354,7 @@ void ItemContainer::mouseReleased(gcn::MouseEvent &event) // Show ItemTooltip void ItemContainer::mouseMoved(gcn::MouseEvent &event) { - Item *item = getItemAt(getSlotIndex(event.getX(), event.getY())); - - if (item) + if (Item *item = getItemAt(getSlotIndex(event.getX(), event.getY()))) { mItemPopup->setItem(item->getInfo()); mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); @@ -375,26 +373,36 @@ void ItemContainer::mouseExited(gcn::MouseEvent &event) void ItemContainer::widgetResized(const gcn::Event &event) { - mGridColumns = std::max(1, getWidth() / BOX_WIDTH); + auto &slotSkin = gui->getTheme()->getSkin(SkinType::ItemSlot); + + mGridColumns = std::max(1, getWidth() / slotSkin.width); adjustHeight(); } void ItemContainer::adjustHeight() { + auto &slotSkin = gui->getTheme()->getSkin(SkinType::ItemSlot); + mGridRows = (mLastUsedSlot + 1) / mGridColumns; if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0) ++mGridRows; - setHeight(mGridRows * BOX_HEIGHT); + setHeight(mGridRows * slotSkin.height); } int ItemContainer::getSlotIndex(int x, int y) const { - if (x < getWidth() && y < getHeight()) - { - return (y / BOX_HEIGHT) * mGridColumns + (x / BOX_WIDTH); - } - return Inventory::NO_SLOT_INDEX; + if (x >= getWidth() || y >= getHeight()) + return Inventory::NO_SLOT_INDEX; + + auto &slotSkin = gui->getTheme()->getSkin(SkinType::ItemSlot); + const auto row = y / slotSkin.height; + const auto column = x / slotSkin.width; + + if (row < 0 || row >= mGridRows || column < 0 || column >= mGridColumns) + return Inventory::NO_SLOT_INDEX; + + return (row * mGridColumns) + column; } void ItemContainer::keyAction() diff --git a/src/gui/widgets/itemcontainer.h b/src/gui/widgets/itemcontainer.h index 0a8ac1e2..c1d611b9 100644 --- a/src/gui/widgets/itemcontainer.h +++ b/src/gui/widgets/itemcontainer.h @@ -21,8 +21,6 @@ #pragma once -#include "resources/resource.h" - #include <guichan/keylistener.hpp> #include <guichan/mouselistener.hpp> #include <guichan/widget.hpp> @@ -182,7 +180,6 @@ class ItemContainer : public gcn::Widget, Inventory *mInventory; int mGridColumns = 1; int mGridRows = 1; - ResourceRef<Image> mSelImg; int mSelectedIndex = -1; int mHighlightedIndex = -1; int mLastUsedSlot = -1; diff --git a/src/gui/widgets/itemlinkhandler.cpp b/src/gui/widgets/itemlinkhandler.cpp index f596fc82..d9d6f506 100644 --- a/src/gui/widgets/itemlinkhandler.cpp +++ b/src/gui/widgets/itemlinkhandler.cpp @@ -33,6 +33,7 @@ #include "gui/widgets/itemlinkhandler.h" +#include "client.h" #include "resources/iteminfo.h" #include "resources/itemdb.h" #include "utils/gettext.h" @@ -42,6 +43,7 @@ ItemLinkHandler::ItemLinkHandler(Window *parent) : mParent(parent) { mItemPopup = std::make_unique<ItemPopup>(); + mItemPopup->addDeathListener(this); } ItemLinkHandler::~ItemLinkHandler() = default; @@ -55,6 +57,24 @@ static bool isUrl(const std::string &link) void ItemLinkHandler::handleLink(const std::string &link) { +#if SDL_VERSION_ATLEAST(2, 0, 14) + // Handle screenshots by constructing full file path + if (startsWith(link, "screenshot:")) + { + std::string filename = link.substr(11); // Remove "screenshot:" prefix + + // Prevent directory traversal attacks or opening malicious files + if (filename.find("..") == std::string::npos && endsWith(filename, ".png")) + { + std::string fileUrl = "file://" + Client::getScreenshotDirectory() + "/" + filename; + if (SDL_OpenURL(fileUrl.c_str()) == -1) + new OkDialog(_("Open URL Failed"), SDL_GetError(), true, mParent); + } + + return; + } +#endif + if (isUrl(link)) { mLink = link; @@ -97,3 +117,10 @@ void ItemLinkHandler::action(const gcn::ActionEvent &actionEvent) #endif } } + +void ItemLinkHandler::death(const gcn::Event &event) +{ + // If somebody else killed the PopupUp, make sure we don't also try to delete it + if (event.getSource() == mItemPopup.get()) + mItemPopup.release(); +} diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h index 58202d33..637482bd 100644 --- a/src/gui/widgets/itemlinkhandler.h +++ b/src/gui/widgets/itemlinkhandler.h @@ -24,13 +24,14 @@ #include "gui/widgets/linkhandler.h" #include <guichan/actionlistener.hpp> +#include <guichan/deathlistener.hpp> #include <memory> class ItemPopup; class Window; -class ItemLinkHandler : public LinkHandler, gcn::ActionListener +class ItemLinkHandler : public LinkHandler, gcn::ActionListener, public gcn::DeathListener { public: ItemLinkHandler(Window *parent = nullptr); @@ -42,6 +43,9 @@ class ItemLinkHandler : public LinkHandler, gcn::ActionListener // ActionListener interface void action(const gcn::ActionEvent &actionEvent) override; + // DeathListener interface + void death(const gcn::Event &event) override; + private: std::unique_ptr<ItemPopup> mItemPopup; diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp index 4d0641b1..b47fa29d 100644 --- a/src/gui/widgets/itemshortcutcontainer.cpp +++ b/src/gui/widgets/itemshortcutcontainer.cpp @@ -49,6 +49,7 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics) { auto *g = static_cast<Graphics*>(graphics); auto theme = gui->getTheme(); + auto &skin = theme->getSkin(SkinType::ShortcutBox); graphics->setFont(getFont()); @@ -57,26 +58,25 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics) WidgetState state; state.x = (i % mGridWidth) * mBoxWidth; state.y = (i / mGridWidth) * mBoxHeight; - theme->drawSkin(g, SkinType::ShortcutBox, state); + skin.draw(g, state); // Draw item keyboard shortcut. const char *key = SDL_GetKeyName( keyboard.getKeyValue(KeyboardConfig::KEY_SHORTCUT_1 + i)); graphics->setColor(Theme::getThemeColor(Theme::TEXT)); - g->drawText(key, state.x + 2, state.y + 2, gcn::Graphics::LEFT); + g->drawText(key, + state.x + skin.padding + 2, + state.y + skin.padding + 2, + gcn::Graphics::LEFT); - if (itemShortcut->getItem(i) < 0) + const int itemId = itemShortcut->getItem(i); + if (itemId < 0) continue; - Item *item = - PlayerInfo::getInventory()->findItem(itemShortcut->getItem(i)); - - if (item) + if (Item *item = PlayerInfo::getInventory()->findItem(itemId)) { // Draw item icon. - Image* image = item->getImage(); - - if (image) + if (Image *image = item->getImage()) { std::string caption; if (item->getQuantity() > 1) @@ -85,11 +85,13 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics) caption = "Eq."; image->setAlpha(1.0f); - g->drawImage(image, state.x, state.y); + g->drawImage(image, state.x + skin.padding, state.y + skin.padding); if (item->isEquipped()) g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED)); - g->drawText(caption, state.x + mBoxWidth / 2, - state.y + mBoxHeight - 14, gcn::Graphics::CENTER); + g->drawText(caption, + state.x + mBoxWidth / 2, + state.y + mBoxHeight - 14, + gcn::Graphics::CENTER); } } } @@ -97,7 +99,7 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics) if (mItemMoved) { // Draw the item image being dragged by the cursor. - if (Image* image = mItemMoved->getImage()) + if (Image *image = mItemMoved->getImage()) { const int tPosX = mCursorPosX - (image->getWidth() / 2); const int tPosY = mCursorPosY - (image->getHeight() / 2); @@ -202,15 +204,7 @@ void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event) // Show ItemTooltip void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event) { - const int index = getIndexFromGrid(event.getX(), event.getY()); - if (index == -1) - return; - - const int itemId = itemShortcut->getItem(index); - if (itemId < 0) - return; - - if (Item *item = PlayerInfo::getInventory()->findItem(itemId)) + if (Item *item = getItemAt(event.getX(), event.getY())) { mItemPopup->setItem(item->getInfo()); mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); @@ -221,6 +215,19 @@ void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event) } } +Item *ItemShortcutContainer::getItemAt(int x, int y) const +{ + const int index = getIndexFromGrid(x, y); + if (index == -1) + return nullptr; + + const int itemId = itemShortcut->getItem(index); + if (itemId < 0) + return nullptr; + + return PlayerInfo::getInventory()->findItem(itemId); +} + // Hide ItemTooltip void ItemShortcutContainer::mouseExited(gcn::MouseEvent &event) { diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h index 63d9e0ef..a01857db 100644 --- a/src/gui/widgets/itemshortcutcontainer.h +++ b/src/gui/widgets/itemshortcutcontainer.h @@ -67,6 +67,8 @@ class ItemShortcutContainer : public ShortcutContainer void mouseExited(gcn::MouseEvent &event) override; void mouseMoved(gcn::MouseEvent &event) override; + Item *getItemAt(int x, int y) const; + bool mItemClicked = false; Item *mItemMoved = nullptr; diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp index 53a82e14..9c0fd3cd 100644 --- a/src/gui/widgets/label.cpp +++ b/src/gui/widgets/label.cpp @@ -21,8 +21,6 @@ #include "gui/widgets/label.h" -#include "textrenderer.h" - #include "resources/theme.h" #include <guichan/exception.hpp> @@ -42,7 +40,7 @@ Label::Label(const std::string &caption) : void Label::draw(gcn::Graphics *graphics) { int textX; - int textY = getHeight() / 2 - getFont()->getHeight() / 2; + int textY = (getHeight() - getFont()->getHeight()) / 2; switch (getAlignment()) { @@ -59,15 +57,15 @@ void Label::draw(gcn::Graphics *graphics) throw GCN_EXCEPTION("Unknown alignment."); } - TextRenderer::renderText(static_cast<Graphics *>(graphics), - getCaption(), - textX, - textY, - getAlignment(), - getForegroundColor(), - getFont(), - mOutlineColor.has_value(), - mShadowColor.has_value(), - mOutlineColor, - mShadowColor); + auto g = static_cast<Graphics *>(graphics); + g->drawText(getCaption(), + textX, + textY, + getAlignment(), + getForegroundColor(), + getFont(), + mOutlineColor.has_value(), + mShadowColor.has_value(), + mOutlineColor, + mShadowColor); } diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp index f1fcfd53..612e785f 100644 --- a/src/gui/widgets/listbox.cpp +++ b/src/gui/widgets/listbox.cpp @@ -41,23 +41,29 @@ void ListBox::draw(gcn::Graphics *graphics) if (!mListModel) return; - const int alpha = gui->getTheme()->getGuiAlpha(); - - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, alpha)); graphics->setFont(getFont()); const int height = getRowHeight(); // Draw filled rectangle around the selected list element if (mSelected >= 0) + { + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = gui->getTheme()->getGuiAlpha(); + graphics->setColor(highlightColor); graphics->fillRectangle(gcn::Rectangle(0, height * mSelected, getWidth(), height)); + } // Draw the list elements - graphics->setColor(Theme::getThemeColor(Theme::TEXT)); for (int i = 0, y = 0; i < mListModel->getNumberOfElements(); ++i, y += height) { + if (mSelected == i) + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT)); + else + graphics->setColor(Theme::getThemeColor(Theme::TEXT)); + graphics->drawText(mListModel->getElementAt(i), 1, y); } } diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp index b7c70fe5..c4c6f652 100644 --- a/src/gui/widgets/popup.cpp +++ b/src/gui/widgets/popup.cpp @@ -22,11 +22,14 @@ #include "gui/widgets/popup.h" +#include "browserbox.h" #include "graphics.h" #include "log.h" +#include "textbox.h" #include "gui/gui.h" #include "gui/viewport.h" +#include "gui/widgets/label.h" #include "gui/widgets/windowcontainer.h" #include <guichan/exception.hpp> @@ -37,12 +40,12 @@ Popup::Popup(const std::string &name, SkinType skinType) , mMaxHeight(graphics->getHeight()) , mSkinType(skinType) { - logger->log("Popup::Popup(\"%s\")", name.c_str()); + Log::debug("Popup::Popup(\"%s\")", name.c_str()); if (!windowContainer) throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set"); - auto &skin = gui->getTheme()->getSkin(skinType); + auto &skin = getSkin(); setFrameSize(skin.frameSize); setPadding(skin.padding); @@ -55,7 +58,7 @@ Popup::Popup(const std::string &name, SkinType skinType) Popup::~Popup() { - logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str()); + Log::debug("Popup::~Popup(\"%s\")", mPopupName.c_str()); } void Popup::setWindowContainer(WindowContainer *wc) @@ -63,6 +66,41 @@ void Popup::setWindowContainer(WindowContainer *wc) windowContainer = wc; } +void Popup::add(gcn::Widget *widget) +{ + Container::add(widget); + widgetAdded(widget); +} + +void Popup::add(gcn::Widget *widget, int x, int y) +{ + Container::add(widget, x, y); + widgetAdded(widget); +} + +void Popup::widgetAdded(gcn::Widget *widget) const +{ + if (const int paletteId = getSkin().palette) + { + if (auto browserBox = dynamic_cast<BrowserBox*>(widget)) + { + browserBox->setPalette(paletteId); + } + else if (auto label = dynamic_cast<Label*>(widget)) + { + auto &palette = gui->getTheme()->getPalette(paletteId); + label->setForegroundColor(palette.getColor(Theme::TEXT)); + label->setOutlineColor(palette.getOutlineColor(Theme::TEXT)); + } + else if (auto textBox = dynamic_cast<TextBox*>(widget)) + { + auto &palette = gui->getTheme()->getPalette(paletteId); + textBox->setTextColor(&palette.getColor(Theme::TEXT)); + textBox->setOutlineColor(palette.getOutlineColor(Theme::TEXT)); + } + } +} + void Popup::draw(gcn::Graphics *graphics) { if (getFrameSize() == 0) @@ -76,7 +114,7 @@ void Popup::drawFrame(gcn::Graphics *graphics) WidgetState state(this); state.width += getFrameSize() * 2; state.height += getFrameSize() * 2; - gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), mSkinType, state); + getSkin().draw(static_cast<Graphics *>(graphics), state); } gcn::Rectangle Popup::getChildrenArea() @@ -119,12 +157,12 @@ void Popup::setLocationRelativeTo(gcn::Widget *widget) void Popup::setMinWidth(int width) { - mMinWidth = std::max(gui->getTheme()->getMinWidth(mSkinType), width); + mMinWidth = std::max(getSkin().getMinWidth(), width); } void Popup::setMinHeight(int height) { - mMinHeight = std::max(gui->getTheme()->getMinHeight(mSkinType), height); + mMinHeight = std::max(getSkin().getMinHeight(), height); } void Popup::setMaxWidth(int width) @@ -159,6 +197,11 @@ void Popup::position(int x, int y) requestMoveToTop(); } +const Skin &Popup::getSkin() const +{ + return gui->getTheme()->getSkin(mSkinType); +} + void Popup::mouseMoved(gcn::MouseEvent &event) { if (viewport) diff --git a/src/gui/widgets/popup.h b/src/gui/widgets/popup.h index 012b55de..b88cafc9 100644 --- a/src/gui/widgets/popup.h +++ b/src/gui/widgets/popup.h @@ -68,6 +68,10 @@ class Popup : public Container, public gcn::MouseListener */ static void setWindowContainer(WindowContainer *windowContainer); + // Container interface + void add(gcn::Widget *widget) override; + void add(gcn::Widget *widget, int x, int y) override; + /** * Draws the popup. */ @@ -156,7 +160,14 @@ class Popup : public Container, public gcn::MouseListener */ void position(int x, int y); + /** + * Returns the Skin used by this popup. + */ + const Skin &getSkin() const; + private: + void widgetAdded(gcn::Widget *widget) const; + std::string mPopupName; /**< Name of the popup */ int mMinWidth = 100; /**< Minimum popup width */ int mMinHeight = 40; /**< Minimum popup height */ diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp index 5cf1b05a..502ab686 100644 --- a/src/gui/widgets/progressbar.cpp +++ b/src/gui/widgets/progressbar.cpp @@ -83,11 +83,16 @@ void ProgressBar::draw(gcn::Graphics *graphics) rect.x = 0; rect.y = 0; + Theme::ProgressPalette palette = Theme::THEME_PROG_END; + if (mProgressPalette >= 0) + palette = static_cast<Theme::ProgressPalette>(mProgressPalette); + gui->getTheme()->drawProgressBar(static_cast<Graphics *>(graphics), rect, mColor, mProgress, - mText); + mText, + palette); } void ProgressBar::setProgress(float progress) diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp index ceba78eb..3474bbd8 100644 --- a/src/gui/widgets/radiobutton.cpp +++ b/src/gui/widgets/radiobutton.cpp @@ -21,11 +21,11 @@ #include "gui/widgets/radiobutton.h" -#include "textrenderer.h" - #include "gui/gui.h" #include "resources/theme.h" +#include <guichan/font.hpp> + RadioButton::RadioButton(const std::string &caption, const std::string &group, bool marked) @@ -44,19 +44,19 @@ void RadioButton::draw(gcn::Graphics* graphics) if (isSelected()) widgetState.flags |= STATE_SELECTED; + auto g = static_cast<Graphics *>(graphics); auto &skin = gui->getTheme()->getSkin(SkinType::RadioButton); - skin.draw(static_cast<Graphics *>(graphics), widgetState); + skin.draw(g, widgetState); if (auto skinState = skin.getState(widgetState.flags)) { auto &textFormat = skinState->textFormat; - TextRenderer::renderText(static_cast<Graphics *>(graphics), - getCaption(), - skin.getMinWidth() + skin.padding + skin.spacing, - skin.padding, - Graphics::LEFT, - textFormat.bold ? boldFont : getFont(), - textFormat); + g->drawText(getCaption(), + skin.getMinWidth() + skin.padding + skin.spacing, + skin.padding, + Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); } } diff --git a/src/gui/widgets/resizegrip.cpp b/src/gui/widgets/resizegrip.cpp index c802a405..0c5a7fb5 100644 --- a/src/gui/widgets/resizegrip.cpp +++ b/src/gui/widgets/resizegrip.cpp @@ -31,7 +31,7 @@ ResizeGrip::ResizeGrip() { auto &skin = gui->getTheme()->getSkin(SkinType::ResizeGrip); - setSize(skin.getMinWidth() + skin.padding, skin.getMinHeight() + skin.padding); + setSize(skin.width, skin.height); } void ResizeGrip::draw(gcn::Graphics *graphics) diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index c4d55072..1dec34be 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -25,6 +25,8 @@ #include "gui/gui.h" +#include <guichan/exception.hpp> + ScrollArea::ScrollArea() { init(); @@ -41,6 +43,11 @@ ScrollArea::~ScrollArea() delete getContent(); } +void ScrollArea::setShowButtons(bool showButtons) +{ + mShowButtons = showButtons; +} + void ScrollArea::init() { // Draw background by default @@ -48,12 +55,15 @@ void ScrollArea::init() auto theme = gui->getTheme(); - int minWidth = theme->getSkin(SkinType::ScrollAreaVBar).getMinWidth(); - if (minWidth > 0) - setScrollbarWidth(minWidth); + int scrollBarWidth = theme->getSkin(SkinType::ScrollAreaVBar).width; + if (scrollBarWidth > 0) + setScrollbarWidth(scrollBarWidth); + + auto &scrollAreaSkin = theme->getSkin(SkinType::ScrollArea); + setShowButtons(scrollAreaSkin.showButtons); if (auto content = getContent()) - content->setFrameSize(theme->getSkin(SkinType::ScrollArea).padding); + content->setFrameSize(scrollAreaSkin.padding); // The base color is only used when rendering a square in the corner where // the scrollbars meet. We disable rendering of this square by setting the @@ -157,23 +167,47 @@ void ScrollArea::drawBackground(gcn::Graphics *graphics) // background is drawn as part of the frame instead } +static void drawButton(gcn::Graphics *graphics, + SkinType skinType, + bool pressed, + const gcn::Rectangle &dim) +{ + WidgetState state(dim); + if (pressed) + state.flags |= STATE_SELECTED; + + gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); +} + void ScrollArea::drawUpButton(gcn::Graphics *graphics) { + if (!mShowButtons) + return; + drawButton(graphics, SkinType::ButtonUp, mUpButtonPressed, getUpButtonDimension()); } void ScrollArea::drawDownButton(gcn::Graphics *graphics) { + if (!mShowButtons) + return; + drawButton(graphics, SkinType::ButtonDown, mDownButtonPressed, getDownButtonDimension()); } void ScrollArea::drawLeftButton(gcn::Graphics *graphics) { + if (!mShowButtons) + return; + drawButton(graphics, SkinType::ButtonLeft, mLeftButtonPressed, getLeftButtonDimension()); } void ScrollArea::drawRightButton(gcn::Graphics *graphics) { + if (!mShowButtons) + return; + drawButton(graphics, SkinType::ButtonRight, mRightButtonPressed, getRightButtonDimension()); } @@ -198,6 +232,9 @@ void ScrollArea::drawHBar(gcn::Graphics *graphics) void ScrollArea::drawVMarker(gcn::Graphics *graphics) { WidgetState state(getVerticalMarkerDimension()); + if (state.height == 0) + return; + if (mHasMouse && (mX > (getWidth() - getScrollbarWidth()))) state.flags |= STATE_HOVERED; @@ -207,22 +244,115 @@ void ScrollArea::drawVMarker(gcn::Graphics *graphics) void ScrollArea::drawHMarker(gcn::Graphics *graphics) { WidgetState state(getHorizontalMarkerDimension()); + if (state.width == 0) + return; + if (mHasMouse && (mY > (getHeight() - getScrollbarWidth()))) state.flags |= STATE_HOVERED; gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), SkinType::ScrollAreaHMarker, state); } -void ScrollArea::drawButton(gcn::Graphics *graphics, - SkinType skinType, - bool pressed, - const gcn::Rectangle &dim) +/** + * Code copied from gcn::ScrollArea::checkPolicies to make sure it takes the + * frame size of the content into account. + */ +void ScrollArea::checkPolicies() { - WidgetState state(dim); - if (pressed) - state.flags |= STATE_SELECTED; + int w = getWidth(); + int h = getHeight(); - gui->getTheme()->drawSkin(static_cast<Graphics *>(graphics), skinType, state); + mHBarVisible = false; + mVBarVisible = false; + + if (!getContent()) + { + mHBarVisible = (mHPolicy == SHOW_ALWAYS); + mVBarVisible = (mVPolicy == SHOW_ALWAYS); + return; + } + + const int contentFrameSize = getContent()->getFrameSize(); + w -= 2 * contentFrameSize; + h -= 2 * contentFrameSize; + + if (mHPolicy == SHOW_AUTO && + mVPolicy == SHOW_AUTO) + { + if (getContent()->getWidth() <= w + && getContent()->getHeight() <= h) + { + mHBarVisible = false; + mVBarVisible = false; + } + + if (getContent()->getWidth() > w) + { + mHBarVisible = true; + } + + if ((getContent()->getHeight() > h) + || (mHBarVisible && getContent()->getHeight() > h - mScrollbarWidth)) + { + mVBarVisible = true; + } + + if (mVBarVisible && getContent()->getWidth() > w - mScrollbarWidth) + { + mHBarVisible = true; + } + + return; + } + + switch (mHPolicy) + { + case SHOW_NEVER: + mHBarVisible = false; + break; + + case SHOW_ALWAYS: + mHBarVisible = true; + break; + + case SHOW_AUTO: + if (mVPolicy == SHOW_NEVER) + { + mHBarVisible = getContent()->getWidth() > w; + } + else // (mVPolicy == SHOW_ALWAYS) + { + mHBarVisible = getContent()->getWidth() > w - mScrollbarWidth; + } + break; + + default: + throw GCN_EXCEPTION("Horizontal scroll policy invalid."); + } + + switch (mVPolicy) + { + case SHOW_NEVER: + mVBarVisible = false; + break; + + case SHOW_ALWAYS: + mVBarVisible = true; + break; + + case SHOW_AUTO: + if (mHPolicy == SHOW_NEVER) + { + mVBarVisible = getContent()->getHeight() > h; + } + else // (mHPolicy == SHOW_ALWAYS) + { + mVBarVisible = getContent()->getHeight() > h - mScrollbarWidth; + } + break; + default: + throw GCN_EXCEPTION("Vertical scroll policy invalid."); + } } void ScrollArea::mouseMoved(gcn::MouseEvent& event) @@ -240,3 +370,300 @@ void ScrollArea::mouseExited(gcn::MouseEvent& event) { mHasMouse = false; } + +/** + * Code copied from gcn::ScrollArea::mousePressed to make it call our custom + * getVerticalMarkerDimension and getHorizontalMarkerDimension functions. + */ +void ScrollArea::mousePressed(gcn::MouseEvent &mouseEvent) +{ + int x = mouseEvent.getX(); + int y = mouseEvent.getY(); + + if (getUpButtonDimension().isPointInRect(x, y)) + { + setVerticalScrollAmount(getVerticalScrollAmount() + - mUpButtonScrollAmount); + mUpButtonPressed = true; + } + else if (getDownButtonDimension().isPointInRect(x, y)) + { + setVerticalScrollAmount(getVerticalScrollAmount() + + mDownButtonScrollAmount); + mDownButtonPressed = true; + } + else if (getLeftButtonDimension().isPointInRect(x, y)) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + - mLeftButtonScrollAmount); + mLeftButtonPressed = true; + } + else if (getRightButtonDimension().isPointInRect(x, y)) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + mRightButtonScrollAmount); + mRightButtonPressed = true; + } + else if (getVerticalMarkerDimension().isPointInRect(x, y)) + { + mIsHorizontalMarkerDragged = false; + mIsVerticalMarkerDragged = true; + + mVerticalMarkerDragOffset = y - getVerticalMarkerDimension().y; + } + else if (getVerticalBarDimension().isPointInRect(x,y)) + { + if (y < getVerticalMarkerDimension().y) + { + setVerticalScrollAmount(getVerticalScrollAmount() + - (int)(getChildrenArea().height * 0.95)); + } + else + { + setVerticalScrollAmount(getVerticalScrollAmount() + + (int)(getChildrenArea().height * 0.95)); + } + } + else if (getHorizontalMarkerDimension().isPointInRect(x, y)) + { + mIsHorizontalMarkerDragged = true; + mIsVerticalMarkerDragged = false; + + mHorizontalMarkerDragOffset = x - getHorizontalMarkerDimension().x; + } + else if (getHorizontalBarDimension().isPointInRect(x,y)) + { + if (x < getHorizontalMarkerDimension().x) + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + - (int)(getChildrenArea().width * 0.95)); + } + else + { + setHorizontalScrollAmount(getHorizontalScrollAmount() + + (int)(getChildrenArea().width * 0.95)); + } + } +} + +/** + * Code copied from gcn::ScrollArea::mouseDragged to make it call our custom + * getVerticalMarkerDimension and getHorizontalMarkerDimension functions. + */ +void ScrollArea::mouseDragged(gcn::MouseEvent &mouseEvent) +{ + if (mIsVerticalMarkerDragged) + { + int pos = mouseEvent.getY() - getVerticalBarDimension().y - mVerticalMarkerDragOffset; + int length = getVerticalMarkerDimension().height; + + gcn::Rectangle barDim = getVerticalBarDimension(); + + if ((barDim.height - length) > 0) + { + setVerticalScrollAmount((getVerticalMaxScroll() * pos) + / (barDim.height - length)); + } + else + { + setVerticalScrollAmount(0); + } + } + + if (mIsHorizontalMarkerDragged) + { + int pos = mouseEvent.getX() - getHorizontalBarDimension().x - mHorizontalMarkerDragOffset; + int length = getHorizontalMarkerDimension().width; + + gcn::Rectangle barDim = getHorizontalBarDimension(); + + if ((barDim.width - length) > 0) + { + setHorizontalScrollAmount((getHorizontalMaxScroll() * pos) + / (barDim.width - length)); + } + else + { + setHorizontalScrollAmount(0); + } + } + + mouseEvent.consume(); +} + +gcn::Rectangle ScrollArea::getUpButtonDimension() +{ + if (!mVBarVisible || !mShowButtons) + return gcn::Rectangle(); + + return gcn::Rectangle(getWidth() - mScrollbarWidth, 0, mScrollbarWidth, mScrollbarWidth); +} + +gcn::Rectangle ScrollArea::getDownButtonDimension() +{ + if (!mVBarVisible || !mShowButtons) + return gcn::Rectangle(); + + gcn::Rectangle dim(getWidth() - mScrollbarWidth, + getHeight() - mScrollbarWidth, + mScrollbarWidth, + mScrollbarWidth); + + if (mHBarVisible) + dim.y -= mScrollbarWidth; + + return dim; +} + +gcn::Rectangle ScrollArea::getLeftButtonDimension() +{ + if (!mHBarVisible || !mShowButtons) + return gcn::Rectangle(); + + return gcn::Rectangle(0, getHeight() - mScrollbarWidth, mScrollbarWidth, mScrollbarWidth); +} + +gcn::Rectangle ScrollArea::getRightButtonDimension() +{ + if (!mHBarVisible || !mShowButtons) + return gcn::Rectangle(); + + gcn::Rectangle dim(getWidth() - mScrollbarWidth, + getHeight() - mScrollbarWidth, + mScrollbarWidth, + mScrollbarWidth); + + if (mVBarVisible) + dim.x -= mScrollbarWidth; + + return dim; +} + +gcn::Rectangle ScrollArea::getVerticalBarDimension() +{ + if (!mVBarVisible) + return gcn::Rectangle(); + + gcn::Rectangle dim(getWidth() - mScrollbarWidth, + getUpButtonDimension().height, + mScrollbarWidth, + getHeight() + - getUpButtonDimension().height + - getDownButtonDimension().height); + + if (mHBarVisible) + dim.height -= mScrollbarWidth; + + if (dim.height < 0) + dim.height = 0; + + return dim; +} + +gcn::Rectangle ScrollArea::getHorizontalBarDimension() +{ + if (!mHBarVisible) + return gcn::Rectangle(); + + gcn::Rectangle dim(getLeftButtonDimension().width, + getHeight() - mScrollbarWidth, + getWidth() + - getLeftButtonDimension().width + - getRightButtonDimension().width, + mScrollbarWidth); + + if (mVBarVisible) + dim.width -= mScrollbarWidth; + + if (dim.width < 0) + dim.width = 0; + + return dim; +} + +static void getMarkerValues(int barSize, + int maxScroll, int scrollAmount, + int contentHeight, int viewHeight, + int fixedMarkerSize, int minMarkerSize, + int &markerSize, int &markerPos) +{ + if (fixedMarkerSize == 0) + { + if (contentHeight != 0 && contentHeight > viewHeight) + markerSize = std::max((barSize * viewHeight) / contentHeight, minMarkerSize); + else + markerSize = barSize; + } + else + { + if (contentHeight > viewHeight) + markerSize = fixedMarkerSize; + else + markerSize = 0; + } + + // Hide the marker when it doesn't fit + if (markerSize > barSize) + markerSize = 0; + + if (maxScroll != 0) + markerPos = ((barSize - markerSize) * scrollAmount + maxScroll / 2) / maxScroll; + else + markerPos = 0; +} + +gcn::Rectangle ScrollArea::getVerticalMarkerDimension() +{ + if (!mVBarVisible) + return gcn::Rectangle(); + + auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaVMarker); + const gcn::Rectangle barDim = getVerticalBarDimension(); + + int contentHeight = 0; + if (auto content = getContent()) + contentHeight = content->getHeight() + content->getFrameSize() * 2; + + int length; + int pos; + + getMarkerValues(barDim.height, + getVerticalMaxScroll(), + getVerticalScrollAmount(), + contentHeight, + getChildrenArea().height, + markerSkin.height, + mScrollbarWidth, + length, + pos); + + return gcn::Rectangle(barDim.x, barDim.y + pos, mScrollbarWidth, length); +} + +gcn::Rectangle ScrollArea::getHorizontalMarkerDimension() +{ + if (!mHBarVisible) + return gcn::Rectangle(); + + auto &markerSkin = gui->getTheme()->getSkin(SkinType::ScrollAreaHMarker); + const gcn::Rectangle barDim = getHorizontalBarDimension(); + + int contentWidth = 0; + if (auto content = getContent()) + contentWidth = content->getWidth() + content->getFrameSize() * 2; + + int length; + int pos; + + getMarkerValues(barDim.width, + getHorizontalMaxScroll(), + getHorizontalScrollAmount(), + contentWidth, + getChildrenArea().width, + markerSkin.width, + mScrollbarWidth, + length, + pos); + + return gcn::Rectangle(barDim.x + pos, barDim.y, length, mScrollbarWidth); +} diff --git a/src/gui/widgets/scrollarea.h b/src/gui/widgets/scrollarea.h index 40f1adc1..a0167a32 100644 --- a/src/gui/widgets/scrollarea.h +++ b/src/gui/widgets/scrollarea.h @@ -21,8 +21,6 @@ #pragma once -#include "resources/theme.h" - #include <guichan/widgets/scrollarea.hpp> /** @@ -32,6 +30,8 @@ * content. However, it won't delete a previously set content widget when * setContent is called! * + * Also overrides several functions to support fixed-size scroll bar markers. + * * \ingroup GUI */ class ScrollArea : public gcn::ScrollArea @@ -56,6 +56,11 @@ class ScrollArea : public gcn::ScrollArea ~ScrollArea() override; /** + * Sets whether the scroll bar buttons are shown. + */ + void setShowButtons(bool showButtons); + + /** * Logic function optionally adapts width or height of contents. This * depends on the scrollbar settings. */ @@ -74,7 +79,7 @@ class ScrollArea : public gcn::ScrollArea /** * Applies clipping to the contents. */ - void drawChildren(gcn::Graphics* graphics) override; + void drawChildren(gcn::Graphics *graphics) override; /** * Sets whether the widget should draw its background or not. @@ -89,17 +94,20 @@ class ScrollArea : public gcn::ScrollArea /** * Called when the mouse moves in the widget area. */ - void mouseMoved(gcn::MouseEvent& event) override; + void mouseMoved(gcn::MouseEvent &event) override; /** * Called when the mouse enteres the widget area. */ - void mouseEntered(gcn::MouseEvent& event) override; + void mouseEntered(gcn::MouseEvent &event) override; /** * Called when the mouse leaves the widget area. */ - void mouseExited(gcn::MouseEvent& event) override; + void mouseExited(gcn::MouseEvent &event) override; + + void mousePressed(gcn::MouseEvent &mouseEvent) override; + void mouseDragged(gcn::MouseEvent &mouseEvent) override; protected: /** @@ -117,13 +125,30 @@ class ScrollArea : public gcn::ScrollArea void drawVMarker(gcn::Graphics *graphics) override; void drawHMarker(gcn::Graphics *graphics) override; - static void drawButton(gcn::Graphics *graphics, - SkinType skinType, - bool pressed, - const gcn::Rectangle &dim); + void checkPolicies() override; + + /** + * Shadowing these functions from gcn::ScrollArea with versions that + * support hiding the buttons. We need to make sure we always use these + * versions. + */ + gcn::Rectangle getUpButtonDimension(); + gcn::Rectangle getDownButtonDimension(); + gcn::Rectangle getLeftButtonDimension(); + gcn::Rectangle getRightButtonDimension(); + gcn::Rectangle getVerticalBarDimension(); + gcn::Rectangle getHorizontalBarDimension(); + + /** + * Shadowing these functions from gcn::ScrollArea with versions that + * supports fixed-size scroll bar markers. We need to make sure we + * always use these versions. + */ + gcn::Rectangle getVerticalMarkerDimension(); + gcn::Rectangle getHorizontalMarkerDimension(); int mX = 0; int mY = 0; bool mHasMouse = false; - bool mOpaque = true; + bool mShowButtons = true; }; diff --git a/src/gui/widgets/shoplistbox.cpp b/src/gui/widgets/shoplistbox.cpp index 31c733a6..afcb97b4 100644 --- a/src/gui/widgets/shoplistbox.cpp +++ b/src/gui/widgets/shoplistbox.cpp @@ -71,14 +71,14 @@ void ShopListBox::draw(gcn::Graphics *gcnGraphics) return; const int alpha = (int)(config.guiAlpha * 255.0f); - const gcn::Color &highlightColor = - Theme::getThemeColor(Theme::HIGHLIGHT, alpha); - const gcn::Color &backgroundColor = - Theme::getThemeColor(Theme::BACKGROUND, alpha); - const gcn::Color &warningColor = - Theme::getThemeColor(Theme::SHOP_WARNING, alpha); - const gcn::Color &textColor = - Theme::getThemeColor(Theme::TEXT); + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + auto backgroundColor = Theme::getThemeColor(Theme::BACKGROUND); + auto warningColor = Theme::getThemeColor(Theme::SHOP_WARNING); + auto textColor = Theme::getThemeColor(Theme::TEXT); + auto highlightTextColor = Theme::getThemeColor(Theme::HIGHLIGHT_TEXT); + highlightColor.a = alpha; + backgroundColor.a = alpha; + warningColor.a = alpha; auto *graphics = static_cast<Graphics*>(gcnGraphics); @@ -103,7 +103,8 @@ void ShopListBox::draw(gcn::Graphics *gcnGraphics) gcn::Color blend = warningColor; blend.r = (blend.r + highlightColor.r) / 2; blend.g = (blend.g + highlightColor.g) / 2; - blend.b = (blend.g + highlightColor.b) / 2; + blend.b = (blend.b + highlightColor.b) / 2; + blend.a = alpha; graphics->setColor(blend); } } @@ -137,7 +138,7 @@ void ShopListBox::draw(gcn::Graphics *gcnGraphics) } } - graphics->setColor(textColor); + graphics->setColor(i == mSelected ? highlightTextColor : textColor); graphics->drawText(mListModel->getElementAt(i), ITEM_ICON_SIZE + 5, y + (ITEM_ICON_SIZE - fontHeight) / 2); @@ -168,8 +169,7 @@ void ShopListBox::mouseMoved(gcn::MouseEvent &event) } else { - Item *item = mShopItems->at(index); - if (item) + if (Item *item = mShopItems->at(index)) { mItemPopup->setItem(item->getInfo()); mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp index ccf4b082..9271b1f4 100644 --- a/src/gui/widgets/shortcutcontainer.cpp +++ b/src/gui/widgets/shortcutcontainer.cpp @@ -31,8 +31,8 @@ ShortcutContainer::ShortcutContainer() addWidgetListener(this); auto &skin = gui->getTheme()->getSkin(SkinType::ShortcutBox); - mBoxWidth = skin.getMinWidth(); - mBoxHeight = skin.getMinHeight(); + mBoxWidth = skin.width; + mBoxHeight = skin.height; } void ShortcutContainer::widgetResized(const gcn::Event &event) diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp index b2779c4f..0f6ca4e5 100644 --- a/src/gui/widgets/tab.cpp +++ b/src/gui/widgets/tab.cpp @@ -73,18 +73,27 @@ void Tab::draw(gcn::Graphics *graphics) if (mTabbedArea && mTabbedArea->isTabSelected(this)) flags |= STATE_SELECTED; - auto &skin = gui->getTheme()->getSkin(SkinType::Tab); + auto theme = gui->getTheme(); + auto &palette = theme->getPalette(0); + auto &skin = theme->getSkin(SkinType::Tab); + if (auto state = skin.getState(flags)) { gcn::Color foregroundColor = state->textFormat.color; + auto outlineColor = state->textFormat.outlineColor; if (mFlash) - foregroundColor = Theme::getThemeColor(Theme::TAB_FLASH); + { + foregroundColor = palette.getColor(Theme::TAB_FLASH); + outlineColor = palette.getOutlineColor(Theme::TAB_FLASH); + } else if (mTabColor) + { foregroundColor = *mTabColor; + } auto label = static_cast<Label*>(mLabel); label->setForegroundColor(foregroundColor); - label->setOutlineColor(state->textFormat.outlineColor); + label->setOutlineColor(outlineColor); label->setShadowColor(state->textFormat.shadowColor); } diff --git a/src/gui/widgets/tabbedarea.cpp b/src/gui/widgets/tabbedarea.cpp index fb5436e0..3609791a 100644 --- a/src/gui/widgets/tabbedarea.cpp +++ b/src/gui/widgets/tabbedarea.cpp @@ -112,7 +112,7 @@ void TabbedArea::addTab(const std::string &caption, gcn::Widget *widget) addTab(tab, widget); } -void TabbedArea::removeTab(Tab *tab) +void TabbedArea::removeTab(gcn::Tab *tab) { if (tab == mSelectedTab) { diff --git a/src/gui/widgets/tabbedarea.h b/src/gui/widgets/tabbedarea.h index 558b2696..5d0ccfcc 100644 --- a/src/gui/widgets/tabbedarea.h +++ b/src/gui/widgets/tabbedarea.h @@ -86,12 +86,12 @@ class TabbedArea final : public gcn::TabbedArea, public gcn::WidgetListener void addTab(const std::string &caption, gcn::Widget *widget) override; /** - * Overload the remove tab function as it's broken in guichan 0.8. + * Override the remove tab function as it's broken in guichan 0.8. */ - void removeTab(Tab *tab); + void removeTab(gcn::Tab *tab) override; /** - * Overload the logic function since it's broken in guichan 0.8. + * Override the logic function since it's broken in guichan 0.8. */ void logic() override; diff --git a/src/gui/widgets/table.cpp b/src/gui/widgets/table.cpp index 7cddc1fa..909617a0 100644 --- a/src/gui/widgets/table.cpp +++ b/src/gui/widgets/table.cpp @@ -77,18 +77,12 @@ void GuiTableActionListener::action(const gcn::ActionEvent& actionEvent) } -GuiTable::GuiTable(TableModel *initial_model, gcn::Color background, +GuiTable::GuiTable(TableModel *initialModel, gcn::Color background, bool opacity) : - mLinewiseMode(false), - mWrappingEnabled(false), mOpaque(opacity), - mBackgroundColor(background), - mModel(nullptr), - mSelectedRow(0), - mSelectedColumn(0), - mTopWidget(nullptr) + mBackgroundColor(background) { - setModel(initial_model); + setModel(initialModel); setFocusable(true); addMouseListener(this); @@ -269,10 +263,15 @@ void GuiTable::draw(gcn::Graphics* graphics) if (mOpaque) { - graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND, guiAlpha)); + auto backgroundColor = Theme::getThemeColor(Theme::BACKGROUND); + backgroundColor.a = guiAlpha; + graphics->setColor(backgroundColor); graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); } + auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT); + highlightColor.a = guiAlpha; + // First, determine how many rows we need to draw, and where we should start. int first_row = -(getY() / getRowHeight()); @@ -314,7 +313,7 @@ void GuiTable::draw(gcn::Graphics* graphics) widget->setDimension(bounds); - graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT, guiAlpha)); + graphics->setColor(highlightColor); if (mLinewiseMode && r == mSelectedRow && c == 0) { @@ -361,7 +360,7 @@ void GuiTable::moveToBottom(gcn::Widget *widget) mTopWidget = nullptr; } -gcn::Rectangle GuiTable::getChildrenArea() const +gcn::Rectangle GuiTable::getChildrenArea() { return gcn::Rectangle(0, 0, getWidth(), getHeight()); } @@ -480,7 +479,7 @@ void GuiTable::modelUpdated(bool completed) } } -gcn::Widget *GuiTable::getWidgetAt(int x, int y) const +gcn::Widget *GuiTable::getWidgetAt(int x, int y) { int row = getRowForY(y); int column = getColumnForX(x); diff --git a/src/gui/widgets/table.h b/src/gui/widgets/table.h index 81071267..88350928 100644 --- a/src/gui/widgets/table.h +++ b/src/gui/widgets/table.h @@ -49,7 +49,7 @@ class GuiTable final : public gcn::Widget, friend class GuiTableActionListener; public: - GuiTable(TableModel * initial_model = nullptr, gcn::Color background = 0xffffff, + GuiTable(TableModel *initialModel = nullptr, gcn::Color background = 0xffffff, bool opacity = true); ~GuiTable() override; @@ -84,7 +84,7 @@ public: void setWrappingEnabled(bool wrappingEnabled) {mWrappingEnabled = wrappingEnabled;} - gcn::Rectangle getChildrenArea() const; + gcn::Rectangle getChildrenArea() override; /** * Toggle whether to use linewise selection mode, in which the table selects @@ -105,7 +105,7 @@ public: // Overridden to disable drawing of the frame void drawFrame(gcn::Graphics* graphics) override {} - virtual gcn::Widget *getWidgetAt(int x, int y) const; + gcn::Widget *getWidgetAt(int x, int y) override; void moveToTop(gcn::Widget *child) override; @@ -122,7 +122,7 @@ public: * * @param opaque True if the table should be opaque, false otherwise. */ - virtual void setOpaque(bool opaque) {mOpaque = opaque;} + void setOpaque(bool opaque) {mOpaque = opaque;} /** * Checks if the table is opaque, that is if the table area displays its @@ -130,7 +130,7 @@ public: * * @return True if the table is opaque, false otherwise. */ - virtual bool isOpaque() const {return mOpaque;} + bool isOpaque() const {return mOpaque;} // Inherited from MouseListener void mousePressed(gcn::MouseEvent& mouseEvent) override; @@ -146,19 +146,20 @@ public: protected: /** Frees all action listeners on inner widgets. */ - virtual void uninstallActionListeners(); + void uninstallActionListeners(); /** Installs all action listeners on inner widgets. */ - virtual void installActionListeners(); + void installActionListeners(); - virtual int getRowHeight() const; - virtual int getColumnWidth(int i) const; + int getRowHeight() const; + int getColumnWidth(int i) const; private: int getRowForY(int y) const; // -1 on error int getColumnForX(int x) const; // -1 on error void recomputeDimensions(); - bool mLinewiseMode; - bool mWrappingEnabled; + + bool mLinewiseMode = false; + bool mWrappingEnabled = false; bool mOpaque; /** @@ -166,16 +167,13 @@ private: */ gcn::Color mBackgroundColor; - TableModel *mModel; - - int mSelectedRow; - int mSelectedColumn; + TableModel *mModel = nullptr; - /** Number of frames to skip upwards when drawing the selected widget. */ - int mPopFramesNr; + int mSelectedRow = 0; + int mSelectedColumn = 0; /** If someone moves a fresh widget to the top, we must display it. */ - gcn::Widget *mTopWidget; + gcn::Widget *mTopWidget = nullptr; /** Vector for compactness; used as a list in practice. */ std::vector<GuiTableActionListener *> mActionListeners; diff --git a/src/gui/widgets/textbox.cpp b/src/gui/widgets/textbox.cpp index 419fa16e..7fd7d626 100644 --- a/src/gui/widgets/textbox.cpp +++ b/src/gui/widgets/textbox.cpp @@ -21,15 +21,19 @@ #include "gui/widgets/textbox.h" +#include "gui/gui.h" #include "resources/theme.h" #include <guichan/font.hpp> #include <sstream> -TextBox::TextBox() : - mTextColor(&Theme::getThemeColor(Theme::TEXT)) +TextBox::TextBox() { + auto &palette = gui->getTheme()->getPalette(0); + mTextColor = &palette.getColor(Theme::TEXT); + mOutlineColor = palette.getOutlineColor(Theme::TEXT); + setOpaque(false); setFrameSize(0); mMinWidth = getWidth(); @@ -91,7 +95,7 @@ void TextBox::setTextWrapped(const std::string &text, int minDimension) xpos = width; wrappedStream << word; } - else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <= + else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <= mMinWidth) { xpos += getFont()->getWidth(" ") + width; @@ -147,3 +151,43 @@ void TextBox::setTextWrapped(const std::string &text, int minDimension) gcn::TextBox::setText(wrappedStream.str()); } + +/** + * Overridden so we can customize the color and outline of the text. + */ +void TextBox::draw(gcn::Graphics *graphics) +{ + unsigned int i; + + if (mOpaque) + { + graphics->setColor(getBackgroundColor()); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (isFocused() && isEditable()) + { + drawCaret(graphics, + getFont()->getWidth(mTextRows[mCaretRow].substr(0, mCaretColumn)), + mCaretRow * getFont()->getHeight()); + } + + graphics->setColor(*mTextColor); + graphics->setFont(getFont()); + + auto g = static_cast<Graphics*>(graphics); + + for (i = 0; i < mTextRows.size(); i++) + { + // Move the text one pixel so we can have a caret before a letter. + g->drawText(mTextRows[i], + 1, + i * getFont()->getHeight(), + gcn::Graphics::LEFT, + *mTextColor, + getFont(), + mOutlineColor.has_value(), + false, + mOutlineColor); + } +} diff --git a/src/gui/widgets/textbox.h b/src/gui/widgets/textbox.h index e4596b9a..49a5a2ad 100644 --- a/src/gui/widgets/textbox.h +++ b/src/gui/widgets/textbox.h @@ -23,6 +23,8 @@ #include <guichan/widgets/textbox.hpp> +#include <optional> + /** * A text box, meant to be used inside a scroll area. Same as the Guichan text * box except this one doesn't have a background or border, instead completely @@ -38,6 +40,9 @@ class TextBox : public gcn::TextBox void setTextColor(const gcn::Color *color) { mTextColor = color; } + void setOutlineColor(const std::optional<gcn::Color> &color) + { mOutlineColor = color; } + /** * Sets the text after wrapping it to the current width of the widget. */ @@ -51,13 +56,10 @@ class TextBox : public gcn::TextBox /** * Draws the text. */ - void draw(gcn::Graphics *graphics) override - { - setForegroundColor(*mTextColor); - gcn::TextBox::draw(graphics); - } + void draw(gcn::Graphics *graphics) override; private: int mMinWidth; const gcn::Color *mTextColor; + std::optional<gcn::Color> mOutlineColor; }; diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp index 9f35a5dd..6c866477 100644 --- a/src/gui/widgets/textfield.cpp +++ b/src/gui/widgets/textfield.cpp @@ -114,7 +114,7 @@ int TextField::getValue() const void TextField::drawCaret(gcn::Graphics *graphics, int x) { - graphics->setColor(getForegroundColor()); + graphics->setColor(Theme::getThemeColor(Theme::CARET)); graphics->drawLine(mPadding + x, mPadding, mPadding + x, getHeight() - mPadding); } diff --git a/src/gui/widgets/textpreview.cpp b/src/gui/widgets/textpreview.cpp index f9e85052..aed04853 100644 --- a/src/gui/widgets/textpreview.cpp +++ b/src/gui/widgets/textpreview.cpp @@ -21,57 +21,22 @@ #include "gui/widgets/textpreview.h" -#include "configuration.h" -#include "textrenderer.h" - #include "gui/gui.h" -#include "gui/truetypefont.h" - -#include <typeinfo> -float TextPreview::mAlpha = 1.0; - -TextPreview::TextPreview(const std::string &text): - mText(text) +TextPreview::TextPreview(const std::string &text) + : mText(text) { mFont = gui->getFont(); mTextColor = &Theme::getThemeColor(Theme::TEXT); - mBGColor = &Theme::getThemeColor(Theme::BACKGROUND); } -void TextPreview::draw(gcn::Graphics* graphics) +void TextPreview::draw(gcn::Graphics *graphics) { - if (config.guiAlpha != mAlpha) - mAlpha = config.guiAlpha; - - int alpha = (int) (mAlpha * 255.0f); - - if (!mTextAlpha) - alpha = 255; - - if (mOpaque) - { - graphics->setColor(gcn::Color((int) mBGColor->r, - (int) mBGColor->g, - (int) mBGColor->b, - (int)(mAlpha * 255.0f))); - graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); - } - - if (mTextBGColor && typeid(*mFont) == typeid(TrueTypeFont)) - { - auto *font = static_cast<TrueTypeFont*>(mFont); - int x = font->getWidth(mText) + 1 + 2 * ((mOutline || mShadow) ? 1 :0); - int y = font->getHeight() + 1 + 2 * ((mOutline || mShadow) ? 1 : 0); - graphics->setColor(gcn::Color((int) mTextBGColor->r, - (int) mTextBGColor->g, - (int) mTextBGColor->b, - (int)(mAlpha * 255.0f))); - graphics->fillRectangle(gcn::Rectangle(1, 1, x, y)); - } - - TextRenderer::renderText(graphics, mText, 2, 2, gcn::Graphics::LEFT, - gcn::Color(mTextColor->r, mTextColor->g, - mTextColor->b, alpha), - mFont, mOutline, mShadow); + auto g = static_cast<Graphics*>(graphics); + g->drawText(mText, 2, 2, gcn::Graphics::LEFT, + gcn::Color(mTextColor->r, + mTextColor->g, + mTextColor->b, + 255), + mFont, mOutline, mShadow); } diff --git a/src/gui/widgets/textpreview.h b/src/gui/widgets/textpreview.h index da24b61e..8246a200 100644 --- a/src/gui/widgets/textpreview.h +++ b/src/gui/widgets/textpreview.h @@ -44,37 +44,6 @@ class TextPreview : public gcn::Widget } /** - * Sets the text to use the set alpha value. - * - * @param alpha whether to use alpha values for the text or not - */ - void useTextAlpha(bool alpha) - { - mTextAlpha = alpha; - } - - /** - * Sets the color the text background is drawn in. This is only the - * rectangle directly behind the text, not to full widget. - * - * @param color the color to set - */ - void setTextBGColor(const gcn::Color *color) - { - mTextBGColor = color; - } - - /** - * Sets the background color of the widget. - * - * @param color the color to set - */ - void setBGColor(const gcn::Color *color) - { - mBGColor = color; - } - - /** * Sets the font to render the text in. * * @param font the font to use. @@ -111,29 +80,10 @@ class TextPreview : public gcn::Widget */ void draw(gcn::Graphics *graphics) override; - /** - * Set opacity for this widget (whether or not to show the background - * color) - * - * @param opaque Whether the widget should be opaque or not - */ - void setOpaque(bool opaque) { mOpaque = opaque; } - - /** - * Gets opacity for this widget (whether or not the background color - * is shown below the widget) - */ - bool isOpaque() const { return mOpaque; } - private: gcn::Font *mFont; std::string mText; const gcn::Color *mTextColor; - const gcn::Color *mBGColor; - const gcn::Color *mTextBGColor = nullptr; - static float mAlpha; - bool mTextAlpha = false; - bool mOpaque = false; bool mShadow = false; bool mOutline = false; }; diff --git a/src/gui/widgets/whispertab.cpp b/src/gui/widgets/whispertab.cpp index 636f48dd..28181971 100644 --- a/src/gui/widgets/whispertab.cpp +++ b/src/gui/widgets/whispertab.cpp @@ -36,7 +36,7 @@ WhisperTab::WhisperTab(const std::string &nick) : ChatTab(nick), mNick(nick) { - setTabColor(&Theme::getThemeColor(Theme::WHISPER)); + setTabColor(&Theme::getThemeColor(Theme::WHISPER_TAB)); } WhisperTab::~WhisperTab() diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp index 61ed7896..14d91af1 100644 --- a/src/gui/widgets/window.cpp +++ b/src/gui/widgets/window.cpp @@ -23,7 +23,6 @@ #include "configuration.h" #include "log.h" -#include "textrenderer.h" #include "gui/gui.h" #include "gui/viewport.h" @@ -32,8 +31,6 @@ #include "gui/widgets/resizegrip.h" #include "gui/widgets/windowcontainer.h" -#include "resources/theme.h" - #include <guichan/exception.hpp> #include <guichan/focushandler.hpp> @@ -44,20 +41,25 @@ int Window::instances = 0; int Window::mouseResize = 0; Window::Window(const std::string &caption, bool modal, Window *parent) + : Window(SkinType::Window, caption, modal, parent) +{} + +Window::Window(SkinType skinType, const std::string &caption, bool modal, Window *parent) : gcn::Window(caption) , mParent(parent) , mModal(modal) + , mSkinType(skinType) , mMaxWinWidth(graphics->getWidth()) , mMaxWinHeight(graphics->getHeight()) { - logger->log("Window::Window(\"%s\")", caption.c_str()); + Log::debug("Window::Window(\"%s\")", caption.c_str()); if (!windowContainer) throw GCN_EXCEPTION("Window::Window(): no windowContainer set"); instances++; - auto &skin = gui->getTheme()->getSkin(SkinType::Window); + auto &skin = getSkin(); setFrameSize(skin.frameSize); setPadding(skin.padding); setTitleBarHeight(skin.titleBarHeight); @@ -79,7 +81,7 @@ Window::Window(const std::string &caption, bool modal, Window *parent) Window::~Window() { - logger->log("Window::~Window(\"%s\")", getCaption().c_str()); + Log::debug("Window::~Window(\"%s\")", getCaption().c_str()); saveWindowState(); @@ -123,13 +125,12 @@ void Window::draw(gcn::Graphics *graphics) void Window::drawFrame(gcn::Graphics *graphics) { auto g = static_cast<Graphics*>(graphics); - auto theme = gui->getTheme(); WidgetState widgetState(this); widgetState.width += getFrameSize() * 2; widgetState.height += getFrameSize() * 2; - auto &skin = theme->getSkin(SkinType::Window); + auto &skin = getSkin(); skin.draw(g, widgetState); if (mShowTitle) @@ -137,13 +138,12 @@ void Window::drawFrame(gcn::Graphics *graphics) if (auto skinState = skin.getState(widgetState.flags)) { auto &textFormat = skinState->textFormat; - TextRenderer::renderText(g, - getCaption(), - getFrameSize() + skin.titleOffsetX, - getFrameSize() + skin.titleOffsetY, - gcn::Graphics::LEFT, - textFormat.bold ? boldFont : getFont(), - textFormat); + g->drawText(getCaption(), + getFrameSize() + skin.titleOffsetX, + getFrameSize() + skin.titleOffsetY, + gcn::Graphics::LEFT, + textFormat.bold ? boldFont : getFont(), + textFormat); } } } @@ -169,7 +169,7 @@ void Window::setMinimumContentSize(int width, int height) { const int padding = getPadding(); const int titleBarHeight = getTitleBarHeight(); - auto &skin = gui->getTheme()->getSkin(SkinType::Window); + auto &skin = getSkin(); setMinWidth(std::max(skin.getMinWidth(), width + 2 * padding)); setMinHeight(std::max(skin.getMinHeight(), height + padding + titleBarHeight)); @@ -189,60 +189,14 @@ void Window::setLocationRelativeTo(gcn::Widget *widget) getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y)); } -void Window::setLocationRelativeTo(ImageRect::ImagePosition position, - int offsetX, int offsetY) -{ - if (position == ImageRect::UPPER_LEFT) - { - } - else if (position == ImageRect::UPPER_CENTER) - { - offsetX += (graphics->getWidth() - getWidth()) / 2; - } - else if (position == ImageRect::UPPER_RIGHT) - { - offsetX += graphics->getWidth() - getWidth(); - } - else if (position == ImageRect::LEFT) - { - offsetY += (graphics->getHeight() - getHeight()) / 2; - } - else if (position == ImageRect::CENTER) - { - offsetX += (graphics->getWidth() - getWidth()) / 2; - offsetY += (graphics->getHeight() - getHeight()) / 2; - } - else if (position == ImageRect::RIGHT) - { - offsetX += graphics->getWidth() - getWidth(); - offsetY += (graphics->getHeight() - getHeight()) / 2; - } - else if (position == ImageRect::LOWER_LEFT) - { - offsetY += graphics->getHeight() - getHeight(); - } - else if (position == ImageRect::LOWER_CENTER) - { - offsetX += (graphics->getWidth() - getWidth()) / 2; - offsetY += graphics->getHeight() - getHeight(); - } - else if (position == ImageRect::LOWER_RIGHT) - { - offsetX += graphics->getWidth() - getWidth(); - offsetY += graphics->getHeight() - getHeight(); - } - - setPosition(offsetX, offsetY); -} - void Window::setMinWidth(int width) { - mMinWinWidth = std::max(gui->getTheme()->getMinWidth(SkinType::Window), width); + mMinWinWidth = std::max(getSkin().getMinWidth(), width); } void Window::setMinHeight(int height) { - mMinWinHeight = std::max(gui->getTheme()->getMinHeight(SkinType::Window), height); + mMinWinHeight = std::max(getSkin().getMinHeight(), height); } void Window::setMaxWidth(int width) @@ -375,6 +329,11 @@ void Window::close() setVisible(false); } +const Skin &Window::getSkin() const +{ + return gui->getTheme()->getSkin(mSkinType); +} + void Window::mouseReleased(gcn::MouseEvent &event) { mouseResize = 0; @@ -592,41 +551,41 @@ void Window::setDefaultSize() } void Window::setDefaultSize(int defaultWidth, int defaultHeight, - ImageRect::ImagePosition position, + WindowAlignment alignment, int offsetX, int offsetY) { int x = 0; int y = 0; - switch (position) + switch (alignment) { - case ImageRect::UPPER_LEFT: + case WindowAlignment::TopLeft: break; - case ImageRect::UPPER_CENTER: + case WindowAlignment::Top: x = (graphics->getWidth() - defaultWidth) / 2; break; - case ImageRect::UPPER_RIGHT: + case WindowAlignment::TopRight: x = graphics->getWidth() - defaultWidth; break; - case ImageRect::LEFT: + case WindowAlignment::Left: y = (graphics->getHeight() - defaultHeight) / 2; break; - case ImageRect::CENTER: + case WindowAlignment::Center: x = (graphics->getWidth() - defaultWidth) / 2; y = (graphics->getHeight() - defaultHeight) / 2; break; - case ImageRect::RIGHT: + case WindowAlignment::Right: x = graphics->getWidth() - defaultWidth; y = (graphics->getHeight() - defaultHeight) / 2; break; - case ImageRect::LOWER_LEFT: + case WindowAlignment::BottomLeft: y = graphics->getHeight() - defaultHeight; break; - case ImageRect::LOWER_CENTER: + case WindowAlignment::Bottom: x = (graphics->getWidth() - defaultWidth) / 2; y = graphics->getHeight() - defaultHeight; break; - case ImageRect::LOWER_RIGHT: + case WindowAlignment::BottomRight: x = graphics->getWidth() - defaultWidth; y = graphics->getHeight() - defaultHeight; break; @@ -707,15 +666,15 @@ gcn::Rectangle Window::getCloseButtonRect() const auto theme = gui->getTheme(); - auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose); - const int closeButtonWidth = closeButtonSkin.getMinWidth(); - const int closeButtonHeight = closeButtonSkin.getMinHeight(); + auto &closeSkin = theme->getSkin(SkinType::ButtonClose); + const int closeWidth = closeSkin.getMinWidth(); + const int closeHeight = closeSkin.getMinHeight(); return { - getWidth() - closeButtonWidth - closeButtonSkin.padding, - closeButtonSkin.padding, - closeButtonWidth, - closeButtonHeight + getWidth() - closeWidth - closeSkin.padding, + closeSkin.padding, + closeWidth, + closeHeight }; } @@ -726,22 +685,22 @@ gcn::Rectangle Window::getStickyButtonRect() const auto theme = gui->getTheme(); - auto &closeButtonSkin = theme->getSkin(SkinType::ButtonClose); - const int closeButtonWidth = closeButtonSkin.getMinWidth(); + auto &closeSkin = theme->getSkin(SkinType::ButtonClose); + const int closeWidth = closeSkin.getMinWidth(); - auto &stickyButtonSkin = theme->getSkin(SkinType::ButtonSticky); - const int stickyButtonWidth = stickyButtonSkin.getMinWidth(); - const int stickyButtonHeight = stickyButtonSkin.getMinHeight(); + auto &stickySkin = theme->getSkin(SkinType::ButtonSticky); + const int stickyWidth = stickySkin.getMinWidth(); + const int stickyHeight = stickySkin.getMinHeight(); - int stickyButtonX = getWidth() - stickyButtonWidth - stickyButtonSkin.padding; + int stickyX = getWidth() - stickyWidth - stickySkin.padding - stickySkin.spacing; if (mCloseButton) - stickyButtonX -= closeButtonWidth + closeButtonSkin.padding; + stickyX -= closeWidth + closeSkin.padding; return { - stickyButtonX, - stickyButtonSkin.padding, - stickyButtonWidth, - stickyButtonHeight + stickyX, + stickySkin.padding, + stickyWidth, + stickyHeight }; } diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h index bf459a32..2a47b0b9 100644 --- a/src/gui/widgets/window.h +++ b/src/gui/widgets/window.h @@ -24,8 +24,9 @@ #include "graphics.h" #include "guichanfwd.h" -#include <guichan/widgetlistener.hpp> +#include "resources/theme.h" +#include <guichan/widgetlistener.hpp> #include <guichan/widgets/window.hpp> class ContainerPlacer; @@ -35,6 +36,19 @@ class ResizeGrip; class Skin; class WindowContainer; +enum class WindowAlignment +{ + TopLeft, + Top, + TopRight, + Left, + Center, + Right, + BottomLeft, + Bottom, + BottomRight +}; + /** * A window. This window can be dragged around and has a title bar. Windows are * invisible by default. @@ -54,7 +68,16 @@ class Window : public gcn::Window, gcn::WidgetListener * this one in the window hiearchy. When reordering, * a window will never go below its parent window. */ - Window(const std::string &caption = "Window", bool modal = false, + Window(const std::string &caption = "Window", + bool modal = false, + Window *parent = nullptr); + + /** + * Constructor that allows customizing the SkinType used by the window. + */ + Window(SkinType skinType, + const std::string &caption = "Window", + bool modal = false, Window *parent = nullptr); /** @@ -93,12 +116,6 @@ class Window : public gcn::Window, gcn::WidgetListener void setLocationRelativeTo(gcn::Widget *widget); /** - * Sets the location relative to the given enumerated position. - */ - void setLocationRelativeTo(ImageRect::ImagePosition position, - int offsetX = 0, int offsetY = 0); - - /** * Sets whether or not the window can be resized. */ void setResizable(bool resize); @@ -293,7 +310,7 @@ class Window : public gcn::Window, gcn::WidgetListener * on a relative enumerated position, rather than a coordinate position. */ void setDefaultSize(int defaultWidth, int defaultHeight, - ImageRect::ImagePosition position, + WindowAlignment alignment, int offsetx = 0, int offsetY = 0); /** @@ -353,6 +370,11 @@ class Window : public gcn::Window, gcn::WidgetListener virtual void close(); /** + * Returns the Skin used by this window. + */ + const Skin &getSkin() const; + + /** * Gets the alpha value used by the window, in a Guichan usable format. */ static int getGuiAlpha(); @@ -400,6 +422,7 @@ class Window : public gcn::Window, gcn::WidgetListener bool mSaveVisible = false; /**< Window will save visibility */ bool mStickyButton = false; /**< Window has a sticky button */ bool mSticky = false; /**< Window resists hiding*/ + SkinType mSkinType; /**< The skin type used when drawing the window. */ int mMinWinWidth = 100; /**< Minimum window width */ int mMinWinHeight = 40; /**< Minimum window height */ int mMaxWinWidth; /**< Maximum window width */ diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp index 0b2d126f..2c1b6211 100644 --- a/src/gui/windowmenu.cpp +++ b/src/gui/windowmenu.cpp @@ -23,9 +23,10 @@ #include "graphics.h" +#include "gui/abilitieswindow.h" #include "gui/emotepopup.h" +#include "gui/questswindow.h" #include "gui/skilldialog.h" -#include "gui/abilitieswindow.h" #include "gui/widgets/button.h" #include "gui/widgets/window.h" @@ -34,6 +35,8 @@ #include "net/net.h" #include "net/playerhandler.h" +#include "resources/questdb.h" + #include "utils/gettext.h" #include <string> @@ -64,6 +67,9 @@ WindowMenu::WindowMenu() if (abilitiesWindow->hasAbilities()) addButton(N_("Abilities"), x, h, "button-icon-abilities.png"); + if (QuestDB::hasQuests()) + addButton(N_("Quests"), x, h, "button-icon-quests.png"); + addButton(N_("Social"), x, h, "button-icon-social.png", KeyboardConfig::KEY_WINDOW_SOCIAL); addButton(N_("Shortcuts"), x, h, "button-icon-shortcut.png", @@ -122,6 +128,10 @@ void WindowMenu::action(const gcn::ActionEvent &event) { window = skillDialog; } + else if (event.getId() == "Quests") + { + window = questsWindow; + } else if (event.getId() == "Abilities") { window = abilitiesWindow; @@ -166,12 +176,18 @@ static std::string createShortcutCaption(const std::string &text, KeyboardConfig::KeyAction key) { std::string caption = gettext(text.c_str()); + if (key != KeyboardConfig::KEY_NO_VALUE) { - caption += " ("; - caption += SDL_GetKeyName(keyboard.getKeyValue(key)); - caption += ")"; + auto keyValue = keyboard.getKeyValue(key); + if (keyValue > 0) + { + caption += " ("; + caption += SDL_GetKeyName(keyValue); + caption += ")"; + } } + return caption; } @@ -179,7 +195,7 @@ void WindowMenu::addButton(const std::string &text, int &x, int &h, const std::string &iconPath, KeyboardConfig::KeyAction key) { - auto *btn = new Button("", text, this); + auto *btn = new Button(std::string(), text, this); if (!iconPath.empty() && btn->setButtonIcon(iconPath)) { btn->setButtonPopupText(createShortcutCaption(text, key)); @@ -187,7 +203,7 @@ void WindowMenu::addButton(const std::string &text, int &x, int &h, else { btn->setCaption(gettext(text.c_str())); - btn->setButtonPopupText(createShortcutCaption("", key)); + btn->setButtonPopupText(createShortcutCaption(std::string(), key)); } btn->setPosition(x, 0); @@ -204,40 +220,45 @@ void WindowMenu::updatePopUpCaptions() if (!button) continue; - std::string eventId = button->getActionEventId(); + const std::string &eventId = button->getActionEventId(); if (eventId == "Status") { - button->setButtonPopupText(createShortcutCaption("Status", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_STATUS)); } else if (eventId == "Equipment") { - button->setButtonPopupText(createShortcutCaption("Equipment", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_EQUIPMENT)); } else if (eventId == "Inventory") { - button->setButtonPopupText(createShortcutCaption("Inventory", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_INVENTORY)); } else if (eventId == "Skills") { - button->setButtonPopupText(createShortcutCaption("Skills", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_SKILL)); } + else if (eventId == "Quests") + { + button->setButtonPopupText( + createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_QUESTS)); + } else if (eventId == "Social") { - button->setButtonPopupText(createShortcutCaption("Social", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_SOCIAL)); } else if (eventId == "Shortcuts") { - button->setButtonPopupText(createShortcutCaption("Shortcuts", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_SHORTCUT)); } else if (eventId == "Setup") { - button->setButtonPopupText(createShortcutCaption("Setup", + button->setButtonPopupText(createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_SETUP)); } } |