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