Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • 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).

TGA can be loaded as normal, 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
languagec++
linenumberstrue
collapsetrue
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;
}