YSTest  PreAlpha_b400_20130424
The YSLib Test Project
 全部  命名空间 文件 函数 变量 类型定义 枚举 枚举值 友元 宏定义  
scroll.cpp
浏览该文件的文档.
1 /*
2  Copyright by FrankHB 2011 - 2013.
3 
4  This file is part of the YSLib project, and may only be used,
5  modified, and distributed under the terms of the YSLib project
6  license, LICENSE.TXT. By continuing to use, modify, or distribute
7  this file you indicate that you have read the license and
8  understand and accept it fully.
9 */
10 
28 #include "YSLib/UI/scroll.h"
29 #include "YSLib/UI/ygui.h"
30 #include "YSLib/Core/ystorage.hpp"
31 #include <ystdex/algorithm.hpp>
32 
33 using namespace ystdex;
34 
36 
38 
39 namespace
40 {
41 
42 pair<bool, bool>
43 FixScrollBarLayout(Size& d, const Size& s, SDst min_width, SDst min_height)
44 {
45  bool need_h(d.Width < s.Width), need_v(d.Height < s.Height);
46 
47  if(need_h)
48  {
49  if(d.Height < min_height)
50  throw GeneralEvent("Scroll bar need more height.");
51  d.Height -= min_height;
52  }
53  if(need_v)
54  {
55  if(d.Width < min_width)
56  throw GeneralEvent("Scroll bar need more width.");
57  d.Width -= min_width;
58  }
59  if(need_h != need_v)
60  {
61  if(!need_h && d.Width < s.Width)
62  {
63  need_h = true;
64  if(d.Height < min_height)
65  throw GeneralEvent("Scroll bar need more height.");
66  d.Height -= min_height;
67  }
68  if(!need_v && d.Height < s.Height)
69  {
70  need_v = true;
71  if(d.Width < min_width)
72  throw GeneralEvent("Scroll bar need more width.");
73  d.Width -= min_width;
74  }
75  }
76  return pair<bool, bool>(need_h, need_v);
77 }
78 
79 const SDst defMinScrollBarWidth(16);
80 const SDst defMinScrollBarHeight(16);
81 
82 } //unnamed namespace;
83 
84 
85 ATrack::ATrack(const Rect& r, SDst uMinThumbLength)
86  : Control({r.GetPoint(), max<SDst>(defMinScrollBarWidth, r.Width),
87  max<SDst>(defMinScrollBarHeight, r.Height)}, NoBackgroundTag()),
88  GMRange<ValueType>(0xFF, 0),
91 {
93  yunseq(
94  Background = std::bind(DrawTrackBackground, std::placeholders::_1,
95  std::ref(*this)),
96  GetThumbDrag() += [this](UIEventArgs&&){
97  LocateThumb(0, ScrollCategory::ThumbTrack);
98  },
99  FetchEvent<TouchMove>(*this) += OnTouchMove,
100  FetchEvent<TouchDown>(*this) += [this](TouchEventArgs&& e){
101  if(e.Strategy == RoutedEventArgs::Direct && &e.GetSender() == this
102  && Rect(GetSizeOf(*this)).Contains(e))
103  {
104  ScrollCategory t;
105 
106  switch(CheckArea(e.GetRef(IsHorizontal())))
107  {
108  case OnPrev:
109  t = ScrollCategory::LargeDecrement;
110  break;
111  case OnNext:
112  t = ScrollCategory::LargeIncrement;
113  break;
114  case None:
115  return;
116  default:
117  t = ScrollCategory::EndScroll;
118  }
119  LocateThumb(0, t);
120  }
121  }
122  );
123 }
124 
125 void
126 ATrack::SetThumbLength(SDst l)
127 {
128  RestrictInInterval(l, min_thumb_length, GetTrackLength());
129 
131  const bool is_h(IsHorizontal());
132 
133  if(l != s.GetRef(is_h))
134  {
136  s.GetRef(is_h) = l;
137  SetSizeOf(tmbScroll, s);
138  }
139 }
140 void
141 ATrack::SetThumbPosition(SPos pos)
142 {
143  RestrictInClosedInterval(pos, 0, GetScrollableLength());
144 
146  const bool is_h(IsHorizontal());
147 
148  if(pos != pt.GetRef(is_h))
149  {
151  pt.GetRef(is_h) = pos;
153  }
154 }
155 void
156 ATrack::SetMaxValue(ValueType m)
157 {
158  if(m > 0)
159  {
160  if(large_delta > m)
161  large_delta = m;
162  max_value = m;
163  }
164 }
165 void
166 ATrack::SetValue(ValueType val)
167 {
168  value = val;
169  SetThumbPosition(SPos(round(val * GetScrollableLength() / max_value)));
170 }
171 void
172 ATrack::SetLargeDelta(ValueType val)
173 {
174  large_delta = val;
175  SetThumbLength(SDst(round(val * GetTrackLength() / (val + max_value))));
176 }
177 
179 ATrack::CheckArea(SPos q) const
180 {
181  if(q >= 0)
182  {
183  yconstexpr Area lst[] = {OnPrev, OnThumb, OnNext};
184  const SPos a[] = {SPos(), SPos(GetThumbPosition()),
185  SPos(GetThumbPosition() + GetThumbLength())};
186  const size_t n(SwitchInterval(q, a, 3));
187 
188  if(n < 3)
189  return lst[n];
190  }
191  return None;
192 }
193 
194 void
195 ATrack::LocateThumb(ValueType val, ScrollCategory t)
196 {
197  ValueType old_value(value);
198 
199  if(t == ScrollCategory::ThumbTrack)
200  value = GetThumbPosition() == GetScrollableLength() ? max_value
201  : max_value * GetThumbPosition()
202  / (GetTrackLength() - GetThumbLength());
203  else
204  {
205  if(t == ScrollCategory::LargeDecrement
206  || t == ScrollCategory::LargeIncrement)
207  val = GetLargeDelta();
208  switch(t)
209  {
210  case ScrollCategory::SmallDecrement:
211  case ScrollCategory::LargeDecrement:
212  if(value > val)
213  {
214  SetValue(value - val);
215  break;
216  }
217  case ScrollCategory::First:
218  value = 0;
219  SetThumbPosition(0);
220  break;
221  case ScrollCategory::SmallIncrement:
222  case ScrollCategory::LargeIncrement:
223  if(value + val < max_value)
224  {
225  SetValue(value + val);
226  break;
227  }
228  case ScrollCategory::Last:
229  value = max_value;
230  SetThumbPosition(GetScrollableLength());
231  default:
232  ;
233  }
234  }
235  GetScroll()(ScrollEventArgs(*this, t, value, old_value));
236 }
237 
238 
239 void
240 DrawTrackBackground(PaintEventArgs&& e, ATrack& trk)
241 {
242  const auto& g(e.Target);
243  const auto& pt(e.Location);
244  const Rect r(pt, GetSizeOf(trk));
245  auto& pal(FetchGUIState().Colors);
246 
247  FillRect(g, r, pal[Styles::Track]);
248 
249 #define YSL_UI_ATRACK_PARTIAL_INVALIDATION
250  // NOTE: Partial invalidation made no efficiency improved here.
251  const auto c(pal[Styles::Light]);
252 #ifdef YSL_UI_ATRACK_PARTIAL_INVALIDATION
253  SPos x(pt.X);
254  SPos y(pt.Y);
255  SPos xr(x + trk.GetWidth());
256  SPos yr(y + trk.GetHeight());
257 #else
258  const SPos xr(pt.X + trk.GetWidth());
259  const SPos yr(pt.Y + trk.GetHeight());
260 #endif
261 
262  if(trk.IsHorizontal())
263  {
264 #ifdef YSL_UI_ATRACK_PARTIAL_INVALIDATION
265  RestrictInInterval(y, r.Y, r.Y + r.Height),
266  RestrictInInterval(yr, r.Y, r.Y + r.Height);
267 #endif
268  DrawHLineSeg(g, pt.Y, pt.X, xr, c),
269  DrawHLineSeg(g, yr, pt.X, xr, c);
270  }
271  else
272  {
273 #ifdef YSL_UI_ATRACK_PARTIAL_INVALIDATION
274  RestrictInInterval(x, r.X, r.X + r.Width),
275  RestrictInInterval(xr, r.X, r.X + r.Width);
276 #endif
277  DrawVLineSeg(g, pt.X, pt.Y, yr, c),
278  DrawVLineSeg(g, xr, pt.Y, yr, c);
279  }
280 #ifndef YSL_UI_ATRACK_PARTIAL_INVALIDATION
281  e.ClipArea = r;
282 #endif
283 }
284 
285 
286 HorizontalTrack::HorizontalTrack(const Rect& r, SDst uMinThumbLength)
287  : ATrack(r, uMinThumbLength)
288 {
289  YAssert(GetWidth() > GetHeight(), "Width is not greater than height.");
290 
291  FetchEvent<TouchMove>(tmbScroll) +=[this](TouchEventArgs&& e){
292  if(e.Strategy == RoutedEventArgs::Direct)
293  {
294  auto& st(FetchGUIState());
295  SPos x(st.LastControlLocation.X + st.DraggingOffset.X);
296 
297  RestrictInClosedInterval(x, 0, GetWidth() - tmbScroll.GetWidth());
300  GetThumbDrag()(UIEventArgs(*this));
301  }
302  };
303 }
304 
305 
306 VerticalTrack::VerticalTrack(const Rect& r, SDst uMinThumbLength)
307  : ATrack(r, uMinThumbLength)
308 {
309  YAssert(GetHeight() > GetWidth(), "height is not greater than width.");
310 
311  FetchEvent<TouchMove>(tmbScroll) += [this](TouchEventArgs&& e){
312  if(e.Strategy == RoutedEventArgs::Direct)
313  {
314  auto& st(FetchGUIState());
315  SPos y(st.LastControlLocation.Y + st.DraggingOffset.Y);
316 
317  RestrictInClosedInterval(y, 0, GetHeight() - tmbScroll.GetHeight());
320  GetThumbDrag()(UIEventArgs(*this));
321  }
322  };
323 }
324 
325 
326 AScrollBar::AScrollBar(const Rect& r, SDst uMinThumbSize, Orientation o)
327  : Control(r, NoBackgroundTag()),
328  pTrack(o == Horizontal
329  ? static_cast<ATrack*>(new HorizontalTrack(
330  Rect(r.Height, 0, r.Width - r.Height * 2, r.Height), uMinThumbSize))
331  : static_cast<ATrack*>(new VerticalTrack(
332  Rect(0, r.Width, r.Width, r.Height - r.Width * 2), uMinThumbSize))),
333  btnPrev(Rect()), btnNext(Rect()), small_delta(2)
334 {
335  SetContainerPtrOf(*pTrack, this),
336  SetContainerPtrOf(btnPrev, this);
337  SetContainerPtrOf(btnNext, this);
338  yunseq(
339  FetchEvent<Resize>(*this) += [this](UIEventArgs&&){
340  auto& track(GetTrack());
341  const bool is_h(track.IsHorizontal());
342  const SDst prev_metric(GetSizeOf(btnPrev).GetRef(is_h));
343  const SDst sum(prev_metric + GetSizeOf(btnNext).GetRef(is_h));
344 
345  YAssert(GetSizeOf(*this).GetRef(is_h) - sum > 0,
346  "No enough space for track.");
347 
348  const SDst tl(GetSizeOf(*this).GetRef(is_h) - sum);
349 
350  yunseq(track.GetView().GetSizeRef().GetRef(is_h) = tl, btnNext
351  .GetView().GetLocationRef().GetRef(is_h) = tl + prev_metric);
352  // NOTE: No event %(Resize, Move) is raised.
353  },
354  FetchEvent<KeyHeld>(*this) += OnKeyHeld,
355  FetchEvent<TouchMove>(btnPrev) += OnTouchMove,
356  FetchEvent<TouchDown>(btnPrev) += [this](TouchEventArgs&&){
357  LocateThumb(small_delta, ScrollCategory::SmallDecrement);
358  },
359  FetchEvent<TouchMove>(btnNext) += OnTouchMove,
360  FetchEvent<TouchDown>(btnNext) += [this](TouchEventArgs&&){
361  LocateThumb(small_delta, ScrollCategory::SmallIncrement);
362  },
363  FetchEvent<KeyUp>(*this) += OnKey_Bound_TouchUpAndLeave,
364  FetchEvent<KeyDown>(*this) += OnKey_Bound_EnterAndTouchDown
365  );
366 
367  Size s(GetSizeOf(*this));
368  const bool bHorizontal(o == Horizontal);
369  const SDst l(s.GetRef(!bHorizontal));
370 
371  s.GetRef(bHorizontal) = l;
372  SetSizeOf(btnPrev, s);
373  SetSizeOf(btnNext, s);
374 // Button.SetLocationOf(btnPrev, Point());
377 }
378 
379 HorizontalScrollBar::HorizontalScrollBar(const Rect& r, SDst uMinThumbLength)
380  : AScrollBar(r, uMinThumbLength, Horizontal)
381 {
382  using namespace std;
383  using namespace placeholders;
384 
385  yunseq(
386  FetchEvent<Paint>(btnPrev) += bind(DrawArrow, _1, ref(btnPrev), 4,
387  RDeg180, ref(ForeColor)),
388  FetchEvent<Paint>(btnNext) += bind(DrawArrow, _1, ref(btnNext), 4,
389  RDeg0, ref(ForeColor))
390  );
391 }
392 
393 IWidget*
394 HorizontalScrollBar::GetBoundControlPtr(const KeyInput& k)
395 {
396  if(k.count() == 1)
397  {
398  if(k[KeyCodes::Left])
399  return &btnPrev;
400  if(k[KeyCodes::Right])
401  return &btnNext;
402  }
403  return nullptr;
404 }
405 
406 
407 VerticalScrollBar::VerticalScrollBar(const Rect& r, SDst uMinThumbLength)
408  : AScrollBar(r, uMinThumbLength, Vertical)
409 {
410  using namespace std;
411  using namespace placeholders;
412 
413  yunseq(
414  FetchEvent<Paint>(btnPrev) += bind(DrawArrow, _1, ref(btnPrev), 4,
415  RDeg90, ref(ForeColor)),
416  FetchEvent<Paint>(btnNext) += bind(DrawArrow, _1, ref(btnNext), 4,
417  RDeg270, ref(ForeColor))
418  );
419 }
420 
421 IWidget*
423 {
424  if(k.count() == 1)
425  {
426  if(k[KeyCodes::Up])
427  return &btnPrev;
428  if(k[KeyCodes::Down])
429  return &btnNext;
430  }
431  return nullptr;
432 }
433 
434 
436  : Control(r),
437  hsbHorizontal(Size(r.Width, defMinScrollBarHeight)),
438  vsbVertical(Size(defMinScrollBarWidth, r.Height))
439 {
440  // TODO: Allow user to choose whether background is drawn.
441  SetContainerPtrOf(hsbHorizontal, this),
442  SetContainerPtrOf(vsbVertical, this);
443  MoveToBottom(hsbHorizontal);
444  MoveToRight(vsbVertical);
445 }
446 
447 Size
449 {
450  Size arena(GetSizeOf(*this));
451 
452  try
453  {
454  const pair<bool, bool> p(FixScrollBarLayout(arena, s,
456 
457  if(p.first && p.second && GetWidth() > defMinScrollBarWidth
459  {
462  }
463  else if(p.first)
464  {
468  }
469  else if(p.second)
470  {
472  GetHeight()));
474  }
475  SetVisibleOf(hsbHorizontal, p.first);
476  SetVisibleOf(vsbVertical, p.second);
477  }
478  catch(GeneralEvent&)
479  {}
480  return arena;
481 }
482 
484 
485 YSL_END
486