1 /* GG is a GUI for OpenGL.
2    Copyright (C) 2003-2008 T. Zachary Laine
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1
7    of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA
18 
19    If you do not wish to comply with the terms of the LGPL please
20    contact the author as other terms are available for a fee.
21 
22    Zach Laine
23    whatwasthataddress@gmail.com */
24 
25 #include <GG/Scroll.h>
26 
27 #include <GG/Button.h>
28 #include <GG/DrawUtil.h>
29 #include <GG/StyleFactory.h>
30 #include <GG/WndEvent.h>
31 
32 
33 using namespace GG;
34 
35 namespace {
36     struct ScrolledEcho
37     {
ScrolledEcho__anon6e49d1180111::ScrolledEcho38         ScrolledEcho(const std::string& name) : m_name(name) {}
operator ()__anon6e49d1180111::ScrolledEcho39         void operator()(int tab_min, int tab_max, int scroll_min, int scroll_max)
40             {
41                 std::cerr << "GG SIGNAL : " << m_name
42                           << "(tab_min=" << tab_min << " tab_max=" << tab_max
43                           << " scroll_min=" << scroll_min << " scroll_max=" << scroll_max
44                           << ")\n";
45             }
46         std::string m_name;
47     };
48 
49     const unsigned int MIN_TAB_SIZE = 5;
50 }
51 
52 ////////////////////////////////////////////////
53 // GG::Scroll
54 ////////////////////////////////////////////////
Scroll(Orientation orientation,Clr color,Clr interior)55 Scroll::Scroll(Orientation orientation, Clr color, Clr interior) :
56     Control(X0, Y0, X1, Y1, INTERACTIVE | REPEAT_BUTTON_DOWN),
57     m_int_color(interior),
58     m_orientation(orientation),
59     m_posn(0),
60     m_range_min(0),
61     m_range_max(99),
62     m_line_sz(5),
63     m_page_sz(25),
64     m_initial_depressed_region(SBR_NONE),
65     m_depressed_region(SBR_NONE)
66 {
67     Control::SetColor(color);
68     const auto& style = GetStyleFactory();
69     if (m_orientation == VERTICAL) {
70         m_decr = style->NewScrollUpButton(color);
71         m_incr = style->NewScrollDownButton(color);
72         m_tab = style->NewVScrollTabButton(color);
73     } else {
74         m_decr = style->NewScrollLeftButton(color);
75         m_incr = style->NewScrollRightButton(color);
76         m_tab = style->NewHScrollTabButton(color);
77     }
78 }
79 
CompleteConstruction()80 void Scroll::CompleteConstruction()
81 {
82     if (m_decr) {
83         AttachChild(m_decr);
84         m_decr->LeftClickedSignal.connect(boost::bind(&Scroll::ScrollLineIncrDecrImpl, this, true, -1));
85     }
86     if (m_incr) {
87         AttachChild(m_incr);
88         m_incr->LeftClickedSignal.connect(boost::bind(&Scroll::ScrollLineIncrDecrImpl, this, true, 1));
89     }
90     AttachChild(m_tab);
91     m_tab->InstallEventFilter(shared_from_this());
92 
93     if (INSTRUMENT_ALL_SIGNALS) {
94         ScrolledSignal.connect(ScrolledEcho("Scroll::ScrolledSignal"));
95         ScrolledAndStoppedSignal.connect(ScrolledEcho("Scroll::ScrolledAndStoppedSignal"));
96     }
97 
98     DoLayout();
99     InitBuffer();
100 }
101 
MinUsableSize() const102 Pt Scroll::MinUsableSize() const
103 {
104     Pt retval;
105     const int MIN_DRAGABLE_SIZE = 2;
106     if (m_orientation == VERTICAL) {
107         retval.x = X(MIN_DRAGABLE_SIZE);
108         Y decr_y = m_decr ? m_decr->MinUsableSize().y : Y0;
109         Y incr_y = m_incr ? m_incr->MinUsableSize().y : Y0;
110         retval.y = decr_y + incr_y + 3 * std::min(decr_y, incr_y);
111     } else {
112         X decr_x = m_decr ? m_decr->MinUsableSize().x : X0;
113         X incr_x = m_incr ? m_incr->MinUsableSize().x : X0;
114         retval.x = decr_x + incr_x + 3 * std::min(decr_x, incr_x);
115         retval.y = Y(MIN_DRAGABLE_SIZE);
116     }
117     return retval;
118 }
119 
PosnRange() const120 std::pair<int, int> Scroll::PosnRange() const
121 { return std::pair<int, int>(m_posn, m_posn + m_page_sz); }
122 
ScrollRange() const123 std::pair<int, int> Scroll::ScrollRange() const
124 { return std::pair<int, int>(m_range_min, m_range_max); }
125 
LineSize() const126 unsigned int Scroll::LineSize() const
127 { return m_line_sz; }
128 
PageSize() const129 unsigned int Scroll::PageSize() const
130 { return m_page_sz; }
131 
InteriorColor() const132 Clr Scroll::InteriorColor() const
133 { return m_int_color; }
134 
ScrollOrientation() const135 Orientation Scroll::ScrollOrientation() const
136 { return m_orientation; }
137 
InitBuffer()138 void Scroll::InitBuffer()
139 {
140     GG::Pt sz = Size();
141     m_buffer.clear();
142     m_buffer.store(0.0f,        0.0f);
143     m_buffer.store(Value(sz.x), 0.0f);
144     m_buffer.store(Value(sz.x), Value(sz.y));
145     m_buffer.store(0.0f,        Value(sz.y));
146     m_buffer.createServerBuffer();
147 }
148 
Render()149 void Scroll::Render()
150 {
151     Pt ul = UpperLeft();
152 
153     glPushMatrix();
154     glLoadIdentity();
155     glTranslatef(static_cast<GLfloat>(Value(ul.x)), static_cast<GLfloat>(Value(ul.y)), 0.0f);
156     glDisable(GL_TEXTURE_2D);
157     glLineWidth(2.0);
158     glEnableClientState(GL_VERTEX_ARRAY);
159 
160     m_buffer.activate();
161     glColor(Disabled() ? DisabledColor(m_int_color) : m_int_color);
162     glDrawArrays(GL_TRIANGLE_FAN,   0, m_buffer.size());
163 
164     glLineWidth(1.0f);
165     glEnable(GL_TEXTURE_2D);
166     glPopMatrix();
167     glDisableClientState(GL_VERTEX_ARRAY);
168 }
169 
SizeMove(const Pt & ul,const Pt & lr)170 void Scroll::SizeMove(const Pt& ul, const Pt& lr)
171 {
172     Pt old_size = Size();
173 
174     Wnd::SizeMove(ul, lr);
175 
176     if (old_size != Size()) {
177         DoLayout();
178         InitBuffer();
179     }
180 }
181 
DoLayout()182 void Scroll::DoLayout()
183 {
184     int bn_width = (m_orientation == VERTICAL) ? Value(Size().x) : Value(Size().y);
185     if(m_decr)
186         m_decr->SizeMove(Pt(), Pt(X(bn_width), Y(bn_width)));
187     if(m_incr)
188         m_incr->SizeMove(Size() - Pt(X(bn_width), Y(bn_width)), Size());
189     m_tab->SizeMove(m_tab->RelativeUpperLeft(),
190                     (m_orientation == VERTICAL) ?
191                     Pt(X(bn_width), m_tab->RelativeLowerRight().y) :
192                     Pt(m_tab->RelativeLowerRight().x, Y(bn_width)));
193     SizeScroll(m_range_min, m_range_max, m_line_sz, m_page_sz); // update tab size and position
194 }
195 
Disable(bool b)196 void Scroll::Disable(bool b/* = true*/)
197 {
198     Control::Disable(b);
199     m_tab->Disable(b);
200     if(m_incr)
201         m_incr->Disable(b);
202     if(m_decr)
203         m_decr->Disable(b);
204 }
205 
SetColor(Clr c)206 void Scroll::SetColor(Clr c)
207 {
208     Control::SetColor(c);
209     m_tab->SetColor(c);
210     if(m_incr)
211         m_incr->SetColor(c);
212     if(m_decr)
213         m_decr->SetColor(c);
214 }
215 
SetInteriorColor(Clr c)216 void Scroll::SetInteriorColor(Clr c)
217 { m_int_color = c; }
218 
SizeScroll(int min,int max,unsigned int line,unsigned int page)219 void Scroll::SizeScroll(int min, int max, unsigned int line, unsigned int page)
220 {
221     m_line_sz = line;
222     m_range_min = std::min(min, max);
223     m_range_max = std::max(min, max);
224     m_page_sz = page;
225 
226     if (m_page_sz > static_cast<unsigned int>(m_range_max - m_range_min + 1))
227         m_page_sz = (m_range_max - m_range_min + 1);
228     if (m_posn > m_range_max - static_cast<int>(m_page_sz - 1))
229         m_posn = m_range_max - (m_page_sz - 1);
230     if (m_posn < m_range_min)
231         m_posn = m_range_min;
232     Pt tab_ul = m_tab->RelativeUpperLeft();
233     Pt tab_lr = m_orientation == VERTICAL ?
234         Pt(m_tab->RelativeLowerRight().x, tab_ul.y + static_cast<int>(TabWidth())):
235         Pt(tab_ul.x + static_cast<int>(TabWidth()), m_tab->RelativeLowerRight().y);
236     m_tab->SizeMove(tab_ul, tab_lr);
237     MoveTabToPosn();
238 }
239 
SetMax(int max)240 void Scroll::SetMax(int max)
241 { SizeScroll(m_range_min, max, m_line_sz, m_page_sz); }
242 
SetMin(int min)243 void Scroll::SetMin(int min)
244 { SizeScroll(min, m_range_max, m_line_sz, m_page_sz); }
245 
SetLineSize(unsigned int line)246 void Scroll::SetLineSize(unsigned int line)
247 { SizeScroll(m_range_min, m_range_max, line, m_page_sz); }
248 
SetPageSize(unsigned int page)249 void Scroll::SetPageSize(unsigned int page)
250 { SizeScroll(m_range_min, m_range_max, m_line_sz, page); }
251 
ScrollTo(int p)252 void Scroll::ScrollTo(int p)
253 {
254     if (p < m_range_min)
255         m_posn = m_range_min;
256     else if (p > static_cast<int>(m_range_max - m_page_sz))
257         m_posn = m_range_max - m_page_sz;
258     else
259         m_posn = p;
260     MoveTabToPosn();
261 }
262 
ScrollLineIncr(int lines)263 void Scroll::ScrollLineIncr(int lines)
264 { ScrollLineIncrDecrImpl(false, lines); }
265 
ScrollLineDecr(int lines)266 void Scroll::ScrollLineDecr(int lines)
267 { ScrollLineIncrDecrImpl(false, -lines); }
268 
ScrollPageIncr()269 void Scroll::ScrollPageIncr()
270 {
271     if (static_cast<int>(m_posn + m_page_sz) <= static_cast<int>(m_range_max - m_page_sz))
272         m_posn += m_page_sz;
273     else
274         m_posn = m_range_max - (m_page_sz - 1);
275     MoveTabToPosn();
276 }
277 
ScrollPageDecr()278 void Scroll::ScrollPageDecr()
279 {
280     if (static_cast<int>(m_posn - m_page_sz) >= m_range_min)
281         m_posn -= m_page_sz;
282     else
283         m_posn = m_range_min;
284     MoveTabToPosn();
285 }
286 
TabSpace() const287 unsigned int Scroll::TabSpace() const
288 {
289     // tab_space is the space the tab has to move about in (the control's width less the width of the incr & decr buttons)
290     return (m_orientation == VERTICAL ?
291             Value(Size().y - (m_incr ? m_incr->Size().y : Y0) - (m_decr ? m_decr->Size().y : Y0)) :
292             Value(Size().x - (m_incr ? m_incr->Size().x : X0) - (m_decr ? m_decr->Size().x : X0)));
293 }
294 
TabWidth() const295 unsigned int Scroll::TabWidth() const
296 { return std::max(static_cast<unsigned int>(TabSpace() / (m_range_max - m_range_min + 1.0) * m_page_sz + 0.5), MIN_TAB_SIZE); }
297 
RegionUnder(const Pt & pt)298 Scroll::ScrollRegion Scroll::RegionUnder(const Pt& pt)
299 {
300     ScrollRegion retval;
301     Pt ul = ClientUpperLeft();
302     if (pt.x - ul.x < m_tab->RelativeUpperLeft().x || pt.y - ul.y <= m_tab->RelativeUpperLeft().y)
303         retval = SBR_PAGE_DN;
304     else
305         retval = SBR_PAGE_UP;
306     return retval;
307 }
308 
TabButton() const309 Button* Scroll::TabButton() const
310 { return m_tab.get(); }
311 
IncrButton() const312 Button* Scroll::IncrButton() const
313 { return m_incr.get(); }
314 
DecrButton() const315 Button* Scroll::DecrButton() const
316 { return m_decr.get(); }
317 
LButtonDown(const Pt & pt,Flags<ModKey> mod_keys)318 void Scroll::LButtonDown(const Pt& pt, Flags<ModKey> mod_keys)
319 {
320     if (!Disabled()) {
321         // when a button is pressed, record the region of the control the cursor is over
322         ScrollRegion region = RegionUnder(pt);
323         if (m_initial_depressed_region == SBR_NONE)
324             m_initial_depressed_region = region;
325         m_depressed_region = region;
326         if (m_depressed_region == m_initial_depressed_region) {
327             switch (m_depressed_region)
328             {
329             case SBR_PAGE_DN: {
330                 int old_posn = m_posn;
331                 ScrollPageDecr();
332                 if (old_posn != m_posn) {
333                     ScrolledSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
334                     ScrolledAndStoppedSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
335                 }
336                 break;
337             }
338             case SBR_PAGE_UP: {
339                 int old_posn = m_posn;
340                 ScrollPageIncr();
341                 if (old_posn != m_posn) {
342                     ScrolledSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
343                     ScrolledAndStoppedSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
344                 }
345                 break;
346             }
347             default: break;
348             }
349         }
350     }
351 }
352 
LButtonUp(const Pt & pt,Flags<ModKey> mod_keys)353 void Scroll::LButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
354 {
355     if (!Disabled()) {
356         if(m_decr)
357             m_decr->SetState(Button::BN_UNPRESSED);
358         if(m_incr)
359             m_incr->SetState(Button::BN_UNPRESSED);
360         m_initial_depressed_region = SBR_NONE;
361         m_depressed_region = SBR_NONE;
362     }
363 }
364 
LClick(const Pt & pt,Flags<ModKey> mod_keys)365 void Scroll::LClick(const Pt& pt, Flags<ModKey> mod_keys)
366 { LButtonUp(pt, mod_keys); }
367 
MouseHere(const Pt & pt,Flags<ModKey> mod_keys)368 void Scroll::MouseHere(const Pt& pt, Flags<ModKey> mod_keys)
369 { LButtonUp(pt, mod_keys); }
370 
EventFilter(Wnd * w,const WndEvent & event)371 bool Scroll::EventFilter(Wnd* w, const WndEvent& event)
372 {
373     if (w == m_tab.get()) {
374         switch (event.Type()) {
375         case WndEvent::LDrag: {
376             if (!Disabled()) {
377                 Pt new_ul = m_tab->RelativeUpperLeft() + event.DragMove();
378                 if (m_orientation == VERTICAL) {
379                     new_ul.x = m_tab->RelativeUpperLeft().x;
380                     new_ul.y = std::max(0 + (m_decr ? m_decr->Height() : Y0),
381                                         std::min(new_ul.y, ClientHeight() - (m_incr ? m_incr->Height() : Y0) - m_tab->Height()));
382                     m_tab_dragged |= bool(m_tab->RelativeUpperLeft().y - new_ul.y);
383                 } else {
384                     new_ul.x = std::max(0 + (m_decr ? m_decr->Width() : X0),
385                                         std::min(new_ul.x, ClientWidth() - (m_incr ? m_incr->Width() : X0) - m_tab->Width()));
386                     new_ul.y = m_tab->RelativeUpperLeft().y;
387                     m_tab_dragged |= bool(m_tab->RelativeUpperLeft().x - new_ul.x);
388                 }
389                 m_tab->MoveTo(new_ul);
390                 UpdatePosn();
391             }
392             return true;
393         }
394         case WndEvent::LButtonDown:
395             m_dragging_tab = true;
396             break;
397         case WndEvent::LButtonUp:
398         case WndEvent::LClick:
399             if (m_tab_dragged)
400                 ScrolledAndStoppedSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
401             m_dragging_tab = false;
402             m_tab_dragged = false;
403             break;
404         case WndEvent::MouseLeave:
405             return m_dragging_tab;
406         default:
407             break;
408         }
409     }
410     return false;
411 }
412 
UpdatePosn()413 void Scroll::UpdatePosn()
414 {
415     int old_posn = m_posn;
416     int before_tab = (m_orientation == VERTICAL ?   // the tabspace before the tab's lower-value side
417                       Value(m_tab->RelativeUpperLeft().y - (m_decr ? m_decr->Size().y : Y0)) :
418                       Value(m_tab->RelativeUpperLeft().x - (m_decr ? m_decr->Size().x : X0)));
419     int tab_space = TabSpace() - (m_orientation == VERTICAL ? Value(m_tab->Size().y) : Value(m_tab->Size().x));
420     int max_posn = static_cast<int>(m_range_max - m_page_sz + 1);
421     m_posn = static_cast<int>(m_range_min + static_cast<double>(before_tab) / tab_space * (max_posn - m_range_min) + 0.5);
422     m_posn = std::max(m_range_min, std::min(m_posn, max_posn));
423     if (old_posn != m_posn)
424         ScrolledSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
425 }
426 
MoveTabToPosn()427 void Scroll::MoveTabToPosn()
428 {
429     int start_tabspace = 0; // the tab's lowest posible extent
430     if (m_decr)
431         start_tabspace = (m_orientation == VERTICAL ?
432                           Value(m_decr->Size().y) :
433                           Value(m_decr->Size().x));
434     int tab_space = TabSpace() - (m_orientation == VERTICAL ? Value(m_tab->Size().y) : Value(m_tab->Size().x));
435     int max_posn = static_cast<int>(m_range_max - m_page_sz + 1);
436     double tab_location =
437         (m_posn - m_range_min) / static_cast<double>(max_posn - m_range_min) * tab_space + start_tabspace + 0.5;
438     if (m_decr && m_posn - m_range_min == 0)
439         tab_location = m_orientation == VERTICAL ? Value(m_decr->Height()) : Value(m_decr->Width());
440 
441     m_tab->MoveTo(m_orientation == VERTICAL ?
442                   Pt(m_tab->RelativeUpperLeft().x, Y(static_cast<int>(tab_location))) :
443                   Pt(X(static_cast<int>(tab_location)), m_tab->RelativeUpperLeft().y));
444 }
445 
ScrollLineIncrDecrImpl(bool signal,int lines)446 void Scroll::ScrollLineIncrDecrImpl(bool signal, int lines)
447 {
448     int old_posn = m_posn;
449     int move = lines * m_line_sz;
450 
451     if (move == 0) {
452         return;
453     } else if (move > 0) {
454         if (static_cast<int>(m_posn + move) <= static_cast<int>(m_range_max - m_page_sz))
455             m_posn += move;
456         else
457             m_posn = m_range_max - m_page_sz;
458     } else {
459         if (static_cast<int>(m_posn + move) >= m_range_min)
460             m_posn += move;
461         else
462             m_posn = m_range_min;
463     }
464 
465     MoveTabToPosn();
466     if (signal && old_posn != m_posn) {
467         ScrolledSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
468         ScrolledAndStoppedSignal(m_posn, m_posn + m_page_sz, m_range_min, m_range_max);
469     }
470 }
471 
472 
473 ////////////////////////////////////////////////
474 // free functions
475 ////////////////////////////////////////////////
SignalScroll(const Scroll & scroll,bool stopped)476 void GG::SignalScroll(const Scroll& scroll, bool stopped)
477 {
478     std::pair<int, int> pr = scroll.PosnRange();
479     std::pair<int, int> sr = scroll.ScrollRange();
480     scroll.ScrolledSignal(pr.first, pr.second, sr.first, sr.second);
481     if (stopped)
482         scroll.ScrolledAndStoppedSignal(pr.first, pr.second, sr.first, sr.second);
483 }
484