--- /dev/null
+#include "arrangement.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GLtk {
+
+Arrangement::Arrangement(Layout &l):
+ layout(l),
+ parent(layout.get_arrangement())
+{
+ layout.push_arrangement(*this);
+}
+
+Arrangement::~Arrangement()
+{
+ layout.pop_arrangement(*this);
+}
+
+void Arrangement::arrange(Widget &wdg)
+{
+ for(unsigned i=0; i<4; ++i)
+ process_widget(wdg, static_cast<Side>(i), true);
+ finish_widget(wdg);
+ finish_slot();
+}
+
+void Arrangement::arrange(Arrangement &arr)
+{
+ for(unsigned i=0; i<4; ++i)
+ {
+ Side side = static_cast<Side>(i);
+ const Edge &edge = arr.get_edge(side);
+ for(list<Widget *>::const_iterator j=edge.widgets.begin(); j!=edge.widgets.end(); ++j)
+ process_widget(**j, side, edge.aligned);
+ }
+ finish_slot();
+}
+
+void Arrangement::add_constraint(Widget &wdg, Layout::ConstraintType type, Side side)
+{
+ add_constraint(wdg, type, edges[side]);
+}
+
+void Arrangement::add_constraint(Widget &wdg, Layout::ConstraintType type, const Edge &edge)
+{
+ for(list<Widget *>::const_iterator i=edge.widgets.begin(); i!=edge.widgets.end(); ++i)
+ layout.add_constraint(wdg, type, **i);
+}
+
+
+Arrangement::Edge::Edge():
+ aligned(false)
+{ }
+
+void Arrangement::Edge::clear()
+{
+ widgets.clear();
+ aligned = false;
+}
+
+void Arrangement::Edge::add(Widget &wdg, bool algn)
+{
+ if(aligned)
+ return;
+
+ if(algn)
+ widgets.clear();
+
+ widgets.push_back(&wdg);
+ aligned = algn;
+}
+
+} // namespace GLtk
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GLTK_ARRANGEMENT_H_
+#define MSP_GLTK_ARRANGEMENT_H_
+
+#include <list>
+#include "layout.h"
+
+namespace Msp {
+namespace GLtk {
+
+class Widget;
+
+class Arrangement
+{
+protected:
+ enum Side
+ {
+ TOP,
+ RIGHT,
+ BOTTOM,
+ LEFT
+ };
+
+ struct Edge
+ {
+ std::list<Widget *> widgets;
+ bool aligned;
+
+ Edge();
+
+ bool empty() { return widgets.empty(); }
+ void clear();
+ void add(Widget &, bool);
+ };
+
+ Layout &layout;
+ Arrangement *parent;
+ Edge edges[4];
+
+ Arrangement(Layout &);
+public:
+ virtual ~Arrangement();
+
+ void arrange(Widget &);
+ void arrange(Arrangement &);
+
+protected:
+ virtual void process_widget(Widget &, Side, bool) = 0;
+ virtual void finish_widget(Widget &) = 0;
+ virtual void finish_slot() = 0;
+ const Edge &get_edge(Side s) const { return edges[s]; }
+ void add_constraint(Widget &, Layout::ConstraintType, Side);
+ void add_constraint(Widget &, Layout::ConstraintType, const Edge &);
+};
+
+} // namespace GLtk
+} // namespace Msp
+
+#endif
namespace Msp {
namespace GLtk {
-Column::Column(bool u):
- uniform(u)
+Column::Column(Layout &l):
+ Arrangement(l),
+ first(true),
+ split_here(false),
+ gravity(1)
{ }
-Layout::Slot *Column::create_slot(Widget &w)
+void Column::split()
{
- Slot *slot = new Slot(*this, w);
+ split_here = true;
+ gravity = -1;
+}
- if(!slots.empty())
+void Column::process_widget(Widget &wdg, Side side, bool aligned)
+{
+ if(side==TOP)
{
- Slot &prev = *slots.back();
- slot->constraints.push_back(Constraint(BELOW, prev));
- slot->constraints.push_back(Constraint(ALIGN_LEFT, prev));
- slot->constraints.push_back(Constraint(ALIGN_RIGHT, prev));
- if(uniform)
- slot->constraints.push_back(Constraint(COPY_HEIGHT, prev));
+ bool snug = (edges[BOTTOM].aligned && aligned && !split_here);
+ add_constraint(wdg, (snug ? Layout::BELOW : Layout::FAR_BELOW), BOTTOM);
+ if(first)
+ edges[TOP].add(wdg, aligned);
}
- slot->horiz_pack.expand = true;
+ else if(side==BOTTOM)
+ next_bottom.add(wdg, (aligned && gravity<0));
+ else
+ {
+ if(edges[side].aligned && aligned)
+ add_constraint(wdg, (side==LEFT ? Layout::ALIGN_LEFT : Layout::ALIGN_RIGHT), side);
+ edges[side].add(wdg, aligned);
+ }
+}
- return slot;
+void Column::finish_widget(Widget &wdg)
+{
+ layout.set_gravity(wdg, -1, gravity);
+}
+
+void Column::finish_slot()
+{
+ edges[BOTTOM] = next_bottom;
+ next_bottom.clear();
+ split_here = false;
+ first = false;
}
} // namespace GLtk
#ifndef MSP_GLTK_COLUMN_H_
#define MSP_GLTK_COLUMN_H_
-#include "layout.h"
+#include "arrangement.h"
namespace Msp {
namespace GLtk {
-/**
-Arranges widgets in a single column.
-*/
-class Column: public Layout
+class Column: public Arrangement
{
private:
- bool uniform;
+ Edge next_bottom;
+ bool first;
+ bool split_here;
+ int gravity;
public:
- Column(bool = false);
+ Column(Layout &);
+
+ void split();
private:
- virtual Slot *create_slot(Widget &);
+ virtual void process_widget(Widget &, Side, bool);
+ virtual void finish_widget(Widget &);
+ virtual void finish_slot();
};
} // namespace GLtk
#include "grid.h"
-using namespace std;
-
namespace Msp {
namespace GLtk {
-Grid::Grid(bool u):
- uniform(u),
- cur_column(0),
- first(true),
- skipped(false)
+Grid::Grid(Layout &l, unsigned c):
+ Arrangement(l),
+ columns(c),
+ first_row(true),
+ column(0)
{ }
-void Grid::start_row()
+void Grid::skip()
{
- cur_column = 0;
- first = true;
+ finish_slot();
}
-void Grid::skip_cell()
+void Grid::next_row()
{
- ++cur_column;
- skipped = true;
- if(cur_column>=columns.size())
- columns.push_back(0);
+ finish_row();
}
-Layout::Slot *Grid::create_slot(Widget &wdg)
+void Grid::process_widget(Widget &wdg, Side side, bool aligned)
{
- Slot *slot = new Slot(*this, wdg);
+ if(side==TOP)
+ {
+ Edge &top_edge = (first_row ? edges[TOP] : row_top);
+ if(top_edge.aligned && aligned)
+ add_constraint(wdg, Layout::ALIGN_TOP, top_edge);
- slot->vert_pack.gravity = 1;
+ bool snug = (edges[BOTTOM].aligned && aligned);
+ add_constraint(wdg, (snug ? Layout::BELOW : Layout::FAR_BELOW), BOTTOM);
- if(!slots.empty())
+ top_edge.add(wdg, aligned);
+ }
+ else if(side==BOTTOM)
{
- Slot &prev = *slots.back();
- if(first)
- slot->constraints.push_back(Constraint(BELOW, prev));
- else
- {
- slot->constraints.push_back(Constraint(ALIGN_TOP, prev));
- slot->constraints.push_back(Constraint(ALIGN_BOTTOM, prev));
- if(!skipped)
- slot->constraints.push_back(Constraint(RIGHT_OF, prev));
- }
+ if(row_bottom.aligned && aligned)
+ add_constraint(wdg, Layout::ALIGN_BOTTOM, row_bottom);
- if(uniform)
+ row_bottom.add(wdg, aligned);
+ }
+ else if(side==LEFT)
+ {
+ if(columns[column].left.aligned && aligned)
+ add_constraint(wdg, Layout::ALIGN_LEFT, columns[column].left);
+ else if(column>0)
{
- slot->constraints.push_back(Constraint(COPY_WIDTH, prev));
- slot->constraints.push_back(Constraint(COPY_HEIGHT, prev));
+ bool snug = (columns[column-1].right.aligned && aligned);
+ add_constraint(wdg, (snug ? Layout::RIGHT_OF : Layout::FAR_RIGHT_OF), columns[column-1].right);
}
- if(cur_column<columns.size() && columns[cur_column])
- {
- Slot &col = *columns[cur_column];
- slot->constraints.push_back(Constraint(ALIGN_LEFT, col));
- slot->constraints.push_back(Constraint(ALIGN_RIGHT, col));
- }
+ edges[LEFT].add(wdg, (aligned && !column));
+ columns[column].left.add(wdg, aligned);
}
+ else if(side==RIGHT)
+ {
+ if(columns[column].right.aligned && aligned)
+ add_constraint(wdg, Layout::ALIGN_RIGHT, columns[column].right);
- if(cur_column>=columns.size())
- columns.push_back(slot);
- else if(!columns[cur_column])
- columns[cur_column] = slot;
+ edges[RIGHT].add(wdg, (aligned && column+1==columns.size()));
+ columns[column].right.add(wdg, aligned);
+ }
+}
- first = false;
- skipped = false;
- ++cur_column;
+void Grid::finish_widget(Widget &wdg)
+{
+ layout.set_gravity(wdg, -1, 1);
+}
- return slot;
+void Grid::finish_slot()
+{
+ ++column;
+ if(column==columns.size())
+ finish_row();
+}
+
+void Grid::finish_row()
+{
+ edges[BOTTOM] = row_bottom;
+ row_bottom.clear();
+ row_top.clear();
+ first_row = false;
+ column = 0;
}
} // namespace GLtk
#ifndef MSP_GLTK_GRID_H_
#define MSP_GLTK_GRID_H_
-#include "layout.h"
+#include "arrangement.h"
namespace Msp {
namespace GLtk {
-/**
-Arranges widgets in a grid.
-
-This layout class places widgets in a grid, lining up their edges in rows and
-columns. Widgets are placed on rows left-to-right and rows are placed top-to-
-bottom.
-
-The start of each row must be indicated manually. Individual cells can be
-skipped to leave them empty.
-*/
-class Grid: public Layout
+class Grid: public Arrangement
{
private:
- bool uniform;
- std::vector<Slot *> columns;
- unsigned cur_column;
- bool first;
- bool skipped;
+ struct Column
+ {
+ Edge left;
+ Edge right;
+ };
+
+ std::vector<Column> columns;
+ Edge row_top;
+ Edge row_bottom;
+ bool first_row;
+ unsigned column;
public:
- Grid(bool = false);
+ Grid(Layout &, unsigned);
- void start_row();
- void skip_cell();
+ void skip();
+ void next_row();
private:
- Slot *create_slot(Widget &);
+ virtual void process_widget(Widget &, Side, bool);
+ virtual void finish_widget(Widget &);
+ virtual void finish_slot();
+ void finish_row();
};
} // namespace GLtk
#include <algorithm>
#include <limits>
+#include "arrangement.h"
#include "container.h"
#include "layout.h"
#include "widget.h"
update();
}
+void Layout::push_arrangement(Arrangement &arr)
+{
+ arrangement_stack.push_back(&arr);
+}
+
+Arrangement *Layout::get_arrangement() const
+{
+ if(arrangement_stack.empty())
+ return 0;
+ else
+ return arrangement_stack.back();
+}
+
+void Layout::pop_arrangement(Arrangement &arr)
+{
+ list<Arrangement *>::iterator begin = find(arrangement_stack.begin(), arrangement_stack.end(), &arr);
+ if(begin==arrangement_stack.end())
+ return;
+
+ while(1)
+ {
+ Arrangement *top = arrangement_stack.back();
+ arrangement_stack.pop_back();
+ if(!arrangement_stack.empty())
+ arrangement_stack.back()->arrange(*top);
+ if(top==&arr)
+ break;
+ }
+}
+
void Layout::add_widget(Widget &wdg)
{
if(!container)
throw logic_error("!container");
- Slot *slot = create_slot(wdg);
- for(list<Constraint>::iterator i=slot->constraints.begin(); i!=slot->constraints.end(); ++i)
- i->target.constraints.push_back(Constraint(complement(i->type), *slot));
- slots.push_back(slot);
+ slots.push_back(new Slot(*this, wdg));
update_slot_indices();
+ if(!arrangement_stack.empty())
+ arrangement_stack.back()->arrange(wdg);
if(container)
update();
}
}
}
-Layout::Slot *Layout::create_slot(Widget &wdg)
-{
- return new Slot(*this, wdg);
-}
-
void Layout::update_slot_indices()
{
n_active_slots = 0;
namespace Msp {
namespace GLtk {
+class Arrangement;
class Container;
class Widget;
dependent widgets have the expand flag set, the results are currently
undefined.
-Since specifiyng constraints manually can be quite tedious, there are some
-derived Layout classes that implement common positioning patterns. See Row,
-Column, MixedRows and Grid.
+Since specifiyng constraints manually can be quite tedious, an Arrangement
+interface is provided to automatically arrange widgets. See classes Row,
+Column and Grid for some commonly used arrangements.
*/
class Layout
{
COPY_HEIGHT = VERTICAL|SELF_DIM|TARGET_DIM
};
-protected:
+private:
struct Slot;
struct Constraint
Packing vert_pack;
Slot(Layout &, Widget &);
- virtual ~Slot() { }
void autosize_changed();
void visibility_changed(bool);
unsigned row_spacing;
unsigned col_spacing;
Geometry autosize_geom;
+ std::list<Arrangement *> arrangement_stack;
static Pointers pointers[2];
public:
Layout();
- virtual ~Layout();
+ ~Layout();
void set_container(Container &);
void set_margin(const Sides &);
LEFT_OF and RIGHT_OF constraints. */
void set_column_spacing(unsigned);
+ void push_arrangement(Arrangement &);
+ Arrangement *get_arrangement() const;
+ void pop_arrangement(Arrangement &);
+
void add_widget(Widget &);
void remove_widget(Widget &);
-protected:
- virtual Slot *create_slot(Widget &);
+private:
void update_slot_indices();
Slot &get_slot_for_widget(Widget &);
static ConstraintType complement(ConstraintType);
void update();
void autosize();
-protected:
+private:
void solve_constraints(int, SolveMode);
};
+++ /dev/null
-#include "mixedrows.h"
-
-namespace Msp {
-namespace GLtk {
-
-MixedRows::MixedRows(bool u):
- uniform_rows(u),
- uniform_cols(false),
- hgravity(-1),
- hsplit(false),
- vgravity(1),
- vsplit(false),
- first(true)
-{ }
-
-void MixedRows::start_row(bool u)
-{
- uniform_cols = u;
- first = true;
- hgravity = -1;
- hsplit = false;
-}
-
-void MixedRows::split_rows()
-{
- vgravity = -1;
- vsplit = true;
-}
-
-void MixedRows::split_columns()
-{
- hgravity = 1;
- hsplit = true;
-}
-
-Layout::Slot *MixedRows::create_slot(Widget &wdg)
-{
- Slot *slot = new Slot(*this, wdg);
-
- slot->horiz_pack.gravity = hgravity;
- slot->vert_pack.gravity = vgravity;
-
- if(!first)
- {
- Slot *prev = slots.back();
- slot->constraints.push_back(Constraint((hsplit ? FAR_RIGHT_OF : RIGHT_OF), *prev));
- slot->constraints.push_back(Constraint(ALIGN_TOP, *prev));
- slot->constraints.push_back(Constraint(ALIGN_BOTTOM, *prev));
- if(uniform_cols)
- slot->constraints.push_back(Constraint(COPY_WIDTH, *prev));
- }
- else if(!slots.empty())
- {
- Slot *prev = slots.back();
- slot->constraints.push_back(Constraint((vsplit ? FAR_BELOW : BELOW), *prev));
- if(uniform_rows)
- slot->constraints.push_back(Constraint(COPY_HEIGHT, *prev));
- }
-
- first = false;
- hsplit = false;
- vsplit = false;
-
- return slot;
-}
-
-} // namespace GLtk
-} // namespace Msp
+++ /dev/null
-#ifndef MSP_GLTK_MIXEDROWS_H_
-#define MSP_GLTK_MIXEDROWS_H_
-
-#include "layout.h"
-
-namespace Msp {
-namespace GLtk {
-
-/**
-Places widgets on multiple heterogeneous rows.
-
-This layout class creates rows of widgets, with no horizontal constraints
-between rows. Widgets are placed on rows left-to-right and rows are placed
-top-to-bottom.
-
-The start of each new row must be indicated manually. The widgets on a row
-can be specified to have a uniform width, and similarly all rows can be set to
-have a uniform height.
-
-By default widgets have a top-left gravity. It's possible to specify a "split"
-to align remaining widgets on a row to the right, or remaining rows to the
-bottom. This does not change the placement order of widgets.
-*/
-class MixedRows: public Layout
-{
-private:
- bool uniform_rows;
- bool uniform_cols;
- int hgravity;
- bool hsplit;
- int vgravity;
- bool vsplit;
- bool first;
-
-public:
- MixedRows(bool = false);
-
- void start_row(bool = false);
- void split_rows();
- void split_columns();
-
-private:
- virtual Slot *create_slot(Widget &);
-};
-
-} // namespace GLtk
-} // namespace Msp
-
-#endif
namespace Msp {
namespace GLtk {
-Row::Row(bool u):
- uniform(u)
+Row::Row(Layout &l):
+ Arrangement(l),
+ first(true),
+ split_here(false),
+ gravity(-1)
{ }
-Layout::Slot *Row::create_slot(Widget &w)
+void Row::split()
{
- Slot *slot = new Slot(*this, w);
+ split_here = true;
+ gravity = 1;
+}
- if(!slots.empty())
+void Row::process_widget(Widget &wdg, Side side, bool aligned)
+{
+ if(side==LEFT)
{
- Slot &prev = *slots.back();
- slot->constraints.push_back(Constraint(RIGHT_OF, prev));
- slot->constraints.push_back(Constraint(ALIGN_TOP, prev));
- slot->constraints.push_back(Constraint(ALIGN_BOTTOM, prev));
- if(uniform)
- slot->constraints.push_back(Constraint(COPY_WIDTH, prev));
+ bool snug = (edges[RIGHT].aligned && aligned && !split_here);
+ add_constraint(wdg, (snug ? Layout::RIGHT_OF : Layout::FAR_RIGHT_OF), RIGHT);
+ if(first)
+ edges[LEFT].add(wdg, aligned);
}
- slot->vert_pack.expand = true;
+ else if(side==RIGHT)
+ next_right.add(wdg, (aligned && gravity>0));
+ else
+ {
+ if(edges[side].aligned && aligned)
+ add_constraint(wdg, (side==TOP ? Layout::ALIGN_TOP : Layout::ALIGN_BOTTOM), side);
+ edges[side].add(wdg, aligned);
+ }
+}
- return slot;
+void Row::finish_widget(Widget &wdg)
+{
+ layout.set_gravity(wdg, gravity, 1);
+}
+
+void Row::finish_slot()
+{
+ edges[RIGHT] = next_right;
+ next_right.clear();
+ split_here = false;
+ first = false;
}
} // namespace GLtk
#ifndef MSP_GLTK_ROW_H_
#define MSP_GLTK_ROW_H_
-#include "layout.h"
+#include "arrangement.h"
namespace Msp {
namespace GLtk {
-/**
-Arranges widgets in a single row.
-*/
-class Row: public Layout
+class Row: public Arrangement
{
private:
- bool uniform;
+ Edge next_right;
+ bool first;
+ bool split_here;
+ int gravity;
public:
- Row(bool = false);
+ Row(Layout &);
+
+ void split();
private:
- virtual Slot *create_slot(Widget &);
+ virtual void process_widget(Widget &, Side, bool);
+ virtual void finish_widget(Widget &);
+ virtual void finish_slot();
};
} // namespace GLtk