diff --git a/LICENSE b/LICENSE index 2e21e7d..7e848f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Artem Chernyshev +Copyright (c) 2019-2020 Artem Chernyshev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/imgui b/imgui index dc66f83..388bf66 160000 --- a/imgui +++ b/imgui @@ -1 +1 @@ -Subproject commit dc66f83db8462e7837aec60fef86c025ac4c485b +Subproject commit 388bf66247bc17cddb704d6e6421cf872bc3f964 diff --git a/samples/simple/layouts.xml b/samples/simple/layouts.xml new file mode 100644 index 0000000..2eba696 --- /dev/null +++ b/samples/simple/layouts.xml @@ -0,0 +1,146 @@ + + + + + diff --git a/samples/simple/main.cpp b/samples/simple/main.cpp index 1a5b1d3..adc7f8b 100644 --- a/samples/simple/main.cpp +++ b/samples/simple/main.cpp @@ -104,9 +104,11 @@ int main(int argc, char** argv) ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); - float scale = 1.5f; + float scale = 1.0f; ImGuiStyle& style = ImGui::GetStyle(); + style.FrameRounding = 5.0f; + style.FrameBorderSize = 1.0f; style.ScaleAllSizes(scale); // Setup Platform/Renderer bindings diff --git a/samples/simple/standard_controls.xml b/samples/simple/standard_controls.xml new file mode 100644 index 0000000..9034474 --- /dev/null +++ b/samples/simple/standard_controls.xml @@ -0,0 +1,84 @@ + + + + diff --git a/samples/simple/styled.xml b/samples/simple/styled.xml index 2dc03f6..b4103db 100644 --- a/samples/simple/styled.xml +++ b/samples/simple/styled.xml @@ -25,10 +25,6 @@ padding: 5px; } - span { - margin-right: 0.2rem; - } - div:not(:last-of-type) { margin-bottom: 0.5rem; } @@ -220,7 +216,7 @@
{{if self.toggle then return self.heartActive else return self.heartInactive end}}
-
APPROVED
+
APPROVED

Forms

