diff --git a/libs/canvas/canvas/cbox.h b/libs/canvas/canvas/cbox.h new file mode 100644 index 0000000000..e3c7932dd9 --- /dev/null +++ b/libs/canvas/canvas/cbox.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 Carl Hetherington + * Copyright (C) 2016 Paul Davis + * Copyright (C) 2017 Robin Gareus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __CANVAS_CBOX_H__ +#define __CANVAS_CBOX_H__ + +#include + +#include "canvas/constraint_packer.h" + +namespace ArdourCanvas +{ + +class Rectangle; +class BoxConstrainedItem; + +class LIBCANVAS_API cBox : public ConstraintPacker +{ +public: + cBox (Canvas *, Orientation); + cBox (Item *, Orientation); + + void set_spacing (double s); + void set_padding (double top, double right = -1.0, double bottom = -1.0, double left = -1.0); + void set_margin (double top, double right = -1.0, double bottom = -1.0, double left = -1.0); + + /* aliases so that CSS box model terms work */ + void set_border_width (double w) { set_outline_width (w); } + void set_border_color (Gtkmm2ext::Color c) { set_outline_color (c); } + + void remove (Item*); + + BoxConstrainedItem* pack_start (Item*, PackOptions primary_axis_packing = PackOptions (0), PackOptions secondary_axis_packing = PackOptions (PackExpand|PackFill)); + BoxConstrainedItem* pack_end (Item*, PackOptions primary_axis_packing = PackOptions (0), PackOptions secondary_axis_packing = PackOptions (PackExpand|PackFill)); + + void set_collapse_on_hide (bool); + void set_homogenous (bool); + + void preferred_size(Duple& minimum, Duple& natural) const; + void size_allocate (Rect const &); + + ConstrainedItem* add_constrained (Item*); + + protected: + Orientation orientation; + + double _spacing; + double _top_padding; + double _bottom_padding; + double _left_padding; + double _right_padding; + double _top_margin; + double _bottom_margin; + double _left_margin; + double _right_margin; + + void child_changed (bool bbox_changed); + + private: + typedef std::list Order; + Order order; + bool collapse_on_hide; + bool homogenous; + + BoxConstrainedItem* pack (Item*, PackOptions primary_axis_packing, PackOptions secondary_axis_packing); +}; + +} + +#endif /* __CANVAS_CBOX_H__ */ diff --git a/libs/canvas/canvas/constrained_item.h b/libs/canvas/canvas/constrained_item.h new file mode 100644 index 0000000000..38b4591cc4 --- /dev/null +++ b/libs/canvas/canvas/constrained_item.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __CANVAS_CONSTRAINED_ITEM_H__ +#define __CANVAS_CONSTRAINED_ITEM_H__ + +#include "kiwi/kiwi.h" + +#include "canvas/types.h" +#include "canvas/visibility.h" + +namespace ArdourCanvas +{ + +class Item; +class ConstraintPacker; + + +class /* LIBCANVAS_API */ ConstrainedItem +{ + public: + ConstrainedItem (Item& i); + virtual ~ConstrainedItem (); + + Item& item() { return _item; } + + kiwi::Variable& left () { return _left; } + kiwi::Variable& right () { return _right; } + kiwi::Variable& top () { return _top; } + kiwi::Variable& bottom () { return _bottom; } + kiwi::Variable& width () { return _width; } + kiwi::Variable& height () { return _height; } + + void constrained (ConstraintPacker const & parent); + virtual bool involved (kiwi::Constraint const &) const; + + std::vector const & constraints() const { return _constraints; } + void add_constraint (kiwi::Constraint const & c) { _constraints.push_back (c); } + + protected: + Item& _item; + std::vector _constraints; + + kiwi::Variable _left; + kiwi::Variable _right; + kiwi::Variable _top; + kiwi::Variable _bottom; + kiwi::Variable _width; + kiwi::Variable _height; + + virtual void dump (std::ostream&); +}; + +class /* LIBCANVAS_API */ BoxConstrainedItem : public ConstrainedItem +{ + public: + BoxConstrainedItem (Item& i, PackOptions primary_axis_opts, PackOptions secondary_axis_opts); + ~BoxConstrainedItem (); + + virtual bool involved (kiwi::Constraint const &) const; + + kiwi::Variable& center_x () { return _center_x; } + kiwi::Variable& center_y () { return _center_y; } + kiwi::Variable& left_margin () { return _left_margin; } + kiwi::Variable& right_margin () { return _right_margin; } + kiwi::Variable& top_margin () { return _top_margin; } + kiwi::Variable& bottom_margin () { return _bottom_margin; } + + /* Padding is not for use by items or anyone except the parent + * (constraint) container. It is used to space out items that are set + * to expand inside a container but not to "fill" (i.e. the extra space + * is assigned to padding around the item, not the item itself). + */ + + kiwi::Variable& left_padding () { return _left_padding; } + kiwi::Variable& right_padding () { return _right_padding; } + kiwi::Variable& top_padding () { return _top_padding; } + kiwi::Variable& bottom_padding () { return _bottom_padding; } + + PackOptions primary_axis_pack_options() const { return _primary_axis_pack_options; } + PackOptions secondary_axis_pack_options() const { return _secondary_axis_pack_options; } + + void dump (std::ostream&); + + private: + kiwi::Variable _center_x; + kiwi::Variable _center_y; + kiwi::Variable _left_margin; + kiwi::Variable _right_margin; + kiwi::Variable _top_margin; + kiwi::Variable _bottom_margin; + kiwi::Variable _left_padding; + kiwi::Variable _right_padding; + kiwi::Variable _top_padding; + kiwi::Variable _bottom_padding; + + PackOptions _primary_axis_pack_options; + PackOptions _secondary_axis_pack_options; +}; + +} + +#endif + diff --git a/libs/canvas/canvas/constraint_packer.h b/libs/canvas/canvas/constraint_packer.h index 1fd42a821e..0934508d4c 100644 --- a/libs/canvas/canvas/constraint_packer.h +++ b/libs/canvas/canvas/constraint_packer.h @@ -19,7 +19,10 @@ #ifndef __CANVAS_CONSTRAINT_PACKER_H__ #define __CANVAS_CONSTRAINT_PACKER_H__ -#include "canvas/item.h" +#include +#include + +#include "canvas/container.h" #include "kiwi/kiwi.h" namespace ArdourCanvas @@ -28,40 +31,43 @@ namespace ArdourCanvas class Rectangle; class ConstrainedItem; -class LIBCANVAS_API ConstraintPacker : public Item +class LIBCANVAS_API ConstraintPacker : public Container { public: ConstraintPacker (Canvas *); ConstraintPacker (Item *); void add (Item *); + void add_front (Item *); void remove (Item *); void constrain (kiwi::Constraint const &); + virtual ConstrainedItem* add_constrained (Item* item); + void solve (); - void apply (); + void apply (kiwi::Solver*); void compute_bounding_box () const; - void render (Rect const & area, Cairo::RefPtr context) const; + void preferred_size (Duple& mininum, Duple& natural) const; void size_allocate (Rect const &); - protected: - void child_changed (); - - private: - typedef std::map ConstraintMap; - ConstraintMap constraint_map; - - kiwi::Solver _solver; kiwi::Variable width; kiwi::Variable height; - Rectangle *self; - bool collapse_on_hide; + protected: + void child_changed (bool bbox_changed); - void reset_self (); - void reposition_children (); + typedef std::map ConstrainedItemMap; + ConstrainedItemMap constrained_map; + typedef std::list ConstraintList; + ConstraintList constraint_list; + + bool in_alloc; + + void add_constrained_internal (Item*, ConstrainedItem*); + + void add_constraints (kiwi::Solver&, ConstrainedItem*) const; }; } diff --git a/libs/canvas/cbox.cc b/libs/canvas/cbox.cc new file mode 100644 index 0000000000..ed1a2bc185 --- /dev/null +++ b/libs/canvas/cbox.cc @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2020 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "pbd/unwind.h" + +#include "canvas/canvas.h" +#include "canvas/cbox.h" +#include "canvas/constrained_item.h" + +using namespace ArdourCanvas; +using namespace kiwi; +using std::cerr; +using std::endl; + +cBox::cBox (Canvas* c, Orientation o) + : ConstraintPacker (c) + , orientation (o) + , _spacing (0) + , _top_padding (0) + , _bottom_padding (0) + , _left_padding (0) + , _right_padding (0) + , _top_margin (0) + , _bottom_margin (0) + , _left_margin (0) + , _right_margin (0) + , collapse_on_hide (false) + , homogenous (true) +{ +} + +cBox::cBox (Item* i, Orientation o) + : ConstraintPacker (i) + , orientation (o) + , _spacing (0) + , _top_padding (0) + , _bottom_padding (0) + , _left_padding (0) + , _right_padding (0) + , _top_margin (0) + , _bottom_margin (0) + , _left_margin (0) + , _right_margin (0) + , collapse_on_hide (false) + , homogenous (true) +{ +} + +void +cBox::set_spacing (double s) +{ + _spacing = s; +} + +void +cBox::set_padding (double top, double right, double bottom, double left) +{ + double last = top; + + _top_padding = last; + + if (right >= 0) { + last = right; + } + _right_padding = last; + + if (bottom >= 0) { + last = bottom; + } + _bottom_padding = last; + + if (left >= 0) { + last = left; + } + _left_padding = last; +} + +void +cBox::set_margin (double top, double right, double bottom, double left) +{ + double last = top; + + _top_margin = last; + + if (right >= 0) { + last = right; + } + _right_margin = last; + + if (bottom >= 0) { + last = bottom; + } + _bottom_margin = last; + + if (left >= 0) { + last = left; + } + _left_margin = last; +} + +void +cBox::remove (Item* item) +{ + for (Order::iterator t = order.begin(); t != order.end(); ++t) { + if (&(*t)->item() == item) { + order.erase (t); + break; + } + } + + ConstraintPacker::remove (item); +} + +ConstrainedItem* +cBox::add_constrained (Item* item) +{ + return pack (item, PackOptions (0), PackOptions (PackExpand|PackFill)); +} + +BoxConstrainedItem* +cBox::pack_start (Item* item, PackOptions primary_axis_opts, PackOptions secondary_axis_opts) +{ + return pack (item, PackOptions (primary_axis_opts|PackFromStart), secondary_axis_opts); +} + +BoxConstrainedItem* +cBox::pack_end (Item* item, PackOptions primary_axis_opts, PackOptions secondary_axis_opts) +{ + return pack (item, PackOptions (primary_axis_opts|PackFromEnd), secondary_axis_opts); +} + +BoxConstrainedItem* +cBox::pack (Item* item, PackOptions primary_axis_opts, PackOptions secondary_axis_opts) +{ + BoxConstrainedItem* ci = new BoxConstrainedItem (*item, primary_axis_opts, secondary_axis_opts); + + add_constrained_internal (item, ci); + order.push_back (ci); + + return ci; +} + +void +cBox::preferred_size (Duple& min, Duple& natural) const +{ + Order::size_type n_expanding = 0; + Order::size_type n_nonexpanding = 0; + Order::size_type total = 0; + Distance non_expanding_used = 0; + Distance largest = 0; + Distance largest_opposite = 0; + Duple i_min, i_natural; + + cerr << "cbox::prefsize (" << (orientation == Vertical ? " vert) " : " horiz) ") << endl; + + for (Order::const_iterator o = order.begin(); o != order.end(); ++o) { + + (*o)->item().preferred_size (i_min, i_natural); + + cerr << '\t' << (*o)->item().debug_name() << " min " << i_min << " nat " << i_natural << endl; + + if ((*o)->primary_axis_pack_options() & PackExpand) { + n_expanding++; + + if (orientation == Vertical) { + if (i_natural.height() > largest) { + largest = i_natural.height(); + } + if (i_natural.width() > largest) { + largest_opposite = i_natural.width(); + } + } else { + if (i_natural.width() > largest) { + largest = i_natural.width(); + } + if (i_natural.height() > largest) { + largest_opposite = i_natural.height(); + } + } + + } else { + n_nonexpanding++; + + if (orientation == Vertical) { + if (i_natural.height() > 0) { + non_expanding_used += i_natural.height(); + } else { + non_expanding_used += i_min.height(); + } + } else { + if (i_natural.width() > 0) { + non_expanding_used += i_natural.width(); + } else { + non_expanding_used += i_min.width(); + } + } + } + total++; + } + + Duple r; + + if (orientation == Vertical) { + cerr << "+++ vertical box, neu = " << non_expanding_used << " largest = " << largest << " opp " << largest_opposite << " total " << total << endl; + min.x = non_expanding_used + (n_expanding * largest_opposite) + _left_margin + _right_margin + ((total - 1) * _spacing); + min.y = non_expanding_used + (n_expanding * largest) + _top_margin + _bottom_margin + ((total - 1) * _spacing); + } else { + cerr << "+++ horiz box, neu = " << non_expanding_used << " largest = " << largest << " opp " << largest_opposite << " total " << total << endl; + min.x = non_expanding_used + (n_expanding * largest) + _left_margin + _right_margin + ((total - 1) * _spacing); + min.y = non_expanding_used + (n_expanding * largest_opposite) + _top_margin + _bottom_margin + ((total - 1) * _spacing); + + } + + cerr << "++++ " << debug_name() << " rpref " << min << endl; + + natural = min; +} + +void +cBox::size_allocate (Rect const & r) +{ + PBD::Unwinder uw (in_alloc, true); + + Item::size_allocate (r); + + kiwi::Solver solver; + + double expanded_size; + Order::size_type n_expanding = 0; + Order::size_type n_nonexpanding = 0; + Order::size_type total = 0; + Distance non_expanding_used = 0; + + for (Order::iterator o = order.begin(); o != order.end(); ++o) { + if ((*o)->primary_axis_pack_options() & PackExpand) { + n_expanding++; + } else { + n_nonexpanding++; + + Duple min, natural; + + (*o)->item().preferred_size (min, natural); + + if (orientation == Vertical) { + non_expanding_used += natural.height(); + } else { + non_expanding_used += natural.width(); + } + + } + total++; + } + + if (orientation == Vertical) { + expanded_size = (r.height() - _top_margin - _bottom_margin - ((total - 1) * _spacing) - non_expanding_used) / n_expanding; + } else { + expanded_size = (r.width() - _left_margin - _right_margin - ((total - 1) * _spacing) - non_expanding_used) / n_expanding; + } + + cerr << "\n\n\n" << debug_name() << " SIZE-ALLOC " << r << " expanded items (" << n_expanding << ")will be " << expanded_size << " neu " << non_expanding_used << " t = " << total << " s " << _spacing << '\n'; + + Order::size_type n = 0; + Order::iterator prev = order.end(); + try { + for (Order::iterator o = order.begin(); o != order.end(); ++o, ++n) { + + Duple min, natural; + (*o)->item().preferred_size (min, natural); + + cerr << "\t" << (*o)->item().debug_name() << " min " << min << " nat " << natural << endl; + + /* setup center_{x,y} variables in case calling/using + * code wants to use them for additional constraints + */ + + solver.addConstraint ((*o)->center_x() == (*o)->left() + ((*o)->width() / 2.)); + solver.addConstraint ((*o)->center_y() == (*o)->top() + ((*o)->height() / 2.)); + + /* Add constraints that will size the item within this box */ + + if (orientation == Vertical) { + + /* set up constraints for expand/fill options, done by + * adjusting height and margins of each item + */ + + if ((*o)->primary_axis_pack_options() & PackExpand) { + + /* item will take up more than it's natural + * size, if space is available + */ + + if ((*o)->primary_axis_pack_options() & PackFill) { + + /* item is expanding to fill all + * available space and wants that space + * for itself. + */ + + solver.addConstraint ((*o)->height() == expanded_size | kiwi::strength::strong); + solver.addConstraint ((*o)->top_padding() == 0. | kiwi::strength::strong); + solver.addConstraint ((*o)->bottom_padding() == 0. | kiwi::strength::strong); + + } else { + + /* item is expanding to fill all + * available space and wants that space + * as padding + */ + + solver.addConstraint ((*o)->height() == natural.height()); + solver.addConstraint ((*o)->top_padding() + (*o)->bottom_padding() + (*o)->height() == expanded_size | kiwi::strength::strong); + solver.addConstraint ((*o)->bottom_padding() == (*o)->top_padding() | kiwi::strength::strong); + } + + } else { + + /* item is not going to expand to fill + * available space. just give it's preferred + * height. + */ + + cerr << (*o)->item().debug_name() << " will use natural height of " << natural.height() << endl; + solver.addConstraint ((*o)->height() == natural.height()); + solver.addConstraint ((*o)->top_padding() == 0.); + solver.addConstraint ((*o)->bottom_padding() == 0.); + } + + + /* now set upper left corner of the item */ + + if (n == 0) { + + /* first item */ + + solver.addConstraint ((*o)->top() == _top_margin + (*o)->top_padding() | kiwi::strength::strong); + + } else { + /* subsequent items */ + + solver.addConstraint ((*o)->top() == (*prev)->bottom() + (*prev)->bottom_padding() + (*o)->top_padding() + _spacing | kiwi::strength::strong); + } + + /* set the side-effect variables and/or constants */ + + solver.addConstraint ((*o)->left() + (*o)->width() == (*o)->right()| kiwi::strength::strong); + solver.addConstraint ((*o)->bottom() == (*o)->top() + (*o)->height()); + solver.addConstraint ((*o)->left() == _left_margin + (*o)->left_padding() | kiwi::strength::strong); + + if (!((*o)->secondary_axis_pack_options() & PackExpand) && natural.width() > 0) { + cerr << "\t\t also using natural width of " << natural.width() << endl; + solver.addConstraint ((*o)->width() == natural.width()); + } else { + cerr << "\t\t also using container width of " << r.width() << endl; + solver.addConstraint ((*o)->width() == r.width() - (_left_margin + _right_margin + (*o)->right_padding()) | kiwi::strength::strong); + } + + + } else { + + /* set up constraints for expand/fill options, done by + * adjusting width and margins of each item + */ + + if ((*o)->primary_axis_pack_options() & PackExpand) { + + /* item will take up more than it's natural + * size, if space is available + */ + + if ((*o)->primary_axis_pack_options() & PackFill) { + + /* item is expanding to fill all + * available space and wants that space + * for itself. + */ + + solver.addConstraint ((*o)->width() == expanded_size | kiwi::strength::strong); + solver.addConstraint ((*o)->left_padding() == 0. | kiwi::strength::strong); + solver.addConstraint ((*o)->right_padding() == 0. | kiwi::strength::strong); + + } else { + + /* item is expanding to fill all + * available space and wants that space + * as padding + */ + + solver.addConstraint ((*o)->width() == natural.width()); + solver.addConstraint ((*o)->left_padding() + (*o)->right_padding() + (*o)->width() == expanded_size | kiwi::strength::strong); + solver.addConstraint ((*o)->left_padding() == (*o)->right_padding() | kiwi::strength::strong); + } + + } else { + + /* item is not going to expand to fill + * available space. just give it's preferred + * width. + */ + + solver.addConstraint ((*o)->width() == natural.width()); + solver.addConstraint ((*o)->left_padding() == 0.); + solver.addConstraint ((*o)->right_padding() == 0.); + } + + + /* now set upper left corner of the item */ + + if (n == 0) { + + /* first item */ + + solver.addConstraint ((*o)->left() == _left_margin + (*o)->left_padding() | kiwi::strength::strong); + + } else { + /* subsequent items */ + + solver.addConstraint ((*o)->left() == (*prev)->right() + (*prev)->right_padding() + (*o)->left_padding() + _spacing | kiwi::strength::strong); + } + + /* set the side-effect variables and/or constants */ + + solver.addConstraint ((*o)->bottom() == (*o)->top() + (*o)->height()); + solver.addConstraint ((*o)->right() == (*o)->left() + (*o)->width()); + solver.addConstraint ((*o)->top() == _top_margin + (*o)->top_padding() | kiwi::strength::strong); + + if (!((*o)->secondary_axis_pack_options() & PackExpand) && natural.height() > 0) { + cerr << "\t\tand natural height of " << natural.height() << endl; + solver.addConstraint ((*o)->height() == natural.height()); + } else { + cerr << "\t\tand container height of " << r.height() << endl; + solver.addConstraint ((*o)->height() == r.height() - (_top_margin + _bottom_margin + (*o)->bottom_padding()) | kiwi::strength::strong); + } + } + + /* Add constraints that come with the item */ + + std::vector const & constraints ((*o)->constraints()); + + for (std::vector::const_iterator c = constraints.begin(); c != constraints.end(); ++c) { + solver.addConstraint (*c); + } + + prev = o; + } + + } catch (std::exception& e) { + cerr << "Setting up sovler failed: " << e.what() << endl; + return; + } + + + solver.updateVariables (); + //solver.dump (cerr); + + for (Order::iterator o = order.begin(); o != order.end(); ++o, ++n) { + (*o)->dump (cerr); + } + + apply (&solver); + + _bounding_box_dirty = true; + +} + +void +cBox::child_changed (bool bbox_changed) +{ +} diff --git a/libs/canvas/constrained_item.cc b/libs/canvas/constrained_item.cc new file mode 100644 index 0000000000..d8ea940e88 --- /dev/null +++ b/libs/canvas/constrained_item.cc @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "canvas/item.h" +#include "canvas/types.h" +#include "canvas/constrained_item.h" + +using namespace ArdourCanvas; + +using std::cerr; +using std::endl; +using kiwi::Constraint; +using kiwi::Variable; + +ConstrainedItem::ConstrainedItem (Item& i) + : _item (i) + , _left (_item.name + " left") + , _right (_item.name + " right") + , _top (_item.name + " top") + , _bottom (_item.name + " bottom") + , _width (_item.name + " width") + , _height (_item.name + " height") +{ +} + +ConstrainedItem::~ConstrainedItem () +{ +} + +void +ConstrainedItem::constrained (ConstraintPacker const & parent) +{ + /* our variables should be set. Deliver computed size to item */ + + Rect r (_left.value(), _top.value(), _right.value(), _bottom.value()); + dump (cerr); + cerr << _item.whatami() << '/' << _item.name << " constrained-alloc " << r << endl; + + _item.size_allocate (r); +} + +void +ConstrainedItem::dump (std::ostream& out) +{ + out << _item.name << " value dump:\n\n"; + + out << '\t' << "left: " << _left.value() << '\n'; + out << '\t' << "right: " << _right.value() << '\n'; + out << '\t' << "top: " << _top.value() << '\n'; + out << '\t' << "bottom: " << _bottom.value() << '\n'; + out << '\t' << "width: " << _width.value() << '\n'; + out << '\t' << "height: " << _height.value() << '\n'; +} + +bool +ConstrainedItem::involved (Constraint const & c) const +{ + if (c.involves (_left) || + c.involves (_right) || + c.involves (_top) || + c.involves (_bottom) || + c.involves (_width) || + c.involves (_height)) { + return true; + } + + return false; +} + +/*** BoxConstrainedItem */ + +BoxConstrainedItem::BoxConstrainedItem (Item& parent, PackOptions primary_axis_opts, PackOptions secondary_axis_opts) + : ConstrainedItem (parent) + , _center_x (_item.name + " center_x") + , _center_y (_item.name + " center_y") + , _left_margin (_item.name + " left_margin") + , _right_margin (_item.name + " right_margin") + , _top_margin (_item.name + " top_margin") + , _bottom_margin (_item.name + " bottom_margin") + , _primary_axis_pack_options (primary_axis_opts) + , _secondary_axis_pack_options (secondary_axis_opts) +{ +} + +BoxConstrainedItem::~BoxConstrainedItem () +{ +} + +bool +BoxConstrainedItem::involved (Constraint const & c) const +{ + if (ConstrainedItem::involved (c)) { + return true; + } + + if (c.involves (_center_x) || + c.involves (_center_y) || + c.involves (_left_margin) || + c.involves (_right_margin) || + c.involves (_top_margin) || + c.involves (_bottom_margin)) { + return true; + } + + return false; +} + +void +BoxConstrainedItem::dump (std::ostream& out) +{ + ConstrainedItem::dump (out); + + out << '\t' << "center_x: " << _center_x.value() << '\n'; + out << '\t' << "center_y: " << _center_y.value() << '\n'; + out << '\t' << "left_margin: " << _left_margin.value() << '\n'; + out << '\t' << "right_margin: " << _right_margin.value() << '\n'; + out << '\t' << "top_margin: " << _top_margin.value() << '\n'; + out << '\t' << "bottom_margin: " << _bottom_margin.value() << '\n'; + + out << '\t' << "right_padding: " << _right_padding.value() << '\n'; + out << '\t' << "left_padding: " << _left_padding.value() << '\n'; + out << '\t' << "top_padding: " << _top_padding.value() << '\n'; + out << '\t' << "bottom_padding: " << _bottom_padding.value() << '\n'; +} + diff --git a/libs/canvas/constraint_packer.cc b/libs/canvas/constraint_packer.cc index a29b65abd3..2ee79eb419 100644 --- a/libs/canvas/constraint_packer.cc +++ b/libs/canvas/constraint_packer.cc @@ -16,8 +16,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "pbd/i18n.h" +#include +#include "pbd/i18n.h" +#include "pbd/unwind.h" +#include "pbd/stacktrace.h" #include "kiwi/kiwi.h" @@ -28,114 +31,279 @@ using namespace ArdourCanvas; +using std::cerr; +using std::endl; +using std::vector; +using kiwi::Constraint; +using namespace kiwi; + ConstraintPacker::ConstraintPacker (Canvas* canvas) - : Item (canvas) - , width (X_("width")) - , height (X_("height")) + : Container (canvas) + , width (X_("packer width")) + , height (X_("packer height")) + , in_alloc (false) { - _solver.addEditVariable (width, kiwi::strength::strong); - _solver.addEditVariable (height, kiwi::strength::strong); + set_fill (false); + set_outline (false); } ConstraintPacker::ConstraintPacker (Item* parent) - : Item (parent) + : Container (parent) + , width (X_("packer width")) + , height (X_("packer height")) + , in_alloc (false) { -} - -void -ConstraintPacker::render (Rect const & area, Cairo::RefPtr context) const -{ - Item::render_children (area, context); + set_fill (false); + set_outline (false); } void ConstraintPacker::compute_bounding_box () const { - _bounding_box = Rect(); - - if (_items.empty()) { - _bounding_box_dirty = false; - return; - } - - add_child_bounding_boxes (!collapse_on_hide); - + _bounding_box = _allocation; _bounding_box_dirty = false; } void -ConstraintPacker::reset_self () +ConstraintPacker::child_changed (bool bbox_changed) { - if (_bounding_box_dirty) { - compute_bounding_box (); - } + Item::child_changed (bbox_changed); - if (!_bounding_box) { - self->hide (); + if (in_alloc || !bbox_changed) { return; } +#if 0 + cerr << "CP, child bbox changed\n"; - Rect r (_bounding_box); + for (ConstrainedItemMap::iterator x = constrained_map.begin(); x != constrained_map.end(); ++x) { - /* XXX need to shrink by margin */ + Duple i = x->first->intrinsic_size(); - self->set (r); -} + if (r) { -void -ConstraintPacker::reposition_children () -{ - _bounding_box_dirty = true; - reset_self (); -} -void -ConstraintPacker::child_changed () -{ - /* catch visibility and size changes */ + // cerr << x->first->whatami() << '/' << x->first->name << " has instrinsic size " << r << endl; - Item::child_changed (); - reposition_children (); + kiwi::Variable& w (x->second->intrinsic_width()); + if (!r.width()) { + if (_solver.hasEditVariable (w)) { + _solver.removeEditVariable (w); + cerr << "\tremoved inttrinsic-width edit var\n"; + } + } else { + if (!_solver.hasEditVariable (w)) { + cerr << "\tadding intrinsic width constraints\n"; + _solver.addEditVariable (w, kiwi::strength::strong); + _solver.addConstraint (Constraint {x->second->width() >= w } | kiwi::strength::strong); + _solver.addConstraint (Constraint (x->second->width() <= w) | kiwi::strength::weak); + } + } + + kiwi::Variable& h (x->second->intrinsic_height()); + if (!r.height()) { + if (_solver.hasEditVariable (h)) { + _solver.removeEditVariable (h); + cerr << "\tremoved inttrinsic-height edit var\n"; + } + } else { + if (!_solver.hasEditVariable (h)) { + cerr << "\tadding intrinsic height constraints\n"; + _solver.addEditVariable (h, kiwi::strength::strong); + _solver.addConstraint (Constraint {x->second->height() >= h } | kiwi::strength::strong); + _solver.addConstraint (Constraint (x->second->height() <= h) | kiwi::strength::weak); + } + } + } + } +#endif } void ConstraintPacker::constrain (kiwi::Constraint const &c) { - _solver.addConstraint (c); + constraint_list.push_back (c); +} + +void +ConstraintPacker::preferred_size (Duple& minimum, Duple& natural) const +{ + /* our parent wants to know how big we are. + + We may have some intrinsic size (i.e. "everything in this constraint + layout should fit into WxH". Just up two constraints on our width + and height, and solve. + + We may have one intrinsic dimension (i.e. "everything in this + constraint layout should fit into this (width|height). Ask all of + our children for the size-given-(W|H). Add constraints to represent + those values, and solve. + + We may have no intrinsic dimensions at all. This is the tricky one. + */ + + kiwi::Solver s; + + if (_intrinsic_width > 0) { + + s.addEditVariable (width, kiwi::strength::strong); + s.suggestValue (width, _intrinsic_width); + + } else if (_intrinsic_height > 0) { + + s.addEditVariable (height, kiwi::strength::strong); + s.suggestValue (height, _intrinsic_height); + + } + + for (ConstrainedItemMap::const_iterator x = constrained_map.begin(); x != constrained_map.end(); ++x) { + + Duple min, natural; + ConstrainedItem* ci = x->second; + + x->first->preferred_size (min, natural); + + s.addConstraint (ci->width() >= min.width() | kiwi::strength::required); + s.addConstraint (ci->height() >= min.height() | kiwi::strength::required); + s.addConstraint (ci->width() == natural.width() | kiwi::strength::medium); + s.addConstraint (ci->height() == natural.width() | kiwi::strength::medium); + + add_constraints (s, ci); + } + + for (ConstraintList::const_iterator c = constraint_list.begin(); c != constraint_list.end(); ++c) { + s.addConstraint (*c); + } + + s.updateVariables (); + + Duple ret; + + natural.x = width.value (); + natural.y = height.value (); + + minimum = natural; + + cerr << "CP::sr returns " << natural<< endl; } void ConstraintPacker::size_allocate (Rect const & r) { + PBD::Unwinder uw (in_alloc, true); + Item::size_allocate (r); - _solver.suggestValue (width, r.width()); - _solver.suggestValue (height, r.height()); + cerr << "CP alloc " << r << " with " << constrained_map.size() << " children\n"; - solve (); + kiwi::Solver s; + + s.addConstraint (width == r.width()); + s.addConstraint (height == r.height()); + +// s.addEditVariable (width, kiwi::strength::strong); +// s.addEditVariable (height, kiwi::strength::strong); +// s.suggestValue (width, r.width()); +// s.suggestValue (height, r.height()); + + for (ConstrainedItemMap::iterator x = constrained_map.begin(); x != constrained_map.end(); ++x) { + + Duple min, natural; + ConstrainedItem* ci = x->second; + + x->first->preferred_size (min, natural); + + s.addConstraint (ci->width() >= min.width() | kiwi::strength::required); + s.addConstraint (ci->height() >= min.height() | kiwi::strength::required); + s.addConstraint (ci->width() == natural.width() | kiwi::strength::medium); + s.addConstraint (ci->height() == natural.width() | kiwi::strength::medium); + + add_constraints (s, ci); + } + + for (ConstraintList::const_iterator c = constraint_list.begin(); c != constraint_list.end(); ++c) { + s.addConstraint (*c); + } + + s.updateVariables (); + apply (0); + + _bounding_box_dirty = true; } void ConstraintPacker::add (Item* item) +{ + (void) add_constrained (item); +} + +void +ConstraintPacker::add_front (Item* item) +{ + (void) add_constrained (item); +} + +void +ConstraintPacker::add_constraints (Solver& s, ConstrainedItem* ci) const +{ + /* add any constraints inherent to this item */ + + vector const & vc (ci->constraints()); + + for (vector::const_iterator x = vc.begin(); x != vc.end(); ++x) { + s.addConstraint (*x); + } +} + +ConstrainedItem* +ConstraintPacker::add_constrained (Item* item) +{ + ConstrainedItem* ci = new ConstrainedItem (*item); + add_constrained_internal (item, ci); + return ci; +} + +void +ConstraintPacker::add_constrained_internal (Item* item, ConstrainedItem* ci) { Item::add (item); - ConstrainedItem* ci = new ConstrainedItem (*item); - constraint_map.insert (std::make_pair (item, ci)); + item->set_layout_sensitive (true); + constrained_map.insert (std::make_pair (item, ci)); + child_changed (true); } void ConstraintPacker::remove (Item* item) { Item::remove (item); - constraint_map.erase (item); + + for (ConstrainedItemMap::iterator x = constrained_map.begin(); x != constrained_map.end(); ++x) { + + if (x->first == item) { + + /* remove any non-builtin constraints for this item */ + + for (ConstraintList::iterator c = constraint_list.begin(); c != constraint_list.end(); ++c) { + if (x->second->involved (*c)) { + constraint_list.erase (c); + } + } + + item->set_layout_sensitive (false); + + /* clean up */ + + delete x->second; + constrained_map.erase (x); + break; + } + + } + } void -ConstraintPacker::solve () +ConstraintPacker::apply (Solver* s) { - _solver.updateVariables (); - - for (ConstraintMap::iterator x = constraint_map.begin(); x != constraint_map.end(); ++x) { - x->first->constrained (*(x->second)); + for (ConstrainedItemMap::iterator x = constrained_map.begin(); x != constrained_map.end(); ++x) { + x->second->constrained (*this); } } - diff --git a/libs/canvas/constraint_test.cc b/libs/canvas/constraint_test.cc index c504a9fb06..121a920655 100644 --- a/libs/canvas/constraint_test.cc +++ b/libs/canvas/constraint_test.cc @@ -1,12 +1,24 @@ +#include + #include #include #include +#include "gtkmm2ext/colors.h" + +#include "canvas/box.h" #include "canvas/canvas.h" +#include "canvas/cbox.h" +#include "canvas/circle.h" +#include "canvas/constrained_item.h" #include "canvas/constraint_packer.h" +#include "canvas/rectangle.h" +#include "canvas/text.h" using namespace ArdourCanvas; using namespace Gtk; +using std::cerr; +using std::endl; int main (int argc, char* argv[]) @@ -17,10 +29,147 @@ main (int argc, char* argv[]) Gtk::Adjustment hadj (0, 0, 1000, 1, 10); Gtk::Adjustment vadj (0, 0, 1000, 1, 10); GtkCanvasViewport cview (hadj, vadj); + Canvas* c = cview.canvas (); + + c->set_background_color (0xffffffff); + + srandom (time ((time_t) 0)); + + // cview.set_size_request (100, 100); win.add (cview); + + Rectangle* r1 = new Rectangle (c); + Rectangle* r2 = new Rectangle (c); + Rectangle* r3 = new Rectangle (c); + + r1->set_fill_color (Gtkmm2ext::random_color()); + r2->set_fill_color (Gtkmm2ext::random_color()); + r3->set_fill_color (Gtkmm2ext::random_color()); + + r1->name = "r1"; + r2->name = "r2"; + r3->name = "r3"; + + //r1->set_intrinsic_size (20, 20); + //r2->set_intrinsic_size (30, 30); + //r3->set_intrinsic_size (40, 40); + +//#define FULL_PACKER +#define CBOX_PACKER +//#define BOX_PACKER + +#ifdef FULL_PACKER + ConstraintPacker* packer = new ConstraintPacker (c->root()); + + ConstrainedItem* left = packer->add_constrained (r1); + ConstrainedItem* right = packer->add_constrained (r2); + ConstrainedItem* center = packer->add_constrained (r3); + + /* x-axis */ + + packer->constrain (left->left() == 0); + packer->constrain (center->left() == left->right()); + packer->constrain (right->left() == center->right()); + + packer->constrain (left->width() == packer->width * 0.4); + packer->constrain (center->width() == packer->width * 0.1); + packer->constrain (left->width() + right->width() + center->width() == packer->width); + + packer->constrain (left->right() == left->left() + left->width()); + packer->constrain (right->right() == right->left() + right->width()); + packer->constrain (center->right() == center->left() + center->width()); + + /* y-axis */ + + packer->constrain (left->top() == 0); + packer->constrain (right->top() == left->top()); + packer->constrain (center->top() == left->top()); + + packer->constrain (left->height() == packer->height); + packer->constrain (right->height() == left->height()); + packer->constrain (center->height() == left->height()); + + packer->constrain (left->bottom() == left->top() + left->height()); + packer->constrain (center->bottom() == center->top() + center->height()); + packer->constrain (right->bottom() == right->top() + right->height()); + +#elif defined(CBOX_PACKER) + + cBox* vbox = new cBox (c->root(), Vertical); + vbox->name = "vbox"; + + vbox->set_margin (10, 20, 30, 40); + + vbox->pack_start (r1, PackOptions(PackExpand|PackFill)); + vbox->pack_start (r2, PackOptions(PackExpand|PackFill)); + vbox->pack_start (r3, PackOptions(PackExpand|PackFill)); + + cBox* hbox1 = new cBox (c, Horizontal); + hbox1->name = "hbox1"; + + hbox1->set_margin (10, 10, 10, 10); + + Rectangle* r4 = new Rectangle (c); + Rectangle* r5 = new Rectangle (c); + Rectangle* r6 = new Rectangle (c); + + r4->set_fill_color (Gtkmm2ext::random_color()); + r5->set_fill_color (Gtkmm2ext::random_color()); + r6->set_fill_color (Gtkmm2ext::random_color()); + + r4->name = "r4"; + r5->name = "r5"; + r6->name = "r6"; + hbox1->pack_start (r4, PackOptions(PackExpand|PackFill)); + hbox1->pack_start (r5, PackOptions(PackExpand|PackFill)); + hbox1->pack_start (r6, PackOptions(PackExpand|PackFill)); + + BoxConstrainedItem* hb1; + ConstrainedItem* ci; + + hb1 = vbox->pack_start (hbox1, PackOptions (PackExpand|PackFill)); + + Circle* circle = new Circle (c); + circle->name = "circle"; + //circle->set_radius (30); + circle->set_fill_color (Gtkmm2ext::random_color()); + circle->set_outline_color (Gtkmm2ext::random_color()); + + ci = vbox->pack_start (circle, PackOptions (PackExpand|PackFill)); + ci->add_constraint (ci->height() == 0.5 * hb1->height()); + +//#endif + + cBox* hbox2 = new cBox (c, Horizontal); + hbox2->name = "hbox2"; + hbox2->set_fill (true); + hbox2->set_fill_color (Gtkmm2ext::random_color()); + + Text* txt = new Text (c); + txt->name = "text"; + Pango::FontDescription font ("Sans"); + txt->set_font_description (font); + txt->set ("hello, world"); + + BoxConstrainedItem* ti = hbox2->pack_start (txt, PackExpand); + BoxConstrainedItem* hb2 = vbox->pack_start (hbox2, PackOptions (PackExpand|PackFill)); + + // c1 == first hbox + // ci = circle + // hb2 == second hbox + // ti == text inside second hbox + + //ti->add_constraint (ti->center_x() == hb2->center_x()); + + cerr << "hbox1 f = " << hbox1->fill() << " o " << hbox1->outline() << endl; + +#endif + win.show_all (); + // cerr << "\n\n\n text center @ " << c3->center_x().value() << " hbox center @ " << c1->center_x().value() << endl; + app.run (); return 0; diff --git a/libs/canvas/wscript b/libs/canvas/wscript index 9e10b22066..82284b3363 100644 --- a/libs/canvas/wscript +++ b/libs/canvas/wscript @@ -32,8 +32,10 @@ canvas_sources = [ 'arrow.cc', 'box.cc', 'canvas.cc', + 'cbox.cc', 'circle.cc', 'container.cc', + 'constrained_item.cc', 'constraint_packer.cc', 'curve.cc', 'debug.cc',