<root> / Track.cpp (8790820e53bec51840093e1db093b9cde8d9aeb2) (5,805B) (mode 100644) [raw]
// libcursed -- C++ classes for dealing with curses
// Copyright (C) 2019 xaizek <xaizek@posteo.net>
//
// This file is part of libcursed.
//
// libcursed is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libcursed is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libcursed.  If not, see <https://www.gnu.org/licenses/>.

#include "Track.hpp"

#include <algorithm>
#include <vector>

#include "guts/Pos.hpp"
#include "guts/Size.hpp"

using namespace cursed;
using namespace cursed::guts;

static std::vector<int> doLayout(std::vector<int> lengths, int max);
static int calculateDesired(const std::vector<int> &lengths);

// Boundary to narrow range of values to avoid integer overflows.
constexpr int Range = 10000;

Track::Track(Orientation orientation) : orientation(orientation)
{ }

void
Track::addItem(Widget *w)
{
    widgets.push_back(w);
}

void
Track::place(Pos newPos, Size newSize)
{
    if (orientation == Orientation::Vertical) {
        placeVertically(newPos, newSize);
    } else {
        placeHorizontally(newPos, newSize);
    }
}

void
Track::placeVertically(Pos newPos, Size newSize)
{
    std::vector<int> lengths;
    lengths.reserve(widgets.size());
    for (Widget *w : widgets) {
        lengths.push_back(w->getDesiredHeight());
    }

    lengths = doLayout(lengths, newSize.lines);

    // Apply placement results.
    for (unsigned int i = 0U; i < lengths.size(); ++i) {
        widgets[i]->place(newPos, Size(lengths[i], newSize.cols));
        newPos.y += lengths[i];
    }
}

void
Track::placeHorizontally(Pos newPos, Size newSize)
{
    std::vector<int> lengths;
    lengths.reserve(widgets.size());
    for (Widget *w : widgets) {
        lengths.push_back(w->getDesiredWidth());
    }

    lengths = doLayout(lengths, newSize.cols);

    // Apply placement results.
    for (unsigned int i = 0U; i < lengths.size(); ++i) {
        widgets[i]->place(newPos, Size(newSize.lines, lengths[i]));
        newPos.x += lengths[i];
    }
}

// Performs distribution of `max` units among widgets that want specified
// lengths.
static std::vector<int>
doLayout(std::vector<int> lengths, int max)
{
    int nFlexible = 0;
    int nFillers = 0;
    int booked = 0;

    std::vector<int> layout;
    int lengthLeft = max;

    auto getBooked = [&nFillers](int len) {
        if (len <= -Range) {
            ++nFillers;
            return 1;
        }
        return -len;
    };

    for (int length : lengths) {
        length = std::max(std::min(length, Range), -Range);

        if (length < 0) {
            ++nFlexible;
            booked += getBooked(length);
            layout.emplace_back();
        } else {
            layout.emplace_back(length);
            lengthLeft = std::max(0, lengthLeft - length);
        }
    }

    if (lengthLeft < booked) {
        int have = lengthLeft;
        int left = nFlexible;
        for (unsigned int i = 0U; i < lengths.size(); ++i) {
            if (lengths[i] < 0) {
                int length = getBooked(lengths[i]);

                --left;
                layout[i] = (left == 0)
                          ? have
                          : lengthLeft*(float(length)/booked);
                have -= layout[i];
            }
        }
        return layout;
    }

    if (nFillers > 0) {
        int slice = (lengthLeft - booked)/nFillers;
        for (unsigned int i = 0U; i < lengths.size(); ++i) {
            if (lengths[i] <= -Range) {
                --nFillers;
                --nFlexible;

                if (nFillers == 0) {
                    layout[i] = lengthLeft - booked;
                    lengthLeft = booked;
                } else {
                    layout[i] = slice + 1;
                    lengthLeft -= slice;
                }

                lengths[i] = 0;
            }
        }
    }

    // Place flexible items giving them share of extra space.
    int extraFraction = (nFlexible != 0)
                      ? (lengthLeft - booked)/nFlexible
                      : 0;
    for (unsigned int i = 0U; i < lengths.size(); ++i) {
        if (lengths[i] < 0) {
            --nFlexible;
            layout[i] = (nFlexible == 0)
                      ? lengthLeft
                      : std::min(lengthLeft, -lengths[i] + extraFraction);
            lengthLeft -= layout[i];
        }
    }

    return layout;
}

void
Track::draw()
{
    for (Widget *w : widgets) {
        w->draw();
    }
}

int
Track::desiredHeight()
{
    std::vector<int> lengths;
    lengths.reserve(widgets.size());
    for (Widget *w : widgets) {
        lengths.push_back(w->getDesiredHeight());
    }
    return calculateDesired(lengths);
}

int
Track::desiredWidth()
{
    std::vector<int> lengths;
    lengths.reserve(widgets.size());
    for (Widget *w : widgets) {
        lengths.push_back(w->getDesiredWidth());
    }
    return calculateDesired(lengths);
}

// Calculates desired length of the track based on desired lengths of widgets.
static int
calculateDesired(const std::vector<int> &lengths)
{
    int minLength = 0;
    int sign = 1;
    for (int length : lengths) {
        length = std::max(std::min(length, Range), -Range);
        if (length != -Range) {
            minLength = std::max(minLength, std::abs(length));
            if (length < 0) {
                sign = -1;
            }
        }
    }
    return sign*minLength;
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/libcursed

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/libcursed

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master