Mercurial > foo_out_sdl
view foosdk/sdk/libPPUI/CListControl.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line source
#include "stdafx.h" #include "CListControl.h" #include "PaintUtils.h" #include "CListControlUserOptions.h" #include "GDIUtils.h" #include "DarkMode.h" #define PrepLayoutCache_Debug 0 #define Scroll_Debug 0 #if Scroll_Debug #define Scroll_Debug_Print(...) PFC_DEBUG_PRINT_FORCED(__VA_ARGS__) #else #define Scroll_Debug_Print(...) #endif CListControlUserOptions * CListControlUserOptions::instance = nullptr; CRect CListControlImpl::GetClientRectHook() const { CRect temp; if ( m_hWnd == NULL || !GetClientRect(temp)) temp.SetRectEmpty(); return temp; } bool CListControlImpl::UserEnabledSmoothScroll() const { auto i = CListControlUserOptions::instance; if ( i != nullptr ) return i->useSmoothScroll(); return false; } LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { SetFocus(); bHandled = FALSE; return 0; } void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) { const CRect rcView = GetVisibleRectAbs(); const CRect rcItem = p_rect; int deltaX = 0, deltaY = 0; const bool centerOnItem = m_ensureVisibleUser; if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) { if (rcItem.Height() > rcView.Height()) { deltaY = rcItem.top - rcView.top; } else { if (centerOnItem) { deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y; } else { if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom; else deltaY = rcItem.top - rcView.top; } } } if (rcItem.left < rcView.left || rcItem.right > rcView.right) { if (rcItem.Width() > rcView.Width()) { if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left; } else { if (centerOnItem) { deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x; } else { if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right; else deltaX = rcItem.left - rcView.left; } } } if (deltaX != 0 || deltaY != 0) { MoveViewOriginDelta(CPoint(deltaX,deltaY)); } } void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) { m_ensureVisibleUser = bUser; PFC_ASSERT(p_item < GetItemCount()); if (this->PrepLayoutCache(m_viewOrigin, p_item, p_item+1 )) { RefreshSliders(); Invalidate(); } EnsureVisibleRectAbs(GetItemRectAbs(p_item)); m_ensureVisibleUser = false; } void CListControlImpl::EnsureHeaderVisible2(size_t atItem) { CRect rect; if (GetGroupHeaderRectAbs2(atItem,rect)) EnsureVisibleRectAbs(rect); } void CListControlImpl::RefreshSlider(bool p_vertical) { const CRect viewArea = GetViewAreaRectAbs(); const CRect rcVisible = GetVisibleRectAbs(); SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; if (AllowScrollbar(p_vertical)) { if (p_vertical) { si.nPage = rcVisible.Height(); si.nMin = viewArea.top; si.nMax = viewArea.bottom - 1; si.nPos = rcVisible.top; } else { si.nPage = rcVisible.Width(); si.nMin = viewArea.left; si.nMax = viewArea.right - 1; si.nPos = rcVisible.left; } } Scroll_Debug_Print("RefreshSlider vertical=", p_vertical, ", nPage=", si.nPage, ", nMin=", si.nMin, ", nMax=", si.nMax, ", nPos=", si.nPos); SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si); } void CListControlImpl::RefreshSliders() { //PROBLEM: while lots of data can be reused across those, it has to be recalculated inbetween because view area etc may change when scroll info changes RefreshSlider(false); RefreshSlider(true); } int CListControlImpl::GetScrollThumbPos(int which) { SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_TRACKPOS; WIN32_OP_D( GetScrollInfo(which,&si) ); return si.nTrackPos; } bool CListControlImpl::ResolveGroupRangeCached(size_t itemInGroup, size_t& outBegin, size_t& outEnd) const { auto end = this->m_groupHeaders.upper_bound(itemInGroup); if (end == this->m_groupHeaders.begin()) return false; auto begin = end; --begin; outBegin = *begin; if (end == this->m_groupHeaders.end()) outEnd = this->GetItemCount(); else outEnd = *end; return true; } size_t CListControlImpl::ResolveGroupRange2(t_size p_base) const { const auto id = this->GetItemGroup(p_base); const size_t count = this->GetItemCount(); size_t walk = p_base + 1; while (walk < count && GetItemGroup(walk) == id) ++walk; return walk - p_base; } static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) { switch(p_code) { case SB_LINEUP: return p_offset - p_line; case SB_LINEDOWN: return p_offset + p_line; case SB_BOTTOM: return p_bottom - p_page; case SB_TOP: return 0; case SB_PAGEUP: return p_offset - p_page; case SB_PAGEDOWN: return p_offset + p_page; case SB_THUMBPOSITION: return p_thumbpos; case SB_THUMBTRACK: return p_thumbpos; default: return p_offset; } } static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) { return CPoint(pfc::clip_t(p_pt.x,p_rect.left,p_rect.right),pfc::clip_t(p_pt.y,p_rect.top,p_rect.bottom)); } void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) { UpdateWindow(); PrepLayoutCache(p_target); const CPoint old = m_viewOrigin; m_viewOrigin = p_target; if (m_viewOrigin != old) { #if PrepLayoutCache_Debug PFC_DEBUGLOG << "MoveViewOriginNoClip: m_viewOrigin=" << m_viewOrigin.x << "," << m_viewOrigin.y; #endif if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x); if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y); const CPoint delta = old - m_viewOrigin; if (FixedOverlayPresent()) Invalidate(); else { DWORD flags = SW_INVALIDATE | SW_ERASE; const DWORD smoothScrollMS = 50; if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) { flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16); } ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags ); } OnViewOriginChange(m_viewOrigin - old); } } CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const { return ClipPointToRect(p_origin,GetValidViewOriginArea()); } void CListControlImpl::MoveViewOrigin(CPoint p_target) { PrepLayoutCache(p_target); MoveViewOriginNoClip(ClipViewOrigin(p_target)); } #ifndef SPI_GETWHEELSCROLLCHARS #define SPI_GETWHEELSCROLLCHARS 0x006C #endif int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) { if ( m_suppressMouseWheel ) return 0; UINT scrollLines = 1; SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0); if (scrollLines == ~0) { p_accum = 0; int rv = -pfc::sgn_t(p_delta); CRect client = GetClientRectHook(); if (bHoriz) rv *= client.Width(); else rv *= client.Height(); return rv; } const int itemHeight = GetItemHeight(); const int extraScale = 10000; p_accum += p_delta * extraScale; if ((int)scrollLines < 1) scrollLines = 1; int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight); if (multiplier<1) multiplier = 1; int delta = pfc::rint32( (double) p_accum / (double) multiplier ); p_accum -= delta * multiplier; return -delta; /* if (p_accum<=-multiplier || p_accum>=multiplier) { int direction; int ov = p_accum; if (ov<0) { direction = -1; ov = -ov; p_accum = - ((-p_accum)%multiplier); } else { p_accum %= multiplier; direction = 1; } return - (direction * (ov + multiplier - 1) ) / multiplier; } else { return 0; } */ } LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM,BOOL&) { const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs(); int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false); const bool canVScroll = client.Height() < view.Height(); const bool canHScroll = client.Width() < view.Width(); CPoint ptDelta; if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) { ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed } else if (canVScroll) { ptDelta = CPoint(0,deltaPixels); } else if (canHScroll) { ptDelta = CPoint(deltaPixels,0); } if ( ptDelta != CPoint(0,0) ) { MoveViewOriginDelta(ptDelta); } return 0; } LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM,BOOL&) { // const CRect client = GetClientRectHook(); int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true); MoveViewOriginDelta(CPoint(-deltaPixels,0)); return 0; } // WM_VSCROLL special fix // We must expect SCROLLINFO to go out of sync with layout, due to group partitioning happening as the user scrolls // SetScrollInfo() is apparently disregarded while the user is scrolling, causing nonsensical behavior if we live update it as we discover new groups // When handling input, we must take the position as % of the set scrollbar range and map it to our coordinates - even though it is mappable directly if no groups etc are in use LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { SCROLLINFO si = {}; si.cbSize = sizeof(si); si.fMask = SIF_ALL; WIN32_OP_D(GetScrollInfo(SB_VERT, &si)); int thumb = si.nTrackPos; // HIWORD(p_wp); auto bottom = GetViewAreaRectAbs().bottom; auto visible = GetVisibleHeight(); if (si.nMax < si.nMin) return 0; double p = (double)(thumb - si.nMin) / (double)(si.nMax + 1 - si.nMin); thumb = pfc::rint32(p * bottom); int target = HandleScroll(LOWORD(p_wp), m_viewOrigin.y, visible, GetItemHeight(), bottom, thumb); Scroll_Debug_Print("OnVScroll thumb=", thumb, ", target=", target, ", bottom=", bottom, ", visible=", visible, ", p=", p); MoveViewOrigin(CPoint(m_viewOrigin.x, target)); return 0; } // ====== Logitech scroll bug explanation ====== // With Logitech wheel hscroll, we must use WPARAM position, not GetScrollInfo() value. // However this is wrong, we'll get nonsense if scroll range doesn't fit in 16-bit! // As a workaround, we use GetScrollInfo() value for vscroll (good) // and workaround Logitech bug by using WPARAM position with hscroll (practically impossible to overflow) LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { int thumb = HIWORD(p_wp); const auto fullWidth = GetViewAreaWidth(); if (fullWidth > INT16_MAX) { // Possible overflow or near-overflow? Drop Logitech stupidity mitigation thumb = GetScrollThumbPos(SB_HORZ); } int target = HandleScroll(LOWORD(p_wp), m_viewOrigin.x, GetVisibleRectAbs().Width(), GetItemHeight() /*fixme*/, fullWidth, thumb); Scroll_Debug_Print("OnHScroll thumb=", thumb, ", target=", target); MoveViewOrigin(CPoint(target,m_viewOrigin.y)); return 0; } LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) { if (!this->m_gestureAPI.IsAvailable()) { bHandled = FALSE; return 0; } HGESTUREINFO hGesture = (HGESTUREINFO) lParam; GESTUREINFO gestureInfo = {sizeof(gestureInfo)}; if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) { //console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments; CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y ); switch(gestureInfo.dwID) { case GID_BEGIN: m_gesturePoint = pt; break; case GID_END: break; case GID_PAN: MoveViewOriginDelta( this->m_gesturePoint - pt); m_gesturePoint = pt; break; } } m_gestureAPI.CloseGestureInfoHandle(hGesture); bHandled = TRUE; return 0; } LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM,BOOL&) { this->PrepLayoutCache(m_viewOrigin); OnSizeAsync_Trigger(); RefreshSliders(); return 0; } void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) { PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground)); } void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) { CDCHandle renderDC(dc); CMemoryDC bufferDC(renderDC,rcPaint); renderDC = bufferDC; this->RenderBackground(renderDC, rcPaint); RenderRect(rcPaint, renderDC); } void CListControlImpl::OnPrintClient(HDC dc, UINT) { CRect rcClient; this->GetClientRect( rcClient ); PaintContent( rcClient, dc ); } void CListControlImpl::OnPaint(CDCHandle target) { auto toggle = pfc::autoToggle(m_paintInProgress, true); if (target) { CRect rcClient; this->GetClientRect(rcClient); PaintContent(rcClient, target); } else { CPaintDC paintDC(*this); PaintContent(paintDC.m_ps.rcPaint, paintDC.m_hDC); } } bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const { return GetItemRangeAbs(this->RectClientToAbs(p_rect), p_base, p_count); } bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const { CRect temp(p_rect); temp.bottom += this->GetGroupHeaderHeight(); return GetItemRangeAbs(temp, p_base, p_count); } bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const { const size_t count = GetItemCount(); if (p_rect.right < 0 || p_rect.left >= GetItemWidth() || count == 0) return false; size_t top = IndexFromPointAbs(CPoint(0, p_rect.top)); size_t bottom = IndexFromPointAbs(CPoint(0, p_rect.bottom)); if (top == SIZE_MAX) return false; if (bottom > count-1) bottom = count - 1; p_base = top; p_count = bottom - top + 1; PFC_ASSERT(p_base + p_count <= count); return true; } void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) { t_size base, count; if (GetItemRange(p_rect,base,count)) { for(t_size walk = 0; walk < count; ++walk) { size_t atItem = base + walk; if (m_groupHeaders.count(atItem) > 0) { CRect rcHeader, rcUpdate; if (GetGroupHeaderRectAbs2(atItem, rcHeader) ) { rcHeader = RectAbsToClient(rcHeader); if (rcUpdate.IntersectRect(rcHeader, p_rect)) { DCStateScope dcState(p_dc); if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { try { RenderGroupHeader2(atItem, rcHeader, rcUpdate, p_dc); } catch (...) { PFC_ASSERT(!"Should not get here"); } } } } } CRect rcUpdate, rcItem = GetItemRect(atItem); if (rcUpdate.IntersectRect(rcItem,p_rect)) { DCStateScope dcState(p_dc); if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { try { RenderItem(atItem,rcItem,rcUpdate,p_dc); } catch(...) { PFC_ASSERT(!"Should not get here"); } } } } if ( this->m_groupHeaders.size() > 0 ) { auto iter = m_groupHeaders.upper_bound(base); if (iter != m_groupHeaders.begin()) { --iter; while ( iter != m_groupHeaders.end() && *iter < base + count) { auto iter2 = iter; ++iter2; size_t begin = *iter; size_t end; if (iter2 == m_groupHeaders.end()) end = this->GetItemCount(); else end = *iter2; CRect rc; rc.top = this->GetItemOffsetAbs(begin); rc.bottom = this->GetItemBottomOffsetAbs(end-1); rc.left = 0; rc.right = this->GetItemWidth(); rc = this->RectAbsToClient(rc); CRect rcUpdate; if (rcUpdate.IntersectRect(rc, p_rect)) { DCStateScope dcState(p_dc); if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { try { this->RenderGroupOverlay(begin, rc, rcUpdate, p_dc); } catch (...) { PFC_ASSERT(!"Should not get here"); } } } iter = iter2; } } } } RenderOverlay2(p_rect,p_dc); } bool CListControlImpl::GetGroupOverlayRectAbs(size_t atItem, CRect& outRect) { auto iter = m_groupHeaders.upper_bound(atItem); if (iter == m_groupHeaders.begin()) return false; auto iter2 = iter; --iter; size_t begin = *iter; size_t end; if (iter2 == m_groupHeaders.end()) end = this->GetItemCount(); else end = *iter2; CRect rc; rc.top = this->GetItemOffsetAbs(begin); rc.bottom = this->GetItemBottomOffsetAbs(end - 1); rc.left = 0; rc.right = this->GetItemWidth(); outRect = rc; return true; } void CListControlImpl::MinGroupHeight2ChangedForGroup(groupID_t groupID, bool reloadWhole) { for (auto iter = m_groupHeaders.begin(); iter != m_groupHeaders.end(); ++iter) { if (groupID == GetItemGroup(*iter)) { this->MinGroupHeight2Changed(*iter, reloadWhole); } } } void CListControlImpl::UpdateGroupOverlayByID(groupID_t groupID, int xFrom, int xTo) { t_size base, count; if (GetItemRangeAbs(GetVisibleRectAbs(), base, count)) { bool on = false; // Have to walk whole range - there may be multiple groups with the same ID for (size_t walk = 0; walk < count; ++walk) { bool test = (groupID == GetItemGroup(base + walk)); if (test && !on) { CRect rc; if (GetGroupOverlayRectAbs(base + walk, rc)) { if (xFrom < xTo) { rc.left = xFrom; rc.right = xTo; } this->InvalidateRect(this->RectAbsToClient(rc)); } } on = test; } } } CRect CListControlImpl::GetItemRect(t_size p_item) const { return this->RectAbsToClient(GetItemRectAbs(p_item)); } bool CListControlImpl::GetGroupHeaderRect2(size_t atItem,CRect & p_rect) const { CRect temp; if (!GetGroupHeaderRectAbs2(atItem,temp)) return false; p_rect = RectAbsToClient(temp); return true; } size_t CListControlImpl::FindGroupBaseCached(size_t itemFor) const { auto iter = m_groupHeaders.upper_bound(itemFor); if (iter == m_groupHeaders.begin()) return 0; --iter; return *iter; } size_t CListControlImpl::FindGroupBase(size_t itemFor) const { return this->FindGroupBase(itemFor, this->GetItemGroup(itemFor)); } size_t CListControlImpl::FindGroupBase(size_t itemFor, groupID_t id) const { size_t walk = itemFor; while (walk > 0) { size_t prev = walk - 1; if (this->GetItemGroup(prev) != id) break; walk = prev; } return walk; } bool CListControlImpl::PrepLayoutCache(CPoint& ptOrigin, size_t indexLo, size_t indexHi) { const size_t count = GetItemCount(); if (count == 0) return false; #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache entry"; PFC_DEBUGLOG << "PrepLayoutCache: count=" << count << " knownGroups=" << this->m_groupHeaders.size(); PFC_DEBUGLOG << "PrepLayoutCache: indexLo=" << pfc::format_index(indexLo) << " indexHi=" << pfc::format_index(indexHi); #endif const int clientHeight = pfc::max_t<int>(this->GetClientRectHook().Height(), 100); // Always walk 2*clientHeight, with area above and below int yMax = -1, yBase = 0; size_t baseItem = 0, endItem = SIZE_MAX; if (!m_greedyGroupLayout) { if (indexLo == SIZE_MAX) { yBase = pfc::max_t<int>(ptOrigin.y - clientHeight / 2, 0); yMax = yBase + clientHeight * 2; baseItem = pfc::min_t<size_t>(this->IndexFromPointAbs(yBase), count - 1); } else { auto itemHeight = GetItemHeight(); size_t extraItems = (size_t)(clientHeight / itemHeight); #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: clientHeight=" << clientHeight << " itemHeight=" << itemHeight << " extraItems=" << extraItems; #endif if (indexLo < extraItems) baseItem = 0; else baseItem = indexLo - extraItems; if (indexHi == SIZE_MAX) { endItem = baseItem + extraItems; } else { endItem = indexHi + extraItems; } if (endItem > count) endItem = count; #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: baseItem=" << baseItem << " endItem=" << endItem; #endif } } size_t item = baseItem; { const auto group = this->GetItemGroup(baseItem); if (group != 0) { size_t hdr = this->FindGroupBase(baseItem, group); if (hdr < baseItem) { item = hdr; } } } #if PrepLayoutCache_Debug if (yMax != -1) { PFC_DEBUGLOG << "PrepLayoutCache: yBase=" << yBase << " yMax=" << yMax; } if (indexLo != SIZE_MAX) { pfc::string_formatter msg; msg << "PrepLayoutCache: indexLo=" << indexLo; if (indexHi != SIZE_MAX) { msg << " indexHi=" << indexHi; } pfc::outputDebugLine(msg); } PFC_DEBUGLOG << "PrepLayoutCache: baseItem=" << baseItem; #endif size_t anchorIdx = m_greedyGroupLayout ? SIZE_MAX : this->IndexFromPointAbs(ptOrigin.y); int anchorDelta = 0; bool anchorIsFirstInGroup = IsItemFirstInGroupCached(anchorIdx); if (anchorIdx != SIZE_MAX) { anchorDelta = ptOrigin.y - GetItemOffsetAbs(anchorIdx); } #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: anchorIdx=" << pfc::format_index(anchorIdx) << " anchorDelta=" << anchorDelta << " anchorIsFirstInGroup=" << anchorIsFirstInGroup; #endif bool bChanged = false; int gh = -1; int ih = -1; int yWalk = yBase; groupID_t prevGroup = 0; if (item > 1) prevGroup = this->GetItemGroup(item - 1); for (; item < count; ++item) { int yDelta = 0; auto group = this->GetItemGroup(item); if (group != prevGroup) { if (m_groupHeaders.insert(item).second) bChanged = true; if (gh < 0) gh = GetGroupHeaderHeight(); yDelta += gh; } else { if (m_groupHeaders.erase(item) > 0) bChanged = true; } prevGroup = group; auto iter = m_varItemHeights.find(item); int varHeight = this->GetItemHeight2(item); if (varHeight < 0) { if (iter != m_varItemHeights.end()) { m_varItemHeights.erase(iter); bChanged = true; } if (ih < 0) ih = this->GetItemHeight(); yDelta += ih; } else { if (iter == m_varItemHeights.end()) { m_varItemHeights[item] = varHeight; bChanged = true; } else if ( iter->second != varHeight ) { iter->second = varHeight; bChanged = true; } yDelta += varHeight; } if (item >= endItem) { break; } if (item >= baseItem && yMax != -1) { yWalk += yDelta; if (yWalk > yMax) break; } } #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: bChanged=" << bChanged << " knownGroups=" << m_groupHeaders.size() << " knownVarHeights=" << m_varItemHeights.size(); #endif if (bChanged) { if (anchorIdx != SIZE_MAX) { int fix = GetItemOffsetAbs(anchorIdx) + anchorDelta; // View would begin exactly with an item that became a first item in a group? if (anchorDelta == 0 && !anchorIsFirstInGroup && IsItemFirstInGroupCached(anchorIdx)) { if (gh < 0) gh = GetGroupHeaderHeight(); fix -= gh; } #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: fixing origin: " << ptOrigin.y << " to " << fix; #endif ptOrigin.y = fix; if (&ptOrigin != &m_viewOrigin && m_hWnd != NULL) { #if PrepLayoutCache_Debug PFC_DEBUGLOG << "PrepLayoutCache: invalidating view"; #endif Invalidate(); } } } if ( bChanged ) { // DO NOT update sliders from here, causes mayhem, SetScrollInfo() in mid-scroll is not really handled // this->RefreshSliders(); } return bChanged; } int CListControlImpl::GetViewAreaHeight() const { auto ret = GetItemOffsetAbs(GetItemCount()); Scroll_Debug_Print("GetViewAreaHeight: " , ret); return ret; } int CListControlImpl::GetItemBottomOffsetAbs(size_t item) const { return GetItemOffsetAbs(item) + GetItemHeightCached(item); } int CListControlImpl::GetItemOffsetAbs2(size_t base, size_t item) const { // Also valid with item == GetItemCount() size_t varcount = 0; int acc = 0; const bool baseValid = (base != SIZE_MAX); const size_t itemDelta = baseValid ? item - base : item; for (auto iter = (baseValid ? m_varItemHeights.lower_bound(base) : m_varItemHeights.begin()); iter != m_varItemHeights.end(); ++iter){ if (iter->first >= item) break; if (iter->second > 0) acc += iter->second; ++varcount; } if (varcount < itemDelta) { acc += GetItemHeight() * (int)(itemDelta - varcount); } int gh = -1; for (auto iter = (baseValid ? m_groupHeaders.upper_bound(base) : m_groupHeaders.begin()); iter != m_groupHeaders.end(); ++iter){ if (*iter > item) break; if (gh < 0) gh = GetGroupHeaderHeight(); acc += gh; } return acc; } int CListControlImpl::GetItemOffsetAbs(size_t item) const { // Also valid with item == GetItemCount() return GetItemOffsetAbs2(SIZE_MAX, item); } int CListControlImpl::GetItemContentHeightCached(size_t item) const { auto iter = m_varItemHeights.find(item); if (iter == m_varItemHeights.end()) return GetItemHeight(); else return this->GetItemHeight2Content( item, iter->second ); } int CListControlImpl::GetItemHeightCached(size_t item) const { auto iter = m_varItemHeights.find(item); if (iter == m_varItemHeights.end()) return GetItemHeight(); else return iter->second; } CRect CListControlImpl::GetItemRectAbs(t_size p_item) const { PFC_ASSERT(p_item < GetItemCount()); // const int normalHeight = GetItemHeight(); CRect rcItem; rcItem.top = GetItemOffsetAbs(p_item); rcItem.bottom = rcItem.top + GetItemContentHeightCached(p_item); rcItem.left = 0; rcItem.right = rcItem.left + GetItemWidth(); return rcItem; } bool CListControlImpl::GetGroupHeaderRectAbs2(size_t atItem,CRect & p_rect) const { if (m_groupHeaders.count(atItem) == 0) return false; p_rect.bottom = GetItemOffsetAbs(atItem); p_rect.top = p_rect.bottom - GetGroupHeaderHeight(); p_rect.left = 0; p_rect.right = GetItemWidth(); return true; } CRect CListControlImpl::GetViewAreaRectAbs() const { return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight()); } CRect CListControlImpl::GetViewAreaRect() const { CRect rc = GetViewAreaRectAbs(); rc.OffsetRect( - GetViewOffset() ); CRect ret; ret.IntersectRect(rc,GetClientRectHook()); return ret; } void CListControlImpl::UpdateGroupHeader2(size_t atItem) { CRect rect; if (GetGroupHeaderRect2(atItem,rect)) { InvalidateRect(rect); } } static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) { CRgn temp; temp.CreateRectRgnIndirect(p_rect); CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); } void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) { PFC_ASSERT(count == GetItemCount()); (void)count; ReloadItems( pfc::bit_array_order_changed(order) ); } void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) { t_size base,count; if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) { const t_size max = base+count; CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); bool found = false; for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { found = true; AddUpdateRect(updateRgn,GetItemRect(walk)); } if (found) { InvalidateRgn(updateRgn); } } } std::pair<size_t, size_t> CListControlImpl::GetVisibleRange() const { const size_t total = GetItemCount(); CRect rcVisible = this->GetVisibleRectAbs(); size_t lo = this->IndexFromPointAbs(rcVisible.top); PFC_ASSERT(lo != SIZE_MAX); if (lo == SIZE_MAX) lo = 0; // should not happen size_t hi = this->IndexFromPointAbs(rcVisible.bottom); if (hi < total) ++hi; else hi = total; return { lo, hi }; } bool CListControlImpl::IsItemVisible(size_t which) const { CRect rcVisible = this->GetVisibleRectAbs(); CRect rcItem = this->GetItemRectAbs(which); return rcItem.top >= rcVisible.top && rcItem.bottom <= rcVisible.bottom; } void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) { t_size base,count; groupID_t groupWalk = 0; if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) { const t_size max = base+count; CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); bool found = false; for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { found = true; const groupID_t groupId = GetItemGroup(walk); if (groupId != groupWalk) { CRect rect; if (GetGroupHeaderRect2(walk,rect)) { AddUpdateRect(updateRgn,rect); } groupWalk = groupId; } AddUpdateRect(updateRgn,GetItemRect(walk)); } if (found) { InvalidateRgn(updateRgn); } } } CRect CListControlImpl::GetValidViewOriginArea() const { const CRect rcView = GetViewAreaRectAbs(); const CRect rcClient = GetClientRectHook(); CRect rcArea = rcView; rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width()); rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height()); return rcArea; } void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) { const CPoint oldViewOrigin = m_viewOrigin; PrepLayoutCache(p_originOverride); m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea()); if (m_viewOrigin != p_originOverride) { // Did clip from the requested? PrepLayoutCache(m_viewOrigin); } #if PrepLayoutCache_Debug PFC_DEBUGLOG << "OnViewAreaChanged: m_viewOrigin=" << m_viewOrigin.x << "," << m_viewOrigin.y; #endif RefreshSliders(); Invalidate(); if (oldViewOrigin != m_viewOrigin) { OnViewOriginChange(m_viewOrigin - oldViewOrigin); } } size_t CListControlImpl::IndexFromPointAbs(CPoint pt) const { if (pt.x < 0 || pt.x >= GetItemWidth()) return SIZE_MAX; return IndexFromPointAbs(pt.y); } size_t CListControlImpl::IndexFromPointAbs(int ptY) const { const size_t count = GetItemCount(); if (count == 0) return SIZE_MAX; class wrapper { public: wrapper(const CListControlImpl & o) : owner(o) {} int operator[] (size_t idx) const { // Return LAST line of this item return owner.GetItemBottomOffsetAbs(idx)-1; } const CListControlImpl & owner; }; wrapper w(*this); size_t result = SIZE_MAX; pfc::binarySearch<>::run(w, 0, count, ptY, result); PFC_ASSERT(result != SIZE_MAX); return result; } bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const { size_t idx = IndexFromPointAbs(p_pt); if (idx >= GetItemCount()) return false; CRect rc = this->GetItemRectAbs(idx); if (!rc.PtInRect(p_pt)) return false; p_item = idx; return true; } size_t CListControlImpl::ItemFromPointAbs(CPoint const& p_pt) const { size_t ret = SIZE_MAX; ItemFromPointAbs(p_pt, ret); return ret; } bool CListControlImpl::GroupHeaderFromPointAbs2(CPoint const & p_pt,size_t & atItem) const { size_t idx = IndexFromPointAbs(p_pt); if (idx == SIZE_MAX) return false; CRect rc; if (!this->GetGroupHeaderRectAbs2(idx, rc)) return false; if (!rc.PtInRect(p_pt)) return false; atItem = idx; return true; } void CListControlImpl::OnThemeChanged() { m_themeCache.remove_all(); } CTheme & CListControlImpl::themeFor(const char * what) { bool bNew; auto & ret = this->m_themeCache.find_or_add_ex( what, bNew ); if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what)); return ret; } void CListControlImpl::SetDarkMode(bool v) { if (m_darkMode != v) { m_darkMode = v; RefreshDarkMode(); } } void CListControlImpl::RefreshDarkMode() { if (m_hWnd != NULL) { Invalidate(); // GOD DAMNIT: Should use ItemsView, but only Explorer fixes scrollbars DarkMode::ApplyDarkThemeCtrl(m_hWnd, m_darkMode, L"Explorer"); } } LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { RefreshDarkMode(); OnViewAreaChanged(); if (m_gestureAPI.IsAvailable()) { GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER}; m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG)); } bHandled = FALSE; return 0; } bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const { t_size item1, item2; if (ItemFromPointAbs(p_point1, item1)) { if (ItemFromPointAbs(p_point2,item2)) { return item1 == item2; } else { return false; } } if (GroupHeaderFromPointAbs2(p_point1, item1)) { if (GroupHeaderFromPointAbs2(p_point2, item2)) { return item1 == item2; } else { return false; } } return false; } void CListControlImpl::OnSizeAsync_Trigger() { if (!m_sizeAsyncPending) { if (PostMessage(MSG_SIZE_ASYNC,0,0)) { m_sizeAsyncPending = true; } else { PFC_ASSERT(!"Shouldn't get here!"); //should not happen ListHandleResize(); } } } void CListControlImpl::ListHandleResize() { MoveViewOriginDelta(CPoint(0,0)); m_sizeAsyncPending = false; } void CListControlImpl::AddGroupHeaderToUpdateRgn2(HRGN p_rgn, size_t atItem) const { CRect rcHeader; if (GetGroupHeaderRect2(atItem,rcHeader)) AddUpdateRect(p_rgn,rcHeader); } void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const { if (p_index < this->GetItemCount()) { AddUpdateRect(p_rgn,GetItemRect(p_index)); } } COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const { if (m_darkMode) { return DarkMode::GetSysColor(colorIndex); } else { return GetSysColor(colorIndex); } } BOOL CListControlImpl::OnEraseBkgnd(CDCHandle dc) { if (paintInProgress()) return FALSE; CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); // SPECIAL CASE: No GetClientRectHook() here, fill physical client area, not logical PaintUtils::FillRectSimple(dc,rcClient,this->GetSysColorHook(COLOR_WINDOW)); return TRUE; } t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const { bInside = false; int y_abs = pt.y + GetViewOffset().y; if (y_abs >= GetViewAreaHeight()) { return GetItemCount(); } size_t itemIdx = IndexFromPointAbs(y_abs); if (itemIdx == SIZE_MAX) return SIZE_MAX; { CRect rc; if (!this->GetGroupHeaderRectAbs2(itemIdx, rc)) { if (y_abs >= rc.top && y_abs <= rc.bottom) { bInside = false; return itemIdx; } } } if (itemIdx != SIZE_MAX) { const CRect rc = GetItemRectAbs(itemIdx); if (y_abs > rc.top + MulDiv(rc.Height(), 2, 3)) itemIdx++; else if (y_abs >= rc.top + MulDiv(rc.Height(), 1, 3)) bInside = true; return itemIdx; } return SIZE_MAX; } t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const { bool dummy; return InsertIndexFromPointEx(pt,dummy); } COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) { return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) ); } COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) { return PaintUtils::BlendColor(bk, tx, 10); } COLORREF CListControlImpl::GridColor() { return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) ); } void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) { switch( this->m_rowStyle ) { case rowStylePlaylistDelimited: PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item,bkColor); { auto blend = BlendGridColor(bkColor); CDCPen pen(p_dc, blend); SelectObjectScope scope(p_dc, pen); p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top ); p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom ); } break; case rowStylePlaylist: PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item,bkColor); break; case rowStyleGrid: PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); { auto blend = BlendGridColor(bkColor); CDCBrush brush(p_dc, blend); p_dc.FrameRect(&p_itemRect, brush); } break; case rowStyleFlat: PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); break; } } void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) { (void)p_group; const t_uint32 bkColor = GetSysColorHook(colorBackground); size_t pretendIndex = 0; switch( this->m_rowStyle ) { default: PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor ); break; case rowStylePlaylistDelimited: case rowStylePlaylist: PaintUtils::RenderItemBackground(p_dc,p_headerRect,pretendIndex,bkColor); break; } } void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) { this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) ); DCStateScope backup(p_dc); p_dc.SetBkMode(TRANSPARENT); p_dc.SetBkColor(GetSysColorHook(colorBackground)); p_dc.SetTextColor(GetSysColorHook(colorText)); RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true); } void CListControlImpl::RenderGroupHeader2(size_t baseItem,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) { this->RenderGroupHeaderBackground(p_dc, p_headerRect, 0 ); DCStateScope backup(p_dc); p_dc.SetBkMode(TRANSPARENT); p_dc.SetBkColor(GetSysColorHook(colorBackground)); p_dc.SetTextColor(GetSysColorHook(colorHighlight)); RenderGroupHeaderText2(baseItem,p_headerRect,p_updateRect,p_dc); } CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() { UpdateGroupHeaderFont(); CalculateHeights(); } void CListControlFontOps::UpdateGroupHeaderFont() { try { m_groupHeaderFont = NULL; LOGFONT lf = {}; WIN32_OP_D( m_font.GetLogFont(lf) ); lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() ); lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight); WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL ); } catch(std::exception const & e) { (void) e; // console::print(e.what()); m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); } } void CListControlFontOps::CalculateHeights() { const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96); m_itemHeight = GetFontHeight( m_font ) + spacing; m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing; } void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) { m_font = font; UpdateGroupHeaderFont(); CalculateHeights(); OnSetFont(bUpdateView); if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged(); } LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) { SetFont((HFONT)wp); return 0; } LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) { return (LRESULT)(HFONT)m_font; } LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) { switch(wp) { case VK_RETURN: return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0; default: SetMsgHandled(FALSE); return 0; } } HWND CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID, CWindow lstReplace) { PFC_ASSERT(lstReplace != NULL); auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN); m_dlgWantEnter = (status & DLGC_WANTMESSAGE); CRect rc; CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE); WIN32_OP_D(lstReplace.GetWindowRect(&rc)); WIN32_OP_D(wndDialog.ScreenToClient(rc)); WIN32_OP_D(lstReplace.DestroyWindow()); WIN32_OP_D(this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID)); if (wndPrev != NULL) this->SetWindowPos(wndPrev, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); // this->BringWindowToTop(); this->SetFont(wndDialog.GetFont()); return m_hWnd; } HWND CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) { return this->CreateInDialog(wndDialog, replaceControlID, wndDialog.GetDlgItem(replaceControlID)); } void CListControlImpl::defer(std::function<void() > f) { m_deferred.push_back( f ); if (!m_defferredMsgPending) { if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true; } } LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) { for ( ;; ) { auto i = m_deferred.begin(); if ( i == m_deferred.end() ) break; auto op = std::move(*i); m_deferred.erase(i); // erase first, execute later - avoid erratic behavior if op alters the list op(); } m_defferredMsgPending = false; return 0; } // ======================================================================================== // Mouse wheel vs drag&drop hacks // Install MouseHookProc for the duration of DoDragDrop and handle the input from there // ======================================================================================== static HHOOK g_hook = NULL; static CListControlImpl * g_dragDropInstance = nullptr; LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION && g_dragDropInstance != nullptr) { switch (wParam) { case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam); break; } } return CallNextHookEx(g_hook, nCode, wParam, lParam); } bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) { MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast<MOUSEHOOKSTRUCTEX const *> ( data ); if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false; LRESULT dummyResult = 0; WPARAM wp = mhs->mouseData; LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y ); // If we get here, m_suppressMouseWheel should be true per our DoDragDrop() pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, false); this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult ); return true; } HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) { HRESULT ret = E_FAIL; // Should not get here with non null g_dragDropInstance - means we have a recursive call PFC_ASSERT(g_dragDropInstance == nullptr); if ( g_dragDropInstance == nullptr ) { // futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, true); g_dragDropInstance = this; g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId()); try { ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect); } catch (...) { } g_dragDropInstance = nullptr; UnhookWindowsHookEx(pfc::replace_null_t(g_hook)); } return ret; } CPoint CListControlImpl::PointAbsToClient(CPoint pt) const { return pt - GetViewOffset(); } CPoint CListControlImpl::PointClientToAbs(CPoint pt) const { return pt + GetViewOffset(); } CRect CListControlImpl::RectAbsToClient(CRect rc) const { CRect ret; #if 1 ret = rc; ret.OffsetRect(-GetViewOffset()); #else ret.TopLeft() = PointAbsToClient(rc.TopLeft()); ret.BottomRight() = PointAbsToClient(rc.BottomRight()); #endif return ret; } CRect CListControlImpl::RectClientToAbs(CRect rc) const { CRect ret; #if 1 ret = rc; ret.OffsetRect(GetViewOffset()); #else ret.TopLeft() = PointClientToAbs(rc.TopLeft()); ret.BottomRight() = PointAbsToClient(rc.BottomRight()); #endif return ret; } size_t CListControlImpl::ItemFromPoint(CPoint const& pt) const { size_t ret = SIZE_MAX; if (!ItemFromPoint(pt, ret)) ret = SIZE_MAX; return ret; } bool CListControlImpl::ItemFromPoint(CPoint const & p_pt, t_size & p_item) const { return ItemFromPointAbs( PointClientToAbs( p_pt ), p_item); } void CListControlImpl::ReloadData() { this->m_varItemHeights.clear(); this->m_groupHeaders.clear(); OnViewAreaChanged(); } void CListControlImpl::ReloadItems(pfc::bit_array const & mask) { bool bReLayout = false; mask.for_each(true, 0, GetItemCount(), [this, &bReLayout] (size_t idx) { int hNew = this->GetItemHeight2(idx); int hOld = -1; { auto iter = m_varItemHeights.find(idx); if (iter != m_varItemHeights.end()) hOld = iter->second; } if (hNew != hOld) { m_varItemHeights[idx] = hNew; bReLayout = true; } }); if (bReLayout) { OnViewAreaChanged(); } else { UpdateItems(mask); } } void CListControlImpl::MinGroupHeight2Changed(size_t itemInGroup, bool reloadWhole) { size_t lo, hi; if (ResolveGroupRangeCached(itemInGroup, lo, hi)) { if (reloadWhole) { CRect rc; if (this->GetGroupOverlayRectAbs(itemInGroup, rc)) { this->InvalidateRect(this->RectAbsToClient(rc)); } { pfc::bit_array_range range(lo, hi-lo); this->ReloadItems(range); } } else { this->ReloadItem(hi - 1); } } } bool CListControlImpl::IsItemFirstInGroupCached(size_t item) const { return m_groupHeaders.count(item) > 0; } bool CListControlImpl::IsItemFirstInGroup(size_t item) const { if (item == 0) return true; return GetItemGroup(item) != GetItemGroup(item - 1); } bool CListControlImpl::IsItemLastInGroup(size_t item) const { size_t next = item + 1; if (next >= GetItemCount()) return true; return GetItemGroup(item) != GetItemGroup(next); } int CListControlImpl::GetItemHeight2(size_t which) const { if (!IsItemLastInGroup(which)) return -1; const int minGroupHeight = this->GetMinGroupHeight2(which); if (minGroupHeight <= 0) return -1; const int heightNormal = this->GetItemHeight(); const size_t base = FindGroupBase(which); const int groupHeightWithout = (which > base ? this->GetItemOffsetAbs2(base,which) : 0); const int minItemHeight = minGroupHeight - groupHeightWithout; // possibly negative if (minItemHeight > heightNormal) return minItemHeight; else return -1; // use normal } void CListControlImpl::wndSetDarkMode(CWindow wndListControl, bool bDark) { wndListControl.SendMessage(DarkMode::msgSetDarkMode(), bDark ? 1 : 0); } LRESULT CListControlImpl::OnSetDark(UINT, WPARAM wp, LPARAM) { switch (wp) { case 0: this->SetDarkMode(false); break; case 1: this->SetDarkMode(true); break; } return 1; } void CListControlImpl::OnItemRemoved(size_t which) { this->OnItemsRemoved(pfc::bit_array_one(which), GetItemCount() + 1); } UINT CListControlImpl::msgSetDarkMode() { return DarkMode::msgSetDarkMode(); } void CListControlImpl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { (void)nRepCnt; (void)nFlags; switch (nChar) { case VK_LEFT: MoveViewOriginDelta(CPoint(-MulDiv(16, m_dpi.cx, 96), 0)); break; case VK_RIGHT: MoveViewOriginDelta(CPoint(MulDiv(16, m_dpi.cx, 96), 0)); break; default: SetMsgHandled(FALSE); break; } } void CListControlImpl::OnItemsInserted(size_t at, size_t count, bool bSelect) { size_t newCount = this->GetItemCount(); this->OnItemsInsertedEx(pfc::bit_array_range(at, count, true), newCount - count, newCount, bSelect); }
