From 905da4b0ccffea18532cd18b1da446b1d7ac5ef3 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Fri, 8 Mar 2019 22:51:44 +0100 Subject: [PATCH] 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 --- repos/gems/src/app/menu_view/button_widget.h | 106 ++++++++++++------ .../gems/src/app/menu_view/scratch_surface.h | 64 +++++++++-- 2 files changed, 126 insertions(+), 44 deletions(-) diff --git a/repos/gems/src/app/menu_view/button_widget.h b/repos/gems/src/app/menu_view/button_widget.h index 38cb19970e..f906a48f16 100644 --- a/repos/gems/src/app/menu_view/button_widget.h +++ b/repos/gems/src/app/menu_view/button_widget.h @@ -27,20 +27,20 @@ namespace Menu_view { struct Button_widget; } struct Menu_view::Button_widget : Widget, Animator::Item { - bool hovered = false; - bool selected = false; + bool _hovered = false; + bool _selected = false; - Texture const * default_texture = nullptr; - Texture const * hovered_texture = nullptr; + Texture const * _prev_texture = nullptr; + Texture const * _curr_texture = nullptr; - Lazy_value blend { }; + Lazy_value _blend { }; - Padding padding { 9, 9, 2, 1 }; + Padding _padding { 9, 9, 2, 1 }; Area _space() const { - return Area(margin.horizontal() + padding.horizontal(), - margin.vertical() + padding.vertical()); + return Area(margin.horizontal() + _padding.horizontal(), + margin.vertical() + _padding.vertical()); } 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_selected = _enabled(node, "selected"); - if (new_selected) { - default_texture = _factory.styles.texture(node, "selected"); - hovered_texture = _factory.styles.texture(node, "hselected"); - } else { - default_texture = _factory.styles.texture(node, "default"); - hovered_texture = _factory.styles.texture(node, "hovered"); - } + char const * const next_texture_name = + new_selected ? (new_hovered ? "hselected" : "selected") + : (new_hovered ? "hovered" : "default"); - if (new_hovered != hovered) { + Texture const * next_texture = + _factory.styles.texture(node, next_texture_name); - if (new_hovered) { - blend.dst(255 << 8, 3); - } else { - blend.dst(0, 20); + 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); } - animated(blend != blend.dst()); } - hovered = new_hovered; - selected = new_selected; + _hovered = new_hovered; + _selected = new_selected; _update_children(node); _children.for_each([&] (Widget &child) { - child.geometry(Rect(Point(margin.left + padding.left, - margin.top + padding.top), + child.geometry(Rect(Point(margin.left + _padding.left, + margin.top + _padding.top), child.min_size())); }); } @@ -97,7 +116,7 @@ struct Menu_view::Button_widget : Widget, Animator::Item child_min_size = child.min_size(); }); /* 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()), max(_space().h() + child_min_size.h(), texture_size.h())); @@ -107,9 +126,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item Surface &alpha_surface, Point at) const override { - static Scratch_surface 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); /* @@ -117,12 +136,27 @@ struct Menu_view::Button_widget : Widget, Animator::Item */ scratch.reset(texture_size); - scratch.apply([&] (Surface &pixel, Surface &alpha) { - Icon_painter::paint(pixel, texture_rect, *default_texture, 255); - Icon_painter::paint(alpha, texture_rect, *default_texture, 255); + scratch.apply([&] (Surface &pixel, Surface &alpha) { - Icon_painter::paint(pixel, texture_rect, *hovered_texture, blend >> 8); - Icon_painter::paint(alpha, texture_rect, *hovered_texture, blend >> 8); + if (_prev_texture && animated()) { + + 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()), scratch.texture(), 255); - if (selected) + if (_selected) at = at + Point(0, 1); _draw_children(pixel_surface, alpha_surface, at); @@ -154,9 +188,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item void animate() override { - blend.animate(); + _blend.animate(); - animated(blend != blend.dst()); + animated(_blend != _blend.dst()); } private: diff --git a/repos/gems/src/app/menu_view/scratch_surface.h b/repos/gems/src/app/menu_view/scratch_surface.h index f61c6ecac8..9e1a436e5d 100644 --- a/repos/gems/src/app/menu_view/scratch_surface.h +++ b/repos/gems/src/app/menu_view/scratch_surface.h @@ -17,10 +17,57 @@ /* local includes */ #include -namespace Menu_view { template 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 + 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 + 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 class Menu_view::Scratch_surface { private: @@ -39,7 +86,8 @@ class Menu_view::Scratch_surface size_t _needed_bytes(Area size) { /* 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() @@ -54,7 +102,7 @@ class Menu_view::Scratch_surface unsigned char *_alpha_base() const { - return _base + _size.count()*sizeof(PT); + return _base + _size.count()*sizeof(Opaque_pixel); } public: @@ -82,14 +130,14 @@ class Menu_view::Scratch_surface template void apply(FN const &fn) { - Surface pixel((PT *)_pixel_base(), _size); - Surface alpha((Pixel_alpha8 *)_alpha_base(), _size); + Surface pixel((Opaque_pixel *)_pixel_base(), _size); + Surface alpha((Additive_alpha *)_alpha_base(), _size); fn(pixel, alpha); } - Texture texture() const + Texture texture() const { - return Texture((PT *)_pixel_base(), _alpha_base(), _size); + return Texture((Pixel_rgb888 *)_pixel_base(), _alpha_base(), _size); } };