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/scrollarea.h"
68
69 #include "settings.h"
70
71 #include "gui/gui.h"
72 #include "gui/skin.h"
73
74 #include "utils/delete2.h"
75 #include "utils/stringutils.h"
76
77 #include "render/graphics.h"
78
79 #include "render/vertexes/imagecollection.h"
80
81 #include "resources/imagerect.h"
82
83 #include "resources/image/image.h"
84
85 #include "debug.h"
86
87 int ScrollArea::instances = 0;
88 float ScrollArea::mAlpha = 1.0;
89 bool ScrollArea::mShowButtons = true;
90 int ScrollArea::mMarkerSize = 0;
91 int ScrollArea::mScrollbarSize = 12;
92 ImageRect ScrollArea::background;
93 ImageRect ScrollArea::vMarker;
94 ImageRect ScrollArea::vMarkerHi;
95 ImageRect ScrollArea::vBackground;
96 ImageRect ScrollArea::hBackground;
97 Image *ScrollArea::buttons[4][2];
98
99 static std::string const buttonFiles[2] =
100 {
101 "scrollbuttons.xml",
102 "scrollbuttons_pressed.xml"
103 };
104
ScrollArea(Widget2 * const widget2,Widget * const widget,const Opaque opaque,const std::string & skin)105 ScrollArea::ScrollArea(Widget2 *const widget2,
106 Widget *const widget,
107 const Opaque opaque,
108 const std::string &skin) :
109 BasicContainer(widget2),
110 MouseListener(),
111 WidgetListener(),
112 mVertexes(new ImageCollection),
113 mVertexes2(new ImageCollection),
114 mHPolicy(SHOW_AUTO),
115 mVPolicy(SHOW_AUTO),
116 mVScroll(0),
117 mHScroll(0),
118 mScrollbarWidth(12),
119 mUpButtonScrollAmount(10),
120 mDownButtonScrollAmount(10),
121 mLeftButtonScrollAmount(10),
122 mRightButtonScrollAmount(10),
123 mHorizontalMarkerDragOffset(0),
124 mVerticalMarkerDragOffset(0),
125 mX(0),
126 mY(0),
127 mClickX(0),
128 mClickY(0),
129 mXOffset(0),
130 mYOffset(0),
131 mDrawWidth(0),
132 mDrawHeight(0),
133 mVBarVisible(false),
134 mHBarVisible(false),
135 mUpButtonPressed(false),
136 mDownButtonPressed(false),
137 mLeftButtonPressed(false),
138 mRightButtonPressed(false),
139 mIsVerticalMarkerDragged(false),
140 mIsHorizontalMarkerDragged(false),
141 mOpaque(Opaque_true),
142 mHasMouse(false)
143 {
144 setContent(widget);
145 addMouseListener(this);
146 mOpaque = opaque;
147 init(skin);
148 }
149
~ScrollArea()150 ScrollArea::~ScrollArea()
151 {
152 if (gui != nullptr)
153 gui->removeDragged(this);
154
155 // Garbage collection
156 delete getContent();
157
158 instances--;
159 if (instances == 0)
160 {
161 Theme::unloadRect(background, 0, 8);
162 Theme::unloadRect(vMarker, 0, 8);
163 Theme::unloadRect(vMarkerHi, 0, 8);
164 Theme::unloadRect(vBackground, 0, 8);
165 Theme::unloadRect(hBackground, 0, 8);
166 for (int i = 0; i < 2; i ++)
167 {
168 for (int f = UP; f < BUTTONS_DIR; f ++)
169 {
170 if (buttons[f][i] != nullptr)
171 buttons[f][i]->decRef();
172 }
173 }
174 }
175
176 delete2(mVertexes)
177 delete2(mVertexes2)
178
179 setContent(nullptr);
180 }
181
init(std::string skinName)182 void ScrollArea::init(std::string skinName)
183 {
184 setOpaque(mOpaque);
185
186 setUpButtonScrollAmount(2);
187 setDownButtonScrollAmount(2);
188 setLeftButtonScrollAmount(2);
189 setRightButtonScrollAmount(2);
190
191 if (instances == 0)
192 {
193 for (int f = 0; f < 9; f ++)
194 {
195 background.grid[f] = nullptr;
196 vMarker.grid[f] = nullptr;
197 vMarkerHi.grid[f] = nullptr;
198 vBackground.grid[f] = nullptr;
199 hBackground.grid[f] = nullptr;
200 }
201
202 // +++ here probably need move background from static
203 if (skinName.empty())
204 skinName = "scroll_background.xml";
205 if (theme != nullptr)
206 {
207 theme->loadRect(background,
208 skinName,
209 "scroll_background.xml",
210 0,
211 8);
212 theme->loadRect(vMarker,
213 "scroll.xml",
214 "",
215 0,
216 8);
217 theme->loadRect(vMarkerHi,
218 "scroll_highlighted.xml",
219 "scroll.xml",
220 0,
221 8);
222 theme->loadRect(vBackground,
223 "scroll_vbackground.xml",
224 "",
225 0,
226 8);
227 theme->loadRect(hBackground,
228 "scroll_hbackground.xml",
229 "",
230 0,
231 8);
232 }
233
234 for (int i = 0; i < 2; i ++)
235 {
236 Skin *skin = nullptr;
237 if (theme != nullptr)
238 {
239 skin = theme->load(buttonFiles[i],
240 "scrollbuttons.xml",
241 true,
242 Theme::getThemePath());
243 }
244 if (skin != nullptr)
245 {
246 const ImageRect &rect = skin->getBorder();
247 for (int f = UP; f < BUTTONS_DIR; f ++)
248 {
249 if (rect.grid[f] != nullptr)
250 rect.grid[f]->incRef();
251 buttons[f][i] = rect.grid[f];
252 }
253 if (i == 0)
254 {
255 mShowButtons = (skin->getOption("showbuttons", 1) == 1);
256 mMarkerSize = skin->getOption("markersize", 0);
257 mScrollbarSize = skin->getOption("scrollbarsize", 12);
258 }
259 }
260 else
261 {
262 for (int f = UP; f < BUTTONS_DIR; f ++)
263 buttons[f][i] = nullptr;
264 }
265 if (theme != nullptr)
266 theme->unload(skin);
267 }
268 }
269 mScrollbarWidth = mScrollbarSize;
270 instances++;
271 }
272
logic()273 void ScrollArea::logic()
274 {
275 BLOCK_START("ScrollArea::logic")
276 if (!isVisible())
277 {
278 BLOCK_END("ScrollArea::logic")
279 return;
280 }
281
282 checkPolicies();
283
284 setVerticalScrollAmount(mVScroll);
285 setHorizontalScrollAmount(mHScroll);
286
287 Widget *const content = getContent();
288 if (content != nullptr)
289 {
290 unsigned int frameSize = content->getFrameSize();
291 content->setPosition(-mHScroll + frameSize, -mVScroll + frameSize);
292 content->logic();
293
294 // When no scrollbar in a certain direction,
295 // adapt content size to match the content dimension exactly.
296 frameSize = 2 * content->getFrameSize();
297 if (mHPolicy == ScrollArea::SHOW_NEVER)
298 {
299 content->setWidth((mVBarVisible ? (mDimension.width
300 - mScrollbarWidth) : mDimension.width) - frameSize);
301 }
302 if (mVPolicy == ScrollArea::SHOW_NEVER)
303 {
304 content->setHeight((mHBarVisible ? (mDimension.height
305 - mScrollbarWidth) : mDimension.height) - frameSize);
306 }
307 }
308
309 if (mUpButtonPressed)
310 setVerticalScrollAmount(mVScroll - mUpButtonScrollAmount);
311 else if (mDownButtonPressed)
312 setVerticalScrollAmount(mVScroll + mDownButtonScrollAmount);
313 else if (mLeftButtonPressed)
314 setHorizontalScrollAmount(mHScroll - mLeftButtonScrollAmount);
315 else if (mRightButtonPressed)
316 setHorizontalScrollAmount(mHScroll + mRightButtonScrollAmount);
317 BLOCK_END("ScrollArea::logic")
318 }
319
updateAlpha()320 void ScrollArea::updateAlpha()
321 {
322 const float alpha = std::max(settings.guiAlpha,
323 theme->getMinimumOpacity());
324
325 if (alpha != mAlpha)
326 {
327 mAlpha = alpha;
328 for (int a = 0; a < 9; a++)
329 {
330 if (background.grid[a] != nullptr)
331 background.grid[a]->setAlpha(mAlpha);
332 if (hBackground.grid[a] != nullptr)
333 hBackground.grid[a]->setAlpha(mAlpha);
334 if (vBackground.grid[a] != nullptr)
335 vBackground.grid[a]->setAlpha(mAlpha);
336 if (vMarker.grid[a] != nullptr)
337 vMarker.grid[a]->setAlpha(mAlpha);
338 if (vMarkerHi.grid[a] != nullptr)
339 vMarkerHi.grid[a]->setAlpha(mAlpha);
340 }
341 }
342 }
343
draw(Graphics * const graphics)344 void ScrollArea::draw(Graphics *const graphics)
345 {
346 BLOCK_START("ScrollArea::draw")
347 if (mVBarVisible || mHBarVisible)
348 {
349 if (mOpaque == Opaque_false)
350 updateCalcFlag(graphics);
351 // need add caching or remove calc calls.
352 // if (mRedraw)
353 {
354 mVertexes->clear();
355 if (mVBarVisible)
356 {
357 if (mShowButtons)
358 {
359 calcButton(graphics, UP);
360 calcButton(graphics, DOWN);
361 }
362 calcVBar(graphics);
363 calcVMarker(graphics);
364 }
365
366 if (mHBarVisible)
367 {
368 if (mShowButtons)
369 {
370 calcButton(graphics, LEFT);
371 calcButton(graphics, RIGHT);
372 }
373 calcHBar(graphics);
374 calcHMarker(graphics);
375 }
376 graphics->finalize(mVertexes);
377 }
378 graphics->drawTileCollection(mVertexes);
379 }
380
381 updateAlpha();
382
383 if (mRedraw)
384 {
385 const bool redraw = graphics->getRedraw();
386 graphics->setRedraw(true);
387 drawChildren(graphics);
388 graphics->setRedraw(redraw);
389 }
390 else
391 {
392 drawChildren(graphics);
393 }
394 mRedraw = false;
395 BLOCK_END("ScrollArea::draw")
396 }
397
safeDraw(Graphics * const graphics)398 void ScrollArea::safeDraw(Graphics *const graphics)
399 {
400 BLOCK_START("ScrollArea::draw")
401 if (mVBarVisible)
402 {
403 if (mShowButtons)
404 {
405 drawButton(graphics, UP);
406 drawButton(graphics, DOWN);
407 }
408 drawVBar(graphics);
409 drawVMarker(graphics);
410 }
411
412 if (mHBarVisible)
413 {
414 if (mShowButtons)
415 {
416 drawButton(graphics, LEFT);
417 drawButton(graphics, RIGHT);
418 }
419 drawHBar(graphics);
420 drawHMarker(graphics);
421 }
422
423 updateAlpha();
424
425 safeDrawChildren(graphics);
426 mRedraw = false;
427 BLOCK_END("ScrollArea::draw")
428 }
429
updateCalcFlag(const Graphics * const graphics)430 void ScrollArea::updateCalcFlag(const Graphics *const graphics)
431 {
432 if (!mRedraw)
433 {
434 // because we don't know where parent windows was moved,
435 // need recalc vertexes
436 const ClipRect &rect = graphics->getTopClip();
437 if (rect.xOffset != mXOffset || rect.yOffset != mYOffset)
438 {
439 mRedraw = true;
440 mXOffset = rect.xOffset;
441 mYOffset = rect.yOffset;
442 }
443 else if (rect.width != mDrawWidth || rect.height != mDrawHeight)
444 {
445 mRedraw = true;
446 mDrawWidth = rect.width;
447 mDrawHeight = rect.height;
448 }
449 else if (graphics->getRedraw())
450 {
451 mRedraw = true;
452 }
453 }
454 }
455
drawFrame(Graphics * const graphics)456 void ScrollArea::drawFrame(Graphics *const graphics)
457 {
458 BLOCK_START("ScrollArea::drawFrame")
459 if (mOpaque == Opaque_true)
460 {
461 updateCalcFlag(graphics);
462
463 if (mRedraw)
464 {
465 const int bs = mFrameSize * 2;
466 const int w = mDimension.width + bs;
467 const int h = mDimension.height + bs;
468 mVertexes2->clear();
469 graphics->calcWindow(mVertexes2,
470 0, 0,
471 w, h,
472 background);
473 graphics->finalize(mVertexes2);
474 }
475 graphics->drawTileCollection(mVertexes2);
476 }
477 BLOCK_END("ScrollArea::drawFrame")
478 }
479
safeDrawFrame(Graphics * const graphics)480 void ScrollArea::safeDrawFrame(Graphics *const graphics)
481 {
482 BLOCK_START("ScrollArea::drawFrame")
483 if (mOpaque == Opaque_true)
484 {
485 const int bs = mFrameSize * 2;
486 const int w = mDimension.width + bs;
487 const int h = mDimension.height + bs;
488
489 updateCalcFlag(graphics);
490 graphics->drawImageRect(0, 0,
491 w, h,
492 background);
493 }
494 BLOCK_END("ScrollArea::drawFrame")
495 }
496
setOpaque(Opaque opaque)497 void ScrollArea::setOpaque(Opaque opaque)
498 {
499 mOpaque = opaque;
500 setFrameSize(mOpaque == Opaque_true ? 2 : 0);
501 }
502
getImageByState(Rect & dim,const BUTTON_DIR dir)503 Image *ScrollArea::getImageByState(Rect &dim, const BUTTON_DIR dir)
504 {
505 int state = 0;
506
507 switch (dir)
508 {
509 case UP:
510 state = mUpButtonPressed ? 1 : 0;
511 dim = getUpButtonDimension();
512 break;
513 case DOWN:
514 state = mDownButtonPressed ? 1 : 0;
515 dim = getDownButtonDimension();
516 break;
517 case LEFT:
518 state = mLeftButtonPressed ? 1 : 0;
519 dim = getLeftButtonDimension();
520 break;
521 case RIGHT:
522 state = mRightButtonPressed ? 1 : 0;
523 dim = getRightButtonDimension();
524 break;
525 case BUTTONS_DIR:
526 default:
527 logger->log("ScrollArea::drawButton unknown dir: "
528 + toString(CAST_U32(dir)));
529 return nullptr;
530 }
531 return buttons[CAST_SIZE(dir)][state];
532 }
533
drawButton(Graphics * const graphics,const BUTTON_DIR dir)534 void ScrollArea::drawButton(Graphics *const graphics,
535 const BUTTON_DIR dir)
536 {
537 Rect dim;
538 const Image *const image = getImageByState(dim, dir);
539
540 if (image != nullptr)
541 graphics->drawImage(image, dim.x, dim.y);
542 }
543
calcButton(Graphics * const graphics,const BUTTON_DIR dir)544 void ScrollArea::calcButton(Graphics *const graphics,
545 const BUTTON_DIR dir)
546 {
547 Rect dim;
548 const Image *const image = getImageByState(dim, dir);
549
550 if (image != nullptr)
551 {
552 static_cast<Graphics*>(graphics)->calcTileCollection(
553 mVertexes, image, dim.x, dim.y);
554 }
555 }
556
drawVBar(Graphics * const graphics) const557 void ScrollArea::drawVBar(Graphics *const graphics) const
558 {
559 const Rect &dim = getVerticalBarDimension();
560
561 if (vBackground.grid[4] != nullptr)
562 {
563 graphics->drawPattern(vBackground.grid[4],
564 dim.x, dim.y, dim.width, dim.height);
565 }
566 if (vBackground.grid[1] != nullptr)
567 {
568 graphics->drawPattern(vBackground.grid[1],
569 dim.x, dim.y,
570 dim.width, vBackground.grid[1]->getHeight());
571 }
572 if (vBackground.grid[7] != nullptr)
573 {
574 graphics->drawPattern(vBackground.grid[7],
575 dim.x, dim.height - vBackground.grid[7]->getHeight() + dim.y,
576 dim.width, vBackground.grid[7]->getHeight());
577 }
578 }
579
calcVBar(const Graphics * const graphics)580 void ScrollArea::calcVBar(const Graphics *const graphics)
581 {
582 const Rect &dim = getVerticalBarDimension();
583
584 if (vBackground.grid[4] != nullptr)
585 {
586 graphics->calcPattern(mVertexes,
587 vBackground.grid[4],
588 dim.x, dim.y,
589 dim.width, dim.height);
590 }
591 if (vBackground.grid[1] != nullptr)
592 {
593 graphics->calcPattern(mVertexes,
594 vBackground.grid[1],
595 dim.x, dim.y,
596 dim.width, vBackground.grid[1]->getHeight());
597 }
598 if (vBackground.grid[7] != nullptr)
599 {
600 graphics->calcPattern(mVertexes,
601 vBackground.grid[7],
602 dim.x, dim.height - vBackground.grid[7]->getHeight() + dim.y,
603 dim.width, vBackground.grid[7]->getHeight());
604 }
605 }
606
drawHBar(Graphics * const graphics) const607 void ScrollArea::drawHBar(Graphics *const graphics) const
608 {
609 const Rect &dim = getHorizontalBarDimension();
610
611 if (hBackground.grid[4] != nullptr)
612 {
613 graphics->drawPattern(hBackground.grid[4],
614 dim.x, dim.y,
615 dim.width, dim.height);
616 }
617
618 if (hBackground.grid[3] != nullptr)
619 {
620 graphics->drawPattern(hBackground.grid[3],
621 dim.x, dim.y,
622 hBackground.grid[3]->getWidth(), dim.height);
623 }
624
625 if (hBackground.grid[5] != nullptr)
626 {
627 graphics->drawPattern(hBackground.grid[5],
628 dim.x + dim.width - hBackground.grid[5]->getWidth(),
629 dim.y,
630 hBackground.grid[5]->getWidth(),
631 dim.height);
632 }
633 }
634
calcHBar(const Graphics * const graphics)635 void ScrollArea::calcHBar(const Graphics *const graphics)
636 {
637 const Rect &dim = getHorizontalBarDimension();
638
639 if (hBackground.grid[4] != nullptr)
640 {
641 graphics->calcPattern(mVertexes,
642 hBackground.grid[4],
643 dim.x, dim.y,
644 dim.width, dim.height);
645 }
646
647 if (hBackground.grid[3] != nullptr)
648 {
649 graphics->calcPattern(mVertexes,
650 hBackground.grid[3],
651 dim.x, dim.y,
652 hBackground.grid[3]->getWidth(), dim.height);
653 }
654
655 if (hBackground.grid[5] != nullptr)
656 {
657 graphics->calcPattern(mVertexes,
658 hBackground.grid[5],
659 dim.x + dim.width - hBackground.grid[5]->getWidth(),
660 dim.y,
661 hBackground.grid[5]->getWidth(),
662 dim.height);
663 }
664 }
665
drawVMarker(Graphics * const graphics)666 void ScrollArea::drawVMarker(Graphics *const graphics)
667 {
668 const Rect &dim = getVerticalMarkerDimension();
669
670 if ((mHasMouse) && (mX > (mDimension.width - mScrollbarWidth)))
671 {
672 graphics->drawImageRect(dim.x, dim.y,
673 dim.width, dim.height,
674 vMarkerHi);
675 }
676 else
677 {
678 graphics->drawImageRect(dim.x, dim.y,
679 dim.width, dim.height,
680 vMarker);
681 }
682 }
683
calcVMarker(Graphics * const graphics)684 void ScrollArea::calcVMarker(Graphics *const graphics)
685 {
686 const Rect &dim = getVerticalMarkerDimension();
687
688 if ((mHasMouse) && (mX > (mDimension.width - mScrollbarWidth)))
689 {
690 graphics->calcWindow(mVertexes,
691 dim.x, dim.y,
692 dim.width, dim.height,
693 vMarkerHi);
694 }
695 else
696 {
697 graphics->calcWindow(mVertexes,
698 dim.x, dim.y,
699 dim.width, dim.height,
700 vMarker);
701 }
702 }
703
drawHMarker(Graphics * const graphics)704 void ScrollArea::drawHMarker(Graphics *const graphics)
705 {
706 const Rect dim = getHorizontalMarkerDimension();
707
708 if ((mHasMouse) && (mY > (mDimension.height - mScrollbarWidth)))
709 {
710 graphics->drawImageRect(dim.x, dim.y,
711 dim.width, dim.height,
712 vMarkerHi);
713 }
714 else
715 {
716 graphics->drawImageRect(
717 dim.x, dim.y,
718 dim.width, dim.height,
719 vMarker);
720 }
721 }
722
calcHMarker(Graphics * const graphics)723 void ScrollArea::calcHMarker(Graphics *const graphics)
724 {
725 const Rect dim = getHorizontalMarkerDimension();
726
727 if ((mHasMouse) && (mY > (mDimension.height - mScrollbarWidth)))
728 {
729 graphics->calcWindow(mVertexes,
730 dim.x, dim.y,
731 dim.width, dim.height,
732 vMarkerHi);
733 }
734 else
735 {
736 graphics->calcWindow(mVertexes,
737 dim.x, dim.y,
738 dim.width, dim.height,
739 vMarker);
740 }
741 }
742
mouseMoved(MouseEvent & event)743 void ScrollArea::mouseMoved(MouseEvent& event)
744 {
745 mX = event.getX();
746 mY = event.getY();
747 }
748
mouseEntered(MouseEvent & event A_UNUSED)749 void ScrollArea::mouseEntered(MouseEvent& event A_UNUSED)
750 {
751 mHasMouse = true;
752 }
753
mouseExited(MouseEvent & event A_UNUSED)754 void ScrollArea::mouseExited(MouseEvent& event A_UNUSED)
755 {
756 mHasMouse = false;
757 }
758
widgetResized(const Event & event A_UNUSED)759 void ScrollArea::widgetResized(const Event &event A_UNUSED)
760 {
761 mRedraw = true;
762 Widget *const content = getContent();
763 if (content != nullptr)
764 {
765 const unsigned int frameSize = 2 * mFrameSize;
766 content->setSize(mDimension.width - frameSize,
767 mDimension.height - frameSize);
768 }
769 }
770
widgetMoved(const Event & event A_UNUSED)771 void ScrollArea::widgetMoved(const Event& event A_UNUSED)
772 {
773 mRedraw = true;
774 }
775
mousePressed(MouseEvent & event)776 void ScrollArea::mousePressed(MouseEvent& event)
777 {
778 const int x = event.getX();
779 const int y = event.getY();
780
781 if (getUpButtonDimension().isPointInRect(x, y))
782 {
783 setVerticalScrollAmount(mVScroll
784 - mUpButtonScrollAmount);
785 mUpButtonPressed = true;
786 event.consume();
787 }
788 else if (getDownButtonDimension().isPointInRect(x, y))
789 {
790 setVerticalScrollAmount(mVScroll
791 + mDownButtonScrollAmount);
792 mDownButtonPressed = true;
793 event.consume();
794 }
795 else if (getLeftButtonDimension().isPointInRect(x, y))
796 {
797 setHorizontalScrollAmount(mHScroll
798 - mLeftButtonScrollAmount);
799 mLeftButtonPressed = true;
800 event.consume();
801 }
802 else if (getRightButtonDimension().isPointInRect(x, y))
803 {
804 setHorizontalScrollAmount(mHScroll
805 + mRightButtonScrollAmount);
806 mRightButtonPressed = true;
807 event.consume();
808 }
809 else if (getVerticalMarkerDimension().isPointInRect(x, y))
810 {
811 mIsHorizontalMarkerDragged = false;
812 mIsVerticalMarkerDragged = true;
813
814 mVerticalMarkerDragOffset = y - getVerticalMarkerDimension().y;
815 event.consume();
816 }
817 else if (getVerticalBarDimension().isPointInRect(x, y))
818 {
819 if (y < getVerticalMarkerDimension().y)
820 {
821 setVerticalScrollAmount(mVScroll
822 - CAST_S32(getChildrenArea().height * 0.1));
823 }
824 else
825 {
826 setVerticalScrollAmount(mVScroll
827 + CAST_S32(getChildrenArea().height * 0.1));
828 }
829 event.consume();
830 }
831 else if (getHorizontalMarkerDimension().isPointInRect(x, y))
832 {
833 mIsHorizontalMarkerDragged = true;
834 mIsVerticalMarkerDragged = false;
835 mHorizontalMarkerDragOffset = x - getHorizontalMarkerDimension().x;
836 event.consume();
837 }
838 else if (getHorizontalBarDimension().isPointInRect(x, y))
839 {
840 if (x < getHorizontalMarkerDimension().x)
841 {
842 setHorizontalScrollAmount(mHScroll
843 - CAST_S32(getChildrenArea().width * 0.1));
844 }
845 else
846 {
847 setHorizontalScrollAmount(mHScroll
848 + CAST_S32(getChildrenArea().width * 0.1));
849 }
850 event.consume();
851 }
852
853 if (event.getButton() == MouseButton::LEFT &&
854 !event.isConsumed())
855 {
856 mClickX = event.getX();
857 mClickY = event.getY();
858 }
859 }
860
mouseReleased(MouseEvent & event)861 void ScrollArea::mouseReleased(MouseEvent& event)
862 {
863 if (event.getButton() == MouseButton::LEFT &&
864 mClickX != 0 &&
865 mClickY != 0)
866 {
867 if (!event.isConsumed())
868 {
869 #ifdef ANDROID
870 int dx = mClickX - event.getX();
871 int dy = mClickY - event.getY();
872 #else // ANDROID
873
874 int dx = event.getX() - mClickX;
875 int dy = event.getY() - mClickY;
876 #endif // ANDROID
877
878 if ((dx < 20 && dx > 0) || (dx > -20 && dx < 0))
879 dx = 0;
880
881 if ((dy < 20 && dy > 0) || (dy > -20 && dy < 0))
882 dy = 0;
883
884 if (abs(dx) > abs(dy))
885 {
886 int s = mHScroll + dx;
887 if (s < 0)
888 {
889 s = 0;
890 }
891 else
892 {
893 const int maxH = getHorizontalMaxScroll();
894 if (s > maxH)
895 s = maxH;
896 }
897
898 setHorizontalScrollAmount(s);
899 }
900 else if (dy != 0)
901 {
902 int s = mVScroll + dy;
903 if (s < 0)
904 {
905 s = 0;
906 }
907 else
908 {
909 const int maxV = getVerticalMaxScroll();
910 if (s > maxV)
911 s = maxV;
912 }
913
914 setVerticalScrollAmount(s);
915 }
916 mClickX = 0;
917 mClickY = 0;
918 if (mMouseConsume && ((dx != 0) || (dy != 0)))
919 event.consume();
920 }
921 }
922 mUpButtonPressed = false;
923 mDownButtonPressed = false;
924 mLeftButtonPressed = false;
925 mRightButtonPressed = false;
926 mIsHorizontalMarkerDragged = false;
927 mIsVerticalMarkerDragged = false;
928 if (mMouseConsume)
929 event.consume();
930 mRedraw = true;
931 }
932
mouseDragged(MouseEvent & event)933 void ScrollArea::mouseDragged(MouseEvent &event)
934 {
935 if (mIsVerticalMarkerDragged)
936 {
937 const Rect barDim = getVerticalBarDimension();
938 const int length = getVerticalMarkerDimension().height;
939
940 if ((barDim.height - length) > 0)
941 {
942 const int pos = event.getY() - barDim.y
943 - mVerticalMarkerDragOffset;
944 setVerticalScrollAmount((getVerticalMaxScroll() * pos)
945 / (barDim.height - length));
946 }
947 else
948 {
949 setVerticalScrollAmount(0);
950 }
951 }
952
953 if (mIsHorizontalMarkerDragged)
954 {
955 const Rect barDim = getHorizontalBarDimension();
956 const int length = getHorizontalMarkerDimension().width;
957
958 if ((barDim.width - length) > 0)
959 {
960 const int pos = event.getX() - barDim.x
961 - mHorizontalMarkerDragOffset;
962 setHorizontalScrollAmount((getHorizontalMaxScroll() * pos)
963 / (barDim.width - length));
964 }
965 else
966 {
967 setHorizontalScrollAmount(0);
968 }
969 }
970
971 event.consume();
972 mRedraw = true;
973 }
974
getVerticalBarDimension() const975 Rect ScrollArea::getVerticalBarDimension() const
976 {
977 if (!mVBarVisible)
978 return Rect(0, 0, 0, 0);
979
980 const int height = mShowButtons ? mScrollbarWidth : 0;
981 if (mHBarVisible)
982 {
983 return Rect(mDimension.width - mScrollbarWidth,
984 height,
985 mScrollbarWidth,
986 mDimension.height - 2 * height - mScrollbarWidth);
987 }
988
989 return Rect(mDimension.width - mScrollbarWidth,
990 height,
991 mScrollbarWidth,
992 mDimension.height - 2 * height);
993 }
994
getHorizontalBarDimension() const995 Rect ScrollArea::getHorizontalBarDimension() const
996 {
997 if (!mHBarVisible)
998 return Rect(0, 0, 0, 0);
999
1000 const int width = mShowButtons ? mScrollbarWidth : 0;
1001 if (mVBarVisible)
1002 {
1003 return Rect(width,
1004 mDimension.height - mScrollbarWidth,
1005 mDimension.width - 2 * width - mScrollbarWidth,
1006 mScrollbarWidth);
1007 }
1008
1009 return Rect(width,
1010 mDimension.height - mScrollbarWidth,
1011 mDimension.width - 2 * width,
1012 mScrollbarWidth);
1013 }
1014
getVerticalMarkerDimension()1015 Rect ScrollArea::getVerticalMarkerDimension()
1016 {
1017 if (!mVBarVisible)
1018 return Rect(0, 0, 0, 0);
1019
1020 int length;
1021 int pos;
1022 int height;
1023 const int h2 = mShowButtons
1024 ? mScrollbarWidth : mMarkerSize / 2;
1025 const Widget *content;
1026 if (!mWidgets.empty())
1027 content = *mWidgets.begin();
1028 else
1029 content = nullptr;
1030
1031 if (mHBarVisible)
1032 height = mDimension.height - 2 * h2 - mScrollbarWidth;
1033 else
1034 height = mDimension.height - 2 * h2;
1035
1036 const int maxV = getVerticalMaxScroll();
1037 if ((mMarkerSize != 0) && (maxV != 0))
1038 {
1039 pos = (mVScroll * height / maxV - mMarkerSize / 2);
1040 length = mMarkerSize;
1041 }
1042 else
1043 {
1044 if (content != nullptr)
1045 {
1046 const int h3 = content->getHeight();
1047 if (h3 != 0)
1048 length = (height * getChildrenArea().height) / h3;
1049 else
1050 length = height;
1051 }
1052 else
1053 {
1054 length = height;
1055 }
1056
1057 if (length < mScrollbarWidth)
1058 length = mScrollbarWidth;
1059
1060 if (length > height)
1061 length = height;
1062
1063 if (maxV != 0)
1064 pos = ((height - length) * mVScroll) / maxV;
1065 else
1066 pos = 0;
1067 }
1068
1069 return Rect(mDimension.width - mScrollbarWidth, h2 + pos,
1070 mScrollbarWidth, length);
1071 }
1072
getHorizontalMarkerDimension()1073 Rect ScrollArea::getHorizontalMarkerDimension()
1074 {
1075 if (!mHBarVisible)
1076 return Rect(0, 0, 0, 0);
1077
1078 int length;
1079 int pos;
1080 int width;
1081 const int w2 = mShowButtons
1082 ? mScrollbarWidth : mMarkerSize / 2;
1083 const Widget *content;
1084 if (!mWidgets.empty())
1085 content = *mWidgets.begin();
1086 else
1087 content = nullptr;
1088
1089 if (mVBarVisible)
1090 width = mDimension.width - 2 * w2 - mScrollbarWidth;
1091 else
1092 width = mDimension.width - w2 - mScrollbarWidth;
1093
1094 const int maxH = getHorizontalMaxScroll();
1095 if (mMarkerSize != 0 && maxH != 0)
1096 {
1097 pos = (mHScroll * width / maxH - mMarkerSize / 2);
1098 length = mMarkerSize;
1099 }
1100 else
1101 {
1102 if (content != nullptr)
1103 {
1104 const int w3 = content->getWidth();
1105 if (w3 != 0)
1106 length = (width * getChildrenArea().width) / w3;
1107 else
1108 length = width;
1109 }
1110 else
1111 {
1112 length = width;
1113 }
1114
1115 if (length < mScrollbarWidth)
1116 length = mScrollbarWidth;
1117
1118 if (length > width)
1119 length = width;
1120
1121 if (maxH != 0)
1122 {
1123 pos = ((width - length) * mHScroll) / maxH;
1124 }
1125 else
1126 {
1127 pos = 0;
1128 }
1129 }
1130
1131 return Rect(w2 + pos, mDimension.height - mScrollbarWidth,
1132 length, mScrollbarWidth);
1133 }
1134
getUpButtonDimension() const1135 Rect ScrollArea::getUpButtonDimension() const
1136 {
1137 if (!mVBarVisible || !mShowButtons)
1138 return Rect(0, 0, 0, 0);
1139
1140 return Rect(mDimension.width - mScrollbarWidth, 0,
1141 mScrollbarWidth, mScrollbarWidth);
1142 }
1143
getDownButtonDimension() const1144 Rect ScrollArea::getDownButtonDimension() const
1145 {
1146 if (!mVBarVisible || !mShowButtons)
1147 return Rect(0, 0, 0, 0);
1148
1149 if (mHBarVisible)
1150 {
1151 return Rect(mDimension.width - mScrollbarWidth,
1152 mDimension.height - mScrollbarWidth * 2,
1153 mScrollbarWidth,
1154 mScrollbarWidth);
1155 }
1156
1157 return Rect(mDimension.width - mScrollbarWidth,
1158 mDimension.height - mScrollbarWidth,
1159 mScrollbarWidth,
1160 mScrollbarWidth);
1161 }
1162
getLeftButtonDimension() const1163 Rect ScrollArea::getLeftButtonDimension() const
1164 {
1165 if (!mHBarVisible || !mShowButtons)
1166 return Rect(0, 0, 0, 0);
1167
1168 return Rect(0, mDimension.height - mScrollbarWidth,
1169 mScrollbarWidth, mScrollbarWidth);
1170 }
1171
getRightButtonDimension() const1172 Rect ScrollArea::getRightButtonDimension() const
1173 {
1174 if (!mHBarVisible || !mShowButtons)
1175 return Rect(0, 0, 0, 0);
1176
1177 if (mVBarVisible)
1178 {
1179 return Rect(mDimension.width - mScrollbarWidth*2,
1180 mDimension.height - mScrollbarWidth,
1181 mScrollbarWidth,
1182 mScrollbarWidth);
1183 }
1184
1185 return Rect(mDimension.width - mScrollbarWidth,
1186 mDimension.height - mScrollbarWidth,
1187 mScrollbarWidth,
1188 mScrollbarWidth);
1189 }
1190
setContent(Widget * widget)1191 void ScrollArea::setContent(Widget* widget)
1192 {
1193 if (widget != nullptr)
1194 {
1195 clear();
1196 add(widget);
1197 widget->setPosition(0, 0);
1198 }
1199 else
1200 {
1201 clear();
1202 }
1203
1204 checkPolicies();
1205 }
1206
getContent()1207 Widget* ScrollArea::getContent()
1208 {
1209 if (!mWidgets.empty())
1210 return *mWidgets.begin();
1211
1212 return nullptr;
1213 }
1214
setHorizontalScrollPolicy(const ScrollPolicy hPolicy)1215 void ScrollArea::setHorizontalScrollPolicy(const ScrollPolicy hPolicy)
1216 {
1217 mHPolicy = hPolicy;
1218 checkPolicies();
1219 }
1220
setVerticalScrollPolicy(const ScrollPolicy vPolicy)1221 void ScrollArea::setVerticalScrollPolicy(const ScrollPolicy vPolicy)
1222 {
1223 mVPolicy = vPolicy;
1224 checkPolicies();
1225 }
1226
setScrollPolicy(const ScrollPolicy hPolicy,const ScrollPolicy vPolicy)1227 void ScrollArea::setScrollPolicy(const ScrollPolicy hPolicy,
1228 const ScrollPolicy vPolicy)
1229 {
1230 mHPolicy = hPolicy;
1231 mVPolicy = vPolicy;
1232 checkPolicies();
1233 }
1234
setVerticalScrollAmount(const int vScroll)1235 void ScrollArea::setVerticalScrollAmount(const int vScroll)
1236 {
1237 const int max = getVerticalMaxScroll();
1238
1239 mVScroll = vScroll;
1240
1241 if (vScroll > max)
1242 mVScroll = max;
1243
1244 if (vScroll < 0)
1245 mVScroll = 0;
1246 }
1247
setHorizontalScrollAmount(int hScroll)1248 void ScrollArea::setHorizontalScrollAmount(int hScroll)
1249 {
1250 const int max = getHorizontalMaxScroll();
1251
1252 mHScroll = hScroll;
1253
1254 if (hScroll > max)
1255 mHScroll = max;
1256 else if (hScroll < 0)
1257 mHScroll = 0;
1258 }
1259
setScrollAmount(const int hScroll,const int vScroll)1260 void ScrollArea::setScrollAmount(const int hScroll, const int vScroll)
1261 {
1262 setHorizontalScrollAmount(hScroll);
1263 setVerticalScrollAmount(vScroll);
1264 }
1265
getHorizontalMaxScroll()1266 int ScrollArea::getHorizontalMaxScroll()
1267 {
1268 checkPolicies();
1269
1270 const Widget *const content = getContent();
1271 if (content == nullptr)
1272 return 0;
1273
1274 const int value = content->getWidth() - getChildrenArea().width +
1275 2 * content->getFrameSize();
1276
1277 if (value < 0)
1278 return 0;
1279
1280 return value;
1281 }
1282
getVerticalMaxScroll()1283 int ScrollArea::getVerticalMaxScroll()
1284 {
1285 checkPolicies();
1286
1287 const Widget *const content = getContent();
1288 if (content == nullptr)
1289 return 0;
1290
1291 int value;
1292
1293 value = content->getHeight() - getChildrenArea().height +
1294 2 * content->getFrameSize();
1295
1296 if (value < 0)
1297 return 0;
1298
1299 return value;
1300 }
1301
setScrollbarWidth(const int width)1302 void ScrollArea::setScrollbarWidth(const int width)
1303 {
1304 if (width > 0)
1305 mScrollbarWidth = width;
1306 }
1307
showWidgetPart(Widget * const widget,const Rect & area)1308 void ScrollArea::showWidgetPart(Widget *const widget, const Rect &area)
1309 {
1310 const Widget *const content = getContent();
1311 if (widget != content || (content == nullptr))
1312 return;
1313
1314 BasicContainer::showWidgetPart(widget, area);
1315
1316 setHorizontalScrollAmount(content->getFrameSize()
1317 - content->getX());
1318 setVerticalScrollAmount(content->getFrameSize()
1319 - content->getY());
1320 }
1321
getChildrenArea()1322 Rect ScrollArea::getChildrenArea()
1323 {
1324 const Rect area = Rect(0, 0,
1325 mVBarVisible ? (getWidth() - mScrollbarWidth) : getWidth(),
1326 mHBarVisible ? (getHeight() - mScrollbarWidth) : getHeight());
1327
1328 if (area.width < 0 || area.height < 0)
1329 return Rect();
1330
1331 return area;
1332 }
1333
getWidgetAt(int x,int y)1334 Widget *ScrollArea::getWidgetAt(int x, int y)
1335 {
1336 if (getChildrenArea().isPointInRect(x, y))
1337 return getContent();
1338
1339 return nullptr;
1340 }
1341
setWidth(int width)1342 void ScrollArea::setWidth(int width)
1343 {
1344 Widget::setWidth(width);
1345 checkPolicies();
1346 }
1347
setHeight(int height)1348 void ScrollArea::setHeight(int height)
1349 {
1350 Widget::setHeight(height);
1351 checkPolicies();
1352 }
1353
setDimension(const Rect & dimension)1354 void ScrollArea::setDimension(const Rect& dimension)
1355 {
1356 Widget::setDimension(dimension);
1357 checkPolicies();
1358 }
1359
mouseWheelMovedUp(MouseEvent & event)1360 void ScrollArea::mouseWheelMovedUp(MouseEvent& event)
1361 {
1362 if (event.isConsumed())
1363 return;
1364
1365 setVerticalScrollAmount(mVScroll
1366 - getChildrenArea().height / 8);
1367
1368 event.consume();
1369 }
1370
mouseWheelMovedDown(MouseEvent & event)1371 void ScrollArea::mouseWheelMovedDown(MouseEvent& event)
1372 {
1373 if (event.isConsumed())
1374 return;
1375
1376 setVerticalScrollAmount(mVScroll
1377 + getChildrenArea().height / 8);
1378
1379 event.consume();
1380 }
1381
checkPolicies()1382 void ScrollArea::checkPolicies()
1383 {
1384 const int w = getWidth();
1385 const int h = getHeight();
1386
1387 mHBarVisible = false;
1388 mVBarVisible = false;
1389
1390 const Widget *const content = getContent();
1391 if (content == nullptr)
1392 {
1393 mHBarVisible = (mHPolicy == SHOW_ALWAYS);
1394 mVBarVisible = (mVPolicy == SHOW_ALWAYS);
1395 return;
1396 }
1397
1398 if (mHPolicy == SHOW_AUTO &&
1399 mVPolicy == SHOW_AUTO)
1400 {
1401 if (content->getWidth() <= w
1402 && content->getHeight() <= h)
1403 {
1404 mHBarVisible = false;
1405 mVBarVisible = false;
1406 }
1407
1408 if (content->getWidth() > w)
1409 {
1410 mHBarVisible = true;
1411 }
1412
1413 if ((content->getHeight() > h)
1414 || (mHBarVisible && content->getHeight()
1415 > h - mScrollbarWidth))
1416 {
1417 mVBarVisible = true;
1418 }
1419
1420 if (mVBarVisible && content->getWidth() > w - mScrollbarWidth)
1421 mHBarVisible = true;
1422
1423 return;
1424 }
1425
1426 switch (mHPolicy)
1427 {
1428 case SHOW_NEVER:
1429 mHBarVisible = false;
1430 break;
1431
1432 case SHOW_ALWAYS:
1433 mHBarVisible = true;
1434 break;
1435
1436 case SHOW_AUTO:
1437 if (mVPolicy == SHOW_NEVER)
1438 {
1439 mHBarVisible = (content->getWidth() > w);
1440 }
1441 else // (mVPolicy == SHOW_ALWAYS)
1442 {
1443 mHBarVisible = (content->getWidth()
1444 > w - mScrollbarWidth);
1445 }
1446 break;
1447
1448 default:
1449 break;
1450 }
1451
1452 switch (mVPolicy)
1453 {
1454 case SHOW_NEVER:
1455 mVBarVisible = false;
1456 break;
1457
1458 case SHOW_ALWAYS:
1459 mVBarVisible = true;
1460 break;
1461
1462 case SHOW_AUTO:
1463 if (mHPolicy == SHOW_NEVER)
1464 {
1465 mVBarVisible = (content->getHeight() > h);
1466 }
1467 else // (mHPolicy == SHOW_ALWAYS)
1468 {
1469 mVBarVisible = (content->getHeight()
1470 > h - mScrollbarWidth);
1471 }
1472 break;
1473 default:
1474 break;
1475 }
1476 }
1477
isSelectable() const1478 bool ScrollArea::isSelectable() const noexcept2
1479 {
1480 if (mVBarVisible || mHBarVisible)
1481 return true;
1482 return Widget::isSelectable();
1483 }
1484