mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-24 07:46:42 +00:00
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:
parent
116bfab449
commit
905da4b0cc
@ -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");
|
|
||||||
hovered_texture = _factory.styles.texture(node, "hovered");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_hovered != hovered) {
|
Texture<Pixel_rgb888> const * next_texture =
|
||||||
|
_factory.styles.texture(node, next_texture_name);
|
||||||
|
|
||||||
if (new_hovered) {
|
if (next_texture != _curr_texture) {
|
||||||
blend.dst(255 << 8, 3);
|
_prev_texture = _curr_texture;
|
||||||
} else {
|
_curr_texture = next_texture;
|
||||||
blend.dst(0, 20);
|
|
||||||
|
/* 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);
|
||||||
}
|
}
|
||||||
animated(blend != blend.dst());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hovered = new_hovered;
|
_hovered = new_hovered;
|
||||||
selected = new_selected;
|
_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:
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user