1 #include <msp/core/raii.h>
2 #include <msp/debug/demangle.h>
3 #include <msp/gl/matrix.h>
4 #include <msp/gl/meshbuilder.h>
5 #include <msp/input/keys.h>
6 #include <msp/strings/format.h>
18 incompatible_data::incompatible_data(const type_info &ti):
19 logic_error("expected "+Debug::demangle(ti.name()))
24 List(*new BasicListData<string>)
29 List::List(ListData &d):
31 observer(new DataObserver(*this))
33 input_type = INPUT_NAVIGATION;
37 slider.signal_value_changed.connect(sigc::mem_fun(this, &List::slider_value_changed));
48 void List::autosize_special(const Part &part, Geometry &ageom) const
50 if(part.get_name()=="items")
52 const Sides &margin = part.get_margin();
56 for(size_t i=0; i<items.size(); ++i)
59 items[i]->autosize(igeom);
60 items_w = max(items_w, igeom.w);
61 items_h = max(items_h, igeom.h);
66 unsigned r = view_rows;
67 unsigned c = view_columns;
69 r = sqrt(items.size());
71 r = (items.size()+c-1)/c;
73 c = (items.size()+r-1)/r;
78 items_h *= (view_rows==0 ? items.size() : view_rows);
80 ageom.w = max(ageom.w, items_w+margin.left+margin.right);
81 ageom.h = max(ageom.h, items_h+margin.top+margin.bottom);
83 else if(part.get_name()=="slider")
84 autosize_child(slider, part, ageom);
87 void List::set_data(ListData &d)
90 item_factory->set_data(d);
98 observer = new DataObserver(*this);
103 size_t n_items = data->size();
104 for(size_t i=0; i<n_items; ++i)
106 Item *item = create_item(i);
107 items.push_back(item);
113 void List::items_changed()
115 signal_autosize_changed.emit();
119 List::Item *List::create_item(size_t index)
121 Item *item = nullptr;
123 item = item_factory->create_item(index);
125 item = new BasicItem(data->get_string(index));
127 item->set_active(true);
130 item->signal_autosize_changed.connect(sigc::bind(sigc::mem_fun(this, &List::item_autosize_changed), item));
134 void List::set_view_size(unsigned s)
139 void List::set_view_size(unsigned r, unsigned c)
143 signal_autosize_changed.emit();
146 void List::set_view_all()
151 void List::set_selected_index(size_t i)
153 if(i>=data->size() && i!=INVALID_INDEX)
154 throw out_of_range("List::set_selected_index");
159 if(sel_index!=INVALID_INDEX)
160 items[sel_index]->set_active(false);
166 set_input_focus(nullptr);
167 signal_selection_cleared.emit();
171 items[sel_index]->set_active(true);
173 set_input_focus(items[focus_index]);
174 signal_item_selected.emit(sel_index);
178 void List::set_selected_item(Widget *item)
180 for(size_t i=rows[first_row].first; (i<items.size() && items[i]->is_visible()); ++i)
182 return set_selected_index(i);
185 void List::rebuild_special(const Part &part)
187 if(part.get_name()=="slider")
188 reposition_child(slider, part);
189 else if(part.get_name()=="items")
191 SetFlag flag(ignore_slider_change);
192 reposition_items(true);
193 size_t old_first_row = first_row;
194 size_t old_max_scroll = max_scroll;
196 if(first_row!=old_first_row || max_scroll!=old_max_scroll)
197 reposition_items(false);
200 Widget::rebuild_special(part);
203 void List::render_special(const Part &part, GL::Renderer &renderer) const
205 if(part.get_name()=="items")
207 for(size_t i=rows[first_row].first; (i<items.size() && items[i]->is_visible()); ++i)
208 items[i]->render(renderer);
210 else if(part.get_name()=="slider")
211 slider.render(renderer);
214 bool List::key_press(unsigned key, unsigned mod)
216 if(key==Input::KEY_UP && mod==MOD_CTRL)
217 move_focus(NAV_UP, false);
218 else if(key==Input::KEY_DOWN && mod==MOD_CTRL)
219 move_focus(NAV_DOWN, false);
226 void List::button_press(int x, int y, unsigned btn)
233 change = min(first_row, change);
234 slider.set_value(max_scroll-(first_row-change));
238 change = min(max_scroll-first_row, change);
239 slider.set_value(max_scroll-(first_row+change));
244 Container::button_press(x, y, btn);
245 if(click_focus && btn==1)
246 set_selected_item(click_focus);
250 void List::touch_press(int x, int y, unsigned finger)
260 void List::touch_release(int x, int y, unsigned finger)
264 int dx = x-drag_start_x;
265 int dy = y-drag_start_y;
268 Container::touch_press(drag_start_x, drag_start_y, finger);
270 set_selected_item(touch_focus);
271 Container::touch_motion(x, y, finger);
272 Container::touch_release(x, y, finger);
278 void List::touch_motion(int, int y, unsigned finger)
280 if(finger==0 && !items.empty() && dragging)
282 int dy = y-drag_start_y;
283 if(dy>0 && first_row<max_scroll)
285 int row_h = rows[first_row].height;
288 drag_start_y += row_h;
289 slider.set_value(max_scroll-(first_row+1));
292 else if(dy<0 && first_row>0)
294 int row_h = rows[first_row-1].height;
297 drag_start_y -= row_h;
298 slider.set_value(max_scroll-(first_row-1));
304 void List::focus_in()
306 Container::focus_in();
307 if(focus_index!=INVALID_INDEX && items[focus_index]->is_visible())
308 set_input_focus(items[focus_index]);
311 if(sel_index!=INVALID_INDEX && items[sel_index]->is_visible())
312 set_focus_index(sel_index);
313 else if(!items.empty())
314 set_focus_index(rows[first_row].first);
318 bool List::navigate(Navigation nav)
320 if((nav==NAV_UP || nav==NAV_DOWN || ((nav==NAV_LEFT || nav==NAV_RIGHT) && view_mode==GRID)) && !items.empty())
321 move_focus(nav, true);
322 else if(nav==NAV_ACTIVATE)
323 set_selected_index(focus_index);
330 void List::on_style_change()
332 items_part = (style ? style->find_part("items") : nullptr);
335 void List::move_focus(Navigation nav, bool select)
337 if(nav==NAV_UP && view_mode==GRID)
339 size_t row = item_index_to_row(focus_index);
341 set_focus_index(rows[row-1].first+focus_index-rows[row].first);
345 else if(nav==NAV_DOWN && view_mode==GRID)
347 size_t row = item_index_to_row(focus_index);
348 if(row+1<rows.size())
349 set_focus_index(rows[row+1].first+focus_index-rows[row].first);
351 set_focus_index(items.size()-1);
353 else if(nav==NAV_UP || (nav==NAV_LEFT && view_mode==GRID))
356 set_focus_index(focus_index-1);
358 else if(nav==NAV_DOWN || (nav==NAV_RIGHT && view_mode==GRID))
360 if(focus_index+1<items.size())
361 set_focus_index(focus_index+1);
365 set_selected_index(focus_index);
368 void List::set_focus_index(size_t i)
371 if(focus_index!=INVALID_INDEX)
375 set_input_focus(items[focus_index]);
379 void List::item_autosize_changed(Item *item)
382 signal_autosize_changed.emit();
386 void List::reposition_items(bool record_rows)
397 const Sides &margin = items_part->get_margin();
398 unsigned view_w = geom.w-min(geom.w, margin.left+margin.right);
402 for(size_t i=0; i<items.size(); ++i)
404 const Geometry &igeom = items[i]->get_geometry();
406 if(view_mode!=GRID || (x>0 && x+igeom.w>view_w))
411 if(record_rows && i>0)
413 rows.back().height = row_h;
419 if(first_row<rows.size() && i==rows[first_row].first)
420 y = geom.h-min(geom.h, margin.top);
423 items[i]->set_visible(false);
424 else if(igeom.h+margin.bottom<=y)
426 items[i]->set_visible(true);
427 unsigned iw = (view_mode==GRID ? igeom.w : view_w);
428 items[i]->set_geometry(Geometry(margin.left+x, y-igeom.h, iw, igeom.h));
432 for(size_t j=rows.back().first; j<=i; ++j)
433 items[j]->set_visible(false);
438 row_h = max(row_h, igeom.h);
442 rows.back().height = row_h;
445 size_t List::last_to_first_row(size_t last) const
450 const Sides &margin = items_part->get_margin();
451 unsigned view_h = geom.h-min(geom.h, margin.top+margin.bottom);
453 unsigned items_h = 0;
454 for(size_t i=last; i<rows.size(); --i)
456 items_h += rows[i].height;
458 return min(i+1, last);
464 size_t List::item_index_to_row(size_t index) const
466 for(size_t i=0; i+1<rows.size(); ++i)
467 if(rows[i+1].first>index)
469 return rows.size()-1;
472 void List::check_view_range()
480 max_scroll = last_to_first_row(rows.size()-1);
482 if(first_row>max_scroll)
483 first_row = max_scroll;
485 slider.set_range(0, max_scroll);
486 slider.set_page_size(rows.size()-max_scroll);
487 slider.set_value(max_scroll-first_row);
490 void List::scroll_to_focus()
492 if(focus_index==INVALID_INDEX || items[focus_index]->is_visible())
495 size_t focus_row = item_index_to_row(focus_index);
496 if(focus_row<first_row)
497 slider.set_value(max_scroll-focus_row);
499 slider.set_value(max_scroll-last_to_first_row(focus_row));
502 void List::slider_value_changed(double value)
504 if(max_scroll>0 && !ignore_slider_change)
506 first_row = max_scroll-static_cast<size_t>(value);
511 void List::adjust_index(size_t &index, size_t pos, ptrdiff_t change)
513 if(index==INVALID_INDEX)
518 index = (change>0 ? index+change : INVALID_INDEX);
522 List::DataObserver::DataObserver(List &l):
525 list.data->signal_item_added.connect(sigc::mem_fun(this, &DataObserver::item_added));
526 list.data->signal_item_removed.connect(sigc::mem_fun(this, &DataObserver::item_removed));
527 list.data->signal_cleared.connect(sigc::mem_fun(this, &DataObserver::cleared));
528 list.data->signal_refresh_item.connect(sigc::mem_fun(this, &DataObserver::refresh_item));
531 void List::DataObserver::item_added(size_t i)
533 adjust_index(list.sel_index, i, 1);
534 adjust_index(list.focus_index, i, 1);
536 Item *item = list.create_item(i);
537 list.items.insert(list.items.begin()+i, item);
538 list.items_changed();
541 void List::DataObserver::item_removed(size_t i)
543 bool had_selection = (list.sel_index!=INVALID_INDEX);
544 adjust_index(list.sel_index, i, -1);
545 adjust_index(list.focus_index, i, -1);
547 delete list.items[i];
548 list.items.erase(list.items.begin()+i);
549 list.items_changed();
551 if(had_selection && list.sel_index==INVALID_INDEX)
552 list.signal_selection_cleared.emit();
555 void List::DataObserver::cleared()
557 list.sel_index = INVALID_INDEX;
558 list.focus_index = INVALID_INDEX;
559 for(Item *i: list.items)
562 list.items_changed();
564 list.signal_selection_cleared.emit();
567 void List::DataObserver::refresh_item(size_t i)
569 delete list.items[i];
570 // Avoid stale pointer while create_item is executing
571 list.items[i] = nullptr;
572 list.items[i] = list.create_item(i);
573 list.items_changed();
579 input_type = INPUT_NAVIGATION;
582 void List::Item::autosize_special(const Part &part, Geometry &ageom) const
584 if(part.get_name()=="children")
586 const Sides &margin = part.get_margin();
587 for(const Child *c: children)
590 c->widget->autosize(cgeom);
591 ageom.w = max(ageom.w, cgeom.x+cgeom.w+margin.right);
592 ageom.h = max(ageom.h, cgeom.y+cgeom.h+margin.top);
597 void List::Item::set_active(bool a)
599 set_state(ACTIVE, (a ? ACTIVE : NORMAL));
602 void List::Item::render_special(const Part &part, GL::Renderer &renderer) const
604 if(part.get_name()=="children")
606 for(const Child *c: children)
607 c->widget->render(renderer);
612 void List::SimpleItem::on_style_change()
614 if(!style || children.empty())
617 Widget *child = children.front()->widget;
619 if(const Part *part = style->find_part("children"))
621 const Sides &margin = part->get_margin();
622 child->set_position(margin.left, margin.bottom);
627 void List::MultiColumnItem::check_widths(vector<unsigned> &widths) const
629 if(widths.size()<children.size())
630 widths.resize(children.size(), 0);
633 for(const Child *c: children)
636 c->widget->autosize(cgeom);
637 // TODO invent a better way to specify spacings
638 widths[n] = max(widths[n], cgeom.w+8);
643 void List::MultiColumnItem::set_widths(const vector<unsigned> &widths)
648 const Part *part = style->find_part("children");
652 const Sides &margin = part->get_margin();
655 for(const Child *c: children)
657 c->widget->set_position(x, margin.bottom);
662 void List::MultiColumnItem::on_style_change()
667 for(const Child *c: children)
668 c->widget->autosize();
670 vector<unsigned> widths;
671 List *list = static_cast<List *>(parent);
672 for(Item *i: list->items)
674 if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(i))
675 mci->check_widths(widths);
677 vector<unsigned> self_widths(widths);
678 check_widths(self_widths);
679 bool update_all = false;
680 for(size_t i=0; (!update_all && i<widths.size() && i<self_widths.size()); ++i)
681 update_all = self_widths[i]>widths[i];
685 for(Item *i: list->items)
686 if(MultiColumnItem *mci = dynamic_cast<MultiColumnItem *>(i))
687 mci->set_widths(self_widths);
690 set_widths(self_widths);
694 List::BasicItem::BasicItem(const string &text):
701 List::Loader::Loader(List &l):
702 DataFile::DerivedObjectLoader<List, Widget::Loader>(l)
704 add("item", &Loader::item);
705 add("view_mode", &List::view_mode);
706 add("view_size", &List::view_rows);
707 add("view_size", &List::view_rows, &List::view_columns);
710 void List::Loader::item(const string &v)
712 dynamic_cast<BasicListData<string> &>(*obj.data).append(v);
716 void operator>>(const LexicalConverter &conv, List::ViewMode &vm)
718 const string &str = conv.get();
724 throw lexical_error(format("conversion of '%s' to List::ViewMode", str));