...
The order of priority for same-named files are: JPG > TGA > PNG > GIF > WBM > DDS
Layout Information
The cassowary implementation in use: https://github.com/nucleic/kiwi
The nwn-side constraints it sets up for layouting are not particularly efficient, but at least they work: https://gist.github.com/niv/9bf60ca649fdf6709c17886a1a467755
| Code Block | ||||||
|---|---|---|---|---|---|---|
| ||||||
std::vector<Constraint> Layoutable::BuildConstraints(const LayoutExpression& layout) const
{
INSTR_SCOPE();
INSTR_SCOPE_PROP_STR("m_id", m_id.c_str());
std::vector<Constraint> ret;
for (auto& c : m_constraint_applicators)
{
const auto mod = c(*this, layout);
assert(!mod.empty());
ret.insert(ret.end(), mod.begin(), mod.end());
}
ret.insert(ret.end(), m_custom_constraints.begin(), m_custom_constraints.end());
ret.insert(ret.end(), m_padding_constraints.begin(), m_padding_constraints.end());
ret.insert(ret.end(), m_margin_constraints.begin(), m_margin_constraints.end());
const auto dep = BuildConstraintDependants(layout);
ret.insert(ret.end(), dep.begin(), dep.end());
return ret;
}
void LayoutGroup::SetChildren(const std::vector<LayoutableRef>& widget)
{
if (m_children == widget)
return;
INSTR_SCOPE();
INSTR_SCOPE_PROP_STR("m_id", m_id.c_str());
for (auto q : m_children)
{
q->OnDetach(q->m_group);
}
Layoutable::SetChildren(widget);
for (auto q : m_children)
{
q->OnAttached(this);
}
// This LG received new children: We need to redo the contained layout.
// Currently, the fastest way to redo the constraints is to rebuild the solver from scratch.
// Possibly later: Optimise this by only removing/adding the constraints of the children that
// have actually changed. This can only be a win when swapping out part of the layout, and
// that's currently not supported.
m_solver.reset();
#ifdef WIP_CHILD_LG_ADVISES_PARENT
assert(!"Resetting the solver clears out all child associations. Have to redo them.");
#endif
// This is the inner layout for the solver. It always starts at (0, 0) and is
// the width and height as suggested by the parent layout group.
// This dummy layout here binds the layout to the solver wh without actually exposing
// the solver parent variables.
const LayoutExpression childLayout = {{0}, {0}, {gw}, {gh}, s_guiscale};
// We suggest layouting to the children of this group by giving them constraints
// on the inner layout. We do this via edit variables, which are updated every time
// geometry changes. The child layout can, of course, still overflow; in which case
// it will either be clipped or scrollable.
m_solver.addEditVariable(gw, medium);
m_solver.addEditVariable(gh, medium);
m_layout_constraints.clear();
m_layout_gui_scale_applied = nk_get_gui_scale();
m_layout_constraints = AddConstraints({s_guiscale == m_layout_gui_scale_applied | required});
m_child_constraints.clear();
for (auto& entry : m_children)
{
const auto cc = entry->BuildConstraints(childLayout);
m_child_constraints.insert(m_child_constraints.end(),
cc.begin(), cc.end());
}
AddConstraints(m_child_constraints);
// Reset cached data so the child entries can reflow. We use the current
// layout values here that, while not entirely accurate, are hopefully
// accurate enough to prevent a scrollbar flicker.
m_cached_content_pane_size = nk_vec2(layout.w.value(), layout.h.value());
m_solver.updateVariables();
}
std::vector<Constraint> Span::BuildConstraintDependants(const LayoutExpression& layout) const
{
INSTR_SCOPE();
INSTR_SCOPE_PROP_STR("m_id", m_id.c_str());
std::vector<Constraint> ret;
const auto primary = m_direction;
const auto secondary = m_direction == Vertical ? Horizontal : Vertical;
const auto layout_position = [layout](Direction axis) { return axis == Vertical ? layout.y : layout.x; };
const auto layout_size = [layout](Direction axis) { return axis == Vertical ? layout.h : layout.w; };
const auto widget_size = [](Direction axis, LayoutableRef q) { return axis == Vertical ? q->h : q->w; };
const auto widget_position = [](Direction axis, LayoutableRef q) { return axis == Vertical ? q->y : q->x; };
const auto widget_margin_front = [](Direction axis, LayoutableRef q) { return axis == Vertical ? q->margin.t : q->margin.l; };
const auto widget_margin_back = [](Direction axis, LayoutableRef q) { return axis == Vertical ? q->margin.b : q->margin.r; };
// The span strength is medium, and the widget strengths are strong. This means that
// a span will overflow/scrollbar, if a span with some strong widgets exceeds the layout.
const auto span_strength = medium;
// Build the child dependencies. We do this first, because we do depth-first layouting.
// Since constraints are applied all in one go, this shouldn't matter; HOWEVER,
// all mutable fields on Layoutable can be changed by this call chain. Sorry!
// This can be improved later by passing this state alongside the constraints; for now, this
// will have to do.
// These dependencies include things like |width() modifiers, which apply with optional strength.
{
for (auto& entry : m_children)
{
const auto mods = entry->BuildConstraints(entry->layout);
ret.insert(ret.end(), mods.begin(), mods.end());
}
}
// This set of constraints places the widgets within the span next to each other.
// Not negotiable, since spans have have fixed requirements:
// - Widgets dont overlap.
// - Widgets as a sum fill the whole span in both dimensions (though they can overflow).
{
for (unsigned i = 0; i < m_children.size(); i++)
{
auto& entry = m_children[i];
if (i == 0)
{
// First starts at layout offset
ret.push_back({
widget_position(primary, entry)
==
layout_position(primary) +
widget_margin_front(primary, entry)
});
}
else
{
// All further parent off the first
auto& prev = m_children[i - 1];
ret.push_back({
widget_position(primary, entry)
==
widget_position(primary, prev) +
widget_margin_front(primary, entry) +
widget_size(primary, prev) +
widget_margin_back(primary, prev)
});
}
}
}
// Since the secondary domain is also filled, all widgets start at the beginning
// of that layout axis.
{
for (auto& entry : m_children)
{
ret.push_back({
widget_position(secondary, entry)
==
layout_position(secondary) +
widget_margin_front(secondary, entry)
});
}
}
// This set of constraints ensures that the primary domain collectively
// never exceeds the layout size.
{
Expression total_primary = 0;
for (auto& entry : m_children)
{
total_primary = total_primary +
widget_size(primary, entry) +
widget_margin_front(primary, entry) +
widget_margin_back(primary, entry);
// Widgets should not outgrow the parent, if possible.
ret.push_back({
layout_size(primary)
>=
widget_size(primary, entry) +
widget_margin_front(primary, entry) +
widget_margin_back(primary, entry)
| span_strength
});
}
// We suggest that the span is the combined total of all widgets.
ret.push_back({ total_primary == layout_size(primary) | span_strength });
// Also, we suggest the total span size should not outgrow the parent.
ret.push_back({ total_primary <= layout_size(primary) | span_strength });
}
// As a convenience, we collect all spaced-equally widgets and suggest they are the same size.
// Spacers are most often used to pad-align rows; but this also works for any other element.
// These suggestions are weak: anything from widget or user overrides it.
{
std::vector<LayoutableRef> spacers;
for (auto& entry : m_children)
{
if (entry->m_spaced_equally)
{
spacers.push_back(entry);
}
}
if (spacers.size() > 1)
{
for (unsigned i = 1; i < spacers.size(); i++)
{
ret.push_back({
widget_size(primary, spacers[i])
==
widget_size(primary, spacers[i - 1])
| weak
});
}
}
}
// We do the same for the secondary domain: It can never outgrow the available layout.
// Since the seconday domain only has one entry to consider for the layout size, we
// can use it directly, instead of adding them all up like above.
{
for (auto& entry : m_children)
{
// Each widget starts at the front of the secondary axis.
// This is not negotiable.
ret.push_back({
widget_position(secondary, entry)
==
layout_position(secondary) +
widget_margin_front(secondary, entry)
});
// Widgets can never outgrow the parent. Not negotiable.
ret.push_back({
layout_size(secondary)
>=
widget_size(secondary, entry) +
widget_margin_front(secondary, entry) +
widget_margin_back(secondary, entry)
});
// And then we offer the solver the secondary for each widget, to select one
// he likes.
ret.push_back({
layout_size(secondary)
==
widget_size(secondary, entry) +
widget_margin_front(secondary, entry) +
widget_margin_back(secondary, entry)
| span_strength
});
}
}
return ret;
} |