]> git.tdb.fi Git - libs/gltk.git/commitdiff
Replace the derived layout classes with a more flexible design
authorMikko Rasa <tdb@tdb.fi>
Fri, 14 Jun 2013 20:32:37 +0000 (23:32 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 14 Jun 2013 20:32:37 +0000 (23:32 +0300)
The new Arrangement interface allows multiple arrangements to be used in
a single layout.  They can even be nested, with a sub-arrangement acting
like a single slot of its parent.

12 files changed:
source/arrangement.cpp [new file with mode: 0644]
source/arrangement.h [new file with mode: 0644]
source/column.cpp
source/column.h
source/grid.cpp
source/grid.h
source/layout.cpp
source/layout.h
source/mixedrows.cpp [deleted file]
source/mixedrows.h [deleted file]
source/row.cpp
source/row.h

diff --git a/source/arrangement.cpp b/source/arrangement.cpp
new file mode 100644 (file)
index 0000000..ec890ef
--- /dev/null
@@ -0,0 +1,75 @@
+#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
diff --git a/source/arrangement.h b/source/arrangement.h
new file mode 100644 (file)
index 0000000..284d3f7
--- /dev/null
@@ -0,0 +1,58 @@
+#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
index 3a7a149f98ebce3a524cfd25538883d64cb75108..6c225ee994a52171fbc041a3e13c499b068b6702 100644 (file)
@@ -3,26 +3,49 @@
 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
index 0e5f0040364d5c52055e1e44112670e84acb824a..c7efd3b503e63769477a92c85eadc3384fa31650 100644 (file)
@@ -1,24 +1,28 @@
 #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
index fb08823817db11fa7b04da88b46ad5169fb45e70..b3e9c43c311898896638e267d13edcdc88cba1a7 100644 (file)
@@ -1,74 +1,87 @@
 #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
index 519151eace3be77a90be986c92b5026bf7c8862d..0e71748b6a71bdd84da98bdd31c8bfa3957094c9 100644 (file)
@@ -1,38 +1,37 @@
 #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
index 993c82f42c7ce0d8f683065d9cf4d01f127cd895..c41dcb87b68172236b1bb67155b48990ca79609e 100644 (file)
@@ -1,5 +1,6 @@
 #include <algorithm>
 #include <limits>
+#include "arrangement.h"
 #include "container.h"
 #include "layout.h"
 #include "widget.h"
@@ -136,16 +137,45 @@ void Layout::set_column_spacing(unsigned s)
                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();
 }
@@ -176,11 +206,6 @@ void Layout::remove_widget(Widget &wdg)
                }
 }
 
-Layout::Slot *Layout::create_slot(Widget &wdg)
-{
-       return new Slot(*this, wdg);
-}
-
 void Layout::update_slot_indices()
 {
        n_active_slots = 0;
index 2bddb77cfa30bef43632036b8e2564e41e59a06e..85d64ae4d64f4f670365a58fbc921cb7406660a6 100644 (file)
@@ -9,6 +9,7 @@
 namespace Msp {
 namespace GLtk {
 
+class Arrangement;
 class Container;
 class Widget;
 
@@ -44,9 +45,9 @@ flag for a widget causes it to use as much space as possible.  If multiple co-
 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
 {
@@ -84,7 +85,7 @@ public:
                COPY_HEIGHT = VERTICAL|SELF_DIM|TARGET_DIM
        };
 
-protected:
+private:
        struct Slot;
 
        struct Constraint
@@ -116,7 +117,6 @@ protected:
                Packing vert_pack;
 
                Slot(Layout &, Widget &);
-               virtual ~Slot() { }
 
                void autosize_changed();
                void visibility_changed(bool);
@@ -139,12 +139,13 @@ protected:
        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 &);
@@ -160,10 +161,13 @@ public:
        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);
@@ -183,7 +187,7 @@ public:
        void update();
        void autosize();
 
-protected:
+private:
        void solve_constraints(int, SolveMode);
 };
 
diff --git a/source/mixedrows.cpp b/source/mixedrows.cpp
deleted file mode 100644 (file)
index 7637020..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#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
diff --git a/source/mixedrows.h b/source/mixedrows.h
deleted file mode 100644 (file)
index 943bb3a..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#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
index 8eb6bd4ac70444679b65cb843427c505a841ca36..fc07cdbf25cb1c92cd56ca7e236edf9cb99c922f 100644 (file)
@@ -3,26 +3,49 @@
 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
index adc2a6e966c963cf4a119d049011ceac1468fc8c..6391008293ecb052439bca810614e1cf8d308c4e 100644 (file)
@@ -1,24 +1,28 @@
 #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