@@ -254,10 +250,10 @@ - {{c}} + {{index}}:{{c}} No choices yet
- + diff --git a/src/css/select.cpp b/src/css/select.cpp index 5f7ca93..633ead2 100644 --- a/src/css/select.cpp +++ b/src/css/select.cpp @@ -360,6 +360,7 @@ namespace ImVue { return CSS_OK; } + // TODO: not implemented yet css_error node_has_attribute_dashmatch(void *pw, void *n, const css_qname *qname, lwc_string *value, @@ -373,6 +374,7 @@ namespace ImVue { return CSS_OK; } + // TODO: not implemented yet css_error node_has_attribute_includes(void *pw, void *n, const css_qname *qname, lwc_string *value, @@ -386,6 +388,7 @@ namespace ImVue { return CSS_OK; } + // TODO: not implemented yet css_error node_has_attribute_prefix(void *pw, void *n, const css_qname *qname, lwc_string *value, @@ -399,6 +402,7 @@ namespace ImVue { return CSS_OK; } + // TODO: not implemented yet css_error node_has_attribute_suffix(void *pw, void *n, const css_qname *qname, lwc_string *value, @@ -412,6 +416,7 @@ namespace ImVue { return CSS_OK; } + // TODO: not implemented yet css_error node_has_attribute_substring(void *pw, void *n, const css_qname *qname, lwc_string *value, diff --git a/src/extras/svg.h b/src/extras/svg.h index 73f03f6..1b1f8d3 100644 --- a/src/extras/svg.h +++ b/src/extras/svg.h @@ -336,7 +336,6 @@ namespace ImVue { mTextureManager->deleteTexture(mTextureID); } - std::cout << "Image is redrawn\n"; mTextureID = mTextureManager->createTexture(mData, (int)drawnSize.x, (int)drawnSize.y); } diff --git a/src/extras/xhtml.h b/src/extras/xhtml.h index 202bdd4..2dad2a6 100644 --- a/src/extras/xhtml.h +++ b/src/extras/xhtml.h @@ -56,6 +56,28 @@ namespace ImVue { } }; + inline void addMaxPadding(Element* element) + { + ImVec2 max; + if(element->style()->widthMode != CSS_WIDTH_SET) { + max.x += element->padding[2]; + } + + if(element->style()->heightMode != CSS_HEIGHT_SET) { + max.y += element->padding[3]; + } + + ImVec2 size = ImGui::GetItemRectMax() - ImGui::GetItemRectMin() + max; + if(size.x < element->minSize.x) { + max.x += element->minSize.x - size.x; + } + if(size.y < element->minSize.y) { + max.y += element->minSize.y - size.y; + } + + ImGui::SetCursorScreenPos(ImGui::GetItemRectMax() + max); + } + /** * Base class for any html node */ @@ -96,7 +118,7 @@ namespace ImVue { void renderBody() { - bool useChild = size.y > 0; + bool useChild = mStyle.overflowX == CSS_OVERFLOW_SCROLL || mStyle.overflowY == CSS_OVERFLOW_SCROLL; bool drawContent = true; ImVec2 pos = ImGui::GetCursorScreenPos(); @@ -119,16 +141,23 @@ namespace ImVue { pad.Max.y = padding[3]; } } + int flags = ImGuiWindowFlags_NavFlattened | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize; + if(mStyle.overflowX == CSS_OVERFLOW_SCROLL) { + flags |= ImGuiWindowFlags_HorizontalScrollbar; + } + + if(mStyle.overflowY != CSS_OVERFLOW_SCROLL) { + flags |= ImGuiWindowFlags_NoScrollbar; + } if(useChild) { ImVec2 childSize(size.x > 0 ? size.x : mWidth, size.y); - /* childSize -= ImVec2( ImMax(mStyle.decoration.thickness[0], mStyle.decoration.thickness[2]), ImMax(mStyle.decoration.thickness[1], mStyle.decoration.thickness[3]) - );*/ + ); ImGui::PushStyleColor(ImGuiCol_ChildBg, 0); - drawContent = ImGui::BeginChild(mID + index, childSize, false, ImGuiWindowFlags_NavFlattened | ImGuiWindowFlags_NoDecoration); + drawContent = ImGui::BeginChild(mID + index, childSize, false, flags); ImGui::PopStyleColor(); ImGui::SetCursorPos(pad.Min); } else { @@ -137,24 +166,39 @@ namespace ImVue { } if(drawContent) { - ContainerElement::renderBody(); + if(mChildren.size() == 0 && mStyle.heightMode != CSS_HEIGHT_SET) { + ImVec2 p = ImGui::GetCursorScreenPos(); + ImVec2 s = size; + if(s.y == 0) { + s.y = ImGui::GetTextLineHeight(); + } + ImRect bb(p, p + s); + ImGui::ItemSize(s); + ImGui::ItemAdd(bb, 0); + } else { + ContainerElement::renderBody(); + } } + addMaxPadding(this); if(useChild) { - ImGui::SetCursorScreenPos(ImGui::GetItemRectMax() + pad.Max); - mWidth = ImGui::GetItemRectSize().x + pad.Min.x + pad.Max.x; + mWidth = ImGui::GetItemRectSize().x + pad.Min.x; + if(mStyle.overflowX != CSS_OVERFLOW_SCROLL) { + ImGui::GetCurrentWindowRead()->DC.CursorMaxPos.x = 0; + } + + if(mStyle.overflowY != CSS_OVERFLOW_SCROLL) { + ImGui::GetCurrentWindowRead()->DC.CursorMaxPos.y = 0; + } ImGui::EndChild(); } else { - ImGui::SetCursorScreenPos(ImGui::GetItemRectMax() + pad.Max); ImGui::EndGroup(); } } private: - ImU32 mID; float mWidth; - }; static int InputTextCallback(ImGuiInputTextCallbackData* data) @@ -163,9 +207,9 @@ namespace ImVue { { // Resize string callback ImVector* str = (ImVector*)data->UserData; - IM_ASSERT(data->Buf == str->Data); - str->resize(data->BufTextLen); - data->Buf = str->Data; + IM_ASSERT(data->Buf == str->begin()); + str->resize(data->BufSize); + data->Buf = str->begin(); } return 0; @@ -215,6 +259,7 @@ namespace ImVue { , mModel(0) , mValueUpdated(false) , mActivate(false) + , mID(0) , mType(TEXT) { mBuffer.resize(48); @@ -307,7 +352,7 @@ namespace ImVue { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize; if(size.x != 0) { - ImGui::PushItemWidth(size.x - pad.Min.x - pad.Max.x); + ImGui::PushItemWidth(contentWidth()); } int col[4] = {0}; bool changed = false; @@ -392,7 +437,7 @@ namespace ImVue { } ImGui::PopStyleColor(popColor); ImGui::PopStyleVar(2); - ImGui::SetCursorScreenPos(ImGui::GetItemRectMax() + pad.Max); + addMaxPadding(this); ImGui::EndGroup(); if(ImGui::IsItemClicked(0)) { @@ -439,6 +484,9 @@ namespace ImVue { if(mType == TEXT || mType == PASSWORD) { mFlags ^= Element::BUTTON; } + + if(type) + ImGui::MemFree(type); } void setValue(char* value) @@ -457,6 +505,7 @@ namespace ImVue { } mValue = ImStrdup(value); + ImGui::MemFree(value); mValueUpdated = true; } @@ -476,6 +525,9 @@ namespace ImVue { invalidateFlags(Element::MODEL); mModel = ImStrdup(key); } + + if(key) + ImGui::MemFree(key); } void setChecked(bool value) @@ -503,30 +555,31 @@ namespace ImVue { if(!mModel || (mFlags & BUTTON)) return; - Object object = (*mScriptState)[mModel]; + ScriptState& scriptState = *mScriptState; + Object object = scriptState[mModel]; if(write) { switch(mType) { case PASSWORD: case TEXT: - (*mScriptState)[mModel] = mBuffer.Data; + scriptState[mModel] = mBuffer.Data; break; case COLOR: - (*mScriptState)[mModel] = ImGui::ColorConvertFloat4ToU32(mColor); + scriptState[mModel] = ImGui::ColorConvertFloat4ToU32(mColor); break; case CHECKBOX: if(object.type() == ObjectType::USERDATA) { updateArray(object); } else { - (*mScriptState)[mModel] = hasState(CHECKED); + scriptState[mModel] = hasState(CHECKED); } break; case RADIO: - (*mScriptState)[mModel] = mValue ? mValue : placeholder; + scriptState[mModel] = mValue ? mValue : placeholder; break; case NUMBER: case RANGE: - (*mScriptState)[mModel] = mSliderValue; + scriptState[mModel] = mSliderValue; default: break; } diff --git a/src/imvue.cpp b/src/imvue.cpp index 702fd7c..fae383d 100644 --- a/src/imvue.cpp +++ b/src/imvue.cpp @@ -295,6 +295,7 @@ namespace ImVue { if(data) { try { mScriptState->initialize(data); + ImGui::MemFree(data); } catch(...) { ImGui::MemFree(data); throw; diff --git a/src/imvue.h b/src/imvue.h index 1399631..b5fb8ba 100644 --- a/src/imvue.h +++ b/src/imvue.h @@ -180,6 +180,7 @@ namespace ImVue { , mMounted(other.mMounted) , mRefs(other.mRefs) { + mLayout = 0; (*mRefs)++; } diff --git a/src/imvue_element.cpp b/src/imvue_element.cpp index d31c240..c4822e1 100644 --- a/src/imvue_element.cpp +++ b/src/imvue_element.cpp @@ -83,6 +83,7 @@ namespace ImVue { } } + MouseEventHandler::MouseEventHandler(Element* element, const char* handlerName, const char* script) : InputHandler(element, handlerName, script) , mType(Click) @@ -228,7 +229,6 @@ namespace ImVue { , mFactory(0) , mBuilder(0) , mTextureManager(0) - , mClickHandler(NULL) , mScriptState(NULL) , mCtx(0) , mScriptContext(0) @@ -238,6 +238,7 @@ namespace ImVue { , mState(0) , mRequiredAttrsCount(0) , mConfigured(false) + , mType(UNTYPED) { padding[0] = -1.0f; padding[1] = -1.0f; @@ -252,9 +253,6 @@ namespace ImVue { Element::~Element() { - if(mClickHandler) - ImGui::MemFree(mClickHandler); - if(mScriptState) { for(Element::ReactiveFields::const_iterator iter = mReactiveFields.begin(); iter != mReactiveFields.end(); ++iter) { bool success = mScriptState->removeListener(iter->first, this); @@ -307,6 +305,7 @@ namespace ImVue { mParent = parent; mScriptContext = sctx ? sctx : (parent ? parent->getContext(true) : NULL); mNode = node; + mType = node ? node->name() : UNTYPED; mFactory = ctx->factory; mTextureManager = ctx->texture; if(ImStricmp(node->name(), "window") == 0) { @@ -351,7 +350,18 @@ namespace ImVue { void Element::setSize(const ImVec2& s) { - size = s; + if(s.x > 0) { + size.x = ImMax(s.x, minSize.x); + } else { + size.y = 0; + } + + if(s.y > 0) { + size.y = ImMax(s.y, minSize.y); + } else { + size.y = 0; + } + if(mFlags & WINDOW) { ImGui::SetNextWindowSize(size); } // widgets have no common method to set size unfortunately @@ -394,8 +404,10 @@ namespace ImVue { if (g.NavDisableMouseHover && !g.NavDisableHighlight) return ImGui::IsItemFocused(); + ImRect itemRect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMin() + getSize()); + itemRect.ClipWith(window->ClipRect); // Test for bounding box overlap, as updated as ItemAdd() - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMin() + getSize()).Contains(ImGui::GetIO().MousePos) ) + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !itemRect.Contains(ImGui::GetIO().MousePos) ) return false; IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function @@ -600,17 +612,20 @@ namespace ImVue { layout->beginElement(this); pos = ImGui::GetCursorPos(); - ImGuiWindow* window = GetCurrentWindowNoDefault(); - if(window) { + if(mStyle.window) { + ImVec2 p = mStyle.localToGlobal(pos); // render decoration mStyle.decoration.render( ImRect( - window->Pos - window->Scroll + pos, - window->Pos - window->Scroll + pos + getSize() + p, + p + getSize() ) ); } + if(size.x > 0) { + ImGui::SetNextItemWidth(size.x); + } try { renderBody(); } catch (...) { @@ -655,9 +670,19 @@ namespace ImVue { } } - ContainerElement* Element::getParent() + ContainerElement* Element::getParent(bool noPseudoElements) { - return mParent ? static_cast(mParent) : NULL; + ContainerElement* element = NULL; + if(mParent) { + element = static_cast(mParent); + + if(noPseudoElements) { + while(element->isPseudoElement()) { + element = element->getParent(); + } + } + } + return element; } void Element::computeProperties() @@ -757,6 +782,8 @@ namespace ImVue { } ContainerElement::ContainerElement() + : mLayout(0) + , mPostRenderHook(0) { mFlags |= Element::CONTAINER; } @@ -764,6 +791,9 @@ namespace ImVue { ContainerElement::~ContainerElement() { removeChildren(); + if(mLayout) { + delete mLayout; + } } void ContainerElement::removeChildren() @@ -777,9 +807,30 @@ namespace ImVue { void ContainerElement::renderChildren() { bool pseudoElement = isPseudoElement(); Layout* backup = mCtx->layout; - if(!pseudoElement) { - mCtx->layout = &mLayout; - mLayout.begin(this); + + if(((mInvalidFlags & Element::LAYOUT) || mLayout == 0) && !pseudoElement) { + if(mLayout) { + delete mLayout; + mLayout = 0; + } + switch(display) { + case CSS_DISPLAY_TABLE: + break; + case CSS_DISPLAY_INLINE_FLEX: + case CSS_DISPLAY_FLEX: + mLayout = new FlexLayout(); + break; + default: + mLayout = new StaticLayout(); + break; + } + + mInvalidFlags ^= Element::LAYOUT; + } + + if(mLayout) { + mCtx->layout = mLayout; + mLayout->begin(this); } int i = 0; @@ -787,14 +838,29 @@ namespace ImVue { Element* element = *el; element->index = i++; element->render(); + if(mPostRenderHook) { + mPostRenderHook(element); + } } - if(!pseudoElement) { - mLayout.end(); + if(mLayout) { + mLayout->end(); mCtx->layout = backup; } } + void ContainerElement::forEachChild(ElementCallback callback, void* userdata) + { + for(Elements::iterator el = mChildren.begin(); el != mChildren.end(); el++) { + Element* element = *el; + if(element->isPseudoElement() && element->isContainer()) { + static_cast(element)->forEachChild(callback, userdata); + } else { + callback(element, userdata); + } + } + } + void cleanupValues(ImVector& values) { for(int i = 0; i < values.size(); ++i) { diff --git a/src/imvue_element.h b/src/imvue_element.h index 9613737..18bce82 100644 --- a/src/imvue_element.h +++ b/src/imvue_element.h @@ -67,6 +67,11 @@ namespace ImVue { */ static const char* TEXT_NODE = "__textnode__"; + /** + * UNTYPED ELEMENT + */ + static const char* UNTYPED = "__untyped__"; + // attribute converting utilities // used for parsing statically defined fields as ints/floats/arrays etc namespace detail { @@ -442,9 +447,10 @@ namespace ImVue { typedef std::vector Elements; enum InvalidationFlag { - BUILD = 1 << 0, - STYLE = 1 << 1, - MODEL = 1 << 2 + BUILD = 1 << 0, + STYLE = 1 << 1, + MODEL = 1 << 2, + LAYOUT = 1 << 3 }; // mutually excluding element states @@ -464,7 +470,8 @@ namespace ImVue { PSEUDO_ELEMENT = 1 << 1, COMPONENT = 1 << 2, WINDOW = 1 << 3, - BUTTON = 1 << 4 + BUTTON = 1 << 4, + NO_LAYOUT = 1 << 5 }; typedef std::unordered_map ReactiveFields; @@ -480,7 +487,7 @@ namespace ImVue { /** * Gets element type */ - inline const char* getType() const { return mNode->name(); } + inline const char* getType() const { (void)UNTYPED; return mType; } /** * Get element flags @@ -495,7 +502,7 @@ namespace ImVue { /** * Get parent */ - ContainerElement* getParent(); + ContainerElement* getParent(bool noPseudoElements = false); /** * Enable element @@ -558,6 +565,14 @@ namespace ImVue { */ void invalidateFlags(unsigned int flags); + /** + * Force element flags + */ + inline void setFlag(Element::Flag flag) + { + mFlags |= flag; + } + /** * Check if Element is container */ @@ -565,6 +580,13 @@ namespace ImVue { return (mFlags & Element::CONTAINER) != 0; } + /** + * Check if Element shouldn't have layouts enabled + */ + inline bool skipLayout() const { + return (mFlags & Element::NO_LAYOUT) != 0; + } + /** * Check if Element is pseudo element */ @@ -607,6 +629,22 @@ namespace ImVue { inline rapidxml::xml_node<>* node() { return mNode; } + /** + * Get width - left and right padding + */ + inline float contentWidth() const + { + return getSize().x - padding[0] - padding[2]; + } + + /** + * Get height - top and bottom padding + */ + inline float contentHeight() const + { + return getSize().y - padding[1] - padding[3]; + } + void setSize(const ImVec2& size); inline ImVec2 getSize() const { @@ -622,6 +660,16 @@ namespace ImVue { return res; } + inline void setDisplay(uint16_t d) + { + if(display != d) { + display = d; + if(isContainer()) { + invalidateFlags(Element::LAYOUT); + } + } + } + void setInlineStyle(char* style); void setClasses(const char* cls, int flags, ScriptState::Fields* fields); @@ -692,7 +740,7 @@ namespace ImVue { * Common size definition */ ImVec2 size; - + ImVec2 minSize; ImVec2 computedSize; /** @@ -746,6 +794,7 @@ namespace ImVue { void addHandler(const char* name, const char* value, const ElementBuilder* builder); + inline void setState(ElementState s) { if((mState & s) == 0) { @@ -779,7 +828,6 @@ namespace ImVue { ElementFactory* mFactory; ElementBuilder* mBuilder; TextureManager* mTextureManager; - char* mClickHandler; ScriptState* mScriptState; ReactiveFields mReactiveFields; DirtyProperties mDirtyProperties; @@ -793,6 +841,7 @@ namespace ImVue { int mRequiredAttrsCount; bool mConfigured; + const char* mType; }; // class Element @@ -882,6 +931,9 @@ namespace ImVue { if(evaluation && std::strncmp(&str[i], "}}", 2) == 0) { Object object = scriptState->getObject(&ss.str()[0], fields, element->getContext()); + if(!object) { + continue; + } ss = std::stringstream(); evaluation = false; result << object.as().c_str(); @@ -1510,7 +1562,19 @@ namespace ImVue { return result; } + typedef void(*ElementCallback)(Element*, void*); + + /** + * Call function for each element in the container and each for each PSEUDO_ELEMENT children stored in the container + * + * @param callback Callback function + * @param userdata To be passed into callback function + */ + void forEachChild(ElementCallback callback, void* userdata); + protected: + typedef void(RenderHook)(Element*); + /** * Render container */ @@ -1526,7 +1590,9 @@ namespace ImVue { void renderChildren(); - Layout mLayout; + Layout* mLayout; + + RenderHook* mPostRenderHook; }; class PseudoElement : public ContainerElement { @@ -1595,13 +1661,14 @@ namespace ImVue { bool build() { + mType = "span"; if(!mBuilder) { mBuilder = mFactory->get(TEXT_NODE); } bool res = Element::build(); // always display as inline block - display = CSS_DISPLAY_INLINE; + setDisplay(CSS_DISPLAY_INLINE); return res; } @@ -1641,6 +1708,7 @@ namespace ImVue { } text = ImStrdup(value); + ImGui::MemFree(value); } char* text; @@ -1655,7 +1723,7 @@ namespace ImVue { return false; } - return element->display == CSS_DISPLAY_INLINE_BLOCK || element->display == CSS_DISPLAY_INLINE; + return element->style()->displayInline; } } diff --git a/src/imvue_generated.h b/src/imvue_generated.h index f33b57a..4826c03 100644 --- a/src/imvue_generated.h +++ b/src/imvue_generated.h @@ -1665,13 +1665,27 @@ namespace ImVue { } void renderBody() { - if(ImGui::CollapsingHeader(label, flags)) + ImGuiWindow* window = GetCurrentWindowNoDefault(); + float backup; + if(window) { + backup = window->WorkRect.Max.x; + Element* parent = getParent(); + window->WorkRect.Max.x = parent->pos.x + parent->contentWidth(); + } + ImGui::BeginGroup(); + if(ImGui::CollapsingHeader(label, flags)) { ContainerElement::renderBody(); + } + ImGui::EndGroup(); + + if(window) + window->WorkRect.Max.x = backup; } char* label; ImGuiTreeNodeFlags flags; }; + // fills in all default elements to the element element factory static ElementFactory* createElementFactory() { diff --git a/src/imvue_layout.cpp b/src/imvue_layout.cpp index a11eea5..2a459cb 100644 --- a/src/imvue_layout.cpp +++ b/src/imvue_layout.cpp @@ -32,6 +32,16 @@ namespace ImVue { element->style()->position == CSS_POSITION_ABSOLUTE; } + Layout::Layout() + : topMargin(0) + , bottomMargin(0) + , height(0) + , currentElement(0) + , container(0) + , index(0) + { + } + void Layout::beginElement(Element* element) { if(skipLayout(element)) { @@ -45,7 +55,7 @@ namespace ImVue { // move to the new line if current element is block // or if it's inline but should be wrapped to the next line - if(element->display == CSS_DISPLAY_BLOCK || + if(element->style()->displayBlock || (contentRegion.x > 0 && lineEnd.x + element->getSize().x > contentRegion.x)) { newLine(); } @@ -64,6 +74,10 @@ namespace ImVue { topMargin = ImMax(element->margins[0], topMargin); } + if(ImGui::GetColumnOffset() != 0) { + pos.x = ImGui::GetColumnOffset() + GetCurrentWindowNoDefault()->Scroll.x; + } + // left if(element->margins[1] == FLT_MIN) { pos.x += style.ItemSpacing.y; @@ -100,7 +114,7 @@ namespace ImVue { lineEnd.x += size.x; height = ImMax(size.y, height); - if(element->display == CSS_DISPLAY_BLOCK) { + if(element->style()->displayBlock) { newLine(); } else { ImGui::SetCursorPos(ImVec2(lineEnd.x, cursorStart.y)); @@ -116,11 +130,13 @@ namespace ImVue { return; } + max = ImMax(lineEnd, max); lineEnd.x = cursorStart.x; lineEnd.y += height + topMargin + bottomMargin; height = 0.0f; topMargin = 0.0f; bottomMargin = 0.0f; + max = ImMax(lineEnd, max); } void Layout::begin(ContainerElement* element) @@ -131,18 +147,134 @@ namespace ImVue { topMargin = 0.0f; bottomMargin = 0.0f; currentElement = 0; + + max = ImVec2(0.0f, 0.0f); } void Layout::end() { - ImVec2 max; - max.x = lineEnd.x; newLine(); - max.y = lineEnd.y; ImGuiWindow* window = GetCurrentWindowNoDefault(); if(window) { window->DC.CursorMaxPos = ImMax(window->Pos - window->Scroll + max, window->DC.CursorMaxPos); } } + + void processElement(Element* element, void* userdata) + { + FlexLayout* layout = (FlexLayout*)userdata; + FlexSettings& flex = element->style()->flex; + IM_ASSERT(element->style()->displayBlock == false); + if(flex.grow == 0) { + ImRect rect = element->getMarginsRect(); + + if(layout->container->style()->flex.direction & FlexSettings::Row) { + layout->remainingLength -= element->getSize().x + rect.Min.x + rect.Max.x; + } else { + layout->remainingLength -= element->getSize().y + rect.Min.y + rect.Max.y; + } + } else { + layout->slices += flex.grow; + } + } + + void FlexLayout::beginElement(Element* element) + { + if(skipLayout(element)) { + return; + } + + if(!currentElement) { + lineEnd = ImGui::GetCursorPos(); + cursorStart = lineEnd; + } + + currentElement = element; + // initial position setup + ImVec2 pos = lineEnd; + // apply element margins + + ImGuiStyle& style = ImGui::GetStyle(); + // top + if(element->margins[0] == FLT_MIN) { + topMargin = style.ItemSpacing.y; + } else { + // always greater than 0 + topMargin = ImMax(element->margins[0], topMargin); + } + + // left + if(element->margins[1] == FLT_MIN) { + pos.x += style.ItemSpacing.y; + } else { + pos.x += element->margins[1]; + } + + FlexSettings& flex = element->style()->flex; + if(flex.grow > 0 && step > 0.0f) { + ImRect rect = element->getMarginsRect(); + + if(container->style()->flex.direction & FlexSettings::Row) { + element->setSize(ImVec2(flex.grow * step - rect.Min.x - rect.Max.x, element->size.y)); + } else { + element->setSize(ImVec2(element->size.x, flex.grow * step - rect.Min.y - rect.Max.y)); + } + } + + pos.y += topMargin; + ImGui::SetCursorPos(pos); + lineEnd.x = pos.x; + } + + void FlexLayout::endElement(Element* element) + { + if(skipLayout(element)) { + if(GetCurrentWindowNoDefault()) + ImGui::SetCursorPos(lineEnd); + return; + } + + IM_ASSERT(currentElement == element); + + // right + if(element->margins[2] != FLT_MIN) { + lineEnd.x += element->margins[2]; + } + + // bottom + if(element->margins[3] != FLT_MIN) { + bottomMargin = ImMax(element->margins[3], bottomMargin); + } + + ImVec2 size = element->getSize(); + lineEnd.x += size.x; + height = ImMax(size.y, height); + + FlexSettings& flex = container->style()->flex; + if(flex.direction & FlexSettings::Column) { + newLine(); + } else { + ImGui::SetCursorPos(ImVec2(lineEnd.x, cursorStart.y)); + } + } + + void FlexLayout::begin(ContainerElement* container) + { + Layout::begin(container); + slices = 0.0f; + FlexSettings& flex = container->style()->flex; + if(flex.direction & FlexSettings::Row) { + remainingLength = contentRegion.x; + } else if(container->style()->heightMode == CSS_HEIGHT_SET) { + remainingLength = contentRegion.y; + } + + // TODO: recalculate only if layout is invalid + container->forEachChild(processElement, this); + if(slices > 0.0f) + step = remainingLength / slices; + else + step = 0.0f; + } } diff --git a/src/imvue_layout.h b/src/imvue_layout.h index 019dafc..db2fea8 100644 --- a/src/imvue_layout.h +++ b/src/imvue_layout.h @@ -28,7 +28,8 @@ namespace ImVue { class ContainerElement; struct Layout { - + Layout(); + virtual ~Layout() {} float topMargin; // top margin max value float bottomMargin; // bottom margin max value float height; // total line height @@ -36,6 +37,7 @@ namespace ImVue { ImVec2 cursorStart; ImVec2 lineEnd; ImVec2 contentRegion; + ImVec2 max; Element* currentElement; ContainerElement* container; @@ -50,7 +52,7 @@ namespace ImVue { * * @param element Next element */ - void beginElement(Element* element); + virtual void beginElement(Element* element); /** * Finalizes element draw @@ -59,15 +61,30 @@ namespace ImVue { * * @param element Element that was just drawn */ - void endElement(Element* element); + virtual void endElement(Element* element); - void newLine(); + virtual void begin(ContainerElement* container); - void endLine(); + virtual void newLine(); + + void end(); + }; + + struct FlexLayout : public Layout { + void beginElement(Element* element); + + void endElement(Element* element); void begin(ContainerElement* container); - void end(); + float slices; + float remainingLength; + float step; + private: + float* getLength(const ImVec2& input); + }; + + struct StaticLayout : public Layout { }; } diff --git a/src/imvue_script.h b/src/imvue_script.h index b74e508..5f64823 100644 --- a/src/imvue_script.h +++ b/src/imvue_script.h @@ -487,7 +487,9 @@ namespace ImVue { * */ inline void addReference(const char* ref, Element* element) { - mRefMap[ImHashStr(ref)] = element; + RefHash hash = ImHashStr(ref); + IM_ASSERT(mRefMap.count(hash) == 0); + mRefMap[hash] = element; } /** diff --git a/src/imvue_style.cpp b/src/imvue_style.cpp index af47aba..46df122 100644 --- a/src/imvue_style.cpp +++ b/src/imvue_style.cpp @@ -223,6 +223,7 @@ namespace ImVue { if(s.x <= 0 && s.y <= 0) { return; } + // TODO: this function definitely has room for improvement // 1. try to implement continuous flow for line when drawing using same color // 2. loops ? @@ -506,7 +507,8 @@ namespace ImVue { changed = true; } - cs.element->setSize(res); + if(changed) + cs.element->setSize(res); return changed; } @@ -518,23 +520,22 @@ namespace ImVue { ImVec2 parentSize = cs.contentRegion; uint16_t position = css_computed_position(cs.style->styles[CSS_PSEUDO_ELEMENT_NONE]); cs.position = position; - ImGuiWindow* window = 0; Element* parent = cs.element->getParent(); switch(position) { + case CSS_POSITION_FIXED: case CSS_POSITION_ABSOLUTE: - window = GetCurrentWindowNoDefault(); - if(window) { + if(cs.window) { if(parent && parent->style()->position == CSS_POSITION_RELATIVE) { - pos = window->Pos + parent->pos; + pos = cs.window->DC.CursorStartPos + parent->pos; } else { - pos = window->Pos; + pos = cs.window->DC.CursorStartPos; } - pos -= window->Scroll; + + if(position == CSS_POSITION_FIXED) + pos += cs.window->Scroll; break; } - case CSS_POSITION_FIXED: - parentSize = ImGui::GetIO().DisplaySize; break; case CSS_POSITION_RELATIVE: pos = cs.elementScreenPosition; @@ -603,7 +604,8 @@ namespace ImVue { } else { ImGui::SetCursorScreenPos(pos + offset); } - cs.element->setSize(size); + if(changed) + cs.element->setSize(size); return changed; } @@ -665,7 +667,6 @@ namespace ImVue { }; bool defined = false; - for(size_t i = 0; i < 4; i++) { if(funcs[i].func(cs.style->styles[CSS_PSEUDO_ELEMENT_NONE], &value, &unit) == CSS_PADDING_SET) { @@ -870,6 +871,78 @@ namespace ImVue { return changed; } + bool setFlexBasis(ComputedStyle& cs) + { + css_fixed value; + css_unit unit = CSS_UNIT_PX; + if(css_computed_flex_basis( + cs.style->styles[CSS_PSEUDO_ELEMENT_NONE], + &value, + &unit) == CSS_FLEX_BASIS_SET) { + cs.flex.basis = parseUnits(value, unit, cs, cs.flex.axis); + return true; + } + + return false; + } + + bool setOverflow(ComputedStyle& cs) + { + cs.overflowX = css_computed_overflow_x( + cs.style->styles[CSS_PSEUDO_ELEMENT_NONE]); + cs.overflowY = css_computed_overflow_y( + cs.style->styles[CSS_PSEUDO_ELEMENT_NONE]); + + bool addCallback = false; + ImRect clipRect = ImRect(ImVec2(FLT_MIN, FLT_MIN), ImVec2(FLT_MAX, FLT_MAX)); + ImVec2 p = cs.localToGlobal(cs.element->pos); + if(cs.overflowX == CSS_OVERFLOW_HIDDEN) { + clipRect.Min.x = p.x; + clipRect.Max.x = p.x + cs.element->getSize().x; + addCallback = true; + } + + if(cs.overflowY == CSS_OVERFLOW_HIDDEN) { + clipRect.Min.y = p.y; + clipRect.Max.y = p.y + cs.element->getSize().y; + addCallback = true; + } + + if(cs.window) { + ImGui::PushClipRect(clipRect.Min, clipRect.Max, true); + cs.nClipRect++; + } + + return addCallback; + } + + bool setMinSize(ComputedStyle& cs) + { + ImVec2 minSize(0.0f, 0.0f); + css_fixed length = 0; + css_unit unit = CSS_UNIT_PX; + if(css_computed_min_width( + cs.style->styles[CSS_PSEUDO_ELEMENT_NONE], + &length, &unit) == CSS_MIN_WIDTH_SET) { + minSize.x = parseUnits(length, unit, cs, ParseUnitsAxis::X); + } + minSize.x = ImMax(minSize.x, cs.decoration.thickness[0] + cs.decoration.thickness[2] + cs.decoration.rounding[0] + cs.decoration.rounding[1]); + + if(css_computed_min_height( + cs.style->styles[CSS_PSEUDO_ELEMENT_NONE], + &length, &unit) == CSS_MIN_HEIGHT_SET) { + minSize.y = parseUnits(length, unit, cs, ParseUnitsAxis::Y); + } + minSize.y = ImMax(minSize.y, cs.decoration.thickness[1] + cs.decoration.thickness[3] + cs.decoration.rounding[2] + cs.decoration.rounding[3]); + + cs.element->minSize = minSize; + if(minSize.x == 0 && minSize.y == 0) { + return false; + } + + return true; + } + ComputedStyle::ComputedStyle(Element* target) : libcssData(0) , fontName(0) @@ -877,17 +950,25 @@ namespace ImVue { , element(target) , style(0) , context(0) + , overflowX(CSS_OVERFLOW_VISIBLE) + , overflowY(CSS_OVERFLOW_VISIBLE) + , displayBlock(true) + , displayInline(false) + , window(0) + , nClipRect(0) , nCol(0) , nStyle(0) , nFonts(0) , fontScale(0) + , widthMode(CSS_WIDTH_AUTO) + , heightMode(CSS_HEIGHT_AUTO) , mSelectCtx(0) - , mWidthMode(CSS_WIDTH_AUTO) - , mHeightMode(CSS_HEIGHT_AUTO) , mInlineStyle(0) , mAutoSize(false) { memset(&decoration, 0, sizeof(Decoration)); + memset(&flex, 0, sizeof(FlexSettings)); + flex.align = FlexSettings::Align::Auto; } ComputedStyle::~ComputedStyle() @@ -904,6 +985,7 @@ namespace ImVue { bool ComputedStyle::compute(Element* e) { + window = GetCurrentWindowNoDefault(); element = e; context = e->context(); css_error code; @@ -935,6 +1017,10 @@ namespace ImVue { return false; } + memset(&decoration, 0, sizeof(Decoration)); + memset(&flex, 0, sizeof(FlexSettings)); + flex.align = FlexSettings::Align::Auto; + if(style) { if(css_select_results_destroy(style) != CSS_OK) { IMVUE_EXCEPTION(StyleError, "failed to destroy style %s", css_error_to_string(code)); @@ -950,9 +1036,41 @@ namespace ImVue { mStyleCallbacks.clear(); - position = CSS_POSITION_INHERIT; + position = css_computed_position(style->styles[CSS_PSEUDO_ELEMENT_NONE]); uint16_t display = css_computed_display(style->styles[CSS_PSEUDO_ELEMENT_NONE], false); + if(position != CSS_POSITION_STATIC && position != CSS_POSITION_INHERIT) { + // override inline layout + if(display == CSS_DISPLAY_INLINE || display == CSS_DISPLAY_INLINE_BLOCK) { + display = CSS_DISPLAY_BLOCK; + } + + // override inline flex + if(display == CSS_DISPLAY_INLINE_FLEX) { + display = CSS_DISPLAY_FLEX; + } + } + + switch(display) { + case CSS_DISPLAY_FLEX: + case CSS_DISPLAY_BLOCK: + case CSS_DISPLAY_TABLE: + displayInline = false; + displayBlock = true; + break; + default: + displayInline = true; + displayBlock = false; + break; + } + + // if parent layout is flex, element is neither block nor inline + Element* parent = element->getParent(true); + if(parent && (parent->display == CSS_DISPLAY_FLEX || parent->display == CSS_DISPLAY_INLINE_FLEX)) { + displayBlock = false; + displayInline = false; + } + // fonts should go first as line height might affect units parser initFonts(&media); @@ -966,17 +1084,17 @@ namespace ImVue { css_fixed width = 0; css_fixed height = 0; css_unit unit = CSS_UNIT_PX; - mWidthMode = css_computed_width( + widthMode = css_computed_width( style->styles[CSS_PSEUDO_ELEMENT_NONE], &width, &unit ); - mHeightMode = css_computed_height( + heightMode = css_computed_height( style->styles[CSS_PSEUDO_ELEMENT_NONE], &height, &unit ); - bool shouldSetDimensions = mWidthMode == CSS_WIDTH_SET || mHeightMode == CSS_HEIGHT_SET; + bool shouldSetDimensions = widthMode == CSS_WIDTH_SET || heightMode == CSS_HEIGHT_SET; if(shouldSetDimensions && display != CSS_DISPLAY_INLINE) mStyleCallbacks.push_back(setDimensions); @@ -989,12 +1107,10 @@ namespace ImVue { // positioning { - position = css_computed_position(style->styles[CSS_PSEUDO_ELEMENT_NONE]); switch(position) { case CSS_POSITION_FIXED: case CSS_POSITION_ABSOLUTE: - display = CSS_DISPLAY_BLOCK; case CSS_POSITION_RELATIVE: mStyleCallbacks.push_back(setPosition); mAutoSize = false; @@ -1038,7 +1154,20 @@ namespace ImVue { mStyleCallbacks.push_back(setBorder); } - element->display = css_computed_display(style->styles[CSS_PSEUDO_ELEMENT_NONE], false); + // overflow + if(setOverflow(*this)) { + mStyleCallbacks.push_back(setOverflow); + } + + // min size + if(setMinSize(*this)) { + mStyleCallbacks.push_back(setMinSize); + } + + element->setDisplay(display); + + // flex layout settings + updateFlexSettings(); end(); return true; @@ -1052,17 +1181,21 @@ namespace ImVue { } ImGuiIO& io = ImGui::GetIO(); - ImGuiWindow* window = GetCurrentWindowNoDefault(); + window = GetCurrentWindowNoDefault(); if(window){ Element* parent = e->getParent(); - while(parent && (parent->display == CSS_DISPLAY_INLINE || (parent->getFlags() & Element::PSEUDO_ELEMENT) != 0)) { + while(parent && (parent->display == CSS_DISPLAY_INLINE || (parent->isPseudoElement()) != 0)) { parent = parent->getParent(); } if(parent && (parent->getFlags() & Element::WINDOW) == 0){ - contentRegion = parent->getSize() - ImVec2(parent->padding[0] + parent->padding[2], parent->padding[1] + parent->padding[3]); + contentRegion = ImVec2(parent->contentWidth(), parent->contentHeight()); } else { - contentRegion = ImGui::GetWindowSize() - ImGui::GetCursorStartPos() - window->Scroll - ImGui::GetStyle().WindowPadding; + contentRegion = window->Size - + ImGui::GetCursorStartPos() - + window->Scroll - + ImGui::GetStyle().WindowPadding - + window->ScrollbarSizes; } } else { contentRegion = io.DisplaySize; @@ -1075,8 +1208,8 @@ namespace ImVue { mStyleCallbacks[i](*this); } - if(element->display == CSS_DISPLAY_BLOCK && mAutoSize) { - if(mWidthMode == CSS_WIDTH_AUTO) { + if(displayBlock && mAutoSize) { + if(widthMode == CSS_WIDTH_AUTO) { ImRect margins = element->getMarginsRect(); element->size.x = contentRegion.x - margins.Max.x - margins.Min.x; } @@ -1085,6 +1218,11 @@ namespace ImVue { void ComputedStyle::end() { + while(nClipRect > 0) { + window->DC.CursorMaxPos = ImMin(window->DC.CursorMaxPos, window->ClipRect.Max); + ImGui::PopClipRect(); + nClipRect--; + } ImGui::PopStyleColor(nCol); ImGui::PopStyleVar(nStyle); if(fontScale) { @@ -1099,6 +1237,15 @@ namespace ImVue { fontScale = 0; } + ImVec2 ComputedStyle::localToGlobal(const ImVec2& pos) + { + if(window) { + return window->Pos - window->Scroll + pos; + } + + return pos; + } + void ComputedStyle::destroy() { end(); @@ -1279,15 +1426,180 @@ namespace ImVue { } } + void ComputedStyle::updateFlexSettings() + { + css_fixed value; + uint8_t status = css_computed_flex_grow( + style->styles[CSS_PSEUDO_ELEMENT_NONE], + &value + ); + + Element* parent = element->getParent(true); + if(parent && (parent->display == CSS_DISPLAY_FLEX || parent->display == CSS_DISPLAY_INLINE_FLEX)) { + memcpy(&flex, &parent->style()->flex, sizeof(FlexSettings)); + + if(status == CSS_FLEX_GROW_SET) { + flex.grow = FIXTOFLT(value); + } + + status = css_computed_flex_grow( + style->styles[CSS_PSEUDO_ELEMENT_NONE], + &value + ); + + if(status == CSS_FLEX_SHRINK_SET) { + flex.shrink = FIXTOFLT(value); + } + + status = css_computed_align_self(style->styles[CSS_PSEUDO_ELEMENT_NONE]); + + switch(status) { + case CSS_ALIGN_SELF_STRETCH: + flex.align = FlexSettings::Stretch; + break; + case CSS_ALIGN_SELF_FLEX_START: + flex.align = FlexSettings::FlexStart; + break; + case CSS_ALIGN_SELF_FLEX_END: + flex.align = FlexSettings::FlexEnd; + break; + case CSS_ALIGN_SELF_CENTER: + flex.align = FlexSettings::Center; + break; + case CSS_ALIGN_SELF_BASELINE: + flex.align = FlexSettings::Baseline; + break; + case CSS_ALIGN_SELF_AUTO: + flex.align = FlexSettings::Auto; + break; + } + + int32_t order = 0; + if(css_computed_order( + style->styles[CSS_PSEUDO_ELEMENT_NONE], + &order + ) == CSS_ORDER_SET) { + flex.order = order; + } + + if(setFlexBasis(*this)) { + mStyleCallbacks.push_back(setFlexBasis); + } + } + + // here goes flex container configuration + if(element->display != CSS_DISPLAY_FLEX && element->display != CSS_DISPLAY_INLINE_FLEX) { + return; + } + + switch(css_computed_flex_direction( + style->styles[CSS_PSEUDO_ELEMENT_NONE] + )) { + case CSS_FLEX_DIRECTION_ROW: + flex.direction = FlexSettings::Row; + flex.axis = ParseUnitsAxis::X; + break; + case CSS_FLEX_DIRECTION_ROW_REVERSE: + flex.direction = FlexSettings::RowReverse; + flex.axis = ParseUnitsAxis::X; + break; + case CSS_FLEX_DIRECTION_COLUMN: + flex.direction = FlexSettings::Column; + flex.axis = ParseUnitsAxis::Y; + break; + case CSS_FLEX_DIRECTION_COLUMN_REVERSE: + flex.direction = FlexSettings::ColumnReverse; + flex.axis = ParseUnitsAxis::Y; + break; + } + + switch(css_computed_flex_wrap( + style->styles[CSS_PSEUDO_ELEMENT_NONE])) { + case CSS_FLEX_WRAP_NOWRAP: + flex.wrap = FlexSettings::NoWrap; + break; + case CSS_FLEX_WRAP_WRAP: + flex.wrap = FlexSettings::Wrap; + break; + case CSS_FLEX_WRAP_WRAP_REVERSE: + flex.wrap = FlexSettings::WrapReverse; + break; + } + + switch(css_computed_justify_content( + style->styles[CSS_PSEUDO_ELEMENT_NONE])) { + case CSS_JUSTIFY_CONTENT_FLEX_START: + flex.justifyContent = FlexSettings::FlexStart; + break; + case CSS_JUSTIFY_CONTENT_FLEX_END: + flex.justifyContent = FlexSettings::FlexEnd; + break; + case CSS_JUSTIFY_CONTENT_CENTER: + flex.justifyContent = FlexSettings::Center; + break; + case CSS_JUSTIFY_CONTENT_SPACE_BETWEEN: + flex.justifyContent = FlexSettings::SpaceBetween; + break; + case CSS_JUSTIFY_CONTENT_SPACE_AROUND: + flex.justifyContent = FlexSettings::SpaceAround; + break; + case CSS_JUSTIFY_CONTENT_SPACE_EVENLY: + flex.justifyContent = FlexSettings::SpaceEvenly; + break; + } + + switch(css_computed_align_content( + style->styles[CSS_PSEUDO_ELEMENT_NONE])) { + case CSS_ALIGN_CONTENT_STRETCH: + flex.alignContent = FlexSettings::Stretch; + break; + case CSS_ALIGN_CONTENT_FLEX_START: + flex.alignContent = FlexSettings::FlexStart; + break; + case CSS_ALIGN_CONTENT_FLEX_END: + flex.alignContent = FlexSettings::FlexEnd; + break; + case CSS_ALIGN_CONTENT_CENTER: + flex.alignContent = FlexSettings::Center; + break; + case CSS_ALIGN_CONTENT_SPACE_BETWEEN: + flex.alignContent = FlexSettings::SpaceBetween; + break; + case CSS_ALIGN_CONTENT_SPACE_AROUND: + flex.alignContent = FlexSettings::SpaceAround; + break; + case CSS_ALIGN_CONTENT_SPACE_EVENLY: + flex.alignContent = FlexSettings::SpaceEvenly; + break; + } + + switch(css_computed_align_items( + style->styles[CSS_PSEUDO_ELEMENT_NONE])) { + case CSS_ALIGN_ITEMS_STRETCH: + flex.alignItems = FlexSettings::Stretch; + break; + case CSS_ALIGN_ITEMS_FLEX_START: + flex.alignItems = FlexSettings::FlexStart; + break; + case CSS_ALIGN_ITEMS_FLEX_END: + flex.alignItems = FlexSettings::FlexEnd; + break; + case CSS_ALIGN_ITEMS_CENTER: + flex.alignItems = FlexSettings::Center; + break; + case CSS_ALIGN_ITEMS_BASELINE: + flex.alignItems = FlexSettings::Baseline; + break; + } + } + const char* cssBase = "* {" "display: block;" "}\n" "html {" - "width: 100%;" "height: 100%;" "}" "body {" - "width: 100%;" "height: 100%;" "}\n" "h1, h2, h3, h4, h5, h6 {" @@ -1306,12 +1618,12 @@ namespace ImVue { "picture, progress, q, ruby, s, samp, script, select, " "select, small, span, strong, sub, sup, svg, textarea, time, u, tt, var, wbr" // imgui items that should be displayed inline - ", menu, same-line, tab-item" + ", menu, same-line, tab-item, next-line" "{" "display: inline;" "}" // imgui items that are actually positioned absolute - "tooltip, popup-modal, indent, unindent, next-column {" + "tooltip, popup-modal, indent, unindent, next-column, same-line, tab-item {" "position: fixed;" "}"; diff --git a/src/imvue_style.h b/src/imvue_style.h index c72d712..daf6718 100644 --- a/src/imvue_style.h +++ b/src/imvue_style.h @@ -87,7 +87,71 @@ namespace ImVue { bool setBorder(ComputedStyle& style); + bool setFlexBasis(ComputedStyle& style); + + bool setOverflow(ComputedStyle& style); + + bool setMinSize(ComputedStyle& style); + class Style; + + struct FlexSettings + { + // item settings + enum Align { + Auto, + Stretch, + FlexStart, + FlexEnd, + Center, + Baseline, + FirstBaseline, + LastBaseline, + Start, + End, + SelfStart, + SelfEnd, + Safe, + Unsafe, + SpaceBetween, + SpaceAround, + SpaceEvenly, + Left, + Right + }; + + float grow; + float shrink; + float basis; + int order; + Align align; + + // container settings + enum Direction + { + Row = 1, + Column = 1 << 1, + Reverse = 1 << 2, + RowReverse = Row | Reverse, + ColumnReverse = Column | Reverse + }; + + Direction direction; + + enum FlexWrap { + NoWrap, + Wrap, + WrapReverse + }; + + FlexWrap wrap; + Align justifyContent; + Align alignContent; + Align alignItems; + + ParseUnitsAxis axis; + }; + /** * Style state */ @@ -114,6 +178,8 @@ namespace ImVue { */ void end(); + ImVec2 localToGlobal(const ImVec2& pos); + void destroy(); void updateClassCache(); @@ -130,6 +196,7 @@ namespace ImVue { , element(other.element) , style(0) , context(0) + , nClipRect(0) , nCol(0) , nStyle(0) , nFonts(0) @@ -191,25 +258,35 @@ namespace ImVue { ImU32 bgCol; }; + uint8_t overflowX; + uint8_t overflowY; Decoration decoration; + bool displayBlock; + bool displayInline; + FlexSettings flex; + ImGuiWindow* window; + // style stack + int nClipRect; int nCol; int nStyle; int nFonts; float fontScale; + uint16_t widthMode; + uint16_t heightMode; private: void initFonts(css_media* media); void cleanupClassCache(); + void updateFlexSettings(); + friend class Style; ImVector mStyleCallbacks; css_select_ctx* mSelectCtx; ImVector mClasses; - uint16_t mWidthMode; - uint16_t mHeightMode; css_stylesheet* mInlineStyle; bool mAutoSize; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6af9b68..a3bd0b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES unit/imvue_tests.cpp unit/state.cpp unit/style.cpp + unit/layout.cpp ) include_directories(${GTEST_INCLUDE_DIRS}) diff --git a/tests/resources/asserts.lua b/tests/resources/asserts.lua new file mode 100644 index 0000000..bee59cb --- /dev/null +++ b/tests/resources/asserts.lua @@ -0,0 +1,9 @@ +local asserts = { + equal = function(arg1, arg2) + if arg1 ~= arg2 then + error("assertion failed: expected " .. tostring(arg1) .. " to be equal to " .. arg2) + end + end +} + +return asserts diff --git a/tests/resources/flex.lua b/tests/resources/flex.lua new file mode 100644 index 0000000..983fd1f --- /dev/null +++ b/tests/resources/flex.lua @@ -0,0 +1,40 @@ +local asserts = require 'asserts' +result = { + success = false +} + +imvue = ImVue.new({ + runTest = function(self) + asserts.equal(self._refs.container.size.x, 300) + asserts.equal(self._refs.element1.size.x, 100) + asserts.equal(self._refs.element2.size.x, 200) + asserts.equal(self._refs.element3.size.x, 0) + + self.elements[3] = { + style = [[ + flex-grow: 0; + height: 50px; + ]] + } + + result.success = true + end, + data = function() + return { + elements = { + { + style = 'flex-grow: 1' + }, + { + style = 'flex-grow: 2' + }, + { + style = 'flex-grow: 0' + } + } + } + end +}) + +return imvue + diff --git a/tests/resources/flex_horizontal.xml b/tests/resources/flex_horizontal.xml new file mode 100644 index 0000000..4d86740 --- /dev/null +++ b/tests/resources/flex_horizontal.xml @@ -0,0 +1,24 @@ + + + diff --git a/tests/unit/layout.cpp b/tests/unit/layout.cpp new file mode 100644 index 0000000..89e32b5 --- /dev/null +++ b/tests/unit/layout.cpp @@ -0,0 +1,104 @@ +#include "imvue.h" +#include +#include "utils.h" +#include "imvue_generated.h" + +#if defined(WITH_LUA) + +#include "imgui_lua_bindings.h" +#include "lua/script.h" +#include + +typedef const char* LayoutsTestParam; + +class TestLayouts : public ::testing::Test, public testing::WithParamInterface { + + public: + + TestLayouts() + : mDoc(0) + { + } + + ~TestLayouts() + { + if(mDoc) { + delete mDoc; + mDoc = 0; + } + } + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + ImVue::registerBindings(L); + } + + void TearDown() override + { + if(mDoc) { + delete mDoc; + mDoc = 0; + } + lua_close(L); + } + + ImVue::Document& createDoc(const char* path) + { + if(mDoc) { + delete mDoc; + } + ImVue::ElementFactory* factory = ImVue::createElementFactory(); + ImVue::Context* ctx = ImVue::createContext(factory, new ImVue::LuaScriptState(L)); + char* data = ctx->fs->load(path); + if(!data) { + throw std::runtime_error("failed to load file"); + } + try { + mDoc = new ImVue::Document(ctx); + mDoc->parse(data); + } catch(...) { + delete mDoc; + mDoc = 0; + ImGui::MemFree(data); + throw; + } + ImGui::MemFree(data); + return *mDoc; + } + + ImVue::Document* mDoc; + lua_State* L; +}; + +inline void getLuaVariable(lua_State* L, const char* table, const char* key, bool& result) { + int top = lua_gettop(L); + lua_getglobal(L, table); + lua_pushstring(L, key); + lua_gettable(L, -2); + result = lua_toboolean(L, -1); + lua_pop(L, lua_gettop(L) - top); +} + +TEST_P(TestLayouts, TestFlex) { + const char* file = GetParam(); + ImVue::Document& doc = createDoc(file); + renderDocument(doc); + bool success = false; + if(luaL_dostring(L, "imvue:runTest()") != 0) { + FAIL() << "Test failed " << lua_tostring(L, -1); + } + getLuaVariable(L, "result", "success", success); + ASSERT_TRUE(success); +} + +INSTANTIATE_TEST_CASE_P( + TestFlex, + TestLayouts, + ::testing::Values( + "flex_vertical.xml", + "flex_horizontal.xml" + )); + +#endif diff --git a/tests/unit/state.cpp b/tests/unit/state.cpp index 87e253b..1a5c4dc 100644 --- a/tests/unit/state.cpp +++ b/tests/unit/state.cpp @@ -892,4 +892,6 @@ INSTANTIATE_TEST_CASE_P( std::make_tuple("validateCallback", "0", false) )); +// TODO: component slot test + #endif diff --git a/tests/unit/style.cpp b/tests/unit/style.cpp index b3934b7..a5573d1 100644 --- a/tests/unit/style.cpp +++ b/tests/unit/style.cpp @@ -836,8 +836,9 @@ TEST_F(TestStylesComponents, Dimensions) renderDocument(d); ImVue::HtmlContainer* div = els[0]; - EXPECT_EQ(div->computedSize.x, 300); - EXPECT_EQ(div->computedSize.y, 300); + ImVec2 size = div->getSize(); + EXPECT_EQ(size.x, 300); + EXPECT_EQ(size.y, 300); } #endif diff --git a/tools/templates/imvue_generated.h.j2 b/tools/templates/imvue_generated.h.j2 index 37ef37c..e861c9f 100644 --- a/tools/templates/imvue_generated.h.j2 +++ b/tools/templates/imvue_generated.h.j2 @@ -92,13 +92,27 @@ namespace ImVue { } void renderBody() { - if(ImGui::CollapsingHeader(label, flags)) + ImGuiWindow* window = GetCurrentWindowNoDefault(); + float backup; + if(window) { + backup = window->WorkRect.Max.x; + Element* parent = getParent(); + window->WorkRect.Max.x = parent->pos.x + parent->contentWidth(); + } + ImGui::BeginGroup(); + if(ImGui::CollapsingHeader(label, flags)) { ContainerElement::renderBody(); + } + ImGui::EndGroup(); + + if(window) + window->WorkRect.Max.x = backup; } char* label; ImGuiTreeNodeFlags flags; }; + // fills in all default elements to the element element factory static ElementFactory* createElementFactory() {