nim_duilib/duilib/Control/VirtualListBox.cpp
jiajia_deng 4933d1f2bc Remove dependency on shared
Signed-off-by: jiajia_deng <2894220@gmail.com>
2019-09-20 16:27:58 +08:00

464 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2017, NetEase (Hangzhou) Network Co., Ltd. All rights reserved.
//
// zqw
// 2015/7/22
//
// VirtualListBox class
#include "stdafx.h"
#include "VirtualListBox.h"
namespace ui
{
CSize VirtualLayout::ArrangeChild(const std::vector<Control*>& items, UiRect rc)
{
CSize sz(rc.GetWidth(), 0);
VirtualListBox *pList = dynamic_cast<VirtualListBox*>(m_pOwner);
ASSERT(pList);
if (pList->UseDefaultLayout()) {
sz = VLayout::ArrangeChild(items, rc);
} else {
sz.cy = pList->CalcElementsHeight(-1);
pList->ReArrangeChild(false);
}
return sz;
}
VirtualListBox::VirtualListBox(ui::Layout* pLayout) :
ui::ListBox(pLayout),
m_pDataProvider(nullptr),
m_nElementHeight(0),
m_nMaxItemCount(0),
m_nOldYScrollPos(0),
m_bArrangedOnce(false),
m_bForceArrange(false)
{
}
void VirtualListBox::SetDataProvider(VirtualListInterface *pProvider)
{
ASSERT(pProvider);
m_pDataProvider = pProvider;
}
void VirtualListBox::SetElementHeight(int nHeight)
{
m_nElementHeight = nHeight;
}
void VirtualListBox::InitElement(int nMaxItemCount)
{
ASSERT(m_pDataProvider);
ASSERT(m_nElementHeight);
m_nMaxItemCount = nMaxItemCount;
int nCount = GetElementCount();
if (nCount > nMaxItemCount)
nCount = nMaxItemCount;
for (int i = 0; i < nCount; i++) {
Control *pControl = CreateElement();
this->Add(pControl);
FillElement(pControl, i);
}
}
void VirtualListBox::Refresh()
{
int nElementCount = GetElementCount();
int nItemCount = GetCount();
if (nItemCount > nElementCount) {
int n = nItemCount - nElementCount;
for (int i = 0; i < n; i++)
this->RemoveAt(0);
}
else if (nItemCount < nElementCount) {
int n = 0;
if (nElementCount <= m_nMaxItemCount) {
n = nElementCount - nItemCount;
}
else {
n = m_nMaxItemCount - nItemCount;
}
for (int i = 0; i < n; i++) {
Control *pControl = CreateElement();
this->Add(pControl);
}
}
if (UseDefaultLayout()) {
// 刚从虚拟列表转换到普通模式时,存在布局错误的情况(虚拟列表滚动条接近底部,
// 然后数据减少,变成普通模式)
if (nItemCount == m_nMaxItemCount) {
this->GetLayout()->ArrangeChild(m_items, m_rcItem);
}
for (int i = 0; i < (int)m_items.size(); i++)
FillElement(m_items[i], i);
}
else {
ASSERT(nElementCount);
if (nElementCount <= 0)
return;
ReArrangeChild(true);
Arrange();
}
}
void VirtualListBox::RemoveAll()
{
__super::RemoveAll();
if (m_pVerticalScrollBar)
m_pVerticalScrollBar->SetScrollPos(0);
m_nOldYScrollPos = 0;
m_bArrangedOnce = false;
m_bForceArrange = false;
}
void VirtualListBox::SetForceArrange(bool bForce)
{
m_bForceArrange = bForce;
}
void VirtualListBox::GetDisplayCollection(std::vector<int>& collection)
{
collection.clear();
if (GetCount() == 0)
return;
UiRect rcThis = this->GetPos(false);
int min = GetScrollPos().cy / m_nElementHeight;
int max = min + (rcThis.GetHeight() / m_nElementHeight);
int nCount = GetElementCount();
if (max >= nCount)
max = nCount - 1;
for (auto i = min; i <= max; i++)
collection.push_back(i);
}
void VirtualListBox::EnsureVisible(int iIndex, bool bToTop)
{
if (iIndex < 0 || iIndex >= GetElementCount())
return;
int nPos = m_pVerticalScrollBar->GetScrollPos();
int nTopIndex = nPos / m_nElementHeight;
int nNewPos = 0;
if (bToTop) {
nNewPos = CalcElementsHeight(iIndex);
if (nNewPos >= m_pVerticalScrollBar->GetScrollRange())
return;
}
else {
if (IsElementDisplay(iIndex))
return;
if (iIndex > nTopIndex) {
// 向下
int height = CalcElementsHeight(iIndex + 1);
nNewPos = height - m_rcItem.GetHeight();
}
else {
// 向上
nNewPos = CalcElementsHeight(iIndex);
}
}
CSize sz(0, nNewPos);
SetScrollPos(sz);
}
void VirtualListBox::ReArrangeChild(bool bForce)
{
ScrollDirection direction = kScrollUp;
if (!bForce && !m_bForceArrange) {
if (!NeedReArrange(direction))
return;
}
//max( pos ) = range
//int range = m_pVerticalScrollBar->GetScrollRange();
//int pos = m_pVerticalScrollBar->GetScrollPos();
int nElementCount = GetElementCount();
int nTopIndexBottom = 0;
int nTopIndex = GetTopElementIndex(nTopIndexBottom);
if (direction == kScrollDown) {
// 向下滚动
ui::UiRect rcItem = m_rcItem;
rcItem.bottom = rcItem.top + nTopIndexBottom;
for (int i = 0; i < (int)m_items.size(); i++) {
rcItem.top = rcItem.bottom;
rcItem.bottom = rcItem.top + m_nElementHeight;
m_items[i]->SetPos(rcItem);
int nElementIndex = nTopIndex + i;
if (nElementIndex < nElementCount)
FillElement(m_items[i], nElementIndex);
}
}
else {
// 向上滚动
int nDisplayCount = m_rcItem.GetHeight() / m_nElementHeight + 1;
int nHideCount = (int)m_items.size() - nDisplayCount;
// 上半部分
UiRect rcItem = m_rcItem;
rcItem.top = m_rcItem.top + nTopIndexBottom;
for (int i = nHideCount - 1; i >= 0; i--) {
rcItem.bottom = rcItem.top;
rcItem.top = rcItem.bottom - m_nElementHeight;
m_items[i]->SetPos(rcItem);
int nElementIndex = nTopIndex - (nHideCount - i);
if (nElementIndex >= 0)
FillElement(m_items[i], nElementIndex);
}
// 下半部分
rcItem = m_rcItem;
rcItem.bottom = m_rcItem.top + nTopIndexBottom;
for (int i = nHideCount; i < (int)m_items.size(); i++) {
rcItem.top = rcItem.bottom;
rcItem.bottom = rcItem.top + m_nElementHeight;
m_items[i]->SetPos(rcItem);
int nElementIndex = nTopIndex + (i - nHideCount);
if (nElementIndex < nElementCount)
FillElement(m_items[i], nElementIndex);
}
}
}
void VirtualListBox::AddElement(int iIndex)
{
int nCount = GetElementCount();
if (nCount <= m_nMaxItemCount) {
Control *pControl = CreateElement();
this->AddAt(pControl, iIndex);
FillElement(pControl, iIndex);
} else {
ReArrangeChild(true);
Arrange();
}
}
void VirtualListBox::RemoveElement(int iIndex)
{
int nCount = GetElementCount();
if (nCount < m_nMaxItemCount) {
this->RemoveAt(iIndex);
} else {
ReArrangeChild(true);
Arrange();
}
}
void VirtualListBox::SetScrollPos(ui::CSize szPos)
{
m_nOldYScrollPos = m_pVerticalScrollBar->GetScrollPos();
ListBox::SetScrollPos(szPos);
if (UseDefaultLayout())
return;
ReArrangeChild(false);
}
void VirtualListBox::HandleMessage(ui::EventArgs& event) {
if (!IsMouseEnabled() && event.Type > ui::kEventMouseBegin && event.Type < ui::kEventMouseEnd) {
if (m_pParent != NULL)
m_pParent->HandleMessageTemplate(event);
else
ui::ScrollableBox::HandleMessage(event);
return;
}
switch (event.Type) {
case kEventKeyDown: {
switch (event.chKey) {
case VK_UP: {
OnKeyDown(VK_UP);
return;
}
case VK_DOWN: {
OnKeyDown(VK_DOWN);
return;
}
case VK_HOME:
SetScrollPosY(0);
return;
case VK_END: {
int range = m_pVerticalScrollBar->GetScrollRange();
SetScrollPosY(range);
return;
}
}
}
case kEventKeyUp: {
switch (event.chKey) {
case VK_UP: {
OnKeyUp(VK_UP);
return;
}
case VK_DOWN: {
OnKeyUp(VK_DOWN);
return;
}
}
}
}
__super::HandleMessage(event);
}
void VirtualListBox::SetPos(UiRect rc)
{
bool bChange = false;
if (!m_rcItem.Equal(rc))
bChange = true;
ListBox::SetPos(rc);
if (bChange) {
if (UseDefaultLayout())
return;
ReArrangeChild(true);
}
}
ui::Control* VirtualListBox::CreateElement()
{
if (m_pDataProvider)
return m_pDataProvider->CreateElement();
return nullptr;
}
void VirtualListBox::FillElement(Control *pControl, int iIndex)
{
if (m_pDataProvider)
m_pDataProvider->FillElement(pControl, iIndex);
}
int VirtualListBox::GetElementCount()
{
if (m_pDataProvider)
return m_pDataProvider->GetElementtCount();
return 0;
}
bool VirtualListBox::UseDefaultLayout()
{
return GetElementCount() <= GetCount();
}
int VirtualListBox::CalcElementsHeight(int nCount)
{
if (nCount < 0)
nCount = GetElementCount();
return nCount * m_nElementHeight;
}
int VirtualListBox::GetTopElementIndex(int &bottom)
{
int nPos = m_pVerticalScrollBar->GetScrollPos();
int iIndex = nPos / m_nElementHeight;
bottom = iIndex * m_nElementHeight;
return iIndex;
}
bool VirtualListBox::IsElementDisplay(int iIndex)
{
if (iIndex < 0)
return false;
int nPos = m_pVerticalScrollBar->GetScrollPos();
int nElementPos = CalcElementsHeight(iIndex);
if (nElementPos >= nPos) {
int nHeight = this->GetHeight();
if (nElementPos + m_nElementHeight <= nPos + nHeight)
return true;
}
return false;
}
bool VirtualListBox::NeedReArrange(ScrollDirection &direction)
{
direction = kScrollUp;
if (!m_bArrangedOnce) {
m_bArrangedOnce = true;
return true;
}
int nCount = GetCount();
if (nCount <= 0)
return false;
if (GetElementCount() <= nCount)
return false;
UiRect rcThis = this->GetPos();
if (rcThis.GetWidth() <= 0)
return false;
int nPos = m_pVerticalScrollBar->GetScrollPos();
UiRect rcItem;
// 补救措施
// 情况一:通讯录列表,一开始不可见,切换后可见,如果提前布局,
// 则Element宽度为0因此必须重新布局
// 情况二:写信窗口联系人列表,列表宽度会不断变化,因此,需要在宽度变化后
// 重新布局否则导致最终Element布局时的宽度不正确
rcItem = m_items[0]->GetPos();
// modified by zqw, 2016/10/12
// 针对情况二,解决方法是,列表宽度变化 或者 拖动写信窗口右侧列表position改变
// 此时在SetPos中强制重新布局
//if (u.GetWidth() != rect.GetWidth()) {
// return true;
//}
if (nPos >= m_nOldYScrollPos) {
// 下
rcItem = m_items[nCount - 1]->GetPos();
int nSub = (rcItem.bottom - rcThis.top) - (nPos + rcThis.GetHeight());
if (nSub < 0) {
direction = kScrollDown;
return true;
}
} else {
// 上
rcItem = m_items[0]->GetPos();
if (nPos < (rcItem.top - rcThis.top)) {
direction = kScrollUp;
return true;
}
}
return false;
}
}