diff --git a/repos/gems/src/app/menu_view/animated_color.h b/repos/gems/src/app/menu_view/animated_color.h new file mode 100644 index 000000000..0531eb9d6 --- /dev/null +++ b/repos/gems/src/app/menu_view/animated_color.h @@ -0,0 +1,117 @@ +/* + * \brief Helper for implementing the fading of colors + * \author Norman Feske + * \date 2020-02-19 + */ + +/* + * 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 _ANIMATED_COLOR_H_ +#define _ANIMATED_COLOR_H_ + +/* demo includes */ +#include +#include +#include + +namespace Genode { class Animated_color; } + + +class Genode::Animated_color : private Animator::Item, Noncopyable +{ + public: + + struct Steps { unsigned value; }; + + private: + + Color _color { }; + + using Lazy_value = ::Lazy_value; + + struct Animated_channel + { + bool _initial = true; + + Lazy_value _value { }; + + Steps _remaining { 0 }; + + void animate() + { + _value.animate(); + + if (_remaining.value > 1) + _remaining.value--; + } + + bool animated() const { return _value != _value.dst(); } + + void fade_to(int value, Steps steps) + { + if (_initial) { + _value = Lazy_value(value << 10); + _initial = false; + + } else { + + /* retarget animation while already in progress */ + if (animated()) + steps.value = max(_remaining.value, 1U); + + _value.dst(value << 10, steps.value); + _remaining = steps; + } + } + + int value() const { return _value >> 10; } + }; + + Animated_channel _r { }, _g { }, _b { }, _a { }; + + public: + + Animated_color(Animator &animator) : Animator::Item(animator) { } + + /** + * Animator::Item interface + */ + void animate() override + { + _r.animate(); _g.animate(); _b.animate(); _a.animate(); + + _color = Color(_r.value(), _g.value(), _b.value(), _a.value()); + + /* schedule / de-schedule animation */ + Animator::Item::animated(_r.animated() || _g.animated() || + _b.animated() || _a.animated()); + } + + /** + * Assign new target color + * + * The first assignment assigns the color directly without animation. + * All subsequent assignments result in an animated transition to the + * target color. + */ + void fade_to(Color color, Steps steps) + { + _r.fade_to(color.r, steps); + _g.fade_to(color.g, steps); + _b.fade_to(color.b, steps); + _a.fade_to(color.a, steps); + + animate(); + } + + bool animated() const { return Animator::Item::animated(); } + + Color color() const { return _color; } +}; + +#endif /* _ANIMATED_COLOR_H_ */ diff --git a/repos/gems/src/app/menu_view/label_widget.h b/repos/gems/src/app/menu_view/label_widget.h index 73450c77f..8f130a8e1 100644 --- a/repos/gems/src/app/menu_view/label_widget.h +++ b/repos/gems/src/app/menu_view/label_widget.h @@ -17,6 +17,7 @@ /* local includes */ #include #include +#include namespace Menu_view { struct Label_widget; } @@ -29,6 +30,8 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position typedef String<200> Text; Text _text { }; + Animated_color _color; + int _min_width = 0; int _min_height = 0; @@ -41,6 +44,7 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position Label_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) : Widget(factory, node, unique_id), + _color(factory.animator), _cursor_update_policy(factory, *this), _selection_update_policy(factory.alloc, *this) { } @@ -58,6 +62,9 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position _min_width = 0; _min_height = 0; + _factory.styles.with_label_style(node, [&] (Label_style style) { + _color.fade_to(style.color, Animated_color::Steps{80}); }); + if (node.has_attribute("text")) { _text = node.attribute_value("text", _text); _text = Xml_unquoted(_text); @@ -100,13 +107,18 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position _selections.for_each([&] (Text_selection const &selection) { selection.draw(pixel_surface, alpha_surface, at, text_size.h()); }); - Text_painter::paint(pixel_surface, - Text_painter::Position(centered.x(), centered.y()), - *_font, Color(0, 0, 0), _text.string()); + Color const color = _color.color(); + int const alpha = color.a; - Text_painter::paint(alpha_surface, - Text_painter::Position(centered.x(), centered.y()), - *_font, Color(255, 255, 255), _text.string()); + if (alpha) { + Text_painter::paint(pixel_surface, + Text_painter::Position(centered.x(), centered.y()), + *_font, color, _text.string()); + + Text_painter::paint(alpha_surface, + Text_painter::Position(centered.x(), centered.y()), + *_font, Color(alpha, alpha, alpha, alpha), _text.string()); + } _cursors.for_each([&] (Cursor const &cursor) { cursor.draw(pixel_surface, alpha_surface, at, text_size.h()); }); diff --git a/repos/gems/src/app/menu_view/main.cc b/repos/gems/src/app/menu_view/main.cc index 7ea820551..f5563f280 100644 --- a/repos/gems/src/app/menu_view/main.cc +++ b/repos/gems/src/app/menu_view/main.cc @@ -111,10 +111,11 @@ struct Menu_view::Main } _vfs_env; - Directory _root_dir { _vfs_env }; - Directory _fonts_dir { _root_dir, "fonts" }; + Directory _root_dir { _vfs_env }; + Directory _fonts_dir { _root_dir, "fonts" }; + Directory _styles_dir { _root_dir, "styles" }; - Style_database _styles { _env.ram(), _env.rm(), _heap, _fonts_dir }; + Style_database _styles { _env.ram(), _env.rm(), _heap, _fonts_dir, _styles_dir }; Animator _animator { }; diff --git a/repos/gems/src/app/menu_view/style_database.h b/repos/gems/src/app/menu_view/style_database.h index 1476d6f84..3cbf2e4b8 100644 --- a/repos/gems/src/app/menu_view/style_database.h +++ b/repos/gems/src/app/menu_view/style_database.h @@ -23,7 +23,17 @@ /* local includes */ #include "types.h" -namespace Menu_view { struct Style_database; } +namespace Menu_view { + + struct Label_style; + struct Style_database; +} + + +struct Menu_view::Label_style +{ + Color color; +}; class Menu_view::Style_database @@ -36,6 +46,35 @@ class Menu_view::Style_database typedef ::File::Reading_failed Reading_failed; + struct Label_style_entry : List::Element, Noncopyable + { + Path const path; /* needed for lookup */ + Label_style const style; + + static Label_style _init_style(Allocator &alloc, + Directory const &styles_dir, + Path const &path) + { + Label_style result { .color = Color(0, 0, 0) }; + + try { + File_content const content(alloc, styles_dir, path, + File_content::Limit{1024}); + content.xml([&] (Xml_node node) { + result.color = node.attribute_value("color", result.color); + }); + } catch (...) { } + + return result; + } + + Label_style_entry(Allocator &alloc, Directory const &styles_dir, + Path const &path) + : + path(path), style(_init_style(alloc, styles_dir, path)) + { } + }; + struct Texture_entry : List::Element { Path const path; @@ -86,13 +125,15 @@ class Menu_view::Style_database Region_map &_rm; Allocator &_alloc; Directory const &_fonts_dir; + Directory const &_styles_dir; /* - * The list is mutable because it is populated as a side effect of - * calling the const lookup function. + * The lists are mutable because they are populated as a side effect of + * calling the const lookup functions. */ - List mutable _textures { }; - List mutable _fonts { }; + List mutable _textures { }; + List mutable _fonts { }; + List mutable _label_styles { }; template T const *_lookup(List &list, char const *path) const @@ -105,28 +146,56 @@ class Menu_view::Style_database } /* - * Assemble path name 'styles//