menu_view: fade between button styles

The button widget already supported an animated transition between
hovered and unhovered states. This patch generalizes the mechanism to
allow animated transitions between arbitrary button states, including
style changes.

This way, the fade-out of non-TCB components in Sculpt CE happens not
abruptly but smooth.

Fixes #3221
This commit is contained in:
Norman Feske 2019-03-08 22:51:44 +01:00 committed by Christian Helmuth
parent 116bfab449
commit 905da4b0cc
2 changed files with 126 additions and 44 deletions

View File

@ -27,20 +27,20 @@ namespace Menu_view { struct Button_widget; }
struct Menu_view::Button_widget : Widget, Animator::Item struct Menu_view::Button_widget : Widget, Animator::Item
{ {
bool hovered = false; bool _hovered = false;
bool selected = false; bool _selected = false;
Texture<Pixel_rgb888> const * default_texture = nullptr; Texture<Pixel_rgb888> const * _prev_texture = nullptr;
Texture<Pixel_rgb888> const * hovered_texture = nullptr; Texture<Pixel_rgb888> const * _curr_texture = nullptr;
Lazy_value<int> blend { }; Lazy_value<int> _blend { };
Padding padding { 9, 9, 2, 1 }; Padding _padding { 9, 9, 2, 1 };
Area _space() const Area _space() const
{ {
return Area(margin.horizontal() + padding.horizontal(), return Area(margin.horizontal() + _padding.horizontal(),
margin.vertical() + padding.vertical()); margin.vertical() + _padding.vertical());
} }
static bool _enabled(Xml_node node, char const *attr) static bool _enabled(Xml_node node, char const *attr)
@ -60,32 +60,51 @@ struct Menu_view::Button_widget : Widget, Animator::Item
bool const new_hovered = _enabled(node, "hovered"); bool const new_hovered = _enabled(node, "hovered");
bool const new_selected = _enabled(node, "selected"); bool const new_selected = _enabled(node, "selected");
if (new_selected) { char const * const next_texture_name =
default_texture = _factory.styles.texture(node, "selected"); new_selected ? (new_hovered ? "hselected" : "selected")
hovered_texture = _factory.styles.texture(node, "hselected"); : (new_hovered ? "hovered" : "default");
} else {
default_texture = _factory.styles.texture(node, "default"); Texture<Pixel_rgb888> const * next_texture =
hovered_texture = _factory.styles.texture(node, "hovered"); _factory.styles.texture(node, next_texture_name);
if (next_texture != _curr_texture) {
_prev_texture = _curr_texture;
_curr_texture = next_texture;
/* don't attempt to fade between different texture sizes */
bool const texture_size_changed = _prev_texture && _curr_texture
&& _prev_texture->size() != _curr_texture->size();
if (texture_size_changed)
_prev_texture = nullptr;
if (_prev_texture) {
/*
* The number of blending animation steps depends on the
* transition. By default, for style changes, a slow animation
* is used. Unhovering happens a bit quicker. But when hovering
* or changing the selection state of a button, the transition
* must be quick to provide a responsive feel.
*/
enum { SLOW = 80, MEDIUM = 40, FAST = 3 };
int steps = SLOW;
if (_hovered && !new_hovered) steps = MEDIUM;
if (!_hovered && new_hovered) steps = FAST;
if (_selected != new_selected) steps = FAST;
_blend.assign(255 << 8);
_blend.dst(0, steps);
animated(true);
}
} }
if (new_hovered != hovered) { _hovered = new_hovered;
_selected = new_selected;
if (new_hovered) {
blend.dst(255 << 8, 3);
} else {
blend.dst(0, 20);
}
animated(blend != blend.dst());
}
hovered = new_hovered;
selected = new_selected;
_update_children(node); _update_children(node);
_children.for_each([&] (Widget &child) { _children.for_each([&] (Widget &child) {
child.geometry(Rect(Point(margin.left + padding.left, child.geometry(Rect(Point(margin.left + _padding.left,
margin.top + padding.top), margin.top + _padding.top),
child.min_size())); }); child.min_size())); });
} }
@ -97,7 +116,7 @@ struct Menu_view::Button_widget : Widget, Animator::Item
child_min_size = child.min_size(); }); child_min_size = child.min_size(); });
/* don't get smaller than the background texture */ /* don't get smaller than the background texture */
Area const texture_size = default_texture->size(); Area const texture_size = _curr_texture->size();
return Area(max(_space().w() + child_min_size.w(), texture_size.w()), return Area(max(_space().w() + child_min_size.w(), texture_size.w()),
max(_space().h() + child_min_size.h(), texture_size.h())); max(_space().h() + child_min_size.h(), texture_size.h()));
@ -107,9 +126,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item
Surface<Pixel_alpha8> &alpha_surface, Surface<Pixel_alpha8> &alpha_surface,
Point at) const override Point at) const override
{ {
static Scratch_surface<Pixel_rgb888> scratch(_factory.alloc); static Scratch_surface scratch(_factory.alloc);
Area const texture_size = default_texture->size(); Area const texture_size = _curr_texture->size();
Rect const texture_rect(Point(0, 0), texture_size); Rect const texture_rect(Point(0, 0), texture_size);
/* /*
@ -117,12 +136,27 @@ struct Menu_view::Button_widget : Widget, Animator::Item
*/ */
scratch.reset(texture_size); scratch.reset(texture_size);
scratch.apply([&] (Surface<Pixel_rgb888> &pixel, Surface<Pixel_alpha8> &alpha) { scratch.apply([&] (Surface<Opaque_pixel> &pixel, Surface<Additive_alpha> &alpha) {
Icon_painter::paint(pixel, texture_rect, *default_texture, 255);
Icon_painter::paint(alpha, texture_rect, *default_texture, 255);
Icon_painter::paint(pixel, texture_rect, *hovered_texture, blend >> 8); if (_prev_texture && animated()) {
Icon_painter::paint(alpha, texture_rect, *hovered_texture, blend >> 8);
int const blend = _blend >> 8;
Icon_painter::paint(pixel, texture_rect, *_curr_texture, 255);
Icon_painter::paint(pixel, texture_rect, *_prev_texture, blend);
Icon_painter::paint(alpha, texture_rect, *_curr_texture, 255 - blend);
Icon_painter::paint(alpha, texture_rect, *_prev_texture, blend);
}
/*
* If no fading is possible or needed, paint only _curr_texture at
* full opacity.
*/
else {
Icon_painter::paint(pixel, texture_rect, *_curr_texture, 255);
Icon_painter::paint(alpha, texture_rect, *_curr_texture, 255);
}
}); });
/* /*
@ -134,7 +168,7 @@ struct Menu_view::Button_widget : Widget, Animator::Item
Icon_painter::paint(alpha_surface, Rect(at, _animated_geometry.area()), Icon_painter::paint(alpha_surface, Rect(at, _animated_geometry.area()),
scratch.texture(), 255); scratch.texture(), 255);
if (selected) if (_selected)
at = at + Point(0, 1); at = at + Point(0, 1);
_draw_children(pixel_surface, alpha_surface, at); _draw_children(pixel_surface, alpha_surface, at);
@ -154,9 +188,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item
void animate() override void animate() override
{ {
blend.animate(); _blend.animate();
animated(blend != blend.dst()); animated(_blend != _blend.dst());
} }
private: private:

View File

@ -17,10 +17,57 @@
/* local includes */ /* local includes */
#include <types.h> #include <types.h>
namespace Menu_view { template <typename PT> class Scratch_surface; } namespace Menu_view {
struct Additive_alpha;
struct Opaque_pixel;
class Scratch_surface;
}
/**
* Custom pixel type for applying painters to an alpha channel
*
* The 'transfer' function of this pixel type applies alpha channel values
* from textures to it's 'pixel' in an additive way. It is designated for
* blending alpha channels from different textures together.
*/
struct Menu_view::Additive_alpha
{
uint8_t pixel;
template <typename TPT, typename PT>
static void transfer(TPT const &, int src_a, int alpha, PT &dst)
{
dst.pixel += (alpha*src_a) >> 8;
}
static Surface_base::Pixel_format format() { return Surface_base::UNKNOWN; }
} __attribute__((packed));
/**
* Custom pixel type to apply painters w/o the texture's alpha channel
*
* This pixel type is useful for limiting the application of painters to color
* values only. It allows for the blending of a texture's color channels
* independent from the texture's alpha channel.
*/
struct Menu_view::Opaque_pixel : Pixel_rgb888
{
template <typename TPT, typename PT>
static void transfer(TPT const &src, int, int alpha, PT &dst)
{
if (alpha) dst.pixel = PT::mix(dst, src, alpha).pixel;
}
static Surface_base::Pixel_format format() { return Surface_base::UNKNOWN; }
} __attribute__((packed));
template <typename PT>
class Menu_view::Scratch_surface class Menu_view::Scratch_surface
{ {
private: private:
@ -39,7 +86,8 @@ class Menu_view::Scratch_surface
size_t _needed_bytes(Area size) size_t _needed_bytes(Area size)
{ {
/* account for pixel buffer and alpha channel */ /* account for pixel buffer and alpha channel */
return size.count()*sizeof(PT) + size.count(); return size.count()*sizeof(Opaque_pixel)
+ size.count()*sizeof(Additive_alpha);
} }
void _release() void _release()
@ -54,7 +102,7 @@ class Menu_view::Scratch_surface
unsigned char *_alpha_base() const unsigned char *_alpha_base() const
{ {
return _base + _size.count()*sizeof(PT); return _base + _size.count()*sizeof(Opaque_pixel);
} }
public: public:
@ -82,14 +130,14 @@ class Menu_view::Scratch_surface
template <typename FN> template <typename FN>
void apply(FN const &fn) void apply(FN const &fn)
{ {
Surface<PT> pixel((PT *)_pixel_base(), _size); Surface<Opaque_pixel> pixel((Opaque_pixel *)_pixel_base(), _size);
Surface<Pixel_alpha8> alpha((Pixel_alpha8 *)_alpha_base(), _size); Surface<Additive_alpha> alpha((Additive_alpha *)_alpha_base(), _size);
fn(pixel, alpha); fn(pixel, alpha);
} }
Texture<PT> texture() const Texture<Pixel_rgb888> texture() const
{ {
return Texture<PT>((PT *)_pixel_base(), _alpha_base(), _size); return Texture<Pixel_rgb888>((Pixel_rgb888 *)_pixel_base(), _alpha_base(), _size);
} }
}; };