From 785aae883935f2b52a1528e1742dbd551af3a537 Mon Sep 17 00:00:00 2001 From: ducphamhong Date: Fri, 30 Oct 2020 15:13:39 +0700 Subject: [PATCH] #6 Update Spliter control behavior (done) --- Projects/Editor/Source/Editor/CEditor.cpp | 8 +- .../Editor/Source/GUI/Controls/CSplitter.cpp | 579 +++++++++++++++--- .../Editor/Source/GUI/Controls/CSplitter.h | 55 +- 3 files changed, 565 insertions(+), 77 deletions(-) diff --git a/Projects/Editor/Source/Editor/CEditor.cpp b/Projects/Editor/Source/Editor/CEditor.cpp index 63cf9ce77..84659a027 100644 --- a/Projects/Editor/Source/Editor/CEditor.cpp +++ b/Projects/Editor/Source/Editor/CEditor.cpp @@ -37,7 +37,7 @@ namespace Skylicht GUI::CSplitter *spliter = new GUI::CSplitter(window1); spliter->dock(GUI::EPosition::Fill); - spliter->setNumberRowCol(4, 1); + spliter->setNumberRowCol(2, 2); GUI::CTabControl *tab1 = new GUI::CTabControl(window1); GUI::CTabControl *tab2 = new GUI::CTabControl(window1); @@ -45,9 +45,9 @@ namespace Skylicht GUI::CTabControl *tab4 = new GUI::CTabControl(window1); spliter->setControl(tab1, 0, 0); - spliter->setControl(tab2, 1, 0); - spliter->setControl(tab3, 2, 0); - spliter->setControl(tab4, 3, 0); + spliter->setControl(tab2, 0, 1); + spliter->setControl(tab3, 1, 0); + spliter->setControl(tab4, 1, 1); GUI::CBase *p1 = tab1->addPage(L"Scene", NULL); GUI::CBase *p2 = tab1->addPage(L"Particle", NULL); diff --git a/Projects/Editor/Source/GUI/Controls/CSplitter.cpp b/Projects/Editor/Source/GUI/Controls/CSplitter.cpp index 4307054a3..723533c82 100644 --- a/Projects/Editor/Source/GUI/Controls/CSplitter.cpp +++ b/Projects/Editor/Source/GUI/Controls/CSplitter.cpp @@ -38,7 +38,15 @@ namespace Skylicht m_row(1), m_col(1), m_expanderSize(4), - m_minSize(50.0f) + m_minSize(50.0f), + m_pressed(false), + m_dragRow(0), + m_dragCol(0), + m_lastSpaceWidth(0.0f), + m_lastSpaceHeight(0.0f), + m_weakCol(0), + m_weakRow(0), + m_firstTimeLayout(true) { for (int x = 0; x < MAX_SPLITER_COL; x++) { @@ -70,6 +78,12 @@ namespace Skylicht { predictChildSize(); + if (m_firstTimeLayout == true) + { + saveUserExpectedSize(); + m_firstTimeLayout = false; + } + float x = 0.0f; float y = 0.0f; float w = 0.0f; @@ -103,35 +117,58 @@ namespace Skylicht float spaceWidth = bound.Width - m_expanderSize * (m_col - 1); float spaceHeight = bound.Height - m_expanderSize * (m_row - 1); - int numColPredict = 0; - int numRowPredict = 0; + if (spaceWidth > 0.0f) + { + if (spaceWidth >= m_lastSpaceWidth) + { + predictChildWidth(spaceWidth); + updateWidthLarger(); + } + else + { + updateWidthSmaller(spaceWidth, m_lastSpaceWidth - spaceWidth); + } + } - float minWidth = m_col * m_minSize + m_expanderSize * (m_col - 1); - float minHeight = m_row * m_minSize + m_expanderSize * (m_row - 1); + if (spaceHeight > 0.0f) + { + if (spaceHeight >= m_lastSpaceHeight) + { + predictChildHeight(spaceHeight); + updateHeightLarger(); + } + else + { + updateHeightSmaller(spaceHeight, m_lastSpaceHeight - spaceHeight); + } + } - bool save = false; + m_lastSpaceWidth = spaceWidth; + m_lastSpaceHeight = spaceHeight; + } + + void CSplitter::predictChildWidth(float spaceWidth) + { + int numColPredict = 0; + float minWidth = m_col * m_minSize + m_expanderSize * (m_col - 1); // note: colwidth == 0, mean it is not yet init layout, we will calculate it - if (spaceWidth > minWidth) + for (u32 i = 0; i < m_col; i++) { - for (u32 i = 0; i < m_col; i++) + if (m_colWidth[i] > 0.0f && spaceWidth > 0.0f) { - if (m_colWidth[i] > 0.0f && spaceWidth > 0.0f) - { - if (m_colWidth[i] > spaceWidth) - m_colWidth[i] = spaceWidth; - - spaceWidth = spaceWidth - m_colWidth[i]; - } - else - { - // predict later - m_colWidth[i] = 0.0f; - numColPredict++; - save = true; - } + spaceWidth = spaceWidth - m_colWidth[i]; } + else + { + // predict later + m_colWidth[i] = 0.0f; + numColPredict++; + } + } + if (spaceWidth > 0) + { if (numColPredict > 0) { float avgColSize = spaceWidth / (float)numColPredict; @@ -143,32 +180,37 @@ namespace Skylicht } else { - if (spaceWidth > 0) - m_colWidth[m_col - 1] = m_colWidth[m_col - 1] + spaceWidth; + u32 weakCol = m_weakCol; + if (weakCol >= m_col) + weakCol = m_col - 1; + + m_colWidth[weakCol] = m_colWidth[weakCol] + spaceWidth; } } + } + + void CSplitter::predictChildHeight(float spaceHeight) + { + int numRowPredict = 0; + float minHeight = m_row * m_minSize + m_expanderSize * (m_row - 1); // note: rowheight == 0, mean it is not yet init layout, we will calculate it - if (spaceHeight > minHeight) + for (u32 i = 0; i < m_row; i++) { - for (u32 i = 0; i < m_row; i++) + if (m_rowHeight[i] > 0.0f && spaceHeight > 0.0f) { - if (m_rowHeight[i] > 0.0f && spaceHeight > 0.0f) - { - if (m_rowHeight[i] > spaceHeight) - m_rowHeight[i] = spaceHeight; - - spaceHeight = spaceHeight - m_rowHeight[i]; - } - else - { - // predict later - m_rowHeight[i] = 0.0f; - numRowPredict++; - save = true; - } + spaceHeight = spaceHeight - m_rowHeight[i]; + } + else + { + // predict later + m_rowHeight[i] = 0.0f; + numRowPredict++; } + } + if (spaceHeight > 0) + { if (numRowPredict > 0) { float avgRowSize = spaceHeight / (float)numRowPredict; @@ -180,55 +222,182 @@ namespace Skylicht } else { - if (spaceHeight > 0) - m_rowHeight[m_row - 1] = m_rowHeight[m_row - 1] + spaceHeight; + u32 weakRow = m_weakRow; + if (weakRow >= m_row) + weakRow = m_row - 1; + + m_rowHeight[weakRow] = m_rowHeight[weakRow] + spaceHeight; } } - - fixForMinSize(); - - if (save) - saveUserExpectedSize(); - - fixForUserExpected(); } - void CSplitter::fixForMinSize() + void CSplitter::getWeakPriorityRow(std::list>& weakRowPriority, bool inverse) { - for (u32 i = 0; i < m_col; i++) + std::list>::iterator it, end; + + for (u32 i = 0; i < m_row; i++) { - if (m_colWidth[i] < m_minSize) + int d = (int)(m_weakRow - i); + float distance = (float)(core::abs_(d)); + + // right is weak + if (i < m_weakRow) + distance = distance + (float)m_row; + + if (weakRowPriority.size() == 0) { - float delta = m_minSize - m_colWidth[i]; + weakRowPriority.push_back(std::pair(i, distance)); + } + else + { + // insert sort + it = weakRowPriority.begin(); + end = weakRowPriority.end(); + bool insert = false; - for (int j = (int)i - 1; j >= 0; j--) + while (it != end) { - if (m_colWidth[j] > delta + m_minSize) + float value = (*it).second; + if (distance < value) { - m_colWidth[j] = m_colWidth[j] - delta; - m_colWidth[i] = m_colWidth[i] + delta; - break; + weakRowPriority.insert(it, std::pair(i, distance)); + insert = true; } + ++it; } + + if (insert == false) + weakRowPriority.push_back(std::pair(i, distance)); } } - for (u32 i = 0; i < m_row; i++) + if (inverse == true) { - if (m_rowHeight[i] < m_minSize) + std::list> reverse; + + it = weakRowPriority.begin(); + end = weakRowPriority.end(); + while (it != end) { - float delta = m_minSize - m_rowHeight[i]; + reverse.push_front((*it)); + ++it; + } + + weakRowPriority = reverse; + } + } + + void CSplitter::getWeakPriorityCol(std::list>& weakColPriority, bool inverse) + { + std::list>::iterator it, end; + + for (u32 i = 0; i < m_col; i++) + { + int d = (int)(m_weakCol - i); + float distance = (float)(core::abs_(d)); - for (int j = (int)i - 1; j >= 0; j--) + // right is weak + if (i < m_weakCol) + distance = distance + (float)m_col; + + if (weakColPriority.size() == 0) + { + weakColPriority.push_back(std::pair(i, distance)); + } + else + { + // insert sort + it = weakColPriority.begin(); + end = weakColPriority.end(); + bool insert = false; + + while (it != end) { - if (m_rowHeight[j] > delta + m_minSize) + float value = (*it).second; + if (distance < value) { - m_rowHeight[j] = m_rowHeight[j] - delta; - m_rowHeight[i] = m_rowHeight[i] + delta; - break; + weakColPriority.insert(it, std::pair(i, distance)); + insert = true; } + ++it; } + + if (insert == false) + weakColPriority.push_back(std::pair(i, distance)); + } + } + + if (inverse == true) + { + std::list> reverse; + + it = weakColPriority.begin(); + end = weakColPriority.end(); + while (it != end) + { + reverse.push_front((*it)); + ++it; } + + weakColPriority = reverse; + } + } + + void CSplitter::updateWidthSmaller(float spaceWidth, float delta) + { + std::list> weakColPriority; + std::list>::iterator it, end; + + getWeakPriorityCol(weakColPriority); + + it = weakColPriority.begin(); + end = weakColPriority.end(); + + while (it != end) + { + u32 i = (*it).first; + + float w = m_colWidth[i] - delta; + if (w < m_minSize) + w = m_minSize; + + delta = delta - (m_colWidth[i] - w); + + m_colWidth[i] = w; + + if (delta <= 0.0f) + return; + + ++it; + } + } + + void CSplitter::updateHeightSmaller(float spaceHeight, float delta) + { + std::list> weakRowPriority; + std::list>::iterator it, end; + + getWeakPriorityRow(weakRowPriority); + + it = weakRowPriority.begin(); + end = weakRowPriority.end(); + + while (it != end) + { + u32 i = (*it).first; + + float h = m_rowHeight[i] - delta; + if (h < m_minSize) + h = m_minSize; + + delta = delta - (m_rowHeight[i] - h); + + m_rowHeight[i] = h; + + if (delta <= 0.0f) + return; + + ++it; } } @@ -241,16 +410,30 @@ namespace Skylicht m_adjustRowHeight[y] = m_rowHeight[y]; } - void CSplitter::fixForUserExpected() + void CSplitter::updateWidthLarger() { - for (u32 i = 0; i < m_col; i++) + std::list> weakPriority; + std::list>::iterator it, end, test; + + getWeakPriorityCol(weakPriority, true); + + it = weakPriority.begin(); + end = weakPriority.end(); + + while (it != end) { + u32 i = (*it).first; + if (m_colWidth[i] < m_adjustColWidth[i]) { float target = m_adjustColWidth[i] - m_colWidth[i]; - for (u32 j = i + 1; j < m_col; j++) + test = it; + ++test; + + while (test != end) { + u32 j = (*test).first; if (m_colWidth[j] > m_minSize) { float delta = m_colWidth[j] - m_minSize; @@ -264,18 +447,60 @@ namespace Skylicht if (target <= 0.0f) break; } + ++test; + } + + if (target > 0) + { + test = weakPriority.begin(); + while (test != it) + { + u32 j = (*test).first; + if (m_colWidth[j] > m_adjustColWidth[j]) + { + float delta = m_colWidth[j] - m_adjustColWidth[j]; + if (delta > target) + delta = target; + + m_colWidth[i] = m_colWidth[i] + delta; + m_colWidth[j] = m_colWidth[j] - delta; + + target = target - delta; + if (target <= 0.0f) + break; + } + ++test; + } } } + ++it; } + } - for (u32 i = 0; i < m_row; i++) + void CSplitter::updateHeightLarger() + { + std::list> weakPriority; + std::list>::iterator it, end, test; + + getWeakPriorityRow(weakPriority, true); + + it = weakPriority.begin(); + end = weakPriority.end(); + + while (it != end) { + u32 i = (*it).first; + if (m_rowHeight[i] < m_adjustRowHeight[i]) { float target = m_adjustRowHeight[i] - m_rowHeight[i]; - for (u32 j = i + 1; j < m_row; j++) + test = it; + ++test; + + while (test != end) { + u32 j = (*test).first; if (m_rowHeight[j] > m_minSize) { float delta = m_rowHeight[j] - m_minSize; @@ -289,8 +514,34 @@ namespace Skylicht if (target <= 0.0f) break; } + ++test; + } + + if (target > 0) + { + test = weakPriority.begin(); + while (test != it) + { + u32 j = (*test).first; + if (m_rowHeight[j] > m_adjustRowHeight[j]) + { + float delta = m_rowHeight[j] - m_adjustRowHeight[j]; + if (delta > target) + delta = target; + + m_rowHeight[i] = m_rowHeight[i] + delta; + m_rowHeight[j] = m_rowHeight[j] - delta; + + target = target - delta; + if (target <= 0.0f) + break; + } + ++test; + } } } + + ++it; } } @@ -346,12 +597,198 @@ namespace Skylicht { if (m_disabled) return; + + SPoint mousePoint(x, y); + mousePoint = canvasPosToLocal(mousePoint); + + EHoverState state = None; + + if (m_pressed == false) + { + // just test hover to update cursor icon + u32 hitRow, hitCol; + state = mouseHittest(mousePoint.X, mousePoint.Y, hitRow, hitCol); + } + else + { + // update drag size + state = m_hitState; + + SRect dragBound = getCellRect(m_dragRow, m_dragCol, m_dragRow + 1, m_dragCol + 1); + + float maxWidth = dragBound.Width - m_expanderSize - m_minSize; + float maxHeight = dragBound.Height - m_expanderSize - m_minSize; + + bool save = false; + + if (state & CSplitter::Row) + { + float h1 = mousePoint.Y - dragBound.Y; + if (h1 < m_minSize) + h1 = m_minSize; + if (h1 > maxHeight) + h1 = maxHeight; + + float h2 = dragBound.Height - h1 - m_expanderSize; + m_rowHeight[m_dragRow] = h1; + m_rowHeight[m_dragRow + 1] = h2; + + save = true; + } + + if (state & CSplitter::Col) + { + float w1 = mousePoint.X - dragBound.X; + if (w1 < m_minSize) + w1 = m_minSize; + if (w1 > maxWidth) + w1 = maxWidth; + + float w2 = dragBound.Width - w1 - m_expanderSize; + m_colWidth[m_dragCol] = w1; + m_colWidth[m_dragCol + 1] = w2; + + save = true; + } + + if (save) + { + invalidate(); + saveUserExpectedSize(); + } + } + + switch (state) + { + case CSplitter::None: + setCursor(GUI::ECursorType::Normal); + break; + case CSplitter::Row: + setCursor(GUI::ECursorType::SizeNS); + break; + case CSplitter::Col: + setCursor(GUI::ECursorType::SizeWE); + break; + case CSplitter::RowAndCol: + setCursor(GUI::ECursorType::SizeAll); + break; + default: + break; + } } void CSplitter::onMouseClickLeft(float x, float y, bool bDown) { if (m_disabled) return; + + SPoint mousePoint(x, y); + mousePoint = canvasPosToLocal(mousePoint); + + m_pressed = bDown; + m_hitState = mouseHittest(mousePoint.X, mousePoint.Y, m_dragRow, m_dragCol); + + if (m_pressed) + CInput::getInput()->setCapture(this); + else + CInput::getInput()->setCapture(NULL); + } + + CSplitter::EHoverState CSplitter::mouseHittest(float x, float y, u32& outRow, u32 &outCol) + { + bool isHoverH = false; + bool isHoverV = false; + float current = 0.0f; + + for (u32 i = 0; i < m_col; i++) + { + current += m_colWidth[i]; + + if ((DWORD)x >= current && (DWORD)x <= current + m_expanderSize) + { + isHoverH = true; + outCol = i; + break; + } + + current += m_expanderSize; + } + + current = 0.0f; + + for (u32 i = 0; i < m_row; i++) + { + current += m_rowHeight[i]; + + if (y >= current && y <= current + m_expanderSize) + { + isHoverV = true; + outRow = i; + break; + } + + current += m_expanderSize; + } + + + if (isHoverH && isHoverV) + return CSplitter::RowAndCol; + + if (isHoverH) + return CSplitter::Col; + + if (isHoverV) + return CSplitter::Row; + + return CSplitter::None; + } + + SRect CSplitter::getCellRect(u32 fromRow, u32 fromCol, u32 toRow, u32 toCol) + { + SRect result; + + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + + for (u32 i = 0; i < m_col; i++) + { + if (i == fromCol) + result.X = x; + + if (i >= fromCol) + w = w + m_colWidth[i] + m_expanderSize; + + x = x + m_colWidth[i] + m_expanderSize; + + if (i == toCol) + { + w = w - m_expanderSize; + result.Width = w; + break; + } + } + + for (u32 i = 0; i < m_row; i++) + { + if (i == fromRow) + result.Y = y; + + if (i >= fromRow) + h = h + m_rowHeight[i] + m_expanderSize; + + y = y + m_rowHeight[i] + m_expanderSize; + + if (i == toRow) + { + h = h - m_expanderSize; + result.Height = h; + break; + } + } + + return result; } } } diff --git a/Projects/Editor/Source/GUI/Controls/CSplitter.h b/Projects/Editor/Source/GUI/Controls/CSplitter.h index 006b5f4f4..3e7db81e9 100644 --- a/Projects/Editor/Source/GUI/Controls/CSplitter.h +++ b/Projects/Editor/Source/GUI/Controls/CSplitter.h @@ -37,6 +37,15 @@ namespace Skylicht class CSplitter : public CBase { + public: + enum EHoverState + { + None = 0, + Row = (1 << 1), + Col = (1 << 2), + RowAndCol = (Row | Col) + }; + protected: u32 m_row; u32 m_col; @@ -51,6 +60,20 @@ namespace Skylicht float m_expanderSize; float m_minSize; + + bool m_pressed; + u32 m_dragRow; + u32 m_dragCol; + EHoverState m_hitState; + + float m_lastSpaceWidth; + float m_lastSpaceHeight; + + u32 m_weakCol; + u32 m_weakRow; + + bool m_firstTimeLayout; + public: CSplitter(CBase* parent); @@ -78,6 +101,16 @@ namespace Skylicht m_minSize = size; } + inline void setWeakRow(u32 r) + { + m_weakRow = r; + } + + inline void setWeakCol(u32 c) + { + m_weakCol = c; + } + inline int getMaxRow() { return MAX_SPLITER_ROW; } inline int getMaxCol() { return MAX_SPLITER_COL; } @@ -90,11 +123,29 @@ namespace Skylicht void predictChildSize(); - void fixForMinSize(); + void predictChildWidth(float spaceWidth); + + void predictChildHeight(float spaceHeight); + + void updateWidthSmaller(float spaceWidth, float delta); + + void updateHeightSmaller(float spaceHeight, float delta); + + void updateWidthLarger(); + + void updateHeightLarger(); void saveUserExpectedSize(); - void fixForUserExpected(); + void getWeakPriorityRow(std::list < std::pair>& row, bool inverse = false); + + void getWeakPriorityCol(std::list < std::pair>& col, bool inverse = false); + + protected: + + EHoverState mouseHittest(float x, float y, u32& outRow, u32 &outCol); + + SRect getCellRect(u32 fromRow, u32 fromCol, u32 toRow, u32 toCol); }; } }