HoverRace  2.0
PickList.h
Go to the documentation of this file.
1 
2 // PickList.h
3 //
4 // Copyright (c) 2015 Michael Imamura.
5 //
6 // Licensed under GrokkSoft HoverRace SourceCode License v1.0(the "License");
7 // you may not use this file except in compliance with the License.
8 //
9 // A copy of the license should have been attached to the package from which
10 // you have taken this file. If you can not find the license you can not use
11 // this file.
12 //
13 //
14 // The author makes no representations about the suitability of
15 // this software for any purpose. It is provided "as is" "AS IS",
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17 // implied.
18 //
19 // See the License for the specific language governing permissions
20 // and limitations under the License.
21 
22 #pragma once
23 
24 #include "../Exception.h"
25 #include "BaseContainer.h"
26 #include "StateButton.h"
27 
28 #if defined(_WIN32) && defined(HR_ENGINE_SHARED)
29 # ifdef MR_ENGINE
30 # define MR_DllDeclare __declspec( dllexport )
31 # else
32 # define MR_DllDeclare __declspec( dllimport )
33 # endif
34 #else
35 # define MR_DllDeclare
36 #endif
37 
38 namespace HoverRace {
39  namespace Display {
40  class Display;
41  }
42 }
43 
44 namespace HoverRace {
45 namespace Display {
46 
52 class PickListItem : public StateButton
53 {
54  using SUPER = StateButton;
55 
56 public:
57  PickListItem(Display &display, const std::string &text, bool showIcon,
58  uiLayoutFlags_t layoutFlags = 0);
59  virtual ~PickListItem() { }
60 
61 private:
62  void Init();
63  void InitIcon(bool enabled, bool checked);
64 };
65 
71 {
73 
74 public:
75  BasePickList(Display &display, const Vec2 &size,
76  uiLayoutFlags_t layoutFlags = 0);
77  virtual ~BasePickList() { }
78 
79 public:
80  enum class Mode
81  {
82  RADIO,
83  LIST,
84  };
85 
86 protected:
91  virtual UiViewModel *GetFocusedChild() const = 0;
92 
98  virtual boost::optional<size_t> FindChildIndex(const UiViewModel &child) const = 0;
99 
108  virtual void SetSelection(const boost::optional<size_t> &newSel) = 0;
109 
110 public:
111  bool OnMouseScrolled(const Control::Mouse::Scroll&) override;
112  bool OnAction() override;
113  bool OnNavigate(const Control::Nav &nav) override;
114 
115 protected:
116  void OnChildRequestedFocus(UiViewModel &child) override;
117  void OnChildRelinquishedFocus(UiViewModel&,
118  const Control::Nav &nav) override;
119 
120 public:
121  bool TryFocus(const Control::Nav &nav = Control::Nav::NEUTRAL) override;
122  void DropFocus() override;
123 
132  void SetIndex(size_t idx)
133  {
134  boost::optional<size_t> newSel;
135  if (idx < filteredItems.size()) {
136  newSel = filteredItems[idx];
137  }
138 
139  SetSelection(newSel);
140  }
141 
146  bool HasSelected() const
147  {
148  return static_cast<bool>(selItem);
149  }
150 
155  {
156  SetSelection(boost::none);
157  }
158 
159 protected:
164  double GetScroll() const { return -(GetChildOffset().y); }
165 
166  void ScrollTo(double y);
167  void ScrollToFocused();
168 
169 public:
170  using valueChangedSignal_t = boost::signals2::signal<void()>;
171  valueChangedSignal_t &GetValueChangedSignal() { return valueChangedSignal; }
172 
173 protected:
174  void Layout() override;
175 
176 protected:
177  std::vector<size_t> filteredItems;
178  boost::optional<size_t> selItem;
179  boost::optional<size_t> focusedItem;
180  double listHeight;
182 };
183 
190 template<class T>
191 class PickList : public BasePickList
192 {
194 
195 public:
196  PickList(Display &display, const Vec2 &size,
197  uiLayoutFlags_t layoutFlags = 0) :
198  PickList(display, Mode::RADIO, size, layoutFlags) { }
199  PickList(Display &display, Mode mode, const Vec2 &size,
200  uiLayoutFlags_t layoutFlags = 0) :
201  SUPER(display, size, layoutFlags), mode(mode) { }
202  virtual ~PickList() { }
203 
204 protected:
205  class DefaultItem : public PickListItem
206  {
208 
209  public:
210  DefaultItem(PickList<T> &pickList, Display &display,
211  size_t idx, const T &value, const std::string &text,
212  bool showIcon, uiLayoutFlags_t layoutFlags = 0) :
213  SUPER(display, text, showIcon, layoutFlags),
214  pickList(pickList), idx(idx), value(value)
215  {
216  clickedConn = GetClickedSignal().connect([&](ClickRegion&) {
217  pickList.SetSelectedItem(this);
218  });
219  }
220  DefaultItem(const DefaultItem&) = delete;
221  DefaultItem(DefaultItem&&) = delete;
222 
223  virtual ~DefaultItem() { }
224 
225  DefaultItem &operator=(const DefaultItem&) = delete;
226  DefaultItem &operator=(DefaultItem&&) = delete;
227 
228  public:
229  size_t GetIndex() const { return idx; }
230  const T &GetValue() const { return value; }
231 
232  private:
234  const size_t idx;
235  const T value;
236  boost::signals2::scoped_connection clickedConn;
237  };
238 
240  class ItemChild
241  {
242  public:
243  ItemChild(Child &child, DefaultItem &item) :
244  child(child), item(item) { }
245 
246  ItemChild &operator=(const ItemChild&) = delete;
247 
248  public:
249  Child &child;
251  };
252 
253 protected:
254  UiViewModel *GetFocusedChild() const override
255  {
256  return focusedItem ?
257  items[filteredItems[*focusedItem]].child.child.get() :
258  nullptr;
259  }
260 
261  boost::optional<size_t> FindChildIndex(const UiViewModel &child) const override
262  {
263  // Currently using a simple linear search.
264  // If this gets prohibitive later due to the number of child elements,
265  // we'll need to rethink this.
266 
267  size_t fi = 0;
268  for (auto i : filteredItems) {
269  if (items[i].child.child.get() == &child) {
270  return fi;
271  }
272  fi++;
273  }
274 
275  return boost::none;
276  }
277 
278 protected:
279  void SetSelection(const boost::optional<size_t> &newSel) override
280  {
281  if (selItem != newSel) {
282  if (selItem) {
283  items[*selItem].item.SetChecked(false);
284  }
285  if (newSel) {
286  items[*newSel].item.SetChecked(true);
287  }
288  selItem = newSel;
289  valueChangedSignal();
290  }
291  }
292 
293 private:
295  {
296  boost::optional<size_t> newSel = sel ?
297  boost::make_optional(sel->GetIndex()) :
298  boost::none;
299 
300  if (selItem != newSel) {
301  if (selItem) {
302  items[*selItem].item.SetChecked(false);
303  }
304  selItem = newSel;
305  if (sel) {
306  sel->SetChecked(true);
307  }
308 
309  valueChangedSignal();
310  }
311  }
312 
313 public:
319  void Add(const std::string &label, const T &value)
320  {
321  size_t idx = items.size();
322  auto item = NewChild<DefaultItem>(*this, display, idx, value, label,
323  (mode == Mode::RADIO));
324  items.emplace_back(*(GetChildren().back()), *item);
325  filteredItems.push_back(items.size() - 1);
326  RequestLayout();
327  }
328 
333  void Add(const T &value)
334  {
335  Add(boost::lexical_cast<std::string>(value), value);
336  }
337 
338 public:
347  void SetValue(const T &val)
348  {
349  // Find the first item matching this value.
350  // We assume that the list is small enough that we don't need a
351  // lookup table.
352  boost::optional<size_t> newSel;
353  for (auto idx : filteredItems) {
354  if (items[idx].item.GetValue() == val) {
355  newSel = idx;
356  break;
357  }
358  }
359 
360  SetSelection(newSel);
361  }
362 
367  boost::optional<const T&> GetValue() const
368  {
369  return selItem ?
370  boost::make_optional<const T&>(items[*selItem].item.GetValue()) :
371  boost::none;
372  }
373 
384  template<class Fn>
385  void ApplyFilter(Fn fn)
386  {
387  auto prevFocusedChild = GetFocusedChild();
388  auto prevFocusedIdx = focusedItem;
389 
390  bool deselected = false;
391  bool foundFocusedChild = false;
392  filteredItems.clear();
393  size_t idx = 0;
394  for (auto &item : items) {
395  bool visible = item.child.visible = fn(item.item.GetValue());
396  if (visible) {
397  if (item.child.child.get() == prevFocusedChild) {
398  // Found the previously-focused child widget in the
399  // new filtered list; just update the index (the widget
400  // was already focused, so no need to TryFocus() it).
401  foundFocusedChild = true;
402  focusedItem = filteredItems.size();
403  }
404  filteredItems.push_back(idx);
405  }
406  else if (selItem && *selItem == idx) {
407  items[*selItem].item.SetChecked(false);
408  selItem = boost::none;
409  deselected = true;
410  }
411  idx++;
412  }
413 
414  if (prevFocusedChild && !foundFocusedChild) {
415  prevFocusedChild->DropFocus();
416  focusedItem = boost::none;
417 
418  // An item was previously focused, but is no longer included
419  // in the filtered list.
420  if (filteredItems.empty()) {
421  RelinquishFocus(Control::Nav::NEUTRAL);
422  }
423  else {
424  // Move focus to another item.
425  focusedItem = std::min(*focusedItem, filteredItems.size() - 1);
426  if (!GetFocusedChild()->TryFocus()) {
427  // Should never happen, but just in case...
428  focusedItem = boost::none;
429  SetFocused(false);
430  }
431  }
432  }
433 
434  RequestLayout();
435 
436  if (deselected) {
437  valueChangedSignal();
438  }
439  }
440 
445  {
446  ApplyFilter([](const T&){ return true; });
447  }
448 
449  void Clear() override
450  {
451  if (focusedItem) {
452  RelinquishFocus(Control::Nav::NEUTRAL);
453  }
454 
455  filteredItems.clear();
456  items.clear();
457 
458  SUPER::Clear();
459 
460  if (selItem) {
461  // The selected item was removed, so no need to uncheck it :)
462  selItem = boost::none;
463  valueChangedSignal();
464  }
465  }
466 
467  void Reserve(size_t capacity) override
468  {
469  SUPER::Reserve(capacity);
470  items.reserve(capacity);
471  filteredItems.reserve(capacity);
472  }
473 
474 private:
476  std::vector<ItemChild> items;
477 };
478 
479 } // namespace Display
480 } // namespace HoverRace
481 
482 #undef MR_DllDeclare
valueChangedSignal_t & GetValueChangedSignal()
Definition: PickList.h:171
void SetValue(const T &val)
Set the selected value.
Definition: PickList.h:347
std::vector< size_t > filteredItems
Indexes of filtered items.
Definition: PickList.h:177
Child & child
Definition: PickList.h:249
boost::signals2::signal< void()> valueChangedSignal_t
Definition: PickList.h:170
Base class for UI (2D) components.
Definition: UiViewModel.h:56
void ClearSelection()
If an item is selected, unset the selection so that nothing is selected.
Definition: PickList.h:154
virtual ~PickList()
Definition: PickList.h:202
virtual ~BasePickList()
Definition: PickList.h:77
MR_DllDeclare void Init()
Definition: ColorTools.cpp:142
ItemChild(Child &child, DefaultItem &item)
Definition: PickList.h:243
boost::optional< const T & > GetValue() const
Get the value of the selected item.
Definition: PickList.h:367
void ApplyFilter(Fn fn)
Apply a filter to the list items.
Definition: PickList.h:385
const T value
Definition: PickList.h:235
Base class for widgets that contain other widgets.
Definition: BaseContainer.h:63
void SetSelectedItem(DefaultItem *sel)
Definition: PickList.h:294
void Add(const T &value)
Add a list item with a default label.
Definition: PickList.h:333
Definition: Vec.h:38
boost::optional< size_t > FindChildIndex(const UiViewModel &child) const override
Search for the index of the given child widget.
Definition: PickList.h:261
void SetSelection(const boost::optional< size_t > &newSel) override
Set the selected item by index.
Definition: PickList.h:279
MR_UInt32 uiLayoutFlags_t
Definition: UiLayoutFlags.h:53
void RemoveFilter()
Undo any applied filter (so all items are visible).
Definition: PickList.h:444
boost::optional< size_t > selItem
items index of selected.
Definition: PickList.h:178
Mode
Definition: PickList.h:80
double GetScroll() const
Retrieve how far down the list we have scrolled.
Definition: PickList.h:164
PickList< T > & pickList
Definition: PickList.h:233
Base class for display managers.
Definition: Display.h:73
Generic base for PickList.
Definition: PickList.h:70
virtual ~PickListItem()
Definition: PickList.h:59
void Clear() override
Remove all child elements.
Definition: PickList.h:449
Mouse scroll events.
Definition: Action.h:77
Definition: Symbol.h:224
valueChangedSignal_t valueChangedSignal
Definition: PickList.h:181
void SetChecked(bool checked)
Set the button state.
Definition: StateButton.cpp:66
luabind::object val
Definition: Rulebook.cpp:52
DefaultItem(PickList< T > &pickList, Display &display, size_t idx, const T &value, const std::string &text, bool showIcon, uiLayoutFlags_t layoutFlags=0)
Definition: PickList.h:210
A navigation direction.
Definition: Nav.h:45
UiViewModel * GetFocusedChild() const override
Get the focused child widget.
Definition: PickList.h:254
PickList(Display &display, const Vec2 &size, uiLayoutFlags_t layoutFlags=0)
Definition: PickList.h:196
Maps the container child wrapper to each item.
Definition: PickList.h:240
bool HasSelected() const
Check if this list has a selected item.
Definition: PickList.h:146
void Reserve(size_t capacity) override
Increase the capacity of the of this container.
Definition: PickList.h:467
std::vector< ItemChild > items
Definition: PickList.h:476
int idx
Definition: SdlDisplay.cpp:254
Base class for clickable areas.
Definition: ClickRegion.h:49
const size_t idx
Definition: PickList.h:234
size_t GetIndex() const
Definition: PickList.h:229
virtual ~DefaultItem()
Definition: PickList.h:223
void Add(const std::string &label, const T &value)
Add a list item with a specific label.
Definition: PickList.h:319
Definition: Announcement.h:24
Scrollable list of selectable items.
Definition: PickList.h:191
const T & GetValue() const
Definition: PickList.h:230
DefaultItem & item
Definition: PickList.h:250
Base class for buttons with state (i.e.
Definition: StateButton.h:50
A single list item.
Definition: PickList.h:52
boost::optional< size_t > focusedItem
filteredItems index of focused.
Definition: PickList.h:179
PickList(Display &display, Mode mode, const Vec2 &size, uiLayoutFlags_t layoutFlags=0)
Definition: PickList.h:199
boost::signals2::scoped_connection clickedConn
Definition: PickList.h:236
void SetIndex(size_t idx)
Select an item by index.
Definition: PickList.h:132
double listHeight
Definition: PickList.h:180
Mode mode
Definition: PickList.h:475