From: Mikko Rasa Date: Mon, 28 Feb 2011 15:29:05 +0000 (+0000) Subject: Add a Layout class to automatically position widgets within a Panel X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=01eaaef90f4c56cd4669d2f621e3061a938753eb;p=libs%2Fgltk.git Add a Layout class to automatically position widgets within a Panel --- diff --git a/source/layout.cpp b/source/layout.cpp new file mode 100644 index 0000000..3501948 --- /dev/null +++ b/source/layout.cpp @@ -0,0 +1,497 @@ +#include +#include "container.h" +#include "layout.h" +#include "widget.h" + +using namespace std; + +namespace Msp { +namespace GLtk { + +class Layout::LinearProgram +{ +public: + class Row + { + private: + LinearProgram &linprog; + unsigned index; + + public: + Row(LinearProgram &, unsigned); + + float &operator[](unsigned); + float &back(); + }; + +private: + struct Column + { + unsigned basic; + std::vector values; + + Column(); + }; + + unsigned n_columns; + unsigned n_rows; + std::vector columns; + bool solved; + bool infeasible; + +public: + LinearProgram(unsigned); + + Row add_row(); + Row operator[](unsigned); + Row get_object_row(); + bool solve(); + float get_variable(unsigned); +private: + unsigned find_minimal_ratio(unsigned); + void make_basic_column(unsigned, unsigned); + bool pivot(); +}; + + +struct Layout::Pointers +{ + int Geometry::*pos; + unsigned Geometry::*dim; + Packing Slot::*packing; + unsigned Sides::*low_margin; + unsigned Sides::*high_margin; + unsigned Layout::*spacing; +}; + +Layout::Pointers Layout::pointers[2] = +{ { + &Geometry::x, &Geometry::w, + &Slot::horiz_pack, + &Sides::left, &Sides::right, + &Layout::col_spacing + }, { + &Geometry::y, &Geometry::h, + &Slot::vert_pack, + &Sides::bottom, &Sides::top, + &Layout::row_spacing +} }; + + +Layout::Layout(): + container(0), + margin(8), + row_spacing(5), + col_spacing(4) +{ } + +Layout::~Layout() +{ + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + delete *i; +} + +void Layout::set_container(Container &c) +{ + if(container) + throw InvalidState("This layout is already assigned to a Container"); + + container = &c; +} + +void Layout::set_margin(const Sides &m) +{ + margin = m; + if(container) + update(); +} + +void Layout::add_widget(Widget &wdg) +{ + if(!container) + throw InvalidState("Can't add Widgets without a 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)); + slot->index = slots.size(); + slots.push_back(slot); + if(container) + update(); +} + +void Layout::remove_widget(Widget &wdg) +{ + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + if(&(*i)->widget==&wdg) + { + for(list::iterator j=slots.begin(); j!=slots.end(); ++j) + if(j!=i) + { + for(list::iterator k=(*j)->constraints.begin(); k!=(*j)->constraints.end(); ) + { + if(&k->target==*i) + (*j)->constraints.erase(k++); + else + ++k; + } + } + + delete *i; + slots.erase(i); + + unsigned n = 0; + for(i=slots.begin(); i!=slots.end(); ++i, ++n) + (*i)->index = n; + + update(); + return; + } +} + +Layout::Slot *Layout::create_slot(Widget &wdg) +{ + return new Slot(*this, wdg); +} + +Layout::Slot &Layout::get_slot_for_widget(Widget &wdg) +{ + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + if(&(*i)->widget==&wdg) + return **i; + + throw InvalidParameterValue("Widget is not in the Layout"); +} + +Layout::ConstraintType Layout::complement(ConstraintType type) +{ + if(type==RIGHT_OF) + return LEFT_OF; + else if(type==LEFT_OF) + return RIGHT_OF; + else if(type==ABOVE) + return BELOW; + else if(type==BELOW) + return ABOVE; + else + return type; +} + +void Layout::add_constraint(Widget &src, ConstraintType type, Widget &tgt) +{ + Slot &src_slot = get_slot_for_widget(src); + Slot &tgt_slot = get_slot_for_widget(tgt); + + for(list::iterator i=src_slot.constraints.begin(); i!=src_slot.constraints.end(); ++i) + if(i->type==type && &i->target==&tgt_slot) + return; + + src_slot.constraints.push_back(Constraint(type, tgt_slot)); + tgt_slot.constraints.push_back(Constraint(complement(type), src_slot)); + + update(); +} + +void Layout::set_gravity(Widget &wdg, int h, int v) +{ + Slot &slot = get_slot_for_widget(wdg); + + slot.horiz_pack.gravity = h; + slot.vert_pack.gravity = v; + + update(); +} + +void Layout::set_expand(Widget &wdg, bool h, bool v) +{ + Slot &slot = get_slot_for_widget(wdg); + + slot.horiz_pack.expand = h; + slot.vert_pack.expand = v; + + update(); +} + +void Layout::update() +{ + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + { + (*i)->widget.autosize(); + (*i)->geom = (*i)->widget.get_geometry(); + } + + solve_constraints(HORIZONTAL); + solve_constraints(VERTICAL); + + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + (*i)->widget.set_geometry((*i)->geom); + +} + +void Layout::solve_constraints(int dir) +{ + Pointers &ptrs = pointers[dir&VERTICAL]; + + LinearProgram linprog(slots.size()*5+1); + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + { + linprog.get_object_row()[(*i)->index*5] = 0.1*((*i)->*(ptrs.packing)).gravity; + linprog.get_object_row()[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? 5 : -1); + + { + LinearProgram::Row row = linprog.add_row(); + row[(*i)->index*5] = 1; + row[(*i)->index*5+2] = -1; + row.back() = margin.*(ptrs.low_margin); + } + + { + LinearProgram::Row row = linprog.add_row(); + row[(*i)->index*5] = 1; + row[(*i)->index*5+1] = 1; + row[(*i)->index*5+3] = 1; + row.back() = container->get_geometry().*(ptrs.dim)-margin.*(ptrs.high_margin); + } + + { + LinearProgram::Row row = linprog.add_row(); + row[(*i)->index*5+1] = 1; + row[(*i)->index*5+4] = -1; + row.back() = (*i)->geom.*(ptrs.dim); + } + + for(list::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j) + { + if((j->type&1)==dir && j->type!=BELOW && j->type!=LEFT_OF) + { + LinearProgram::Row row = linprog.add_row(); + if(j->type&SELF_POS) + row[(*i)->index*5] = 1; + if(j->type&SELF_DIM) + row[(*i)->index*5+1] = 1; + if(j->type&TARGET_POS) + row[j->target.index*5] = -1; + if(j->type&TARGET_DIM) + row[j->target.index*5+1] = -1; + if(j->type&SPACING) + row.back() = this->*(ptrs.spacing); + } + } + } + + if(!linprog.solve()) + return; + + for(list::iterator i=slots.begin(); i!=slots.end(); ++i) + { + (*i)->geom.*(ptrs.pos) = linprog.get_variable((*i)->index*5); + (*i)->geom.*(ptrs.dim) = linprog.get_variable((*i)->index*5+1); + } +} + + +Layout::Constraint::Constraint(ConstraintType t, Slot &s): + type(t), + target(s) +{ } + + +Layout::Packing::Packing(): + gravity(-1), + expand(false) +{ } + + +Layout::Slot::Slot(Layout &l, Widget &w): + layout(l), + index(0), + widget(w) +{ + vert_pack.gravity = 1; + widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed)); +} + +void Layout::Slot::autosize_changed() +{ + layout.update(); +} + + +Layout::LinearProgram::LinearProgram(unsigned s): + n_columns(s), + n_rows(1), + columns(n_columns), + solved(false), + infeasible(false) +{ } + +Layout::LinearProgram::Row Layout::LinearProgram::add_row() +{ + return Row(*this, n_rows++); +} + +Layout::LinearProgram::Row Layout::LinearProgram::operator[](unsigned r) +{ + if(r>=n_rows) + throw InvalidParameterValue("Row index out of range"); + + return Row(*this, r); +} + +Layout::LinearProgram::Row Layout::LinearProgram::get_object_row() +{ + return Row(*this, 0); +} + +float Layout::LinearProgram::get_variable(unsigned i) +{ + if(!solved || infeasible) + throw InvalidState("Not solved"); + if(i+1>=n_columns) + throw InvalidParameterValue("Variable index out of range"); + + unsigned r = columns[i].basic; + return columns.back().values[r]; +} + +bool Layout::LinearProgram::solve() +{ + if(solved || infeasible) + return !infeasible; + + // Force all columns fully into existence and relocate objective row to bottom + for(vector::iterator i=columns.begin(); i!=columns.end(); ++i) + { + float objective = i->values.front(); + i->values.front() = 0.0f; + for(vector::iterator j=i->values.begin(); j!=i->values.end(); ++j) + i->values.front() += *j; + i->values.resize(n_rows+1, 0.0f); + i->values.back() = objective; + } + + // Create artificial variables for phase 1 + columns.resize(n_columns+n_rows-1); + columns.back() = columns[n_columns-1]; + columns[n_columns-1].values.clear(); + for(unsigned i=1; i::iterator i=columns.begin(); i!=columns.end(); ++i) + if(!i->basic) + { + i->values.front() = i->values.back(); + i->values.pop_back(); + } + + while(pivot()) ; + + solved = true; + + return true; +} + +unsigned Layout::LinearProgram::find_minimal_ratio(unsigned c) +{ + float best = numeric_limits::infinity(); + unsigned row = 0; + /* Intentionally use n_rows since we need to ignore the relocated original + objective row in phase 1 */ + for(unsigned i=1; i0) + { + float ratio = columns.back().values[i]/columns[c].values[i]; + if(ratio0) + if(unsigned row = find_minimal_ratio(i)) + { + make_basic_column(i, row); + return true; + } + + return false; +} + + +Layout::LinearProgram::Row::Row(LinearProgram &lp, unsigned i): + linprog(lp), + index(i) +{ } + +float &Layout::LinearProgram::Row::operator[](unsigned c) +{ + if(c>=linprog.n_columns) + throw InvalidParameterValue("Column index out of range"); + + Column &column = linprog.columns[c]; + if(column.values.size()<=index) + column.values.resize(index+1); + + return column.values[index]; +} + +float &Layout::LinearProgram::Row::back() +{ + return (*this)[linprog.n_columns-1]; +} + + +Layout::LinearProgram::Column::Column(): + basic(0) +{ } + +} // namespace GLtk +} // namespace Msp diff --git a/source/layout.h b/source/layout.h new file mode 100644 index 0000000..540c5ee --- /dev/null +++ b/source/layout.h @@ -0,0 +1,122 @@ +#ifndef MSP_GLTK_LAYOUT_H_ +#define MSP_GLTK_LAYOUT_H_ + +#include +#include +#include +#include "geometry.h" + +namespace Msp { +namespace GLtk { + +class Container; +class Widget; + +class Layout +{ +private: + enum + { + HORIZONTAL = 0, + VERTICAL = 1, + SELF_POS = 2, + SELF_DIM = 4, + TARGET_POS = 8, + TARGET_DIM = 16, + SPACING = 32 + }; + +public: + enum ConstraintType + { + ABOVE = VERTICAL|SELF_POS|TARGET_POS|TARGET_DIM|SPACING, + BELOW = VERTICAL|SELF_POS|SELF_DIM|TARGET_POS|SPACING, + RIGHT_OF = HORIZONTAL|SELF_POS|TARGET_POS|TARGET_DIM|SPACING, + LEFT_OF = HORIZONTAL|SELF_POS|SELF_DIM|TARGET_POS|SPACING, + ALIGN_TOP = VERTICAL|SELF_POS|SELF_DIM|TARGET_POS|TARGET_DIM, + ALIGN_BOTTOM = VERTICAL|SELF_POS|TARGET_POS, + ALIGN_RIGHT = HORIZONTAL|SELF_POS|SELF_DIM|TARGET_POS|TARGET_DIM, + ALIGN_LEFT = HORIZONTAL|SELF_POS|TARGET_POS, + COPY_WIDTH = HORIZONTAL|SELF_DIM|TARGET_DIM, + COPY_HEIGHT = VERTICAL|SELF_DIM|TARGET_DIM + }; + +protected: + struct Slot; + + struct Constraint + { + ConstraintType type; + Slot ⌖ + + Constraint(ConstraintType, Slot &); + }; + + struct Packing + { + int gravity; + bool expand; + + Packing(); + }; + + struct Slot: public sigc::trackable + { + Layout &layout; + unsigned index; + Widget &widget; + Geometry geom; + std::list constraints; + Packing horiz_pack; + Packing vert_pack; + + Slot(Layout &, Widget &); + virtual ~Slot() { } + + void autosize_changed(); + }; + + class LinearProgram; + struct Pointers; + + Container *container; + std::list slots; + Sides margin; + unsigned row_spacing; + unsigned col_spacing; + + static Pointers pointers[2]; + +public: + Layout(); + virtual ~Layout(); + + void set_container(Container &); + void set_margin(const Sides &); + void set_spacing(unsigned); + void set_row_spacing(unsigned); + void set_column_spacing(unsigned); + + void add_widget(Widget &); + void remove_widget(Widget &); +protected: + virtual Slot *create_slot(Widget &); + Slot &get_slot_for_widget(Widget &); + static ConstraintType complement(ConstraintType); +public: + void add_constraint(Widget &, ConstraintType, Widget &); + void set_gravity(Widget &, int, int); + void set_expand(Widget &, bool, bool); + + void update(); + +protected: + void find_constraint_group(Slot &, ConstraintType, std::set &); + void sort_slots(std::list &, ConstraintType); + void solve_constraints(int); +}; + +} // namespace GLtk +} // namespace Msp + +#endif diff --git a/source/panel.cpp b/source/panel.cpp index 0ecdf10..a1a453a 100644 --- a/source/panel.cpp +++ b/source/panel.cpp @@ -13,6 +13,7 @@ Distributed under the LGPL #include "hslider.h" #include "indicator.h" #include "label.h" +#include "layout.h" #include "list.h" #include "panel.h" #include "part.h" @@ -26,11 +27,25 @@ namespace Msp { namespace GLtk { Panel::Panel(): + layout(0), pointer_focus(0), pointer_grabbed(false), input_focus(0) { } +Panel::~Panel() +{ + delete layout; + layout = 0; +} + +void Panel::set_layout(Layout *l) +{ + l->set_container(*this); + delete layout; + layout = l; +} + Panel::Child *Panel::create_child(Widget *wdg) { return new Child(*this, wdg); @@ -134,6 +149,19 @@ void Panel::key_release(unsigned key, unsigned mod) void Panel::focus_out() { set_input_focus(0); + Widget::focus_out(); +} + +void Panel::on_child_added(Widget &wdg) +{ + if(layout) + layout->add_widget(wdg); +} + +void Panel::on_child_removed(Widget &wdg) +{ + if(layout) + layout->remove_widget(wdg); } void Panel::set_pointer_focus(Widget *wdg) @@ -230,6 +258,13 @@ void Panel::Child::visibility_changed(bool v) } } +void Panel::Child::autosize_changed() +{ + Panel &panel = static_cast(container); + if(panel.layout) + panel.layout->update(); +} + void Panel::Child::request_focus() { Panel &panel = static_cast(container); diff --git a/source/panel.h b/source/panel.h index 54e75ef..a5483c4 100644 --- a/source/panel.h +++ b/source/panel.h @@ -13,6 +13,8 @@ Distributed under the LGPL namespace Msp { namespace GLtk { +class Layout; + /** Panels are containers for other widgets. Panel styles should have a special part "children" to render the child widgets. All properties of this part are @@ -45,12 +47,14 @@ protected: virtual ~Child(); void visibility_changed(bool); + void autosize_changed(); void request_focus(); void grab_pointer(); void ungrab_pointer(); void raise(); }; + Layout *layout; Widget *pointer_focus; bool pointer_grabbed; Widget *input_focus; @@ -59,9 +63,12 @@ protected: Panel &operator=(const Panel &); public: Panel(); + virtual ~Panel(); virtual const char *get_class() const { return "panel"; } + void set_layout(Layout *); + protected: virtual Child *create_child(Widget *); @@ -81,8 +88,10 @@ public: virtual void key_press(unsigned, unsigned, wchar_t); virtual void key_release(unsigned, unsigned); virtual void focus_out(); - protected: + virtual void on_child_added(Widget &); + virtual void on_child_removed(Widget &); + void set_pointer_focus(Widget *); void set_input_focus(Widget *); };