/*
 * \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 <scout_gfx/icon_painter.h>
#include <util/lazy_value.h>

/* 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<Pixel_rgb888> const * _prev_texture = nullptr;
	Texture<Pixel_rgb888> const * _curr_texture = nullptr;

	Lazy_value<int> _blend { };

	Padding _padding { 9, 9, 2, 1 };

	Area _space() const
	{
		return Area(margin.horizontal() + _padding.horizontal(),
		            margin.vertical()   + _padding.vertical());
	}

	static bool _enabled(Node const &node, char const *attr)
	{
		return node.attribute_value(attr, false);
	}

	Button_widget(Widget_factory &factory, Widget::Attr const &attr)
	:
		Widget(factory, attr), Animator::Item(factory.animator)
	{
		margin = { 4, 4, 4, 4 };
	}

	void update(Node const &node) override
	{
		bool const new_hovered  = _enabled(node, "hovered");
		bool const new_selected = _enabled(node, "selected");

		_stretch = node.attribute_value("stretch", true);
		_padding = node.attribute_value("pad", true) ? Padding { 9, 9, 2, 1 }
		                                             : Padding { 0, 0, 0, 0 };

		char const * const next_texture_name =
			new_selected ? (new_hovered ? "hselected" : "selected")
			             : (new_hovered ? "hovered"   : "default");

		Texture<Pixel_rgb888> 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 = 40, MEDIUM = 20, FAST = 2 };
				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_rgb888> &pixel_surface,
	          Surface<Pixel_alpha8> &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<Opaque_pixel> &pixel, Surface<Additive_alpha> &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_ */
