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