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