menu_view: basic support for styling labels
This patch allows for the customization of the text color and alpha value of the label widget by the means of a style-definition file. The mechanism is exemplified with the new "invisible" label style that sets the alpha value to zero. Issue #3629
This commit is contained in:
committed by
Christian Helmuth
parent
38aef49428
commit
1713583a19
117
repos/gems/src/app/menu_view/animated_color.h
Normal file
117
repos/gems/src/app/menu_view/animated_color.h
Normal file
@@ -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 <util/lazy_value.h>
|
||||||
|
#include <util/color.h>
|
||||||
|
#include <gems/animator.h>
|
||||||
|
|
||||||
|
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<int>;
|
||||||
|
|
||||||
|
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_ */
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
/* local includes */
|
/* local includes */
|
||||||
#include <widget_factory.h>
|
#include <widget_factory.h>
|
||||||
#include <text_selection.h>
|
#include <text_selection.h>
|
||||||
|
#include <animated_color.h>
|
||||||
|
|
||||||
namespace Menu_view { struct Label_widget; }
|
namespace Menu_view { struct Label_widget; }
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position
|
|||||||
typedef String<200> Text;
|
typedef String<200> Text;
|
||||||
Text _text { };
|
Text _text { };
|
||||||
|
|
||||||
|
Animated_color _color;
|
||||||
|
|
||||||
int _min_width = 0;
|
int _min_width = 0;
|
||||||
int _min_height = 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)
|
Label_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
|
||||||
:
|
:
|
||||||
Widget(factory, node, unique_id),
|
Widget(factory, node, unique_id),
|
||||||
|
_color(factory.animator),
|
||||||
_cursor_update_policy(factory, *this),
|
_cursor_update_policy(factory, *this),
|
||||||
_selection_update_policy(factory.alloc, *this)
|
_selection_update_policy(factory.alloc, *this)
|
||||||
{ }
|
{ }
|
||||||
@@ -58,6 +62,9 @@ struct Menu_view::Label_widget : Widget, Cursor::Glyph_position
|
|||||||
_min_width = 0;
|
_min_width = 0;
|
||||||
_min_height = 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")) {
|
if (node.has_attribute("text")) {
|
||||||
_text = node.attribute_value("text", _text);
|
_text = node.attribute_value("text", _text);
|
||||||
_text = Xml_unquoted(_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) {
|
_selections.for_each([&] (Text_selection const &selection) {
|
||||||
selection.draw(pixel_surface, alpha_surface, at, text_size.h()); });
|
selection.draw(pixel_surface, alpha_surface, at, text_size.h()); });
|
||||||
|
|
||||||
Text_painter::paint(pixel_surface,
|
Color const color = _color.color();
|
||||||
Text_painter::Position(centered.x(), centered.y()),
|
int const alpha = color.a;
|
||||||
*_font, Color(0, 0, 0), _text.string());
|
|
||||||
|
|
||||||
Text_painter::paint(alpha_surface,
|
if (alpha) {
|
||||||
Text_painter::Position(centered.x(), centered.y()),
|
Text_painter::paint(pixel_surface,
|
||||||
*_font, Color(255, 255, 255), _text.string());
|
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) {
|
_cursors.for_each([&] (Cursor const &cursor) {
|
||||||
cursor.draw(pixel_surface, alpha_surface, at, text_size.h()); });
|
cursor.draw(pixel_surface, alpha_surface, at, text_size.h()); });
|
||||||
|
|||||||
@@ -111,10 +111,11 @@ struct Menu_view::Main
|
|||||||
|
|
||||||
} _vfs_env;
|
} _vfs_env;
|
||||||
|
|
||||||
Directory _root_dir { _vfs_env };
|
Directory _root_dir { _vfs_env };
|
||||||
Directory _fonts_dir { _root_dir, "fonts" };
|
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 { };
|
Animator _animator { };
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,17 @@
|
|||||||
/* local includes */
|
/* local includes */
|
||||||
#include "types.h"
|
#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
|
class Menu_view::Style_database
|
||||||
@@ -36,6 +46,35 @@ class Menu_view::Style_database
|
|||||||
|
|
||||||
typedef ::File::Reading_failed Reading_failed;
|
typedef ::File::Reading_failed Reading_failed;
|
||||||
|
|
||||||
|
struct Label_style_entry : List<Label_style_entry>::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<Texture_entry>::Element
|
struct Texture_entry : List<Texture_entry>::Element
|
||||||
{
|
{
|
||||||
Path const path;
|
Path const path;
|
||||||
@@ -86,13 +125,15 @@ class Menu_view::Style_database
|
|||||||
Region_map &_rm;
|
Region_map &_rm;
|
||||||
Allocator &_alloc;
|
Allocator &_alloc;
|
||||||
Directory const &_fonts_dir;
|
Directory const &_fonts_dir;
|
||||||
|
Directory const &_styles_dir;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The list is mutable because it is populated as a side effect of
|
* The lists are mutable because they are populated as a side effect of
|
||||||
* calling the const lookup function.
|
* calling the const lookup functions.
|
||||||
*/
|
*/
|
||||||
List<Texture_entry> mutable _textures { };
|
List<Texture_entry> mutable _textures { };
|
||||||
List<Font_entry> mutable _fonts { };
|
List<Font_entry> mutable _fonts { };
|
||||||
|
List<Label_style_entry> mutable _label_styles { };
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T const *_lookup(List<T> &list, char const *path) const
|
T const *_lookup(List<T> &list, char const *path) const
|
||||||
@@ -105,28 +146,56 @@ class Menu_view::Style_database
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assemble path name 'styles/<widget>/<style>/<name>.<extension>'
|
* Assemble path name 'styles/<widget>/<style>/<name>.png'
|
||||||
*/
|
*/
|
||||||
static Path _construct_path(Xml_node node, char const *name,
|
static Path _construct_png_path(Xml_node node, char const *name)
|
||||||
char const *extension)
|
|
||||||
{
|
{
|
||||||
typedef String<64> Style;
|
typedef String<64> Style;
|
||||||
Style const style = node.attribute_value("style", Style("default"));
|
Style const style = node.attribute_value("style", Style("default"));
|
||||||
|
|
||||||
return Path("/styles/", node.type(), "/", style, "/", name, ".", extension);
|
return Path("/styles/", node.type(), "/", style, "/", name, ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assemble path of style file relative to the styles directory
|
||||||
|
*/
|
||||||
|
static Path _widget_style_path(Xml_node const &node)
|
||||||
|
{
|
||||||
|
typedef String<64> Style;
|
||||||
|
Style const style = node.attribute_value("style", Style("default"));
|
||||||
|
|
||||||
|
return Path(node.type(), "/", style, "/", "style");
|
||||||
|
}
|
||||||
|
|
||||||
|
Label_style const &_label_style(Xml_node node) const
|
||||||
|
{
|
||||||
|
Path const path = _widget_style_path(node);
|
||||||
|
|
||||||
|
if (Label_style_entry const *e = _lookup(_label_styles, path.string()))
|
||||||
|
return e->style;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load and remember style
|
||||||
|
*/
|
||||||
|
Label_style_entry &e = *new (_alloc)
|
||||||
|
Label_style_entry(_alloc, _styles_dir, path);
|
||||||
|
|
||||||
|
_label_styles.insert(&e);
|
||||||
|
return e.style;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Style_database(Ram_allocator &ram, Region_map &rm, Allocator &alloc,
|
Style_database(Ram_allocator &ram, Region_map &rm, Allocator &alloc,
|
||||||
Directory const &fonts_dir)
|
Directory const &fonts_dir, Directory const &styles_dir)
|
||||||
:
|
:
|
||||||
_ram(ram), _rm(rm), _alloc(alloc), _fonts_dir(fonts_dir)
|
_ram(ram), _rm(rm), _alloc(alloc),
|
||||||
|
_fonts_dir(fonts_dir), _styles_dir(styles_dir)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
Texture<Pixel_rgb888> const *texture(Xml_node node, char const *png_name) const
|
Texture<Pixel_rgb888> const *texture(Xml_node node, char const *png_name) const
|
||||||
{
|
{
|
||||||
Path const path = _construct_path(node, png_name, "png");
|
Path const path = _construct_png_path(node, png_name);
|
||||||
|
|
||||||
if (Texture_entry const *e = _lookup(_textures, path.string()))
|
if (Texture_entry const *e = _lookup(_textures, path.string()))
|
||||||
return &e->texture;
|
return &e->texture;
|
||||||
@@ -173,6 +242,12 @@ class Menu_view::Style_database
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void with_label_style(Xml_node node, FN const &fn) const
|
||||||
|
{
|
||||||
|
fn(_label_style(node));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _STYLE_DATABASE_H_ */
|
#endif /* _STYLE_DATABASE_H_ */
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
<style color="#00000000"/>
|
||||||
Reference in New Issue
Block a user