mirror of https://github.com/minetest/minetest.git
285 lines
7.8 KiB
C++
285 lines
7.8 KiB
C++
/*
|
|
Minetest
|
|
Copyright (C) 2023 v-rob, Vincent Robinson <robinsonvincent89@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "gui/window.h"
|
|
|
|
#include "debug.h"
|
|
#include "log.h"
|
|
#include "settings.h"
|
|
#include "client/client.h"
|
|
#include "client/renderingengine.h"
|
|
#include "client/tile.h"
|
|
#include "gui/manager.h"
|
|
#include "gui/texture.h"
|
|
#include "util/serialize.h"
|
|
|
|
namespace ui
|
|
{
|
|
WindowType toWindowType(u8 type)
|
|
{
|
|
if (type >= (u8)WindowType::MAX_TYPE) {
|
|
return WindowType::HUD;
|
|
}
|
|
return (WindowType)type;
|
|
}
|
|
|
|
Elem *Window::getElem(const std::string &id, bool required)
|
|
{
|
|
// Empty IDs may be valid values if the element is optional.
|
|
if (id.empty() && !required) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If the ID is not empty, then we need to search for an actual
|
|
// element. Not finding one means that an error occurred.
|
|
auto it = m_elems.find(id);
|
|
if (it != m_elems.end()) {
|
|
return it->second.get();
|
|
}
|
|
|
|
errorstream << "Element \"" << id << "\" does not exist" << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
const std::string *Window::getStyleStr(u32 index) const
|
|
{
|
|
if (index < m_style_strs.size()) {
|
|
return &m_style_strs[index];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Window::reset()
|
|
{
|
|
m_elems.clear();
|
|
m_ordered_elems.clear();
|
|
|
|
m_root_elem = nullptr;
|
|
|
|
m_style_strs.clear();
|
|
}
|
|
|
|
void Window::read(std::istream &is, bool opening)
|
|
{
|
|
std::unordered_map<Elem *, std::string> elem_contents;
|
|
readElems(is, elem_contents);
|
|
|
|
readRootElem(is);
|
|
readStyles(is);
|
|
|
|
if (opening)
|
|
m_type = toWindowType(readU8(is));
|
|
|
|
// Assuming no earlier step failed, we can proceed to read in all the
|
|
// properties. Otherwise, reset the window entirely.
|
|
if (m_root_elem != nullptr) {
|
|
updateElems(elem_contents);
|
|
} else {
|
|
reset();
|
|
}
|
|
}
|
|
|
|
float Window::getPixelSize() const
|
|
{
|
|
return g_manager.getPixelSize(m_type);
|
|
}
|
|
|
|
d2f32 Window::getScreenSize() const
|
|
{
|
|
return g_manager.getScreenSize(m_type);
|
|
}
|
|
|
|
void Window::drawAll()
|
|
{
|
|
if (m_root_elem == nullptr) {
|
|
return;
|
|
}
|
|
|
|
rf32 parent_rect(getScreenSize());
|
|
m_root_elem->layout(parent_rect, parent_rect);
|
|
|
|
Canvas canvas(Texture::screen, getPixelSize());
|
|
m_root_elem->drawAll(canvas);
|
|
}
|
|
|
|
void Window::readElems(std::istream &is,
|
|
std::unordered_map<Elem *, std::string> &elem_contents)
|
|
{
|
|
// Read in all the new elements and updates to existing elements.
|
|
u32 num_elems = readU32(is);
|
|
|
|
std::unordered_map<std::string, std::unique_ptr<Elem>> new_elems;
|
|
|
|
for (size_t i = 0; i < num_elems; i++) {
|
|
u32 type = readU8(is);
|
|
std::string id = readNullStr(is);
|
|
|
|
// Make sure that elements have valid IDs. If the string has non-ID
|
|
// characters in it, though, we don't particularly care.
|
|
if (id.empty()) {
|
|
errorstream << "Element has empty ID" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// Each element has a size prefix stating how big the element is.
|
|
// This allows new fields to be added to elements without breaking
|
|
// compatibility. So, read it in as a string and save it for later.
|
|
std::string contents = readStr32(is);
|
|
|
|
// If this is a duplicate element, skip it right away.
|
|
if (new_elems.find(id) != new_elems.end()) {
|
|
errorstream << "Duplicate element \"" << id << "\"" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
/* Now we need to decide whether to create a new element or to
|
|
* modify the state of an already existing one. This allows
|
|
* changing attributes of an element (like the style or the
|
|
* element's children) while leaving leaving persistent state
|
|
* intact (such as the position of a scrollbar or the contents of a
|
|
* text field).
|
|
*/
|
|
std::unique_ptr<Elem> elem = nullptr;
|
|
|
|
// Search for a pre-existing element.
|
|
auto it = m_elems.find(id);
|
|
|
|
if (it == m_elems.end() || it->second->getType() != type) {
|
|
// If the element was not found or the existing element has the
|
|
// wrong type, create a new element.
|
|
elem = Elem::create((Elem::Type)type, *this, id);
|
|
|
|
// If we couldn't create the element, the type was invalid.
|
|
// Skip this element entirely.
|
|
if (elem == nullptr) {
|
|
errorstream << "Element \"" << id << "\" has an invalid type: " <<
|
|
type << std::endl;
|
|
continue;
|
|
}
|
|
} else {
|
|
// Otherwise, use the existing element.
|
|
elem = std::move(it->second);
|
|
}
|
|
|
|
// Now that we've gotten our element, reset its contents.
|
|
elem->reset();
|
|
|
|
// We need to read in all elements before updating each element, so
|
|
// save the element's contents for later.
|
|
elem_contents[elem.get()] = contents;
|
|
new_elems.emplace(id, std::move(elem));
|
|
}
|
|
|
|
// Set these elements as our list of new elements.
|
|
m_elems = std::move(new_elems);
|
|
|
|
// Clear the ordered elements for now. They will be regenerated later.
|
|
m_ordered_elems.clear();
|
|
}
|
|
|
|
void Window::readRootElem(std::istream &is)
|
|
{
|
|
// Get the root element of the window and make sure it's valid.
|
|
m_root_elem = getElem(readNullStr(is), true);
|
|
|
|
if (m_root_elem == nullptr) {
|
|
errorstream << "Window " << m_id << " has no root element" << std::endl;
|
|
reset();
|
|
} else if (m_root_elem->getType() != Elem::ROOT) {
|
|
errorstream << "Window " << m_id <<
|
|
" has wrong type for root element" << std::endl;
|
|
reset();
|
|
}
|
|
}
|
|
|
|
void Window::readStyles(std::istream &is)
|
|
{
|
|
// Styles are stored in their raw binary form; every time a style needs
|
|
// to be recalculated, these binary strings can be applied one over the
|
|
// other, resulting in automatic cascading styles.
|
|
u32 num_styles = readU32(is);
|
|
m_style_strs.clear();
|
|
|
|
for (size_t i = 0; i < num_styles; i++) {
|
|
m_style_strs.push_back(readStr16(is));
|
|
}
|
|
}
|
|
|
|
void Window::updateElems(std::unordered_map<Elem *, std::string> &elem_contents)
|
|
{
|
|
// Now that we have a fully updated window, we can update each element
|
|
// with its contents. We couldn't do this before because elements need
|
|
// to be able to call getElem() and getStyleStr().
|
|
for (auto &contents : elem_contents) {
|
|
auto is = newIs(std::move(contents.second));
|
|
contents.first->read(is);
|
|
}
|
|
|
|
// Check the depth of the element tree; if it's too deep, there's
|
|
// potential for stack overflow.
|
|
if (!checkTree(m_root_elem, 1)) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
// Update the ordering of the elements so we can do iteration rather
|
|
// than recursion when searching through the elements in order.
|
|
updateElemOrdering(m_root_elem, 0);
|
|
}
|
|
|
|
bool Window::checkTree(Elem *elem, size_t depth) const
|
|
{
|
|
if (depth > MAX_TREE_DEPTH) {
|
|
errorstream << "Window " << m_id <<
|
|
" exceeds max tree depth: " << MAX_TREE_DEPTH << std::endl;
|
|
return false;
|
|
}
|
|
|
|
for (Elem *child : elem->getChildren()) {
|
|
if (child->getType() == Elem::ROOT) {
|
|
errorstream << "Element of root type \"" << child->getId() <<
|
|
"\" is not root of window" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!checkTree(child, depth + 1)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t Window::updateElemOrdering(Elem *elem, size_t order)
|
|
{
|
|
// The parent gets ordered before its children since the ordering of
|
|
// elements follows draw order.
|
|
elem->setOrder(order);
|
|
m_ordered_elems.push_back(elem);
|
|
|
|
for (Elem *child : elem->getChildren()) {
|
|
// Order this element's children using the next index after the
|
|
// parent, returning the index of the last child element.
|
|
order = updateElemOrdering(child, order + 1);
|
|
}
|
|
|
|
return order;
|
|
}
|
|
}
|