...
- PNG - standard high quality lossless compressed image format
- GIF - can be animated, but otherwise a lossy compressed image format
- JPG - JPEG file a lossy photo-like image file, but much lower size then PNG or TGA/DDS
- WBM - WebM files, technically video so can be animated, but won't play sound.
- DDS - Added in 8193.36. Loaded only if no other valid files are found (so loaded last).
DDS and TGA can be loaded as normal. PLT are not supported., PLT is not supported however (if a DDS version of a TGA file exists, for instance, it just won't ever find the DDS version and loads the TGA one instead).
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;
} |