diff --git a/repos/gems/src/app/menu_view/cursor.h b/repos/gems/src/app/menu_view/cursor.h new file mode 100644 index 000000000..e856ac1ab --- /dev/null +++ b/repos/gems/src/app/menu_view/cursor.h @@ -0,0 +1,146 @@ +/* + * \brief Text cursor + * \author Norman Feske + * \date 2020-01-14 + */ + +/* + * Copyright (C) 2020 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _CURSOR_H_ +#define _CURSOR_H_ + +/* Genode includes */ +#include +#include +#include + +/* local includes */ +#include +#include + +namespace Menu_view { struct Cursor; } + +class Menu_view::Cursor : List_model::Element +{ + public: + + /** + * Interface for requesting the pixel position for a given char index + */ + struct Glyph_position : Interface, Noncopyable + { + virtual int xpos_of_glyph(unsigned at) const = 0; + }; + + private: + + friend class List_model; + friend class List; + + using Steps = Animated_rect::Steps; + + Texture const * const _texture; + + Glyph_position &_glyph_position; + + enum { NAME_MAX_LEN = 32 }; + typedef String Name; + + Name const _name; + + /* cursor position in pixels, only p1.x is used */ + Animated_rect _position; + + int _xpos() const { return _position.p1().x(); } + + static Name _node_name(Xml_node node) + { + return node.attribute_value("name", Name(node.type())); + } + + int _position_from_xml_node(Xml_node node) + { + return _glyph_position.xpos_of_glyph(node.attribute_value("at", 0U)); + } + + void _move_to(int position, Steps steps) + { + _position.move_to(Rect(Point(position, 0), Point()), steps); + } + + /* + * Noncopyable + */ + Cursor(Cursor const &); + void operator = (Cursor const &); + + public: + + Cursor(Xml_node node, Animator &animator, Glyph_position &glyph_position, + Style_database &styles) + : + _texture(styles.texture(node, "cursor")), + _glyph_position(glyph_position), + _name(_node_name(node)), + _position(animator) + { + _move_to(_position_from_xml_node(node), Steps{0}); + } + + + void draw(Surface &pixel_surface, + Surface &alpha_surface, + Point at, unsigned height) const + { + if (_texture == nullptr) { + Box_painter::paint(pixel_surface, + Rect(at + Point(_xpos(), 0), Area(1, height)), + Color(0, 0, 0, 255)); + } else { + unsigned const w = _texture->size().w(); + Rect const rect(Point(_xpos() + at.x() - w/2 + 1, at.y()), + Area(w, height)); + + Icon_painter::paint(pixel_surface, rect, *_texture, 255); + Icon_painter::paint(alpha_surface, rect, *_texture, 255); + } + } + + struct Model_update_policy : List_model::Update_policy + { + Widget_factory &_factory; + Glyph_position &_glyph_position; + + Model_update_policy(Widget_factory &factory, Glyph_position &glyph_position) + : + _factory(factory), _glyph_position(glyph_position) + { } + + void destroy_element(Cursor &c) { destroy(_factory.alloc, &c); } + + Cursor &create_element(Xml_node node) + { + return *new (_factory.alloc) + Cursor(node, _factory.animator, _glyph_position, _factory.styles); + } + + void update_element(Cursor &c, Xml_node node) + { + c._move_to(c._position_from_xml_node(node), Steps{12}); + } + + static bool element_matches_xml_node(Cursor const &c, Xml_node node) + { + return node.has_type("cursor") && _node_name(node) == c._name; + } + + static bool node_is_element(Xml_node node) { return node.has_type("cursor"); } + }; +}; + +#endif /* _CURSOR_H_ */ diff --git a/repos/gems/src/app/menu_view/label_widget.h b/repos/gems/src/app/menu_view/label_widget.h index 422b52167..7041e4d3f 100644 --- a/repos/gems/src/app/menu_view/label_widget.h +++ b/repos/gems/src/app/menu_view/label_widget.h @@ -16,43 +16,55 @@ /* local includes */ #include +#include namespace Menu_view { struct Label_widget; } -struct Menu_view::Label_widget : Widget +struct Menu_view::Label_widget : Widget, Cursor::Glyph_position { - Text_painter::Font const *font = nullptr; + Text_painter::Font const *_font = nullptr; enum { LABEL_MAX_LEN = 256 }; typedef String<200> Text; - Text text { }; + Text _text { }; + + Cursor::Model_update_policy _cursor_update_policy; + + List_model _cursors { }; Label_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) : - Widget(factory, node, unique_id) + Widget(factory, node, unique_id), _cursor_update_policy(factory, *this) { } + ~Label_widget() + { + _cursors.destroy_all_elements(_cursor_update_policy); + } + void update(Xml_node node) override { - font = _factory.styles.font(node); - text = node.attribute_value("text", Text("")); + _font = _factory.styles.font(node); + _text = node.attribute_value("text", Text("")); + + _cursors.update_from_xml(_cursor_update_policy, node); } Area min_size() const override { - if (!font) + if (!_font) return Area(0, 0); - return Area(font->string_width(text.string()).decimal(), - font->height()); + return Area(_font->string_width(_text.string()).decimal(), + _font->height()); } void draw(Surface &pixel_surface, Surface &alpha_surface, Point at) const override { - if (!font) return; + if (!_font) return; Area text_size = min_size(); @@ -63,13 +75,26 @@ struct Menu_view::Label_widget : Widget Text_painter::paint(pixel_surface, Text_painter::Position(centered.x(), centered.y()), - *font, Color(0, 0, 0), text.string()); + *_font, Color(0, 0, 0), _text.string()); Text_painter::paint(alpha_surface, Text_painter::Position(centered.x(), centered.y()), - *font, Color(255, 255, 255), text.string()); + *_font, Color(255, 255, 255), _text.string()); + + _cursors.for_each([&] (Cursor const &cursor) { + cursor.draw(pixel_surface, alpha_surface, at, text_size.h()); }); } + /** + * Cursor::Glyph_position interface + */ + int xpos_of_glyph(unsigned at) const override + { + Text const truncated_at(Cstring(_text.string(), at)); + + return _font->string_width(truncated_at.string()).decimal(); + } + private: /** diff --git a/repos/gems/src/app/menu_view/styles/cursor/default/cursor.png b/repos/gems/src/app/menu_view/styles/cursor/default/cursor.png new file mode 100644 index 000000000..c27315605 Binary files /dev/null and b/repos/gems/src/app/menu_view/styles/cursor/default/cursor.png differ diff --git a/repos/gems/src/app/menu_view/styles/cursor/hover/cursor.png b/repos/gems/src/app/menu_view/styles/cursor/hover/cursor.png new file mode 100644 index 000000000..05ff7e867 Binary files /dev/null and b/repos/gems/src/app/menu_view/styles/cursor/hover/cursor.png differ