diff --git a/repos/gems/src/app/menu_view/animated_geometry.h b/repos/gems/src/app/menu_view/animated_geometry.h new file mode 100644 index 0000000000..471044dfe2 --- /dev/null +++ b/repos/gems/src/app/menu_view/animated_geometry.h @@ -0,0 +1,96 @@ +/* + * \brief Helper for implementing geometric transitions + * \author Norman Feske + * \date 2017-08-17 + */ + +/* + * Copyright (C) 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 _ANIMATED_GEOMETRY_H_ +#define _ANIMATED_GEOMETRY_H_ + +/* demo includes */ +#include +#include + +/* local includes */ +#include "types.h" + +namespace Menu_view { class Animated_rect; } + + +class Menu_view::Animated_rect : public Rect, Animator::Item, Noncopyable +{ + public: + + struct Steps { unsigned value; }; + + private: + + struct Animated_point + { + bool _initial = true; + + Lazy_value _x, _y; + + void animate() { _x.animate(); _y.animate(); } + + bool animated() const { return _x != _x.dst() || _y != _y.dst(); } + + void move_to(Point p, Steps steps) + { + if (_initial) { + _x = Lazy_value(p.x() << 10); + _y = Lazy_value(p.y() << 10); + _initial = false; + } else { + _x.dst(p.x() << 10, steps.value); + _y.dst(p.y() << 10, steps.value); + } + } + + int x() const { return _x >> 10; } + int y() const { return _y >> 10; } + }; + + Animated_point _p1, _p2; + + public: + + Animated_rect(Animator &animator) : Animator::Item(animator) { } + + /** + * Animator::Item interface + */ + void animate() override + { + _p1.animate(); _p2.animate(); + + static_cast(*this) = Rect(Point(_p1.x(), _p1.y()), + Point(_p2.x(), _p2.y())); + + /* schedule / de-schedule animation */ + animated(_p1.animated() || _p2.animated()); + } + + /** + * Assign new target coordinates + * + * The first assignment moves the rectangle directly to the target + * position without animation. All subsequent assignments result in an + * animated movement. + */ + void move_to(Rect rect, Steps steps) + { + _p1.move_to(rect.p1(), steps); + _p2.move_to(rect.p2(), steps); + animate(); + } +}; + +#endif /* _ANIMATED_GEOMETRY_H_ */ diff --git a/repos/gems/src/app/menu_view/box_layout_widget.h b/repos/gems/src/app/menu_view/box_layout_widget.h index c13ce2df24..aaf8655524 100644 --- a/repos/gems/src/app/menu_view/box_layout_widget.h +++ b/repos/gems/src/app/menu_view/box_layout_widget.h @@ -56,18 +56,18 @@ struct Menu_view::Box_layout_widget : Widget Area const child_min_size = w->min_size(); if (_direction == VERTICAL) { - w->geometry = Rect(position, Area(largest_size, child_min_size.h())); + w->geometry(Rect(position, Area(largest_size, child_min_size.h()))); unsigned const next_top_margin = w->next() ? w->next()->margin.top : 0; unsigned const dy = child_min_size.h() - min(w->margin.bottom, next_top_margin); position = position + Point(0, dy); } else { - w->geometry = Rect(position, Area(child_min_size.w(), largest_size)); + w->geometry(Rect(position, Area(child_min_size.w(), largest_size))); unsigned const next_left_margin = w->next() ? w->next()->margin.left : 0; unsigned const dx = child_min_size.w() - min(w->margin.right, next_left_margin); position = position + Point(dx, 0); } - _min_size = Area(w->geometry.x2() + 1, w->geometry.y2() + 1); + _min_size = Area(w->geometry().x2() + 1, w->geometry().y2() + 1); } } @@ -87,9 +87,9 @@ struct Menu_view::Box_layout_widget : Widget { for (Widget *w = _children.first(); w; w = w->next()) { if (_direction == VERTICAL) - w->size(Area(geometry.w(), w->min_size().h())); + w->size(Area(geometry().w(), w->min_size().h())); else - w->size(Area(w->min_size().w(), geometry.h())); + w->size(Area(w->min_size().w(), geometry().h())); } } }; diff --git a/repos/gems/src/app/menu_view/button_widget.h b/repos/gems/src/app/menu_view/button_widget.h index 6d1134926a..34a8f87299 100644 --- a/repos/gems/src/app/menu_view/button_widget.h +++ b/repos/gems/src/app/menu_view/button_widget.h @@ -86,9 +86,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item bool const dy = selected ? 1 : 0; if (Widget *child = _children.first()) - child->geometry = Rect(Point(margin.left + padding.left, - margin.top + padding.top + dy), - child->min_size()); + child->geometry(Rect(Point(margin.left + padding.left, + margin.top + padding.top + dy), + child->min_size())); } Area min_size() const override @@ -130,10 +130,10 @@ struct Menu_view::Button_widget : Widget, Animator::Item /* * Apply blended texture to target surface */ - Icon_painter::paint(pixel_surface, Rect(at, geometry.area()), + Icon_painter::paint(pixel_surface, Rect(at, _animated_geometry.area()), scratch.texture(), 255); - Icon_painter::paint(alpha_surface, Rect(at, geometry.area()), + Icon_painter::paint(alpha_surface, Rect(at, _animated_geometry.area()), scratch.texture(), 255); _draw_children(pixel_surface, alpha_surface, at); @@ -142,8 +142,8 @@ struct Menu_view::Button_widget : Widget, Animator::Item void _layout() override { for (Widget *w = _children.first(); w; w = w->next()) - w->size(Area(geometry.w() - _space().w(), - geometry.h() - _space().h())); + w->size(Area(geometry().w() - _space().w(), + geometry().h() - _space().h())); } diff --git a/repos/gems/src/app/menu_view/depgraph_widget.h b/repos/gems/src/app/menu_view/depgraph_widget.h index db878f09ac..f65f4be22e 100644 --- a/repos/gems/src/app/menu_view/depgraph_widget.h +++ b/repos/gems/src/app/menu_view/depgraph_widget.h @@ -289,18 +289,18 @@ struct Menu_view::Depgraph_widget : Widget int centered_breadth_pos(Depth_direction dir) const { - return dir.horizontal() ? (_widget.geometry.y1() + _widget.geometry.y2()) / 2 - : (_widget.geometry.x1() + _widget.geometry.x2()) / 2; + return dir.horizontal() ? (_widget.geometry().y1() + _widget.geometry().y2()) / 2 + : (_widget.geometry().x1() + _widget.geometry().x2()) / 2; } unsigned _edge_size(Depth_direction dir) const { if (dir.horizontal()) - return max(0, (int)_widget.geometry.h() - (int)_widget.margin.top - - (int)_widget.margin.bottom); + return max(0, (int)_widget.geometry().h() - (int)_widget.margin.top + - (int)_widget.margin.bottom); else - return max(0, (int)_widget.geometry.w() - (int)_widget.margin.left - - (int)_widget.margin.right); + return max(0, (int)_widget.geometry().w() - (int)_widget.margin.left + - (int)_widget.margin.right); } /** @@ -496,7 +496,7 @@ struct Menu_view::Depgraph_widget : Widget if (client && !server) { warning("node '", client_name, "' depends on " "non-existing node '", server_name, "'"); - client->_widget.geometry = Rect(Point(0, 0), Area(0, 0)); + client->_widget.geometry(Rect(Point(0, 0), Area(0, 0))); } }); @@ -548,9 +548,9 @@ struct Menu_view::Depgraph_widget : Widget : Rect(Point(breadth_pos, depth_pos), Area(breadth_size, depth_size)); - w->geometry = Rect(node_rect.center(w->min_size()), w->min_size()); + w->geometry(Rect(node_rect.center(w->min_size()), w->min_size())); - bounding_box = Rect::compound(bounding_box, w->geometry); + bounding_box = Rect::compound(bounding_box, w->geometry()); }); } @@ -562,15 +562,15 @@ struct Menu_view::Depgraph_widget : Widget for (Widget *w = _children.first(); w; w = w->next()) { - int x = w->geometry.x1(), y = w->geometry.y1(); + int x = w->geometry().x1(), y = w->geometry().y1(); if (_depth_direction.value == Depth_direction::NORTH) - y = (int)bounding_box.h() - y - w->geometry.h(); + y = (int)bounding_box.h() - y - w->geometry().h(); if (_depth_direction.value == Depth_direction::WEST) - x = (int)bounding_box.w() - x - w->geometry.w(); + x = (int)bounding_box.w() - x - w->geometry().w(); - w->geometry = Rect(Point(x, y), w->geometry.area()); + w->geometry(Rect(Point(x, y), w->geometry().area())); } } _min_size = bounding_box.area(); @@ -658,7 +658,7 @@ struct Menu_view::Depgraph_widget : Widget void _layout() override { for (Widget *w = _children.first(); w; w = w->next()) - w->size(w->geometry.area()); + w->size(w->geometry().area()); } }; diff --git a/repos/gems/src/app/menu_view/float_widget.h b/repos/gems/src/app/menu_view/float_widget.h index f99eb3f365..15c18986d7 100644 --- a/repos/gems/src/app/menu_view/float_widget.h +++ b/repos/gems/src/app/menu_view/float_widget.h @@ -32,18 +32,18 @@ struct Menu_view::Float_widget : Widget Area const child_min = child.min_size(); /* space around the minimal-sized child */ - int const w_space = geometry.w() - child_min.w(); - int const h_space = geometry.h() - child_min.h(); + int const w_space = geometry().w() - child_min.w(); + int const h_space = geometry().h() - child_min.h(); /* stretch child size opposite attributes are specified */ - int const w = (_east && _west) ? geometry.w() : child_min.w(); - int const h = (_north && _south) ? geometry.h() : child_min.h(); + int const w = (_east && _west) ? geometry().w() : child_min.w(); + int const h = (_north && _south) ? geometry().h() : child_min.h(); /* align / center child position */ int const x = _east ? 0 : _west ? w_space : w_space / 2; int const y = _north ? 0 : _south ? h_space : h_space / 2; - child.geometry = Rect(Point(x, y), Area(w, h)); + child.geometry(Rect(Point(x, y), Area(w, h))); } void update(Xml_node node) override @@ -77,7 +77,7 @@ struct Menu_view::Float_widget : Widget { if (Widget *child = _children.first()) { _place_child(*child); - child->size(child->geometry.area()); + child->size(child->geometry().area()); } } }; diff --git a/repos/gems/src/app/menu_view/frame_widget.h b/repos/gems/src/app/menu_view/frame_widget.h index db35332e41..2f4419fd6a 100644 --- a/repos/gems/src/app/menu_view/frame_widget.h +++ b/repos/gems/src/app/menu_view/frame_widget.h @@ -48,9 +48,9 @@ struct Menu_view::Frame_widget : Widget * layout */ if (Widget *child = _children.first()) - child->geometry = Rect(Point(margin.left + padding.left, - margin.top + padding.top), - child->min_size()); + child->geometry(Rect(Point(margin.left + padding.left, + margin.top + padding.top), + child->min_size())); } Area min_size() const override @@ -70,10 +70,10 @@ struct Menu_view::Frame_widget : Widget Surface &alpha_surface, Point at) const { - Icon_painter::paint(pixel_surface, Rect(at, geometry.area()), + Icon_painter::paint(pixel_surface, Rect(at, _animated_geometry.area()), *texture, 255); - Icon_painter::paint(alpha_surface, Rect(at, geometry.area()), + Icon_painter::paint(alpha_surface, Rect(at, _animated_geometry.area()), *texture, 255); _draw_children(pixel_surface, alpha_surface, at); @@ -82,8 +82,8 @@ struct Menu_view::Frame_widget : Widget void _layout() override { if (Widget *child = _children.first()) - child->size(Area(geometry.w() - _space().w(), - geometry.h() - _space().h())); + child->size(Area(geometry().w() - _space().w(), + geometry().h() - _space().h())); } }; diff --git a/repos/gems/src/app/menu_view/label_widget.h b/repos/gems/src/app/menu_view/label_widget.h index 51c8be7f84..11bfabf618 100644 --- a/repos/gems/src/app/menu_view/label_widget.h +++ b/repos/gems/src/app/menu_view/label_widget.h @@ -56,8 +56,8 @@ struct Menu_view::Label_widget : Widget Area text_size = min_size(); - int const dx = (int)geometry.w() - text_size.w(), - dy = (int)geometry.h() - text_size.h(); + int const dx = (int)geometry().w() - text_size.w(), + dy = (int)geometry().h() - text_size.h(); Point const centered = Point(dx/2, dy/2); diff --git a/repos/gems/src/app/menu_view/root_widget.h b/repos/gems/src/app/menu_view/root_widget.h index 0352f1c3e6..5c961ad006 100644 --- a/repos/gems/src/app/menu_view/root_widget.h +++ b/repos/gems/src/app/menu_view/root_widget.h @@ -27,6 +27,14 @@ struct Menu_view::Root_widget : Widget Widget(factory, node, unique_id) { } + Area animated_size() const + { + if (Widget const * const child = _children.first()) + return child->animated_geometry().area(); + + return Area(1, 1); + } + void update(Xml_node node) override { char const *dialog_tag = "dialog"; @@ -42,6 +50,9 @@ struct Menu_view::Root_widget : Widget } _update_children(node); + + if (Widget *child = _children.first()) + child->geometry(Rect(Point(0, 0), child->min_size())); } Area min_size() const override @@ -62,7 +73,7 @@ struct Menu_view::Root_widget : Widget void _layout() override { if (Widget *child = _children.first()) { - child->size(geometry.area()); + child->size(geometry().area()); child->position(Point(0, 0)); } } diff --git a/repos/gems/src/app/menu_view/widget.h b/repos/gems/src/app/menu_view/widget.h index 38c7c6b91b..0d1c1eba15 100644 --- a/repos/gems/src/app/menu_view/widget.h +++ b/repos/gems/src/app/menu_view/widget.h @@ -20,6 +20,7 @@ /* local includes */ #include #include +#include namespace Menu_view { @@ -136,7 +137,7 @@ class Menu_view::Widget : public List::Element Point at) const { for (Widget const *w = _children.first(); w; w = w->next()) - w->draw(pixel_surface, alpha_surface, at + w->geometry.p1()); + w->draw(pixel_surface, alpha_surface, at + w->_animated_geometry.p1()); } virtual void _layout() { } @@ -144,28 +145,42 @@ class Menu_view::Widget : public List::Element Rect _inner_geometry() const { return Rect(Point(margin.left, margin.top), - Area(geometry.w() - margin.horizontal(), - geometry.h() - margin.vertical())); + Area(geometry().w() - margin.horizontal(), + geometry().h() - margin.vertical())); } - public: - - Margin margin { 0, 0, 0, 0 }; - /* * Position relative to the parent widget and actual size, defined by * the parent */ - Rect geometry; + Rect _geometry; + + Animated_rect _animated_geometry { _factory.animator }; + + public: + + Margin margin { 0, 0, 0, 0 }; + + void geometry(Rect geometry) + { + _geometry = geometry; + _animated_geometry.move_to(_geometry, Animated_rect::Steps{60}); + } + + Rect geometry() const { return _geometry; } + + Rect animated_geometry() const { return _animated_geometry; } /* * Return x/y positions of the edges of the widget with the margin * applied */ - Rect edges() const { return Rect(Point(geometry.x1() + margin.left, - geometry.y1() + margin.top), - Point(geometry.x2() - margin.right, - geometry.y2() - margin.bottom)); } + Rect edges() const + { + Rect const r = _animated_geometry; + return Rect(Point(r.x1() + margin.left, r.y1() + margin.top), + Point(r.x2() - margin.right, r.y2() - margin.bottom)); + } Widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) : @@ -195,14 +210,14 @@ class Menu_view::Widget : public List::Element void size(Area size) { - geometry = Rect(geometry.p1(), size); + _geometry = Rect(_geometry.p1(), size); _layout(); } void position(Point position) { - geometry = Rect(position, geometry.area()); + _geometry = Rect(position, _geometry.area()); } /** @@ -216,7 +231,7 @@ class Menu_view::Widget : public List::Element return Unique_id(); for (Widget const *w = _children.first(); w; w = w->next()) { - Unique_id res = w->hovered(at - w->geometry.p1()); + Unique_id res = w->hovered(at - w->geometry().p1()); if (res.valid()) return res; } @@ -236,13 +251,13 @@ class Menu_view::Widget : public List::Element xml.node(_type_name.string(), [&]() { xml.attribute("name", _name.string()); - xml.attribute("xpos", geometry.x1()); - xml.attribute("ypos", geometry.y1()); - xml.attribute("width", geometry.w()); - xml.attribute("height", geometry.h()); + xml.attribute("xpos", geometry().x1()); + xml.attribute("ypos", geometry().y1()); + xml.attribute("width", geometry().w()); + xml.attribute("height", geometry().h()); for (Widget const *w = _children.first(); w; w = w->next()) { - w->gen_hover_model(xml, at - w->geometry.p1()); + w->gen_hover_model(xml, at - w->geometry().p1()); } }); }