Files
panopainter/engine/layout.cpp

299 lines
9.1 KiB
C++

#include "pch.h"
#include "layout.h"
#include "util.h"
Plane WidgetBorder::m_plane;
void Node::update(float width, float height)
{
YGNodeStyleSetWidth(y_node, width);
YGNodeStyleSetHeight(y_node, height);
YGNodeCalculateLayout(y_node, YGUndefined, YGUndefined, YGDirectionLTR);
glm::mat4 proj = glm::ortho(0.f, width, height, 0.f, -1.f, 1.f);
update_internal({ 0, 0 }, proj);
}
void Node::update_internal(const glm::vec2& origin, const glm::mat4& proj)
{
float x = YGNodeLayoutGetLeft(y_node);
float y = YGNodeLayoutGetTop(y_node);
float w = YGNodeLayoutGetWidth(y_node);
float h = YGNodeLayoutGetHeight(y_node);
m_pos = origin + glm::vec2(x, y);
m_size = glm::vec2(w, h);
if (parent)
{
// correct the padding clip
// should not clip the padded area
// useful to draw decorations
float pt = YGNodeLayoutGetPadding(parent->y_node, YGEdgeTop);
float pr = YGNodeLayoutGetPadding(parent->y_node, YGEdgeRight);
float pb = YGNodeLayoutGetPadding(parent->y_node, YGEdgeBottom);
float pl = YGNodeLayoutGetPadding(parent->y_node, YGEdgeLeft);
glm::vec2 off_p(pl, pt);
glm::vec2 off_s(pr, pb);
m_clip = glm::vec4(m_pos - off_p, m_size + off_p + off_s);
m_clip = rect_intersection(m_clip, parent->m_clip);
}
else
{
m_clip = glm::vec4(m_pos, m_size);
}
if (m_widget)
{
glm::mat4 pivot = glm::translate(glm::vec3(.5f, .5f, 0.f));
glm::mat4 scale = glm::scale(glm::vec3(m_size, 1.f));
glm::mat4 pos = glm::translate(glm::vec3(m_pos, 0));
m_widget->mvp = proj * pos * scale * pivot;
m_widget->clip = m_clip;
}
for (auto& c : m_children)
c.update_internal(m_pos, proj);
}
void Node::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
switch (ka)
{
case kAttribute::Width:
if (strcmp(attr->Value(), "auto") == 0)
{
YGNodeStyleSetWidth(y_node, YGUndefined);
YGNodeStyleSetWidthPercent(y_node, YGUndefined);
}
else
{
if (strchr(attr->Value(), '%'))
YGNodeStyleSetWidthPercent(y_node, attr->FloatValue());
else
YGNodeStyleSetWidth(y_node, attr->FloatValue());
}
break;
case kAttribute::MinWidth:
if (strchr(attr->Value(), '%'))
YGNodeStyleSetMinWidthPercent(y_node, attr->FloatValue());
else
YGNodeStyleSetMinWidth(y_node, attr->FloatValue());
break;
case kAttribute::MaxWidth:
YGNodeStyleSetMaxWidth(y_node, attr->FloatValue());
break;
case kAttribute::Height:
if (strcmp(attr->Value(), "auto") == 0)
{
YGNodeStyleSetHeight(y_node, YGUndefined);
YGNodeStyleSetHeightPercent(y_node, YGUndefined);
}
else
{
if (strchr(attr->Value(), '%'))
YGNodeStyleSetHeightPercent(y_node, attr->FloatValue());
else
YGNodeStyleSetHeight(y_node, attr->FloatValue());
}
break;
case kAttribute::MinHeight:
YGNodeStyleSetMinHeight(y_node, attr->FloatValue());
break;
case kAttribute::MaxHeight:
YGNodeStyleSetMaxHeight(y_node, attr->FloatValue());
break;
case kAttribute::Grow:
YGNodeStyleSetFlexGrow(y_node, attr->FloatValue());
break;
case kAttribute::Shrink:
YGNodeStyleSetFlexShrink(y_node, attr->FloatValue());
break;
case kAttribute::FlexDir:
{
YGFlexDirection dir = YGFlexDirectionRow;
if (strcmp("col", attr->Value()) == 0)
dir = YGFlexDirectionColumn;
else if (strcmp("col-reverse", attr->Value()) == 0)
dir = YGFlexDirectionColumnReverse;
else if (strcmp("row", attr->Value()) == 0)
dir = YGFlexDirectionRow;
else if (strcmp("row-reverse", attr->Value()) == 0)
dir = YGFlexDirectionRowReverse;
YGNodeStyleSetFlexDirection(y_node, dir);
break;
}
case kAttribute::FlexWrap:
YGNodeStyleSetFlexWrap(y_node, attr->IntValue() ? YGWrapWrap : YGWrapNoWrap);
break;
case kAttribute::Padding:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.x);
}
else
{
YGNodeStyleSetPadding(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetPadding(y_node, YGEdgeRight, pad.y);
YGNodeStyleSetPadding(y_node, YGEdgeBottom, pad.z);
YGNodeStyleSetPadding(y_node, YGEdgeLeft, pad.w);
}
break;
}
case kAttribute::Margin:
{
glm::vec4 pad;
int n = sscanf(attr->Value(), "%f %f %f %f", &pad.x, &pad.y, &pad.z, &pad.w);
if (n == 1)
{
YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.x);
}
else
{
YGNodeStyleSetMargin(y_node, YGEdgeTop, pad.x);
YGNodeStyleSetMargin(y_node, YGEdgeRight, pad.y);
YGNodeStyleSetMargin(y_node, YGEdgeBottom, pad.z);
YGNodeStyleSetMargin(y_node, YGEdgeLeft, pad.w);
}
break;
}
default:
if (m_widget)
m_widget->parse_attributes(ka, attr);
break;
}
}
void Node::load_internal(const tinyxml2::XMLElement* x_node)
{
m_name = x_node->Name();
auto attr = x_node->FirstAttribute();
kWidget widget_id = (kWidget)const_hash(x_node->Name());
switch (widget_id)
{
case kWidget::Border:
m_widget = std::make_unique<WidgetBorder>();
break;
case kWidget::Shape:
m_widget = std::make_unique<WidgetShape>();
break;
}
while (attr)
{
parse_attributes((kAttribute)const_hash(attr->Name()), attr);
attr = attr->Next();
}
if (m_widget)
m_widget->create();
auto x_child = x_node->FirstChildElement();
while (x_child)
{
//Node n;
if (strcmp("ref", x_child->Name()) == 0)
{
auto ids = x_child->Attribute("id");
auto id = const_hash(ids);
auto& ref = (*m_manager)[id];
m_children.push_back(ref.clone());
m_children.back().parent = this;
YGNodeInsertChild(y_node, m_children.back().y_node, YGNodeGetChildCount(y_node));
}
else
{
m_children.emplace_back();
auto& n = m_children.back();
n.parent = this;
n.m_manager = m_manager;
YGNodeInsertChild(y_node, n.y_node, YGNodeGetChildCount(y_node));
n.load_internal(x_child);
}
x_child = x_child->NextSiblingElement();
}
}
Node Node::clone()
{
Node n;
YGNodeCopyStyle(n.y_node, y_node);
if (m_widget)
n.m_widget = m_widget->clone();
n.m_manager = m_manager;
for (auto& c : m_children)
{
n.m_children.push_back(std::move(c.clone()));
auto& cn = n.m_children.back(); // child node reference
cn.parent = &n;
YGNodeInsertChild(n.y_node, cn.y_node, YGNodeGetChildCount(n.y_node));
}
return std::move(n);
}
bool LayoutManager::load(const char* path)
{
struct stat tmp_info;
if (stat(path, &tmp_info) != 0)
return false;
if (tmp_info.st_mtime <= m_file_info.st_mtime)
return false;
m_file_info = tmp_info;
m_path = path;
auto old = std::move(m_layouts);
tinyxml2::XMLDocument xml;
auto ret = xml.LoadFile(path);
if (ret != tinyxml2::XMLError::XML_SUCCESS)
return false;
tinyxml2::XMLElement* current = xml.RootElement()->FirstChildElement();
while (current)
{
auto id_str = current->Attribute("id");
if (!id_str)
{
printf("Layout node without id\n");
return false;
}
printf("Parsing layout: %s\n", id_str);
uint16_t id = const_hash(id_str);
auto& p = m_layouts.try_emplace(id);
if (p.second)
{
auto& node = p.first->second;
node.m_manager = this;
// try to copy the old size values
if (old.count(id))
{
const auto& old_node = old[id];
YGNodeCopyStyle(node.y_node, old_node.y_node);
}
if (!current->NoChildren())
node.load_internal(current->FirstChildElement());
}
else
{
printf("Layout id \"%s\" duplicated\n", id_str);
}
current = current->NextSiblingElement("layout");
}
return true;
}
bool LayoutManager::reload()
{
// avoid conflict when assigning the same string from c_str
std::string path_copy = m_path;
return load(path_copy.c_str());
}