1 /* 2 * The ManaPlus Client 3 * Copyright (C) 2011-2019 The ManaPlus Developers 4 * Copyright (C) 2019-2021 Andrei Karas 5 * 6 * This file is part of The ManaPlus Client. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 22 #ifndef GUI_WIDGETS_SKILLRECTANGLELISTBOX_H 23 #define GUI_WIDGETS_SKILLRECTANGLELISTBOX_H 24 25 #include "const/resources/skill.h" 26 27 #include "dragdrop.h" 28 #include "settings.h" 29 30 #include "gui/skin.h" 31 #include "gui/viewport.h" 32 33 #include "gui/fonts/font.h" 34 35 #include "gui/models/skillmodel.h" 36 37 #include "gui/popups/popupmenu.h" 38 #include "gui/popups/skillpopup.h" 39 40 #include "utils/delete2.h" 41 42 #include "render/graphics.h" 43 44 #include "localconsts.h" 45 46 class SkillModel; 47 48 class SkillRectangleListBox final : public Widget, 49 public MouseListener 50 { 51 public: SkillRectangleListBox(const Widget2 * const widget,SkillModel * const model)52 SkillRectangleListBox(const Widget2 *const widget, 53 SkillModel *const model) : 54 Widget(widget), 55 MouseListener(), 56 mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)), 57 mTextColor(getThemeColor(ThemeColorId::TEXT, 255U)), 58 mTextColor2(getThemeColor(ThemeColorId::TEXT_OUTLINE, 255U)), 59 mCooldownColor(getThemeColor(ThemeColorId::SKILL_COOLDOWN, 255U)), 60 mForegroundSelectedColor(getThemeColor( 61 ThemeColorId::LISTBOX_SELECTED, 255U)), 62 mForegroundSelectedColor2(getThemeColor( 63 ThemeColorId::LISTBOX_SELECTED_OUTLINE, 255U)), 64 mModel(model), 65 mSkin(nullptr), 66 mSelected(-1), 67 mPadding(2), 68 mBoxWidth(80), 69 mBoxHeight(70), 70 mIconXOffset(24), 71 mIconYOffset(10), 72 mTextXOffset(0), 73 mTextYOffset(44), 74 mSkillClicked(false) 75 { 76 if (theme != nullptr) 77 { 78 mSkin = theme->load("skillrectanglelistbox.xml", 79 "listbox.xml", 80 true, 81 Theme::getThemePath()); 82 } 83 84 if (mSkin != nullptr) 85 { 86 mPadding = mSkin->getPadding(); 87 mBoxWidth = mSkin->getOption("boxWidth", 80); 88 mBoxHeight = mSkin->getOption("boxHeight", 70); 89 mIconXOffset = mSkin->getOption("iconXOffset", 24); 90 mIconYOffset = mSkin->getOption("iconYOffset", 10); 91 mTextXOffset = mSkin->getOption("textXOffset", 0); 92 mTextYOffset = mSkin->getOption("textYOffset", 44); 93 } 94 Font *const font = getFont(); 95 int minWidth = font->getWidth("Lvl: 10/10") + mTextXOffset + 2; 96 int minHeight = font->getHeight() + mTextYOffset + 2; 97 if (mBoxWidth < minWidth) 98 mBoxWidth = minWidth; 99 if (mBoxHeight < minHeight) 100 mBoxHeight = minHeight; 101 int maxX = 0; 102 int maxY = 0; 103 for (int i = 0; 104 i < model->getNumberOfElements(); 105 ++i) 106 { 107 SkillInfo *const e = model->getSkillAt(i); 108 if (e != nullptr) 109 { 110 if (e->x > maxX) 111 maxX = e->x; 112 if (e->y > maxY) 113 maxY = e->y; 114 } 115 } 116 maxX ++; 117 maxY ++; 118 setWidth(maxX * mBoxWidth); 119 setHeight(maxY * mBoxHeight); 120 addMouseListener(this); 121 } 122 123 A_DELETE_COPY(SkillRectangleListBox) 124 ~SkillRectangleListBox()125 ~SkillRectangleListBox() override final 126 { 127 delete2(mModel) 128 } 129 getSelectedInfo()130 SkillInfo *getSelectedInfo() const 131 { 132 if (mModel == nullptr) 133 return nullptr; 134 const int selected = mSelected; 135 if (selected < 0 || 136 selected > mModel->getNumberOfElements()) 137 { 138 return nullptr; 139 } 140 141 return mModel->getSkillAt(selected); 142 } 143 draw(Graphics * const graphics)144 void draw(Graphics *const graphics) override final A_NONNULL(2) 145 { 146 if (mModel == nullptr) 147 return; 148 149 SkillModel *const model = mModel; 150 updateAlpha(); 151 152 int maxX = 0; 153 int maxY = 0; 154 mHighlightColor.a = CAST_S32(mAlpha * 255.0F); 155 graphics->setColor(mHighlightColor); 156 Font *const font = getFont(); 157 if (mSelected >= 0) 158 { 159 SkillInfo *const e = model->getSkillAt(mSelected); 160 if (e != nullptr) 161 { 162 const int x = e->x * mBoxWidth + mPadding; 163 const int y = e->y * mBoxHeight + mPadding; 164 165 graphics->fillRectangle(Rect(x, y, 166 mBoxWidth, mBoxHeight)); 167 168 const int xOffset = (mBoxWidth - 169 font->getWidth(e->skillLevel)) / 2; 170 font->drawString(graphics, 171 mForegroundSelectedColor, 172 mForegroundSelectedColor, 173 e->skillLevel, 174 x + mTextXOffset + xOffset, 175 y + mTextYOffset); 176 } 177 } 178 179 // +++ need split drawing icons and text 180 for (int i = 0; 181 i < model->getNumberOfElements(); 182 ++i) 183 { 184 SkillInfo *const e = model->getSkillAt(i); 185 if (e != nullptr) 186 { 187 if (e->x > maxX) 188 maxX = e->x; 189 if (e->y > maxY) 190 maxY = e->y; 191 const SkillData *const data = e->data; 192 const int x = e->x * mBoxWidth + mPadding; 193 const int y = e->y * mBoxHeight + mPadding; 194 195 graphics->drawImage(data->icon, 196 x + mIconXOffset, 197 y + mIconYOffset); 198 199 if (i != mSelected) 200 { 201 const int width1 = font->getWidth(e->skillLevel); 202 const int xOffset = (mBoxWidth - 203 width1) / 2; 204 font->drawString(graphics, 205 mTextColor, 206 mTextColor2, 207 e->skillLevel, 208 x + mTextXOffset + xOffset, 209 y + mTextYOffset); 210 if (e->skillLevelWidth < 0) 211 { 212 // Add one for padding 213 e->skillLevelWidth = width1 + 1; 214 } 215 } 216 else 217 { 218 if (e->skillLevelWidth < 0) 219 { 220 // Add one for padding 221 e->skillLevelWidth = font->getWidth( 222 e->skillLevel) + 1; 223 } 224 } 225 } 226 } 227 maxX ++; 228 maxY ++; 229 setWidth(maxX * mBoxWidth); 230 setHeight(maxY * mBoxHeight); 231 } 232 safeDraw(Graphics * const graphics)233 void safeDraw(Graphics *const graphics) override final A_NONNULL(2) 234 { 235 SkillRectangleListBox::draw(graphics); 236 } 237 getSkillByEvent(const MouseEvent & event)238 const SkillInfo *getSkillByEvent(const MouseEvent &event) const 239 { 240 if (mModel == nullptr) 241 return nullptr; 242 const int posX = (event.getX() - mPadding) / mBoxWidth; 243 const int posY = (event.getY() - mPadding) / mBoxHeight; 244 for (int i = 0; 245 i < mModel->getNumberOfElements(); 246 ++i) 247 { 248 SkillInfo *const e = mModel->getSkillAt(i); 249 if (e != nullptr) 250 { 251 if (posX == e->x && posY == e->y) 252 return e; 253 } 254 } 255 return nullptr; 256 } 257 getSelectionByEvent(const MouseEvent & event)258 int getSelectionByEvent(const MouseEvent &event) const 259 { 260 if (mModel == nullptr) 261 return -1; 262 const int posX = (event.getX() - mPadding) / mBoxWidth; 263 const int posY = (event.getY() - mPadding) / mBoxHeight; 264 for (int i = 0; 265 i < mModel->getNumberOfElements(); 266 ++i) 267 { 268 SkillInfo *const e = mModel->getSkillAt(i); 269 if (e != nullptr) 270 { 271 if (posX == e->x && posY == e->y) 272 return i; 273 } 274 } 275 return -1; 276 } 277 mouseMoved(MouseEvent & event)278 void mouseMoved(MouseEvent &event) override final 279 { 280 if ((viewport == nullptr) || !dragDrop.isEmpty()) 281 return; 282 283 const SkillInfo *const skill = getSkillByEvent(event); 284 if (skill == nullptr) 285 return; 286 skillPopup->show(skill, 287 skill->customSelectedLevel, 288 skill->customCastType, 289 skill->customOffsetX, 290 skill->customOffsetY); 291 skillPopup->position(viewport->mMouseX, 292 viewport->mMouseY); 293 } 294 mouseDragged(MouseEvent & event)295 void mouseDragged(MouseEvent &event) override final 296 { 297 if (event.getButton() != MouseButton::LEFT) 298 return; 299 setSelected(std::max(0, getSelectionByEvent(event))); 300 301 if (dragDrop.isEmpty()) 302 { 303 if (mSkillClicked) 304 { 305 mSkillClicked = false; 306 const SkillInfo *const skill = getSkillByEvent(event); 307 if (skill == nullptr) 308 return; 309 dragDrop.dragSkill(skill, DragDropSource::Skills, 0); 310 dragDrop.setItem(skill->id + SKILL_MIN_ID); 311 dragDrop.setItemData(skill->toDataStr()); 312 } 313 } 314 } 315 mousePressed(MouseEvent & event)316 void mousePressed(MouseEvent &event) override final 317 { 318 const MouseButtonT button = event.getButton(); 319 if (button == MouseButton::LEFT || 320 button == MouseButton::RIGHT) 321 { 322 const SkillInfo *const skill = getSkillByEvent(event); 323 if (skill == nullptr) 324 return; 325 event.consume(); 326 mSkillClicked = true; 327 SkillModel *const model = mModel; 328 if ((model != nullptr) && 329 mSelected >= 0 && 330 model->getSkillAt(mSelected) == skill) 331 { 332 skillPopup->hide(); 333 334 const int x = skill->x * mBoxWidth + mPadding; 335 const int y = skill->y * mBoxHeight + mPadding; 336 Font *const font = getFont(); 337 const int height = font->getHeight(); 338 const int eventX = event.getX(); 339 const int eventY = event.getY() - mTextYOffset; 340 if (button == MouseButton::LEFT) 341 { 342 if (eventX >= x + mTextXOffset && 343 eventX <= x + mBoxWidth - mTextXOffset && 344 eventY >= y && 345 eventY <= y + height) 346 { 347 popupMenu->showSkillLevelPopup(skill); 348 } 349 } 350 else if (button == MouseButton::RIGHT) 351 { 352 popupMenu->showSkillPopup(skill); 353 } 354 } 355 } 356 } 357 mouseReleased(MouseEvent & event)358 void mouseReleased(MouseEvent &event) override final 359 { 360 if (event.getButton() == MouseButton::LEFT) 361 { 362 setSelected(std::max(0, getSelectionByEvent(event))); 363 distributeActionEvent(); 364 } 365 } 366 mouseExited(MouseEvent & event A_UNUSED)367 void mouseExited(MouseEvent &event A_UNUSED) override final 368 { 369 skillPopup->hide(); 370 } 371 updateAlpha()372 void updateAlpha() 373 { 374 const float alpha = std::max(settings.guiAlpha, 375 theme->getMinimumOpacity()); 376 377 if (mAlpha != alpha) 378 mAlpha = alpha; 379 } 380 setSelected(const int selected)381 void setSelected(const int selected) 382 { 383 if (mModel == nullptr) 384 { 385 mSelected = -1; 386 } 387 else 388 { 389 if (selected < 0) 390 mSelected = -1; 391 else if (selected >= mModel->getNumberOfElements()) 392 mSelected = mModel->getNumberOfElements() - 1; 393 else 394 mSelected = selected; 395 } 396 } 397 398 private: 399 Color mHighlightColor; 400 Color mTextColor; 401 Color mTextColor2; 402 Color mCooldownColor; 403 Color mForegroundSelectedColor; 404 Color mForegroundSelectedColor2; 405 SkillModel *mModel; 406 Skin *mSkin; 407 int mSelected; 408 int mPadding; 409 int mBoxWidth; 410 int mBoxHeight; 411 int mIconXOffset; 412 int mIconYOffset; 413 int mTextXOffset; 414 int mTextYOffset; 415 bool mSkillClicked; 416 static float mAlpha; 417 }; 418 419 float SkillRectangleListBox::mAlpha = 1.0; 420 421 #endif // GUI_WIDGETS_SKILLRECTANGLELISTBOX_H 422