add/update constraint packing containers, test code

This commit is contained in:
Paul Davis 2020-06-10 14:10:26 -06:00
parent 606866ea00
commit a3039d3895
8 changed files with 1232 additions and 73 deletions

87
libs/canvas/canvas/cbox.h Normal file
View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2016 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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 <list>
#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<BoxConstrainedItem*> Order;
Order order;
bool collapse_on_hide;
bool homogenous;
BoxConstrainedItem* pack (Item*, PackOptions primary_axis_packing, PackOptions secondary_axis_packing);
};
}
#endif /* __CANVAS_CBOX_H__ */

View file

@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Paul Davis <paul@linuxaudiosystems.com>
*
* 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<kiwi::Constraint> const & constraints() const { return _constraints; }
void add_constraint (kiwi::Constraint const & c) { _constraints.push_back (c); }
protected:
Item& _item;
std::vector<kiwi::Constraint> _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

View file

@ -19,7 +19,10 @@
#ifndef __CANVAS_CONSTRAINT_PACKER_H__
#define __CANVAS_CONSTRAINT_PACKER_H__
#include "canvas/item.h"
#include <list>
#include <map>
#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<Cairo::Context> context) const;
void preferred_size (Duple& mininum, Duple& natural) const;
void size_allocate (Rect const &);
protected:
void child_changed ();
private:
typedef std::map<Item*,ConstrainedItem*> 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<Item*,ConstrainedItem*> ConstrainedItemMap;
ConstrainedItemMap constrained_map;
typedef std::list<kiwi::Constraint> ConstraintList;
ConstraintList constraint_list;
bool in_alloc;
void add_constrained_internal (Item*, ConstrainedItem*);
void add_constraints (kiwi::Solver&, ConstrainedItem*) const;
};
}

486
libs/canvas/cbox.cc Normal file
View file

@ -0,0 +1,486 @@
/*
* Copyright (C) 2020 Paul Davis <paul@linuxaudiosystems.com>
*
* 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 <iostream>
#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<bool> 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<Constraint> const & constraints ((*o)->constraints());
for (std::vector<Constraint>::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)
{
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (C) 2020 Paul Davis <paul@linuxaudiosystems.com>
*
* 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 <iostream>
#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';
}

View file

@ -16,8 +16,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "pbd/i18n.h"
#include <iostream>
#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<Cairo::Context> 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<bool> 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<Constraint> const & vc (ci->constraints());
for (vector<Constraint>::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);
}
}

View file

@ -1,12 +1,24 @@
#include <iostream>
#include <gtkmm/adjustment.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#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;

View file

@ -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',