#include "BoxLayout.hpp" #include #include #include "BoxLayoutSizeStore.hpp" namespace gui { BoxLayout::BoxLayout(Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h) : Rect(parent, x, y, w, h) { alignment = gui::Alignment(Alignment::Horizontal::None, Alignment::Vertical::None); sizeStore = std::make_unique(); } BoxLayout::BoxLayout() : BoxLayout(nullptr, 0, 0, 0, 0) {} bool BoxLayout::onInput(const InputEvent &inputEvent) { if (inputCallback && inputCallback(*this, inputEvent)) { return true; } if (focusItem && focusItem->onInput(inputEvent)) { return true; } if (handleNavigation(inputEvent)) { return true; } if (borderCallback && borderCallback(inputEvent)) { outOfDrawAreaItems.clear(); return true; } // let item logic rule it return false; } bool BoxLayout::onFocus(bool state) { if (state) this->setVisible(state); else this->setFocusItem(nullptr); this->setNavigation(); if (this->focusChangedCallback) { this->focusChangedCallback(*this); } return true; } void BoxLayout::resizeItems() {} void BoxLayout::setPosition(const short &x, const short &y) { Rect::setPosition(x, y); } void BoxLayout::setSize(const unsigned short w, const unsigned short h) { Rect::setSize(w, h); if (children.size() != 0u) { for (auto it : outOfDrawAreaItems) { it->setVisible(true); } } outOfDrawAreaItems.clear(); resizeItems(); setNavigation(); } void BoxLayout::setAlignment(const Alignment &value) { if (alignment != value) { alignment = value; resizeItems(); } } void BoxLayout::addWidget(Item *item) { Rect::addWidget(item); resizeItems(); } bool BoxLayout::removeWidget(Item *item) { bool ret = Rect::removeWidget(item); resizeItems(); return ret; } bool BoxLayout::erase(Item *item) { auto ret = Item::erase(item); outOfDrawAreaItems.clear(); resizeItems(); return ret; } void BoxLayout::erase() { Item::erase(); } std::list BoxLayout::buildDrawList() { auto el = Rect::buildDrawList(); return el; } void BoxLayout::setVisible(bool value, bool previous) { visible = value; // maybe use parent setVisible(...)? would be better but which one? if (value == true) { resizeItems(); // move items in box in proper places setNavigation(); // set navigation through kids -> TODO handle out of last/first to parent if (children.size()) { // set first visible kid as focused item - TODO should check for actionItems too... /// this if back / front is crappy :| if (previous) { for (auto el = children.rbegin(); el != children.rend(); ++el) { if ((*el)->visible && (*el)->activeItem) { setFocusItem(*el); break; } } } else { for (auto &el : children) { if (el->visible && el->activeItem) { setFocusItem(el); break; } } } } } } void BoxLayout::setVisible(bool value) { setVisible(value, false); } void BoxLayout::setReverseOrder(bool value) { reverseOrder = value; } void BoxLayout::addToOutOfDrawAreaList(Item *it) { if (it->visible) { outOfDrawAreaItems.push_back(it); it->visible = false; } } // space left distposition `first is better` tactics // there could be other i.e. socialism: each element take in equal part up to it's max size // not needed now == not implemented template void BoxLayout::resizeItems() { int32_t pos = reverseOrder ? this->area().size(axis) : 0; int32_t pos_left = this->getSize(axis); int32_t to_split = sizeLeft(this); for (auto &el : children) { if (!el->visible) continue; auto axisItemPosition = 0; auto orthogonalItemPosition = 0; auto axisItemSize = 0; auto orthogonalItemSize = 0; auto grantedSize = sizeStore->get(el); // Check if item can be resized int32_t left_in_el = 0; if (!grantedSize.isZero()) { left_in_el = grantedSize.get(axis) - el->area(Area::Min).size(axis); } else { left_in_el = el->area(Area::Max).size(axis) - el->area(Area::Min).size(axis); } if (to_split > 0 && left_in_el > 0) { int32_t resize = std::min(left_in_el, to_split); axisItemSize = el->area(Area::Min).size(axis) + resize; to_split -= resize; } else { axisItemSize = el->area(Area::Min).size(axis); } orthogonalItemSize = std::min(this->area(Area::Normal).size(orthogonal(axis)), el->area(Area::Max).size(orthogonal(axis))); // Check if there is still position left if (axisItemSize <= pos_left) { if (reverseOrder) { pos -= el->getMargins().getMarginInAxis(axis, MarginInAxis::Second); pos -= axisItemSize; axisItemPosition = pos; pos -= el->getMargins().getMarginInAxis(axis, MarginInAxis::First); } if (!reverseOrder) { pos += el->getMargins().getMarginInAxis(axis, MarginInAxis::First); axisItemPosition = pos; pos += axisItemSize; pos += el->getMargins().getMarginInAxis(axis, MarginInAxis::Second); } pos_left -= axisItemSize + el->getMargins().getSumInAxis(axis); } else { addToOutOfDrawAreaList(el); } // Recalculate lead Axis position if lead axis alignment provided. axisItemPosition = getAxisAlignmentValue(axisItemPosition); // Calculate orthogonal Axis position based on Box Alignment or if not specified child Alignment. orthogonalItemPosition = el->getAxisAlignmentValue(orthogonal(axis)); if (el->visible) el->setAreaInAxis(axis, axisItemPosition, orthogonalItemPosition, axisItemSize, orthogonalItemSize); } Rect::updateDrawArea(); } template void BoxLayout::addWidget(Item *item) { outOfDrawAreaItems.clear(); Rect::addWidget(item); resizeItems(); } std::list::iterator BoxLayout::nextNavigationItem(std::list::iterator from) { return std::find_if(from, this->children.end(), [](auto &el) -> bool { if (el->visible && el->activeItem) { return true; } return false; }); } template uint16_t BoxLayout::getAxisAlignmentValue(uint16_t calcPos) { switch (getAlignment(axis).vertical) { case gui::Alignment::Vertical::Top: if (reverseOrder) { return calcPos - sizeLeft(this); } break; case gui::Alignment::Vertical::Center: if (reverseOrder) { return calcPos - sizeLeft(this) / 2; } else { return calcPos + sizeLeft(this) / 2; } break; case gui::Alignment::Vertical::Bottom: if (!reverseOrder) { return calcPos + sizeLeft(this); } break; default: break; } switch (getAlignment(axis).horizontal) { case gui::Alignment::Horizontal::Left: if (reverseOrder) { return calcPos - sizeLeft(this); } break; case gui::Alignment::Horizontal::Center: if (reverseOrder) { return calcPos - sizeLeft(this) / 2; } else { return calcPos + sizeLeft(this) / 2; } break; case gui::Alignment::Horizontal::Right: if (!reverseOrder) { return calcPos + sizeLeft(this); } break; default: break; } return calcPos; } void BoxLayout::setNavigation() { auto previous = nextNavigationItem(children.begin()), next = children.end(); if (type == ItemType::VBOX) { while ((previous != children.end()) && ((next = nextNavigationItem(std::next(previous))) != std::end(children))) { (*previous)->setNavigationItem(reverseOrder ? NavigationDirection::UP : NavigationDirection::DOWN, *next); (*next)->setNavigationItem(reverseOrder ? NavigationDirection::DOWN : NavigationDirection::UP, *previous); previous = next; } } if (type == ItemType::HBOX) { while ((previous != children.end()) && ((next = nextNavigationItem(std::next(previous))) != std::end(children))) { (*previous)->setNavigationItem(reverseOrder ? NavigationDirection::LEFT : NavigationDirection::RIGHT, *next); (*next)->setNavigationItem(reverseOrder ? NavigationDirection::RIGHT : NavigationDirection::LEFT, *previous); previous = next; } } } void BoxLayout::setFocusOnElement(uint32_t elementNumber) { uint32_t i = 0; for (auto child : children) { if (child->activeItem == true && child->visible == true) { if (elementNumber == i) { child->setFocus(true); focusItem = child; } else { child->setFocus(false); } ++i; } } } template auto BoxLayout::handleRequestResize(const Item *child, unsigned short request_w, unsigned short request_h) -> Size { auto el = std::find(children.begin(), children.end(), child); if (el == std::end(children)) { return {0, 0}; } Size granted = {std::min((*el)->area(Area::Max).w, request_w), std::min((*el)->area(Area::Max).h, request_h)}; sizeStore->store(*el, granted); BoxLayout::resizeItems(); // vs mark dirty return granted; } void BoxLayout::setFocusOnLastElement() { auto last = true; for (auto child = children.rbegin(); child != children.rend(); child++) { if ((*child)->activeItem && (*child)->visible && last) { (*child)->setFocus(true); focusItem = (*child); last = false; } else { (*child)->setFocus(false); } } } HBox::HBox() : BoxLayout() { type = ItemType::HBOX; } HBox::HBox(Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h) : BoxLayout(parent, x, y, w, h) { type = ItemType::HBOX; } void HBox::resizeItems() { BoxLayout::resizeItems(); } void HBox::addWidget(Item *item) { BoxLayout::addWidget(item); } auto HBox::handleRequestResize(const Item *child, unsigned short request_w, unsigned short request_h) -> Size { return BoxLayout::handleRequestResize(child, request_w, request_h); } VBox::VBox() : BoxLayout() { type = ItemType::VBOX; } VBox::VBox(Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h) : BoxLayout(parent, x, y, w, h) { type = ItemType::VBOX; } void VBox::resizeItems() { BoxLayout::resizeItems(); } void VBox::addWidget(Item *item) { BoxLayout::addWidget(item); } auto VBox::handleRequestResize(const Item *child, unsigned short request_w, unsigned short request_h) -> Size { return BoxLayout::handleRequestResize(child, request_w, request_h); } } /* namespace gui */