#include <algorithm>
#include <limits>
+#include <msp/core/maputils.h>
#include <msp/strings/format.h>
#include "arrangement.h"
#include "container.h"
row_spacing(5),
col_spacing(4)
{
- n_slack_constraints[0] = 0;
- n_slack_constraints[1] = 0;
+ n_slack_vars[0] = 0;
+ n_slack_vars[1] = 0;
}
Layout::~Layout()
void Layout::update_slot_indices()
{
n_active_slots = 0;
+ unsigned n_floating = 0;
for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
{
- if((*i)->widget.is_visible())
+ if((*i)->widget.is_visible() || (*i)->ghost)
+ {
(*i)->index = n_active_slots++;
+ if((*i)->floating)
+ ++n_floating;
+ }
else
(*i)->index = -1;
}
- n_slack_constraints[0] = 0;
- n_slack_constraints[1] = 0;
+ n_slack_vars[0] = n_floating*2;
+ n_slack_vars[1] = n_floating*2;
for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
if((*i)->index>=0)
{
+ if(!(*i)->floating)
+ {
+ for(unsigned j=0; j<2; ++j)
+ if(((*i)->*(pointers[j].packing)).gravity==0)
+ n_slack_vars[j] += 2;
+ }
+
for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
if(j->target.index>(*i)->index && (j->type&SLACK))
- ++n_slack_constraints[j->type&1];
+ ++n_slack_vars[j->type&1];
}
}
void Layout::create_constraint(Widget &src, ConstraintType type, Widget &tgt, int sp)
{
if(&src==&tgt)
- throw invalid_argument("&src==&tgt");
+ throw invalid_argument("Layout::create_constraint");
Slot &src_slot = get_slot_for_widget(src);
Slot &tgt_slot = get_slot_for_widget(tgt);
slot.horiz_pack.gravity = h;
slot.vert_pack.gravity = v;
+ update_slot_indices();
update();
}
update();
}
+void Layout::set_ghost(Widget &wdg, bool g)
+{
+ Slot &slot = get_slot_for_widget(wdg);
+
+ slot.ghost = g;
+
+ if(!wdg.is_visible())
+ {
+ update_slot_indices();
+ update();
+ }
+}
+
+void Layout::set_floating(Widget &wdg, bool f)
+{
+ Slot &slot = get_slot_for_widget(wdg);
+
+ slot.floating = f;
+
+ update_slot_indices();
+ update();
+}
+
void Layout::update()
{
solve_constraints(HORIZONTAL, UPDATE);
(*i)->widget.set_geometry((*i)->geom);
}
-void Layout::autosize()
+void Layout::autosize(Geometry &geom)
{
solve_constraints(HORIZONTAL, AUTOSIZE);
solve_constraints(VERTICAL, AUTOSIZE);
- container->set_size(autosize_geom.w, autosize_geom.h);
+ geom.w = max(geom.w, autosize_geom.w);
+ geom.h = max(geom.h, autosize_geom.h);
}
void Layout::solve_constraints(int dir, SolveMode mode)
five columns for each widget, and one constant column. The first and second
columns of a widget are its position and dimension, respectively. The
remaining three are slack columns; see below for their purposes. */
- LinearProgram linprog(n_active_slots*5+n_slack_constraints[dir]+1);
- float weight = slots.size();
+ LinearProgram linprog(n_active_slots*5+n_slack_vars[dir]+1);
+ float weight = slots.size()+1;
+ unsigned k = n_active_slots*5;
for(list<Slot *>::iterator i=slots.begin(); i!=slots.end(); ++i)
{
if((*i)->index<0)
}
else
{
- objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
+ if(!(*i)->floating)
+ objective[(*i)->index*5] = ((*i)->*(ptrs.packing)).gravity/weight;
objective[(*i)->index*5+1] = (((*i)->*(ptrs.packing)).expand ? weight : -1);
}
row.back() = geom.*(ptrs.dim)-margin.*(ptrs.high_margin);
}
- if(((*i)->*(ptrs.packing)).gravity==0)
+ if((*i)->floating || ((*i)->*(ptrs.packing)).gravity==0)
{
- /* This forces the widget's distance from the left and right edge of
- the container to be equal. It's a bit of a hack, but more time and
- thought is needed for a better solution. */
+ /* Try to keep the widget as close to a target position as possible.
+ Since linear programs can't express absolute values directly, use two
+ opposing slack variables that are optimized for a low value. */
+ float a = ((*i)->*(ptrs.packing)).gravity*0.5+0.5;
LinearProgram::Row row = linprog.add_row();
- row[(*i)->index*5+2] = 1;
- row[(*i)->index*5+3] = -1;
+ row[(*i)->index*5] = 1;
+ row[(*i)->index*5+1] = a;
+ row[k] = 1;
+ row[k+1] = -1;
+ if((*i)->floating)
+ {
+ const Geometry &cgeom = (*i)->widget.get_geometry();
+ row.back() = cgeom.*(ptrs.pos)+cgeom.*(ptrs.dim)*a;
+ }
+ else
+ row.back() = geom.*(ptrs.dim)/2;
+ objective[k] = -1;
+ objective[k+1] = -1;
+ k += 2;
}
{
/* Add rows for user-defined constraints. Constraints are always added
in pairs, so it's only necessary to create a row for one half. */
- unsigned k = n_active_slots*5;
for(list<Constraint>::iterator j=(*i)->constraints.begin(); j!=(*i)->constraints.end(); ++j)
if(j->target.index>(*i)->index && (j->type&1)==dir)
{
LinearProgram::Row row = linprog.add_row();
float polarity = ((j->type&SELF_DIM) ? -1 : 1);
+ float dim_weight = ((j->type&HALF_DIM) ? 0.5f : 1);
if(j->type&SELF_POS)
row[(*i)->index*5] = polarity;
if(j->type&SELF_DIM)
- row[(*i)->index*5+1] = polarity;
+ row[(*i)->index*5+1] = polarity*dim_weight;
if(j->type&TARGET_POS)
row[j->target.index*5] = -polarity;
if(j->type&TARGET_DIM)
- row[j->target.index*5+1] = -polarity;
+ row[j->target.index*5+1] = -polarity*dim_weight;
if(j->type&SPACING)
row.back() = (j->spacing>=0 ? j->spacing : this->*(ptrs.spacing));
if(j->type&SLACK)
Layout::Slot::Slot(Layout &l, Widget &w):
layout(l),
index(0),
- widget(w)
+ widget(w),
+ ghost(false),
+ floating(false)
{
vert_pack.gravity = 1;
widget.signal_autosize_changed.connect(sigc::mem_fun(this, &Slot::autosize_changed));
widget.signal_visibility_changed.connect(sigc::mem_fun(this, &Slot::visibility_changed));
- widget.autosize();
- autosize_geom = widget.get_geometry();
+ widget.autosize(autosize_geom);
}
void Layout::Slot::autosize_changed()
{
- widget.autosize();
- autosize_geom = widget.get_geometry();
+ widget.autosize(autosize_geom);
- if(!widget.is_visible())
+ if(!widget.is_visible() && !ghost)
return;
- // If the widget fits in the area it had, just leave it there.
- if(autosize_geom.w<=geom.w && autosize_geom.h<=geom.h)
- widget.set_geometry(geom);
- else
+ // Only trigger an update if the widget won't fit in its current area.
+ if(autosize_geom.w>geom.w || autosize_geom.h>geom.h)
{
layout.container->signal_autosize_changed.emit();
layout.update();
void Layout::Slot::visibility_changed(bool v)
{
layout.update_slot_indices();
- if(v)
+ if(v || ghost)
{
layout.container->signal_autosize_changed.emit();
layout.update();
}
+Layout::Loader::Loader(Layout &l, const WidgetMap &wm):
+ DataFile::ObjectLoader<Layout>(l),
+ wdg_map(wm)
+{
+ add("column_spacing", &Loader::column_spacing);
+ add("margin", &Loader::margin);
+ add("row_spacing", &Loader::row_spacing);
+ add("spacing", &Loader::spacing);
+ add("widget", &Loader::widget);
+}
+
+void Layout::Loader::column_spacing(unsigned s)
+{
+ obj.set_column_spacing(s);
+}
+
+void Layout::Loader::margin()
+{
+ Sides sides;
+ load_sub(sides);
+ obj.set_margin(sides);
+}
+
+void Layout::Loader::spacing(unsigned s)
+{
+ obj.set_spacing(s);
+}
+
+void Layout::Loader::row_spacing(unsigned s)
+{
+ obj.set_row_spacing(s);
+}
+
+void Layout::Loader::widget(const string &n)
+{
+ Widget &wdg = *get_item(wdg_map, n);
+ WidgetLoader ldr(obj, wdg, wdg_map);
+ load_sub_with(ldr);
+}
+
+
+Layout::WidgetLoader::WidgetLoader(Layout &l, Widget &w, const Layout::Loader::WidgetMap &wm):
+ layout(l),
+ widget(w),
+ wdg_map(wm)
+{
+ add("constraint", &WidgetLoader::constraint);
+ add("expand", &WidgetLoader::expand);
+ add("ghost", &WidgetLoader::ghost);
+ add("gravity", &WidgetLoader::gravity);
+}
+
+void Layout::WidgetLoader::constraint(ConstraintType type, const string &n)
+{
+ Widget &target = *get_item(wdg_map, n);
+ layout.add_constraint(widget, type, target);
+}
+
+void Layout::WidgetLoader::expand(bool h, bool v)
+{
+ layout.set_expand(widget, h, v);
+}
+
+void Layout::WidgetLoader::ghost(bool g)
+{
+ layout.set_ghost(widget, g);
+}
+
+void Layout::WidgetLoader::gravity(int h, int v)
+{
+ layout.set_gravity(widget, h, v);
+}
+
+
void operator>>(const LexicalConverter &conv, Layout::ConstraintType &ctype)
{
const string &str = conv.get();
ctype = Layout::FAR_LEFT_OF;
else if(str=="ALIGN_TOP")
ctype = Layout::ALIGN_TOP;
+ else if(str=="ALIGN_VCENTER")
+ ctype = Layout::ALIGN_VCENTER;
else if(str=="ALIGN_BOTTOM")
ctype = Layout::ALIGN_BOTTOM;
else if(str=="ALIGN_RIGHT")
ctype = Layout::ALIGN_RIGHT;
+ else if(str=="ALIGN_HCENTER")
+ ctype = Layout::ALIGN_HCENTER;
else if(str=="ALIGN_LEFT")
ctype = Layout::ALIGN_LEFT;
else if(str=="COPY_WIDTH")