/* * \brief Widget that handles hovered/selected state and hosts a child widget * \author Norman Feske * \date 2009-09-11 */ /* * Copyright (C) 2014-2017 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 _BUTTON_WIDGET_H_ #define _BUTTON_WIDGET_H_ /* demo includes */ #include #include /* local includes */ #include "widget.h" #include "scratch_surface.h" namespace Menu_view { struct Button_widget; } struct Menu_view::Button_widget : Widget, Animator::Item { bool _hovered = false; bool _selected = false; Texture const * _prev_texture = nullptr; Texture const * _curr_texture = nullptr; Lazy_value _blend { }; Padding _padding { 9, 9, 2, 1 }; Area _space() const { return Area(margin.horizontal() + _padding.horizontal(), margin.vertical() + _padding.vertical()); } static bool _enabled(Xml_node node, char const *attr) { return node.attribute_value(attr, false); } Button_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) : Widget(factory, node, unique_id), Animator::Item(factory.animator) { margin = { 4, 4, 4, 4 }; } void update(Xml_node node) override { bool const new_hovered = _enabled(node, "hovered"); bool const new_selected = _enabled(node, "selected"); char const * const next_texture_name = new_selected ? (new_hovered ? "hselected" : "selected") : (new_hovered ? "hovered" : "default"); Texture const * next_texture = _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); } } _hovered = new_hovered; _selected = new_selected; _update_children(node); } Area min_size() const override { /* determine minimum child size */ Area child_min_size(300, 10); _children.for_each([&] (Widget const &child) { child_min_size = child.min_size(); }); if (!_curr_texture) return child_min_size; /* don't get smaller than the background texture */ 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())); } void draw(Surface &pixel_surface, Surface &alpha_surface, Point at) const override { static Scratch_surface scratch(_factory.alloc); Area const texture_size = _curr_texture ? _curr_texture->size() : Area(0, 0); Rect const texture_rect(Point(0, 0), texture_size); /* * Mix from_texture and to_texture according to the blend value */ scratch.reset(texture_size); scratch.apply([&] (Surface &pixel, Surface &alpha) { if (!_curr_texture) return; 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); } }); /* * Apply blended texture to target surface */ Icon_painter::paint(pixel_surface, Rect(at, _animated_geometry.area()), scratch.texture(), 255); Icon_painter::paint(alpha_surface, Rect(at, _animated_geometry.area()), scratch.texture(), 255); if (_selected) at = at + Point(0, 1); _draw_children(pixel_surface, alpha_surface, at); } void _layout() override { _children.for_each([&] (Widget &child) { child.position(Point(margin.left + _padding.left, margin.top + _padding.top)); Area const avail = geometry().area(); unsigned const w = avail.w() >= _space().w() ? avail.w() - _space().w() : 0, h = avail.h() >= _space().h() ? avail.h() - _space().w() : 0; child.size(Area(max(w, child.min_size().w()), max(h, child.min_size().h()))); }); } /****************************** ** Animator::Item interface ** ******************************/ void animate() override { _blend.animate(); animated(_blend != _blend.dst()); } private: /** * Noncopyable */ Button_widget(Button_widget const &); Button_widget &operator = (Button_widget const &); }; #endif /* _BUTTON_WIDGET_H_ */