// vi:ft=cpp /* * (c) 2010 Alexander Warg * economic rights: Technische Universität Dresden (Germany) * * This file is part of TUD:OS and distributed under the terms of the * GNU General Public License 2. * Please see the COPYING-GPL-2 file for details. */ #pragma once #include #include #include #include #include namespace Mag_server { template< typename C > class Menu : private cxx::Observer { private: typedef C Content; typedef typename C::Item Item; View *_view; Core_api const *_core; Content const *_content; Item const *_current; Area _size; Area _cell; int _height; int _offset; bool _scroll; int _speed; Font const *_font() const { return _core->label_font(); } View_stack *_vstack() const { return _core->user_state()->vstack(); } public: int scroll_button_height() const { return 15; } int item_sep() const { return 2; } int menu_border() const { return 4; } int min_width() const { return 180; } Menu(Core_api const *core, View *view, Content const *content) : _view(view), _core(core), _content(content), _current(0), _cell(min_width(), _font()->str_h("X") + 2 * item_sep()), _height(0), _offset(0), _scroll(false), _speed(0) {} Area calc_geometry(Area const &size); Item *find(Point const &pos, Rect *cell, int *y); bool find(Item const *x, Rect *cell, int *y); void draw(Canvas *canvas, Point const &pos) const; Item *handle_event(L4Re::Event_buffer::Event const &e, Point const &mouse); void scroll(int dist) { int h = _size.h() - 2 * scroll_button_height(); _offset = std::max(0, std::min(_height - h, _offset + dist)); } void optimize_offset(int current_pos) { if (!_scroll) { _offset = 0; return; } /* usable height */ int h = _size.h() - 2 * scroll_button_height(); int o_min = std::max(current_pos + _cell.h() - h, 0); int o_max = std::min(current_pos, _height - h); if (_offset <= o_max && _offset >= o_min) return; if (_offset > o_max) _offset = o_max; if (_offset < o_min) _offset = o_min; } private: template< typename F > Item *_find(Rect const &_bb, F const &f) const; struct Find_for_pos { Point pos; Rect *cell; int *offs; Find_for_pos(Point const &p, Rect *cell, int *offs) : pos(p), cell(cell), offs(offs) {} bool valid(Rect const &bb) const { return bb.contains(pos); } bool operator () (Item *, Rect const &bb, int y) const { if (!bb.contains(pos)) return false; *cell = bb; *offs = y; return true; } }; struct Find_item { Item const *item; Rect *cell; int *offs; Find_item(Item const *item, Rect *cell, int *offs) : item(item), cell(cell), offs(offs) {} bool valid(Rect const &) const { return true; } bool operator () (Item *e, Rect const &bb, int y) const { if (e != item) return false; *offs = y; *cell = bb; return true; } }; struct Draw_item { private: Draw_item(Draw_item const &); public: Canvas *canvas; Menu const *menu; mutable Clip_guard g; Draw_item(Menu const *m, Canvas *c) : canvas(c), menu(m) {} bool valid(Rect const &bb) const { g.init(canvas, bb); return canvas->clip_valid(); } bool operator () (Item *e, Rect br, int) const { Rgb32::Color lcol = e->ignore() ? Rgb32::Color(255, 200, 0) : Rgb32::White; Rgb32::Color bcol = e == menu->_current ? Rgb32::Color(99, 120, 180) : Rgb32::Color(99, 99, 88); canvas->draw_box(br, bcol); br = br.offset(menu->menu_border(), menu->item_sep(), -menu->menu_border(), -menu->item_sep()); Clip_guard g(canvas, br); canvas->draw_string(br.p1(), menu->_font(), lcol, e->label()); return false; } }; // handle timer ticks void notify(); }; template Area Menu::calc_geometry(Area const &max_size) { Area fs(min_width(), 0); //2 * menu_border()); bool cf = !_current; int cp = 0; for (Item *e = _content->first(); e; e = _content->next(e)) { int w = _font()->str_w(e->label()) + 2 * menu_border(); if (w > fs.w()) fs.w(w); if (_current == e) { cp = fs.h(); cf = true; } fs.h(fs.h() + _cell.h()); } /* the current item is gone so select none */ if (!cf) _current = 0; _height = fs.h(); if (fs.h() > max_size.h()) { fs.h(max_size.h()); _scroll = true; } else _scroll = false; if (_current) optimize_offset(cp); if (fs.w() > max_size.w()) fs.w(max_size.w()); _cell.w(fs.w()); _size = fs; return fs; } template< typename C > template< typename F > typename Menu::Item * Menu::_find(Rect const &_bb, F const &f) const { Rect bb = _bb; if (_scroll) bb = _bb.offset(0, scroll_button_height(), 0, -scroll_button_height()); if (!f.valid(bb)) return 0; int y = 0; for (Item *e = _content->first(); e; e = _content->next(e)) { int ny = y + _cell.h(); if (y - _offset > bb.h()) break; if (ny >= _offset) { Rect br(Point(bb.x1(), bb.y1() + y - _offset), _cell); if (f(e, br, y)) return e; } y = ny; } return 0; } template< typename C > typename Menu::Item * Menu::find(Point const &pos, Rect *cell, int *y) { return _find(Rect(Point(), _size), Find_for_pos(pos, cell, y)); } template< typename C > bool Menu::find(Item const *x, Rect *cell, int *y) { return _find(Rect(Point(), _size), Find_item(x, cell, y)); } template< typename C > void Menu::draw(Canvas *canvas, Point const &pos) const { using Mag_gfx::Clip_guard; Rect bb(pos, _size); Clip_guard g(canvas, bb); if (!canvas->clip_valid()) return; if (_scroll) { Rect s(pos, Area(_size.w(), scroll_button_height())); canvas->draw_box(s, Rgb32::Color(99, 99, 88)); //, 220)); s = s + Point(0, _size.h() - scroll_button_height()); canvas->draw_box(s, Rgb32::Color(99, 99, 88)); //, 220)); } // Hm, make g++ 4.2 happy, that tries to call the copy ctor // in the case of directly using a temporary Draw_item di(this, canvas); _find(bb, di); } template< typename C > typename Menu::Item * Menu::handle_event(L4Re::Event_buffer::Event const &e, Point const &mouse) { if (e.payload.type == L4RE_EV_ABS && e.payload.code == 1) { if (_scroll) { int const sbh = scroll_button_height(); int speed; if ((speed = sbh - mouse.y()) > 0) { _speed = -speed; if (!l_in_list()) _core->get_ticks(this); } else if ((speed = sbh - (_size.h() - mouse.y())) > 0) { _speed = speed; if (!l_in_list()) _core->get_ticks(this); } else { static_cast(this)->l_remove(); _speed = 0; } if (speed > 0) return 0; } Rect nr; int ny; Item const *s = find(mouse, &nr, &ny); if (_current != s) { Rect cr; int cy; bool refresh_cr = false; if (_current && find(_current, &cr, &cy)) refresh_cr = true; _current = s; int old_offs = _offset; if (_current) optimize_offset(ny); if (old_offs != _offset) _vstack()->refresh_view(_view, 0, *_view); else { if (refresh_cr) _vstack()->refresh_view(_view, 0, cr + _view->p1()); if (s) _vstack()->refresh_view(_view, 0, nr + _view->p1()); } } } if (e.payload.type == L4RE_EV_KEY && e.payload.code == L4RE_BTN_LEFT && e.payload.value == 0) { Rect nr; int ny; return find(mouse, &nr, &ny); } return 0; } template< typename C > void Menu::notify() { if (!_speed) static_cast(this)->l_remove(); int old = _offset; scroll(_speed); if (old != _offset) _vstack()->refresh_view(_view, 0, *_view); else static_cast(this)->l_remove(); } }