#include "pch.h" #include "layout.h" #include "util.h" Plane NodeBorder::m_plane; Plane NodeImage::m_plane; Sampler NodeImage::m_sampler; 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(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::remove_child(Node* n) { auto i = std::find_if(m_children.begin(), m_children.end(), [=](std::unique_ptr& ptr) { return ptr.get() == n; }); m_children.erase(i); YGNodeRemoveChild(y_node, n->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::Positioning: { YGPositionType v = YGPositionTypeRelative; if (strcmp("relative", attr->Value()) == 0) v = YGPositionTypeRelative; else if (strcmp("absolute", attr->Value()) == 0) v = YGPositionTypeAbsolute; YGNodeStyleSetPositionType(y_node, v); break; } case kAttribute::Position: { glm::vec4 v; int n = sscanf(attr->Value(), "%f %f %f %f", &v.x, &v.y, &v.z, &v.w); if (n == 2) { YGNodeStyleSetPosition(y_node, YGEdgeLeft, v.x); YGNodeStyleSetPosition(y_node, YGEdgeTop, v.y); } else { YGNodeStyleSetPadding(y_node, YGEdgeLeft, v.x); YGNodeStyleSetPadding(y_node, YGEdgeTop, v.y); YGNodeStyleSetPadding(y_node, YGEdgeRight, v.z); YGNodeStyleSetPadding(y_node, YGEdgeBottom, v.w); } 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); clone_children(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; dest->m_name = m_name; dest->m_nodeID_s = m_nodeID_s; dest->m_nodeID = m_nodeID; dest->m_display = m_display; dest->m_pos = m_pos; dest->m_size = m_size; dest->m_clip = m_clip; } void Node::clone_children(Node* dest) const { for (auto& c : m_children) { Node* cn = c->clone(); dest->m_children.emplace_back(cn); cn->parent = dest; cn->m_manager = m_manager; 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()); }