435 lines
13 KiB
C++
435 lines
13 KiB
C++
#include "pch.h"
|
|
#include "layout.h"
|
|
#include "util.h"
|
|
|
|
Plane NodeBorder::m_plane;
|
|
Plane NodeImage::m_plane;
|
|
Sampler NodeImage::m_sampler;
|
|
|
|
Node* Node::find(const char* ids)
|
|
{
|
|
uint16_t id = const_hash(ids);
|
|
if (id == m_nodeID)
|
|
return this;
|
|
for (auto& c : *this)
|
|
if (c.m_nodeID == id)
|
|
return &c;
|
|
return nullptr;
|
|
}
|
|
|
|
kEventResult Node::on_event(Event* e)
|
|
{
|
|
for (auto& c : m_children)
|
|
if (c->on_event(e) == kEventResult::Consumed)
|
|
return kEventResult::Consumed;
|
|
switch (e->m_cat)
|
|
{
|
|
case kEventCategory::MouseEvent:
|
|
{
|
|
MouseEvent* me = static_cast<MouseEvent*>(e);
|
|
bool inside = point_in_rect(me->m_pos, m_clip);
|
|
bool inside_old = m_mouse_inside;
|
|
switch (e->m_type)
|
|
{
|
|
case kEventType::MouseDownL:
|
|
case kEventType::MouseDownR:
|
|
case kEventType::MouseUpL:
|
|
case kEventType::MouseUpR:
|
|
if (inside && handle_event(e) == kEventResult::Consumed)
|
|
return kEventResult::Consumed;
|
|
break;
|
|
case kEventType::MouseMove:
|
|
if (inside_old == false && inside == true)
|
|
{
|
|
MouseEvent e2 = *me;
|
|
e2.m_type = kEventType::MouseEnter;
|
|
handle_event(&e2);
|
|
}
|
|
m_mouse_inside = inside;
|
|
handle_event(e);
|
|
if (inside_old == true && inside == false)
|
|
{
|
|
MouseEvent e2 = *me;
|
|
e2.m_type = kEventType::MouseLeave;
|
|
handle_event(&e2);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
if (handle_event(e) == kEventResult::Consumed)
|
|
return kEventResult::Consumed;
|
|
break;
|
|
}
|
|
return kEventResult::Available;
|
|
}
|
|
|
|
void Node::add_child(Node* n)
|
|
{
|
|
m_children.emplace_back(n);
|
|
n->parent = this;
|
|
n->m_manager = m_manager;
|
|
YGNodeInsertChild(y_node, n->y_node, YGNodeGetChildCount(y_node));
|
|
}
|
|
|
|
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 = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeTop);
|
|
float pr = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeRight);
|
|
float pb = 0;//YGNodeLayoutGetPadding(parent->y_node, YGEdgeBottom);
|
|
float pl = 0;//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);
|
|
}
|
|
|
|
glm::mat4 pivot = glm::translate(glm::vec3(.5f, .5f, 0.f));
|
|
glm::mat4 scale = glm::scale(glm::vec3(glm::ceil(m_size), 1.f));
|
|
glm::mat4 pos = glm::translate(glm::vec3(glm::floor(m_pos), 0));
|
|
m_mvp = proj * pos * scale * pivot;
|
|
m_proj = proj;
|
|
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::id:
|
|
m_nodeID_s = attr->Value();
|
|
m_nodeID = const_hash(attr->Value());
|
|
break;
|
|
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::Justify:
|
|
{
|
|
YGJustify v = YGJustifyFlexStart;
|
|
if (strcmp("center", attr->Value()) == 0)
|
|
v = YGJustifyCenter;
|
|
else if (strcmp("flex-start", attr->Value()) == 0)
|
|
v = YGJustifyFlexStart;
|
|
else if (strcmp("flex-end", attr->Value()) == 0)
|
|
v = YGJustifyFlexEnd;
|
|
else if (strcmp("space-around", attr->Value()) == 0)
|
|
v = YGJustifySpaceAround;
|
|
else if (strcmp("space-between", attr->Value()) == 0)
|
|
v = YGJustifySpaceBetween;
|
|
YGNodeStyleSetJustifyContent(y_node, v);
|
|
break;
|
|
}
|
|
case kAttribute::Align:
|
|
{
|
|
YGAlign v = YGAlignStretch;
|
|
if (strcmp("stretch", attr->Value()) == 0)
|
|
v = YGAlignStretch;
|
|
else if (strcmp("flex-start", attr->Value()) == 0)
|
|
v = YGAlignFlexStart;
|
|
else if (strcmp("flex-end", attr->Value()) == 0)
|
|
v = YGAlignFlexEnd;
|
|
else if (strcmp("center", attr->Value()) == 0)
|
|
v = YGAlignCenter;
|
|
YGNodeStyleSetAlignItems(y_node, v);
|
|
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:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Node::load_internal(const tinyxml2::XMLElement* x_node)
|
|
{
|
|
m_name = x_node->Name();
|
|
|
|
init();
|
|
|
|
auto attr = x_node->FirstAttribute();
|
|
while (attr)
|
|
{
|
|
parse_attributes((kAttribute)const_hash(attr->Name()), attr);
|
|
attr = attr->Next();
|
|
}
|
|
|
|
create();
|
|
|
|
auto x_child = x_node->FirstChildElement();
|
|
while (x_child)
|
|
{
|
|
kWidget child_id = (kWidget)const_hash(x_child->Name());
|
|
switch (child_id)
|
|
{
|
|
case kWidget::Border:
|
|
{
|
|
auto n = new NodeBorder();
|
|
add_child(n);
|
|
n->load_internal(x_child);
|
|
break;
|
|
}
|
|
// case kWidget::Shape:
|
|
// break;
|
|
case kWidget::Image:
|
|
{
|
|
auto n = new NodeImage();
|
|
add_child(n);
|
|
n->load_internal(x_child);
|
|
break;
|
|
}
|
|
case kWidget::Text:
|
|
{
|
|
auto n = new NodeText();
|
|
add_child(n);
|
|
n->load_internal(x_child);
|
|
break;
|
|
}
|
|
case kWidget::Button:
|
|
{
|
|
auto n = new NodeButton();
|
|
add_child(n);
|
|
n->load_internal(x_child);
|
|
break;
|
|
}
|
|
case kWidget::Ref:
|
|
{
|
|
auto ids = x_child->Attribute("id");
|
|
auto id = const_hash(ids);
|
|
auto& ref = (*m_manager)[id].m_children[0];
|
|
auto n = ref->clone();
|
|
add_child(n);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
auto n = new Node();
|
|
add_child(n);
|
|
n->load_internal(x_child);
|
|
break;
|
|
}
|
|
}
|
|
x_child = x_child->NextSiblingElement();
|
|
}
|
|
}
|
|
|
|
Node* Node::clone()
|
|
{
|
|
Node* n = clone_instantiate();
|
|
clone_copy(n);
|
|
return n;
|
|
}
|
|
|
|
Node* Node::clone_instantiate() const
|
|
{
|
|
return new Node();
|
|
}
|
|
|
|
void Node::clone_copy(Node* dest) const
|
|
{
|
|
YGNodeCopyStyle(dest->y_node, y_node);
|
|
dest->m_manager = m_manager;
|
|
for (auto& c : m_children)
|
|
{
|
|
Node* cn = c->clone();
|
|
dest->m_children.emplace_back(cn);
|
|
cn->parent = dest;
|
|
YGNodeInsertChild(dest->y_node, cn->y_node, YGNodeGetChildCount(dest->y_node));
|
|
}
|
|
}
|
|
|
|
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.find(id);
|
|
if (p == m_layouts.end())
|
|
{
|
|
auto& node = m_layouts[id];
|
|
kWidget node_id = (kWidget)const_hash(current->Name());
|
|
switch (node_id)
|
|
{
|
|
case kWidget::Border:
|
|
node.reset(new NodeBorder());
|
|
break;
|
|
default:
|
|
node.reset(new Node());
|
|
break;
|
|
}
|
|
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);
|
|
}
|
|
node->load_internal(current);
|
|
}
|
|
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());
|
|
}
|