From 646af3dd91a6ca981572a5b69a0f0bd205d9af49 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 14 Jun 2013 23:32:37 +0300 Subject: [PATCH] Replace the derived layout classes with a more flexible design 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. --- source/arrangement.cpp | 75 +++++++++++++++++++++++++++++ source/arrangement.h | 58 +++++++++++++++++++++++ source/column.cpp | 49 ++++++++++++++----- source/column.h | 20 ++++---- source/grid.cpp | 105 +++++++++++++++++++++++------------------ source/grid.h | 41 ++++++++-------- source/layout.cpp | 43 +++++++++++++---- source/layout.h | 22 +++++---- source/mixedrows.cpp | 68 -------------------------- source/mixedrows.h | 49 ------------------- source/row.cpp | 49 ++++++++++++++----- source/row.h | 20 ++++---- 12 files changed, 355 insertions(+), 244 deletions(-) create mode 100644 source/arrangement.cpp create mode 100644 source/arrangement.h delete mode 100644 source/mixedrows.cpp delete mode 100644 source/mixedrows.h diff --git a/source/arrangement.cpp b/source/arrangement.cpp new file mode 100644 index 0000000..ec890ef --- /dev/null +++ b/source/arrangement.cpp @@ -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(i), true); + finish_widget(wdg); + finish_slot(); +} + +void Arrangement::arrange(Arrangement &arr) +{ + for(unsigned i=0; i<4; ++i) + { + Side side = static_cast(i); + const Edge &edge = arr.get_edge(side); + for(list::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::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 index 0000000..284d3f7 --- /dev/null +++ b/source/arrangement.h @@ -0,0 +1,58 @@ +#ifndef MSP_GLTK_ARRANGEMENT_H_ +#define MSP_GLTK_ARRANGEMENT_H_ + +#include +#include "layout.h" + +namespace Msp { +namespace GLtk { + +class Widget; + +class Arrangement +{ +protected: + enum Side + { + TOP, + RIGHT, + BOTTOM, + LEFT + }; + + struct Edge + { + std::list 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 diff --git a/source/column.cpp b/source/column.cpp index 3a7a149..6c225ee 100644 --- a/source/column.cpp +++ b/source/column.cpp @@ -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 diff --git a/source/column.h b/source/column.h index 0e5f004..c7efd3b 100644 --- a/source/column.h +++ b/source/column.h @@ -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 diff --git a/source/grid.cpp b/source/grid.cpp index fb08823..b3e9c43 100644 --- a/source/grid.cpp +++ b/source/grid.cpp @@ -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_columnconstraints.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 diff --git a/source/grid.h b/source/grid.h index 519151e..0e71748 100644 --- a/source/grid.h +++ b/source/grid.h @@ -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 columns; - unsigned cur_column; - bool first; - bool skipped; + struct Column + { + Edge left; + Edge right; + }; + + std::vector 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 diff --git a/source/layout.cpp b/source/layout.cpp index 993c82f..c41dcb8 100644 --- a/source/layout.cpp +++ b/source/layout.cpp @@ -1,5 +1,6 @@ #include #include +#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::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::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; diff --git a/source/layout.h b/source/layout.h index 2bddb77..85d64ae 100644 --- a/source/layout.h +++ b/source/layout.h @@ -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_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 index 7637020..0000000 --- a/source/mixedrows.cpp +++ /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 index 943bb3a..0000000 --- a/source/mixedrows.h +++ /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 diff --git a/source/row.cpp b/source/row.cpp index 8eb6bd4..fc07cdb 100644 --- a/source/row.cpp +++ b/source/row.cpp @@ -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 diff --git a/source/row.h b/source/row.h index adc2a6e..6391008 100644 --- a/source/row.h +++ b/source/row.h @@ -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 -- 2.43.0