1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2004-2009  The Mana World Development Team
4  *  Copyright (C) 2009-2010  The Mana Developers
5  *  Copyright (C) 2011-2019  The ManaPlus Developers
6  *  Copyright (C) 2019-2021  Andrei Karas
7  *
8  *  This file is part of The ManaPlus Client.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /*      _______   __   __   __   ______   __   __   _______   __   __
25  *     / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___  /\ /  |\/ /\
26  *    / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27  *   / / /__   / / // / // / // / /    / ___  / // ___  / // /| ' / /
28  *  / /_// /\ / /_// / // / // /_/_   / / // / // /\_/ / // / |  / /
29  * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30  * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31  *
32  * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33  *
34  *
35  * Per Larsson a.k.a finalman
36  * Olof Naessén a.k.a jansem/yakslem
37  *
38  * Visit: http://guichan.sourceforge.net
39  *
40  * License: (BSD)
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in
48  *    the documentation and/or other materials provided with the
49  *    distribution.
50  * 3. Neither the name of Guichan nor the names of its contributors may
51  *    be used to endorse or promote products derived from this software
52  *    without specific prior written permission.
53  *
54  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65  */
66 
67 #include "gui/widgets/slider.h"
68 
69 #include "settings.h"
70 
71 #include "enums/gui/slidergrid.h"
72 
73 #include "gui/gui.h"
74 
75 #include "utils/delete2.h"
76 
77 #include "resources/imagerect.h"
78 
79 #include "resources/image/image.h"
80 
81 #include "render/graphics.h"
82 
83 #include "render/vertexes/imagecollection.h"
84 
85 #include "debug.h"
86 
87 ImageRect Slider::buttons[2];
88 float Slider::mAlpha = 1.0;
89 int Slider::mInstances = 0;
90 
91 static std::string const data[2] =
92 {
93     "slider.xml",
94     "slider_highlighted.xml"
95 };
96 
Slider(Widget2 * const widget,const double scaleEnd,const double stepLength)97 Slider::Slider(Widget2 *const widget,
98                const double scaleEnd,
99                const double stepLength) :
100     Widget(widget),
101     MouseListener(),
102     KeyListener(),
103     mValue(0),
104     mStepLength(stepLength),
105     mScaleStart(0),
106     mScaleEnd(scaleEnd),
107     mOrientation(Orientation::HORIZONTAL),
108     mVertexes(new ImageCollection),
109     mMarkerLength(10),
110     mHasMouse(false)
111 {
112     init();
113 }
114 
Slider(Widget2 * const widget,const double scaleStart,const double scaleEnd,const double stepLength)115 Slider::Slider(Widget2 *const widget,
116                const double scaleStart,
117                const double scaleEnd,
118                const double stepLength) :
119     Widget(widget),
120     MouseListener(),
121     KeyListener(),
122     mValue(scaleStart),
123     mStepLength(stepLength),
124     mScaleStart(scaleStart),
125     mScaleEnd(scaleEnd),
126     mOrientation(Orientation::HORIZONTAL),
127     mVertexes(new ImageCollection),
128     mMarkerLength(10),
129     mHasMouse(false)
130 {
131     init();
132 }
133 
~Slider()134 Slider::~Slider()
135 {
136     if (gui != nullptr)
137         gui->removeDragged(this);
138 
139     delete2(mVertexes)
140     mInstances--;
141     if (mInstances == 0)
142     {
143         for (int mode = 0; mode < 2; mode ++)
144             Theme::unloadRect(buttons[mode], 0, 8);
145     }
146 }
147 
init()148 void Slider::init()
149 {
150     mAllowLogic = false;
151     setFocusable(true);
152     setFrameSize(1);
153 
154     addMouseListener(this);
155     addKeyListener(this);
156 
157     setFrameSize(0);
158 
159     // Load resources
160     if (mInstances == 0)
161     {
162         if (theme != nullptr)
163         {
164             for (int mode = 0; mode < 2; mode ++)
165                 theme->loadRect(buttons[mode], data[mode], "slider.xml", 0, 8);
166         }
167         updateAlpha();
168     }
169 
170     mInstances++;
171 
172     if (buttons[0].grid[SliderGrid::HGRIP] != nullptr)
173         setMarkerLength(buttons[0].grid[SliderGrid::HGRIP]->getWidth());
174 }
175 
updateAlpha()176 void Slider::updateAlpha()
177 {
178     const float alpha = std::max(settings.guiAlpha,
179         theme->getMinimumOpacity());
180 
181     if (alpha != mAlpha)
182     {
183         mAlpha = alpha;
184         for (int f = 0; f < 2; f ++)
185         {
186             for (int d = 0; d < SliderGrid::SLIDER_MAX; d ++)
187             {
188                 if (buttons[f].grid[d] != nullptr)
189                     buttons[f].grid[d]->setAlpha(mAlpha);
190             }
191         }
192     }
193 }
194 
draw(Graphics * const graphics)195 void Slider::draw(Graphics *const graphics)
196 {
197     BLOCK_START("Slider::draw")
198     if ((buttons[0].grid[SliderGrid::HSTART] == nullptr) ||
199         (buttons[1].grid[SliderGrid::HSTART] == nullptr) ||
200         (buttons[0].grid[SliderGrid::HEND] == nullptr))
201     {
202         BLOCK_END("Slider::draw")
203         return;
204     }
205 
206     int w = getWidth();
207     const int h = getHeight();
208     const int y = mHasMouse ?
209         (h - buttons[1].grid[SliderGrid::HSTART]->getHeight()) / 2 :
210         (h - buttons[0].grid[SliderGrid::HSTART]->getHeight()) / 2;
211 
212     updateAlpha();
213 
214     if (mRedraw || graphics->getRedraw())
215     {
216         int x = 0;
217         mRedraw = false;
218         mVertexes->clear();
219         if (!mHasMouse)
220         {
221             graphics->calcTileCollection(mVertexes,
222                 buttons[0].grid[SliderGrid::HSTART],
223                 x, y);
224 
225             const int width = buttons[0].grid[SliderGrid::HSTART]->getWidth();
226             w -= width + buttons[0].grid[SliderGrid::HEND]->getWidth();
227             x += width;
228 
229             if (buttons[0].grid[SliderGrid::HMID] != nullptr)
230             {
231                 const Image *const hMid = buttons[0].grid[SliderGrid::HMID];
232                 graphics->calcPattern(mVertexes,
233                     hMid,
234                     x, y,
235                     w, hMid->getHeight());
236             }
237 
238             x += w;
239             graphics->calcTileCollection(mVertexes,
240                 buttons[0].grid[SliderGrid::HEND],
241                 x, y);
242 
243             const Image *const img = buttons[0].grid[SliderGrid::HGRIP];
244             if (img != nullptr)
245             {
246                 graphics->calcTileCollection(mVertexes,
247                     img,
248                     getMarkerPosition(),
249                     (mDimension.height - img->getHeight()) / 2);
250             }
251         }
252         else
253         {
254             graphics->calcTileCollection(mVertexes,
255                 buttons[1].grid[SliderGrid::HSTART],
256                 x, y);
257 
258             const int width = buttons[1].grid[SliderGrid::HSTART]->getWidth();
259             w -= width;
260             if (buttons[1].grid[SliderGrid::HEND] != nullptr)
261                 w -= buttons[1].grid[SliderGrid::HEND]->getWidth();
262             x += width;
263 
264             if (buttons[1].grid[SliderGrid::HMID] != nullptr)
265             {
266                 const Image *const hMid = buttons[1].grid[SliderGrid::HMID];
267                 graphics->calcPattern(mVertexes,
268                     hMid,
269                     x, y,
270                     w, hMid->getHeight());
271             }
272 
273             x += w;
274             if (buttons[1].grid[SliderGrid::HEND] != nullptr)
275             {
276                 graphics->calcTileCollection(mVertexes,
277                     buttons[1].grid[SliderGrid::HEND], x, y);
278             }
279 
280             const Image *const img = buttons[1].grid[SliderGrid::HGRIP];
281             if (img != nullptr)
282             {
283                 graphics->calcTileCollection(mVertexes,
284                     img,
285                     getMarkerPosition(),
286                     (mDimension.height - img->getHeight()) / 2);
287             }
288         }
289         graphics->finalize(mVertexes);
290     }
291     graphics->drawTileCollection(mVertexes);
292 
293     BLOCK_END("Slider::draw")
294 }
295 
safeDraw(Graphics * const graphics)296 void Slider::safeDraw(Graphics *const graphics)
297 {
298     BLOCK_START("Slider::draw")
299     if ((buttons[0].grid[SliderGrid::HSTART] == nullptr) ||
300         (buttons[1].grid[SliderGrid::HSTART] == nullptr) ||
301         (buttons[0].grid[SliderGrid::HEND] == nullptr))
302     {
303         BLOCK_END("Slider::draw")
304         return;
305     }
306 
307     int w = getWidth();
308     const int h = getHeight();
309     int x = 0;
310     const int y = mHasMouse ?
311         (h - buttons[1].grid[SliderGrid::HSTART]->getHeight()) / 2 :
312         (h - buttons[0].grid[SliderGrid::HSTART]->getHeight()) / 2;
313 
314     updateAlpha();
315 
316     if (!mHasMouse)
317     {
318         graphics->drawImage(buttons[0].grid[SliderGrid::HSTART], x, y);
319         const int width = buttons[0].grid[SliderGrid::HSTART]->getWidth();
320         w -= width + buttons[0].grid[SliderGrid::HEND]->getWidth();
321         x += width;
322 
323         if (buttons[0].grid[SliderGrid::HMID] != nullptr)
324         {
325             const Image *const hMid = buttons[0].grid[SliderGrid::HMID];
326             graphics->drawPattern(hMid, x, y, w, hMid->getHeight());
327         }
328 
329         x += w;
330         graphics->drawImage(buttons[0].grid[SliderGrid::HEND], x, y);
331 
332         const Image *const img = buttons[0].grid[SliderGrid::HGRIP];
333         if (img != nullptr)
334         {
335             graphics->drawImage(img, getMarkerPosition(),
336                 (mDimension.height - img->getHeight()) / 2);
337         }
338     }
339     else
340     {
341         graphics->drawImage(buttons[1].grid[SliderGrid::HSTART], x, y);
342 
343         const int width = buttons[1].grid[SliderGrid::HSTART]->getWidth();
344         w -= width;
345         if (buttons[1].grid[SliderGrid::HEND] != nullptr)
346             w -= buttons[1].grid[SliderGrid::HEND]->getWidth();
347         x += width;
348 
349         if (buttons[1].grid[SliderGrid::HMID] != nullptr)
350         {
351             const Image *const hMid = buttons[1].grid[SliderGrid::HMID];
352             graphics->drawPattern(hMid, x, y, w, hMid->getHeight());
353         }
354 
355         x += w;
356         if (buttons[1].grid[SliderGrid::HEND] != nullptr)
357             graphics->drawImage(buttons[1].grid[SliderGrid::HEND], x, y);
358 
359         const Image *const img = buttons[1].grid[SliderGrid::HGRIP];
360         if (img != nullptr)
361         {
362             graphics->drawImage(img, getMarkerPosition(),
363                 (mDimension.height - img->getHeight()) / 2);
364         }
365     }
366 
367     BLOCK_END("Slider::draw")
368 }
369 
mouseEntered(MouseEvent & event A_UNUSED)370 void Slider::mouseEntered(MouseEvent& event A_UNUSED)
371 {
372     mHasMouse = true;
373     mRedraw = true;
374 }
375 
mouseExited(MouseEvent & event A_UNUSED)376 void Slider::mouseExited(MouseEvent& event A_UNUSED)
377 {
378     mHasMouse = false;
379     mRedraw = true;
380 }
381 
mousePressed(MouseEvent & event)382 void Slider::mousePressed(MouseEvent &event)
383 {
384     const int x = event.getX();
385     const int y = event.getY();
386     const int width = mDimension.width;
387     const int height = mDimension.height;
388 
389     if (event.getButton() == MouseButton::LEFT
390         && x >= 0 && x <= width && y >= 0 && y <= height)
391     {
392         event.consume();
393         if (mOrientation == Orientation::HORIZONTAL)
394             setValue(markerPositionToValue(x - mMarkerLength / 2));
395         else
396             setValue(markerPositionToValue(height - y - mMarkerLength / 2));
397         distributeActionEvent();
398     }
399 }
400 
mouseDragged(MouseEvent & event)401 void Slider::mouseDragged(MouseEvent &event)
402 {
403     if (mOrientation == Orientation::HORIZONTAL)
404     {
405         setValue(markerPositionToValue(event.getX() - mMarkerLength / 2));
406     }
407     else
408     {
409         setValue(markerPositionToValue(
410             mDimension.height - event.getY() - mMarkerLength / 2));
411     }
412 
413     distributeActionEvent();
414 
415     event.consume();
416 }
417 
mouseWheelMovedUp(MouseEvent & event)418 void Slider::mouseWheelMovedUp(MouseEvent &event)
419 {
420     setValue(mValue + mStepLength);
421     distributeActionEvent();
422     event.consume();
423 }
424 
mouseWheelMovedDown(MouseEvent & event)425 void Slider::mouseWheelMovedDown(MouseEvent &event)
426 {
427     setValue(mValue - mStepLength);
428     distributeActionEvent();
429     event.consume();
430 }
431 
keyPressed(KeyEvent & event)432 void Slider::keyPressed(KeyEvent& event)
433 {
434     const InputActionT action = event.getActionId();
435 
436     if (mOrientation == Orientation::HORIZONTAL)
437     {
438         if (action == InputAction::GUI_RIGHT)
439         {
440             setValue(mValue + mStepLength);
441             distributeActionEvent();
442             event.consume();
443         }
444         else if (action == InputAction::GUI_LEFT)
445         {
446             setValue(mValue - mStepLength);
447             distributeActionEvent();
448             event.consume();
449         }
450     }
451     else
452     {
453         if (action == InputAction::GUI_UP)
454         {
455             setValue(mValue + mStepLength);
456             distributeActionEvent();
457             event.consume();
458         }
459         else if (action == InputAction::GUI_DOWN)
460         {
461             setValue(mValue - mStepLength);
462             distributeActionEvent();
463             event.consume();
464         }
465     }
466 }
467 
setScale(const double scaleStart,const double scaleEnd)468 void Slider::setScale(const double scaleStart, const double scaleEnd)
469 {
470     mScaleStart = scaleStart;
471     mScaleEnd = scaleEnd;
472 }
473 
setValue(const double value)474 void Slider::setValue(const double value)
475 {
476     mRedraw = true;
477     if (value > mScaleEnd)
478         mValue = mScaleEnd;
479     else if (value < mScaleStart)
480         mValue = mScaleStart;
481     else
482         mValue = value;
483     mValue = CAST_S32((mValue - mScaleStart) / mStepLength)
484         * mStepLength + mScaleStart;
485 }
486 
markerPositionToValue(const int v) const487 double Slider::markerPositionToValue(const int v) const
488 {
489     int w;
490     if (mOrientation == Orientation::HORIZONTAL)
491         w = mDimension.width;
492     else
493         w = mDimension.height;
494 
495     const double pos = v / (static_cast<double>(w) - mMarkerLength);
496     return (1.0 - pos) * mScaleStart + pos * mScaleEnd;
497 }
498 
valueToMarkerPosition(const double value) const499 int Slider::valueToMarkerPosition(const double value) const
500 {
501     int v;
502     if (mOrientation == Orientation::HORIZONTAL)
503         v = mDimension.width;
504     else
505         v = mDimension.height;
506 
507     const int w = CAST_S32((v - mMarkerLength)
508             * (value  - mScaleStart)
509             / (mScaleEnd - mScaleStart));
510 
511     if (w < 0)
512         return 0;
513 
514     if (w > v - mMarkerLength)
515         return v - mMarkerLength;
516 
517     return w;
518 }
519