Mercurial > foo_out_sdl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "stdafx.h" | |
| 2 #include "CListControl.h" | |
| 3 #include "PaintUtils.h" | |
| 4 #include "CListControlUserOptions.h" | |
| 5 #include "GDIUtils.h" | |
| 6 #include "DarkMode.h" | |
| 7 | |
| 8 #define PrepLayoutCache_Debug 0 | |
| 9 #define Scroll_Debug 0 | |
| 10 | |
| 11 #if Scroll_Debug | |
| 12 #define Scroll_Debug_Print(...) PFC_DEBUG_PRINT_FORCED(__VA_ARGS__) | |
| 13 #else | |
| 14 #define Scroll_Debug_Print(...) | |
| 15 #endif | |
| 16 | |
| 17 CListControlUserOptions * CListControlUserOptions::instance = nullptr; | |
| 18 | |
| 19 CRect CListControlImpl::GetClientRectHook() const { | |
| 20 CRect temp; | |
| 21 if ( m_hWnd == NULL || !GetClientRect(temp)) temp.SetRectEmpty(); | |
| 22 return temp; | |
| 23 } | |
| 24 | |
| 25 bool CListControlImpl::UserEnabledSmoothScroll() const { | |
| 26 auto i = CListControlUserOptions::instance; | |
| 27 if ( i != nullptr ) return i->useSmoothScroll(); | |
| 28 return false; | |
| 29 } | |
| 30 | |
| 31 LRESULT CListControlImpl::SetFocusPassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { | |
| 32 SetFocus(); | |
| 33 bHandled = FALSE; | |
| 34 return 0; | |
| 35 } | |
| 36 | |
| 37 void CListControlImpl::EnsureVisibleRectAbs(const CRect & p_rect) { | |
| 38 const CRect rcView = GetVisibleRectAbs(); | |
| 39 const CRect rcItem = p_rect; | |
| 40 int deltaX = 0, deltaY = 0; | |
| 41 | |
| 42 const bool centerOnItem = m_ensureVisibleUser; | |
| 43 | |
| 44 if (rcItem.top < rcView.top || rcItem.bottom > rcView.bottom) { | |
| 45 if (rcItem.Height() > rcView.Height()) { | |
| 46 deltaY = rcItem.top - rcView.top; | |
| 47 } else { | |
| 48 if (centerOnItem) { | |
| 49 deltaY = rcItem.CenterPoint().y - rcView.CenterPoint().y; | |
| 50 } else { | |
| 51 if (rcItem.bottom > rcView.bottom) deltaY = rcItem.bottom - rcView.bottom; | |
| 52 else deltaY = rcItem.top - rcView.top; | |
| 53 | |
| 54 } | |
| 55 } | |
| 56 } | |
| 57 if (rcItem.left < rcView.left || rcItem.right > rcView.right) { | |
| 58 if (rcItem.Width() > rcView.Width()) { | |
| 59 if (rcItem.left > rcView.left || rcItem.right < rcView.right) deltaX = rcItem.left - rcView.left; | |
| 60 } else { | |
| 61 if (centerOnItem) { | |
| 62 deltaX = rcItem.CenterPoint().x - rcView.CenterPoint().x; | |
| 63 } else { | |
| 64 if (rcItem.right > rcView.right) deltaX = rcItem.right - rcView.right; | |
| 65 else deltaX = rcItem.left - rcView.left; | |
| 66 } | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 if (deltaX != 0 || deltaY != 0) { | |
| 71 MoveViewOriginDelta(CPoint(deltaX,deltaY)); | |
| 72 } | |
| 73 } | |
| 74 void CListControlImpl::EnsureItemVisible(t_size p_item, bool bUser) { | |
| 75 m_ensureVisibleUser = bUser; | |
| 76 PFC_ASSERT(p_item < GetItemCount()); | |
| 77 if (this->PrepLayoutCache(m_viewOrigin, p_item, p_item+1 )) { | |
| 78 RefreshSliders(); Invalidate(); | |
| 79 } | |
| 80 EnsureVisibleRectAbs(GetItemRectAbs(p_item)); | |
| 81 m_ensureVisibleUser = false; | |
| 82 } | |
| 83 void CListControlImpl::EnsureHeaderVisible2(size_t atItem) { | |
| 84 CRect rect; | |
| 85 if (GetGroupHeaderRectAbs2(atItem,rect)) EnsureVisibleRectAbs(rect); | |
| 86 } | |
| 87 | |
| 88 void CListControlImpl::RefreshSlider(bool p_vertical) { | |
| 89 const CRect viewArea = GetViewAreaRectAbs(); | |
| 90 const CRect rcVisible = GetVisibleRectAbs(); | |
| 91 SCROLLINFO si = {}; | |
| 92 si.cbSize = sizeof(si); | |
| 93 si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; | |
| 94 | |
| 95 | |
| 96 if (AllowScrollbar(p_vertical)) { | |
| 97 if (p_vertical) { | |
| 98 si.nPage = rcVisible.Height(); | |
| 99 si.nMin = viewArea.top; | |
| 100 si.nMax = viewArea.bottom - 1; | |
| 101 si.nPos = rcVisible.top; | |
| 102 } else { | |
| 103 si.nPage = rcVisible.Width(); | |
| 104 si.nMin = viewArea.left; | |
| 105 si.nMax = viewArea.right - 1; | |
| 106 si.nPos = rcVisible.left; | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 Scroll_Debug_Print("RefreshSlider vertical=", p_vertical, ", nPage=", si.nPage, ", nMin=", si.nMin, ", nMax=", si.nMax, ", nPos=", si.nPos); | |
| 111 | |
| 112 SetScrollInfo(p_vertical ? SB_VERT : SB_HORZ, &si); | |
| 113 } | |
| 114 | |
| 115 void CListControlImpl::RefreshSliders() { | |
| 116 //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 | |
| 117 RefreshSlider(false); RefreshSlider(true); | |
| 118 } | |
| 119 | |
| 120 int CListControlImpl::GetScrollThumbPos(int which) { | |
| 121 SCROLLINFO si = {}; | |
| 122 si.cbSize = sizeof(si); | |
| 123 si.fMask = SIF_TRACKPOS; | |
| 124 WIN32_OP_D( GetScrollInfo(which,&si) ); | |
| 125 return si.nTrackPos; | |
| 126 } | |
| 127 | |
| 128 bool CListControlImpl::ResolveGroupRangeCached(size_t itemInGroup, size_t& outBegin, size_t& outEnd) const { | |
| 129 auto end = this->m_groupHeaders.upper_bound(itemInGroup); | |
| 130 if (end == this->m_groupHeaders.begin()) return false; | |
| 131 auto begin = end; --begin; | |
| 132 outBegin = *begin; | |
| 133 if (end == this->m_groupHeaders.end()) outEnd = this->GetItemCount(); | |
| 134 else outEnd = *end; | |
| 135 return true; | |
| 136 } | |
| 137 | |
| 138 size_t CListControlImpl::ResolveGroupRange2(t_size p_base) const { | |
| 139 const auto id = this->GetItemGroup(p_base); | |
| 140 const size_t count = this->GetItemCount(); | |
| 141 size_t walk = p_base + 1; | |
| 142 while (walk < count && GetItemGroup(walk) == id) ++walk; | |
| 143 return walk - p_base; | |
| 144 } | |
| 145 | |
| 146 | |
| 147 static int HandleScroll(WORD p_code,int p_offset,int p_page, int p_line, int p_bottom, int p_thumbpos) { | |
| 148 switch(p_code) { | |
| 149 case SB_LINEUP: | |
| 150 return p_offset - p_line; | |
| 151 case SB_LINEDOWN: | |
| 152 return p_offset + p_line; | |
| 153 case SB_BOTTOM: | |
| 154 return p_bottom - p_page; | |
| 155 case SB_TOP: | |
| 156 return 0; | |
| 157 case SB_PAGEUP: | |
| 158 return p_offset - p_page; | |
| 159 case SB_PAGEDOWN: | |
| 160 return p_offset + p_page; | |
| 161 case SB_THUMBPOSITION: | |
| 162 return p_thumbpos; | |
| 163 case SB_THUMBTRACK: | |
| 164 return p_thumbpos; | |
| 165 default: | |
| 166 return p_offset; | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 static CPoint ClipPointToRect(CPoint const & p_pt,CRect const & p_rect) { | |
| 171 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)); | |
| 172 } | |
| 173 | |
| 174 void CListControlImpl::MoveViewOriginNoClip(CPoint p_target) { | |
| 175 UpdateWindow(); | |
| 176 PrepLayoutCache(p_target); | |
| 177 const CPoint old = m_viewOrigin; | |
| 178 m_viewOrigin = p_target; | |
| 179 | |
| 180 if (m_viewOrigin != old) { | |
| 181 #if PrepLayoutCache_Debug | |
| 182 PFC_DEBUGLOG << "MoveViewOriginNoClip: m_viewOrigin=" << m_viewOrigin.x << "," << m_viewOrigin.y; | |
| 183 #endif | |
| 184 | |
| 185 if (m_viewOrigin.x != old.x) SetScrollPos(SB_HORZ,m_viewOrigin.x); | |
| 186 if (m_viewOrigin.y != old.y) SetScrollPos(SB_VERT,m_viewOrigin.y); | |
| 187 | |
| 188 const CPoint delta = old - m_viewOrigin; | |
| 189 if (FixedOverlayPresent()) Invalidate(); | |
| 190 else { | |
| 191 DWORD flags = SW_INVALIDATE | SW_ERASE; | |
| 192 const DWORD smoothScrollMS = 50; | |
| 193 if (this->UserEnabledSmoothScroll() && this->CanSmoothScroll()) { | |
| 194 flags |= SW_SMOOTHSCROLL | (smoothScrollMS << 16); | |
| 195 } | |
| 196 | |
| 197 ScrollWindowEx(delta.x,delta.y,GetClientRectHook(),NULL,0,0,flags ); | |
| 198 } | |
| 199 | |
| 200 OnViewOriginChange(m_viewOrigin - old); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 CPoint CListControlImpl::ClipViewOrigin(CPoint p_origin) const { | |
| 205 return ClipPointToRect(p_origin,GetValidViewOriginArea()); | |
| 206 } | |
| 207 void CListControlImpl::MoveViewOrigin(CPoint p_target) { | |
| 208 PrepLayoutCache(p_target); | |
| 209 MoveViewOriginNoClip(ClipViewOrigin(p_target)); | |
| 210 } | |
| 211 | |
| 212 #ifndef SPI_GETWHEELSCROLLCHARS | |
| 213 #define SPI_GETWHEELSCROLLCHARS 0x006C | |
| 214 #endif | |
| 215 int CListControlImpl::HandleWheel(int & p_accum,int p_delta, bool bHoriz) { | |
| 216 if ( m_suppressMouseWheel ) return 0; | |
| 217 UINT scrollLines = 1; | |
| 218 SystemParametersInfo(bHoriz ? SPI_GETWHEELSCROLLCHARS : SPI_GETWHEELSCROLLLINES,0,&scrollLines,0); | |
| 219 if (scrollLines == ~0) { | |
| 220 p_accum = 0; | |
| 221 int rv = -pfc::sgn_t(p_delta); | |
| 222 CRect client = GetClientRectHook(); | |
| 223 if (bHoriz) rv *= client.Width(); | |
| 224 else rv *= client.Height(); | |
| 225 return rv; | |
| 226 } | |
| 227 | |
| 228 const int itemHeight = GetItemHeight(); | |
| 229 const int extraScale = 10000; | |
| 230 | |
| 231 p_accum += p_delta * extraScale; | |
| 232 if ((int)scrollLines < 1) scrollLines = 1; | |
| 233 int multiplier = (WHEEL_DELTA * extraScale) / (scrollLines * itemHeight); | |
| 234 if (multiplier<1) multiplier = 1; | |
| 235 | |
| 236 int delta = pfc::rint32( (double) p_accum / (double) multiplier ); | |
| 237 p_accum -= delta * multiplier; | |
| 238 return -delta; | |
| 239 | |
| 240 /* | |
| 241 if (p_accum<=-multiplier || p_accum>=multiplier) { | |
| 242 int direction; | |
| 243 int ov = p_accum; | |
| 244 if (ov<0) { | |
| 245 direction = -1; | |
| 246 ov = -ov; | |
| 247 p_accum = - ((-p_accum)%multiplier); | |
| 248 } else { | |
| 249 p_accum %= multiplier; | |
| 250 direction = 1; | |
| 251 } | |
| 252 | |
| 253 return - (direction * (ov + multiplier - 1) ) / multiplier; | |
| 254 } else { | |
| 255 return 0; | |
| 256 } | |
| 257 */ | |
| 258 } | |
| 259 | |
| 260 LRESULT CListControlImpl::OnVWheel(UINT,WPARAM p_wp,LPARAM,BOOL&) { | |
| 261 const CRect client = GetClientRectHook(), view = this->GetViewAreaRectAbs(); | |
| 262 int deltaPixels = HandleWheel(m_wheelAccumY,(short)HIWORD(p_wp), false); | |
| 263 | |
| 264 const bool canVScroll = client.Height() < view.Height(); | |
| 265 const bool canHScroll = client.Width() < view.Width(); | |
| 266 | |
| 267 CPoint ptDelta; | |
| 268 if ( canVScroll && canHScroll && GetHotkeyModifierFlags() == MOD_SHIFT) { | |
| 269 ptDelta = CPoint(deltaPixels, 0); // default to horizontal scroll if shift is pressed | |
| 270 } else if (canVScroll) { | |
| 271 ptDelta = CPoint(0,deltaPixels); | |
| 272 } else if (canHScroll) { | |
| 273 ptDelta = CPoint(deltaPixels,0); | |
| 274 } | |
| 275 | |
| 276 if ( ptDelta != CPoint(0,0) ) { | |
| 277 MoveViewOriginDelta(ptDelta); | |
| 278 } | |
| 279 return 0; | |
| 280 } | |
| 281 LRESULT CListControlImpl::OnHWheel(UINT,WPARAM p_wp,LPARAM,BOOL&) { | |
| 282 // const CRect client = GetClientRectHook(); | |
| 283 int deltaPixels = HandleWheel(m_wheelAccumX,(short)HIWORD(p_wp), true); | |
| 284 MoveViewOriginDelta(CPoint(-deltaPixels,0)); | |
| 285 return 0; | |
| 286 } | |
| 287 | |
| 288 // WM_VSCROLL special fix | |
| 289 // We must expect SCROLLINFO to go out of sync with layout, due to group partitioning happening as the user scrolls | |
| 290 // SetScrollInfo() is apparently disregarded while the user is scrolling, causing nonsensical behavior if we live update it as we discover new groups | |
| 291 // 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 | |
| 292 LRESULT CListControlImpl::OnVScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { | |
| 293 SCROLLINFO si = {}; | |
| 294 si.cbSize = sizeof(si); | |
| 295 si.fMask = SIF_ALL; | |
| 296 WIN32_OP_D(GetScrollInfo(SB_VERT, &si)); | |
| 297 int thumb = si.nTrackPos; // HIWORD(p_wp); | |
| 298 auto bottom = GetViewAreaRectAbs().bottom; | |
| 299 auto visible = GetVisibleHeight(); | |
| 300 | |
| 301 if (si.nMax < si.nMin) return 0; | |
| 302 double p = (double)(thumb - si.nMin) / (double)(si.nMax + 1 - si.nMin); | |
| 303 thumb = pfc::rint32(p * bottom); | |
| 304 int target = HandleScroll(LOWORD(p_wp), m_viewOrigin.y, visible, GetItemHeight(), bottom, thumb); | |
| 305 | |
| 306 Scroll_Debug_Print("OnVScroll thumb=", thumb, ", target=", target, ", bottom=", bottom, ", visible=", visible, ", p=", p); | |
| 307 | |
| 308 MoveViewOrigin(CPoint(m_viewOrigin.x, target)); | |
| 309 | |
| 310 return 0; | |
| 311 } | |
| 312 | |
| 313 // ====== Logitech scroll bug explanation ====== | |
| 314 // With Logitech wheel hscroll, we must use WPARAM position, not GetScrollInfo() value. | |
| 315 // However this is wrong, we'll get nonsense if scroll range doesn't fit in 16-bit! | |
| 316 // As a workaround, we use GetScrollInfo() value for vscroll (good) | |
| 317 // and workaround Logitech bug by using WPARAM position with hscroll (practically impossible to overflow) | |
| 318 LRESULT CListControlImpl::OnHScroll(UINT,WPARAM p_wp,LPARAM,BOOL&) { | |
| 319 int thumb = HIWORD(p_wp); | |
| 320 const auto fullWidth = GetViewAreaWidth(); | |
| 321 if (fullWidth > INT16_MAX) { // Possible overflow or near-overflow? Drop Logitech stupidity mitigation | |
| 322 thumb = GetScrollThumbPos(SB_HORZ); | |
| 323 } | |
| 324 int target = HandleScroll(LOWORD(p_wp), m_viewOrigin.x, GetVisibleRectAbs().Width(), GetItemHeight() /*fixme*/, fullWidth, thumb); | |
| 325 Scroll_Debug_Print("OnHScroll thumb=", thumb, ", target=", target); | |
| 326 MoveViewOrigin(CPoint(target,m_viewOrigin.y)); | |
| 327 return 0; | |
| 328 } | |
| 329 | |
| 330 LRESULT CListControlImpl::OnGesture(UINT,WPARAM,LPARAM lParam,BOOL& bHandled) { | |
| 331 if (!this->m_gestureAPI.IsAvailable()) { | |
| 332 bHandled = FALSE; | |
| 333 return 0; | |
| 334 } | |
| 335 HGESTUREINFO hGesture = (HGESTUREINFO) lParam; | |
| 336 GESTUREINFO gestureInfo = {sizeof(gestureInfo)}; | |
| 337 if (m_gestureAPI.GetGestureInfo(hGesture, &gestureInfo)) { | |
| 338 //console::formatter() << "WM_GESTURE " << pfc::format_hex( gestureInfo.dwFlags ) << " " << (int)gestureInfo.dwID << " X:" << gestureInfo.ptsLocation.x << " Y:" << gestureInfo.ptsLocation.y << " arg:" << (__int64) gestureInfo.ullArguments; | |
| 339 CPoint pt( gestureInfo.ptsLocation.x, gestureInfo.ptsLocation.y ); | |
| 340 switch(gestureInfo.dwID) { | |
| 341 case GID_BEGIN: | |
| 342 m_gesturePoint = pt; | |
| 343 break; | |
| 344 case GID_END: | |
| 345 break; | |
| 346 case GID_PAN: | |
| 347 MoveViewOriginDelta( this->m_gesturePoint - pt); | |
| 348 m_gesturePoint = pt; | |
| 349 break; | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 m_gestureAPI.CloseGestureInfoHandle(hGesture); | |
| 354 bHandled = TRUE; | |
| 355 return 0; | |
| 356 } | |
| 357 | |
| 358 LRESULT CListControlImpl::OnSize(UINT,WPARAM,LPARAM,BOOL&) { | |
| 359 this->PrepLayoutCache(m_viewOrigin); | |
| 360 OnSizeAsync_Trigger(); | |
| 361 RefreshSliders(); | |
| 362 return 0; | |
| 363 } | |
| 364 | |
| 365 | |
| 366 void CListControlImpl::RenderBackground( CDCHandle dc, CRect const & rc ) { | |
| 367 PaintUtils::FillRectSimple(dc,rc,GetSysColorHook(colorBackground)); | |
| 368 } | |
| 369 | |
| 370 void CListControlImpl::PaintContent(CRect rcPaint, HDC dc) { | |
| 371 CDCHandle renderDC(dc); | |
| 372 | |
| 373 CMemoryDC bufferDC(renderDC,rcPaint); | |
| 374 renderDC = bufferDC; | |
| 375 this->RenderBackground(renderDC, rcPaint); | |
| 376 | |
| 377 RenderRect(rcPaint, renderDC); | |
| 378 } | |
| 379 | |
| 380 void CListControlImpl::OnPrintClient(HDC dc, UINT) { | |
| 381 CRect rcClient; this->GetClientRect( rcClient ); | |
| 382 PaintContent( rcClient, dc ); | |
| 383 } | |
| 384 | |
| 385 void CListControlImpl::OnPaint(CDCHandle target) { | |
| 386 auto toggle = pfc::autoToggle(m_paintInProgress, true); | |
| 387 if (target) { | |
| 388 CRect rcClient; this->GetClientRect(rcClient); | |
| 389 PaintContent(rcClient, target); | |
| 390 } else { | |
| 391 CPaintDC paintDC(*this); | |
| 392 PaintContent(paintDC.m_ps.rcPaint, paintDC.m_hDC); | |
| 393 } | |
| 394 } | |
| 395 | |
| 396 bool CListControlImpl::GetItemRange(const CRect & p_rect,t_size & p_base,t_size & p_count) const { | |
| 397 return GetItemRangeAbs(this->RectClientToAbs(p_rect), p_base, p_count); | |
| 398 } | |
| 399 | |
| 400 | |
| 401 | |
| 402 bool CListControlImpl::GetItemRangeAbsInclHeaders(const CRect & p_rect,t_size & p_base,t_size & p_count) const { | |
| 403 CRect temp(p_rect); | |
| 404 temp.bottom += this->GetGroupHeaderHeight(); | |
| 405 return GetItemRangeAbs(temp, p_base, p_count); | |
| 406 } | |
| 407 | |
| 408 bool CListControlImpl::GetItemRangeAbs(const CRect & p_rect,t_size & p_base,t_size & p_count) const { | |
| 409 const size_t count = GetItemCount(); | |
| 410 if (p_rect.right < 0 || p_rect.left >= GetItemWidth() || count == 0) return false; | |
| 411 | |
| 412 size_t top = IndexFromPointAbs(CPoint(0, p_rect.top)); | |
| 413 size_t bottom = IndexFromPointAbs(CPoint(0, p_rect.bottom)); | |
| 414 if (top == SIZE_MAX) return false; | |
| 415 if (bottom > count-1) bottom = count - 1; | |
| 416 p_base = top; | |
| 417 p_count = bottom - top + 1; | |
| 418 PFC_ASSERT(p_base + p_count <= count); | |
| 419 return true; | |
| 420 } | |
| 421 | |
| 422 void CListControlImpl::RenderRect(const CRect & p_rect,CDCHandle p_dc) { | |
| 423 t_size base, count; | |
| 424 if (GetItemRange(p_rect,base,count)) { | |
| 425 for(t_size walk = 0; walk < count; ++walk) { | |
| 426 size_t atItem = base + walk; | |
| 427 if (m_groupHeaders.count(atItem) > 0) { | |
| 428 CRect rcHeader, rcUpdate; | |
| 429 if (GetGroupHeaderRectAbs2(atItem, rcHeader) ) { | |
| 430 rcHeader = RectAbsToClient(rcHeader); | |
| 431 if (rcUpdate.IntersectRect(rcHeader, p_rect)) { | |
| 432 DCStateScope dcState(p_dc); | |
| 433 if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { | |
| 434 try { | |
| 435 RenderGroupHeader2(atItem, rcHeader, rcUpdate, p_dc); | |
| 436 } catch (...) { | |
| 437 PFC_ASSERT(!"Should not get here"); | |
| 438 } | |
| 439 } | |
| 440 } | |
| 441 } | |
| 442 } | |
| 443 | |
| 444 CRect rcUpdate, rcItem = GetItemRect(atItem); | |
| 445 if (rcUpdate.IntersectRect(rcItem,p_rect)) { | |
| 446 DCStateScope dcState(p_dc); | |
| 447 if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { | |
| 448 try { | |
| 449 RenderItem(atItem,rcItem,rcUpdate,p_dc); | |
| 450 } catch(...) { | |
| 451 PFC_ASSERT(!"Should not get here"); | |
| 452 } | |
| 453 } | |
| 454 } | |
| 455 } | |
| 456 | |
| 457 if ( this->m_groupHeaders.size() > 0 ) { | |
| 458 auto iter = m_groupHeaders.upper_bound(base); | |
| 459 if (iter != m_groupHeaders.begin()) { | |
| 460 --iter; | |
| 461 while ( iter != m_groupHeaders.end() && *iter < base + count) { | |
| 462 auto iter2 = iter; ++iter2; | |
| 463 | |
| 464 size_t begin = *iter; | |
| 465 size_t end; | |
| 466 if (iter2 == m_groupHeaders.end()) end = this->GetItemCount(); | |
| 467 else end = *iter2; | |
| 468 | |
| 469 CRect rc; | |
| 470 rc.top = this->GetItemOffsetAbs(begin); | |
| 471 rc.bottom = this->GetItemBottomOffsetAbs(end-1); | |
| 472 rc.left = 0; | |
| 473 rc.right = this->GetItemWidth(); | |
| 474 rc = this->RectAbsToClient(rc); | |
| 475 CRect rcUpdate; | |
| 476 if (rcUpdate.IntersectRect(rc, p_rect)) { | |
| 477 DCStateScope dcState(p_dc); | |
| 478 if (p_dc.IntersectClipRect(rcUpdate) != NULLREGION) { | |
| 479 try { | |
| 480 this->RenderGroupOverlay(begin, rc, rcUpdate, p_dc); | |
| 481 } catch (...) { | |
| 482 PFC_ASSERT(!"Should not get here"); | |
| 483 } | |
| 484 } | |
| 485 } | |
| 486 | |
| 487 | |
| 488 iter = iter2; | |
| 489 } | |
| 490 } | |
| 491 } | |
| 492 } | |
| 493 | |
| 494 RenderOverlay2(p_rect,p_dc); | |
| 495 } | |
| 496 | |
| 497 bool CListControlImpl::GetGroupOverlayRectAbs(size_t atItem, CRect& outRect) { | |
| 498 auto iter = m_groupHeaders.upper_bound(atItem); | |
| 499 if (iter == m_groupHeaders.begin()) return false; | |
| 500 auto iter2 = iter; --iter; | |
| 501 | |
| 502 size_t begin = *iter; | |
| 503 size_t end; | |
| 504 if (iter2 == m_groupHeaders.end()) end = this->GetItemCount(); | |
| 505 else end = *iter2; | |
| 506 | |
| 507 CRect rc; | |
| 508 | |
| 509 rc.top = this->GetItemOffsetAbs(begin); | |
| 510 rc.bottom = this->GetItemBottomOffsetAbs(end - 1); | |
| 511 rc.left = 0; | |
| 512 rc.right = this->GetItemWidth(); | |
| 513 | |
| 514 outRect = rc; | |
| 515 return true; | |
| 516 } | |
| 517 | |
| 518 void CListControlImpl::MinGroupHeight2ChangedForGroup(groupID_t groupID, bool reloadWhole) { | |
| 519 for (auto iter = m_groupHeaders.begin(); iter != m_groupHeaders.end(); ++iter) { | |
| 520 if (groupID == GetItemGroup(*iter)) { | |
| 521 this->MinGroupHeight2Changed(*iter, reloadWhole); | |
| 522 } | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 void CListControlImpl::UpdateGroupOverlayByID(groupID_t groupID, int xFrom, int xTo) { | |
| 527 t_size base, count; | |
| 528 if (GetItemRangeAbs(GetVisibleRectAbs(), base, count)) { | |
| 529 bool on = false; // Have to walk whole range - there may be multiple groups with the same ID | |
| 530 for (size_t walk = 0; walk < count; ++walk) { | |
| 531 bool test = (groupID == GetItemGroup(base + walk)); | |
| 532 if (test && !on) { | |
| 533 CRect rc; | |
| 534 if (GetGroupOverlayRectAbs(base + walk, rc)) { | |
| 535 if (xFrom < xTo) { | |
| 536 rc.left = xFrom; rc.right = xTo; | |
| 537 } | |
| 538 this->InvalidateRect(this->RectAbsToClient(rc)); | |
| 539 } | |
| 540 } | |
| 541 | |
| 542 on = test; | |
| 543 } | |
| 544 } | |
| 545 } | |
| 546 | |
| 547 CRect CListControlImpl::GetItemRect(t_size p_item) const { | |
| 548 return this->RectAbsToClient(GetItemRectAbs(p_item)); | |
| 549 } | |
| 550 | |
| 551 bool CListControlImpl::GetGroupHeaderRect2(size_t atItem,CRect & p_rect) const { | |
| 552 CRect temp; | |
| 553 if (!GetGroupHeaderRectAbs2(atItem,temp)) return false; | |
| 554 p_rect = RectAbsToClient(temp); | |
| 555 return true; | |
| 556 } | |
| 557 | |
| 558 size_t CListControlImpl::FindGroupBaseCached(size_t itemFor) const { | |
| 559 auto iter = m_groupHeaders.upper_bound(itemFor); | |
| 560 if (iter == m_groupHeaders.begin()) return 0; | |
| 561 --iter; | |
| 562 return *iter; | |
| 563 } | |
| 564 | |
| 565 size_t CListControlImpl::FindGroupBase(size_t itemFor) const { | |
| 566 return this->FindGroupBase(itemFor, this->GetItemGroup(itemFor)); | |
| 567 } | |
| 568 | |
| 569 size_t CListControlImpl::FindGroupBase(size_t itemFor, groupID_t id) const { | |
| 570 size_t walk = itemFor; | |
| 571 while (walk > 0) { | |
| 572 size_t prev = walk - 1; | |
| 573 if (this->GetItemGroup(prev) != id) break; | |
| 574 walk = prev; | |
| 575 } | |
| 576 return walk; | |
| 577 } | |
| 578 | |
| 579 bool CListControlImpl::PrepLayoutCache(CPoint& ptOrigin, size_t indexLo, size_t indexHi) { | |
| 580 const size_t count = GetItemCount(); | |
| 581 if (count == 0) return false; | |
| 582 #if PrepLayoutCache_Debug | |
| 583 PFC_DEBUGLOG << "PrepLayoutCache entry"; | |
| 584 PFC_DEBUGLOG << "PrepLayoutCache: count=" << count << " knownGroups=" << this->m_groupHeaders.size(); | |
| 585 PFC_DEBUGLOG << "PrepLayoutCache: indexLo=" << pfc::format_index(indexLo) << " indexHi=" << pfc::format_index(indexHi); | |
| 586 #endif | |
| 587 const int clientHeight = pfc::max_t<int>(this->GetClientRectHook().Height(), 100); | |
| 588 | |
| 589 // Always walk 2*clientHeight, with area above and below | |
| 590 int yMax = -1, yBase = 0; | |
| 591 size_t baseItem = 0, endItem = SIZE_MAX; | |
| 592 | |
| 593 if (!m_greedyGroupLayout) { | |
| 594 if (indexLo == SIZE_MAX) { | |
| 595 yBase = pfc::max_t<int>(ptOrigin.y - clientHeight / 2, 0); | |
| 596 yMax = yBase + clientHeight * 2; | |
| 597 baseItem = pfc::min_t<size_t>(this->IndexFromPointAbs(yBase), count - 1); | |
| 598 } else { | |
| 599 auto itemHeight = GetItemHeight(); | |
| 600 size_t extraItems = (size_t)(clientHeight / itemHeight); | |
| 601 #if PrepLayoutCache_Debug | |
| 602 PFC_DEBUGLOG << "PrepLayoutCache: clientHeight=" << clientHeight << " itemHeight=" << itemHeight << " extraItems=" << extraItems; | |
| 603 #endif | |
| 604 if (indexLo < extraItems) baseItem = 0; | |
| 605 else baseItem = indexLo - extraItems; | |
| 606 | |
| 607 if (indexHi == SIZE_MAX) { | |
| 608 endItem = baseItem + extraItems; | |
| 609 } else { | |
| 610 endItem = indexHi + extraItems; | |
| 611 } | |
| 612 if (endItem > count) endItem = count; | |
| 613 | |
| 614 #if PrepLayoutCache_Debug | |
| 615 PFC_DEBUGLOG << "PrepLayoutCache: baseItem=" << baseItem << " endItem=" << endItem; | |
| 616 #endif | |
| 617 } | |
| 618 } | |
| 619 | |
| 620 | |
| 621 | |
| 622 | |
| 623 size_t item = baseItem; | |
| 624 { | |
| 625 const auto group = this->GetItemGroup(baseItem); | |
| 626 if (group != 0) { | |
| 627 size_t hdr = this->FindGroupBase(baseItem, group); | |
| 628 if (hdr < baseItem) { | |
| 629 item = hdr; | |
| 630 } | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 #if PrepLayoutCache_Debug | |
| 635 if (yMax != -1) { | |
| 636 PFC_DEBUGLOG << "PrepLayoutCache: yBase=" << yBase << " yMax=" << yMax; | |
| 637 } | |
| 638 if (indexLo != SIZE_MAX) { | |
| 639 pfc::string_formatter msg; | |
| 640 msg << "PrepLayoutCache: indexLo=" << indexLo; | |
| 641 if (indexHi != SIZE_MAX) { | |
| 642 msg << " indexHi=" << indexHi; | |
| 643 } | |
| 644 pfc::outputDebugLine(msg); | |
| 645 } | |
| 646 PFC_DEBUGLOG << "PrepLayoutCache: baseItem=" << baseItem; | |
| 647 #endif | |
| 648 | |
| 649 size_t anchorIdx = m_greedyGroupLayout ? SIZE_MAX : this->IndexFromPointAbs(ptOrigin.y); | |
| 650 int anchorDelta = 0; | |
| 651 bool anchorIsFirstInGroup = IsItemFirstInGroupCached(anchorIdx); | |
| 652 if (anchorIdx != SIZE_MAX) { | |
| 653 anchorDelta = ptOrigin.y - GetItemOffsetAbs(anchorIdx); | |
| 654 } | |
| 655 | |
| 656 #if PrepLayoutCache_Debug | |
| 657 PFC_DEBUGLOG << "PrepLayoutCache: anchorIdx=" << pfc::format_index(anchorIdx) << " anchorDelta=" << anchorDelta << " anchorIsFirstInGroup=" << anchorIsFirstInGroup; | |
| 658 #endif | |
| 659 | |
| 660 bool bChanged = false; | |
| 661 int gh = -1; | |
| 662 int ih = -1; | |
| 663 int yWalk = yBase; | |
| 664 groupID_t prevGroup = 0; | |
| 665 if (item > 1) prevGroup = this->GetItemGroup(item - 1); | |
| 666 for (; item < count; ++item) { | |
| 667 int yDelta = 0; | |
| 668 auto group = this->GetItemGroup(item); | |
| 669 if (group != prevGroup) { | |
| 670 if (m_groupHeaders.insert(item).second) bChanged = true; | |
| 671 if (gh < 0) gh = GetGroupHeaderHeight(); | |
| 672 yDelta += gh; | |
| 673 } else { | |
| 674 if (m_groupHeaders.erase(item) > 0) bChanged = true; | |
| 675 } | |
| 676 prevGroup = group; | |
| 677 | |
| 678 auto iter = m_varItemHeights.find(item); | |
| 679 int varHeight = this->GetItemHeight2(item); | |
| 680 if (varHeight < 0) { | |
| 681 if (iter != m_varItemHeights.end()) { | |
| 682 m_varItemHeights.erase(iter); | |
| 683 bChanged = true; | |
| 684 } | |
| 685 if (ih < 0) ih = this->GetItemHeight(); | |
| 686 yDelta += ih; | |
| 687 } else { | |
| 688 if (iter == m_varItemHeights.end()) { | |
| 689 m_varItemHeights[item] = varHeight; | |
| 690 bChanged = true; | |
| 691 } else if ( iter->second != varHeight ) { | |
| 692 iter->second = varHeight; | |
| 693 bChanged = true; | |
| 694 } | |
| 695 | |
| 696 yDelta += varHeight; | |
| 697 } | |
| 698 | |
| 699 if (item >= endItem) { | |
| 700 break; | |
| 701 } | |
| 702 if (item >= baseItem && yMax != -1) { | |
| 703 yWalk += yDelta; | |
| 704 if (yWalk > yMax) break; | |
| 705 } | |
| 706 } | |
| 707 | |
| 708 #if PrepLayoutCache_Debug | |
| 709 PFC_DEBUGLOG << "PrepLayoutCache: bChanged=" << bChanged << " knownGroups=" << m_groupHeaders.size() << " knownVarHeights=" << m_varItemHeights.size(); | |
| 710 #endif | |
| 711 | |
| 712 if (bChanged) { | |
| 713 if (anchorIdx != SIZE_MAX) { | |
| 714 int fix = GetItemOffsetAbs(anchorIdx) + anchorDelta; | |
| 715 | |
| 716 // View would begin exactly with an item that became a first item in a group? | |
| 717 if (anchorDelta == 0 && !anchorIsFirstInGroup && IsItemFirstInGroupCached(anchorIdx)) { | |
| 718 if (gh < 0) gh = GetGroupHeaderHeight(); | |
| 719 fix -= gh; | |
| 720 } | |
| 721 | |
| 722 #if PrepLayoutCache_Debug | |
| 723 PFC_DEBUGLOG << "PrepLayoutCache: fixing origin: " << ptOrigin.y << " to " << fix; | |
| 724 #endif | |
| 725 | |
| 726 ptOrigin.y = fix; | |
| 727 if (&ptOrigin != &m_viewOrigin && m_hWnd != NULL) { | |
| 728 #if PrepLayoutCache_Debug | |
| 729 PFC_DEBUGLOG << "PrepLayoutCache: invalidating view"; | |
| 730 #endif | |
| 731 Invalidate(); | |
| 732 } | |
| 733 } | |
| 734 } | |
| 735 | |
| 736 if ( bChanged ) { | |
| 737 // DO NOT update sliders from here, causes mayhem, SetScrollInfo() in mid-scroll is not really handled | |
| 738 // this->RefreshSliders(); | |
| 739 } | |
| 740 return bChanged; | |
| 741 } | |
| 742 | |
| 743 int CListControlImpl::GetViewAreaHeight() const { | |
| 744 auto ret = GetItemOffsetAbs(GetItemCount()); | |
| 745 Scroll_Debug_Print("GetViewAreaHeight: " , ret); | |
| 746 return ret; | |
| 747 } | |
| 748 | |
| 749 int CListControlImpl::GetItemBottomOffsetAbs(size_t item) const { | |
| 750 return GetItemOffsetAbs(item) + GetItemHeightCached(item); | |
| 751 } | |
| 752 | |
| 753 int CListControlImpl::GetItemOffsetAbs2(size_t base, size_t item) const { | |
| 754 // Also valid with item == GetItemCount() | |
| 755 size_t varcount = 0; | |
| 756 int acc = 0; | |
| 757 const bool baseValid = (base != SIZE_MAX); | |
| 758 const size_t itemDelta = baseValid ? item - base : item; | |
| 759 for (auto iter = (baseValid ? m_varItemHeights.lower_bound(base) : m_varItemHeights.begin()); iter != m_varItemHeights.end(); ++iter){ | |
| 760 if (iter->first >= item) break; | |
| 761 if (iter->second > 0) acc += iter->second; | |
| 762 ++varcount; | |
| 763 } | |
| 764 if (varcount < itemDelta) { | |
| 765 acc += GetItemHeight() * (int)(itemDelta - varcount); | |
| 766 } | |
| 767 | |
| 768 int gh = -1; | |
| 769 for (auto iter = (baseValid ? m_groupHeaders.upper_bound(base) : m_groupHeaders.begin()); iter != m_groupHeaders.end(); ++iter){ | |
| 770 if (*iter > item) break; | |
| 771 if (gh < 0) gh = GetGroupHeaderHeight(); | |
| 772 acc += gh; | |
| 773 } | |
| 774 | |
| 775 return acc; | |
| 776 } | |
| 777 | |
| 778 int CListControlImpl::GetItemOffsetAbs(size_t item) const { | |
| 779 // Also valid with item == GetItemCount() | |
| 780 return GetItemOffsetAbs2(SIZE_MAX, item); | |
| 781 } | |
| 782 | |
| 783 int CListControlImpl::GetItemContentHeightCached(size_t item) const { | |
| 784 auto iter = m_varItemHeights.find(item); | |
| 785 if (iter == m_varItemHeights.end()) return GetItemHeight(); | |
| 786 else return this->GetItemHeight2Content( item, iter->second ); | |
| 787 } | |
| 788 | |
| 789 int CListControlImpl::GetItemHeightCached(size_t item) const { | |
| 790 auto iter = m_varItemHeights.find(item); | |
| 791 if (iter == m_varItemHeights.end()) return GetItemHeight(); | |
| 792 else return iter->second; | |
| 793 } | |
| 794 | |
| 795 CRect CListControlImpl::GetItemRectAbs(t_size p_item) const { | |
| 796 PFC_ASSERT(p_item < GetItemCount()); | |
| 797 // const int normalHeight = GetItemHeight(); | |
| 798 CRect rcItem; | |
| 799 rcItem.top = GetItemOffsetAbs(p_item); | |
| 800 rcItem.bottom = rcItem.top + GetItemContentHeightCached(p_item); | |
| 801 rcItem.left = 0; | |
| 802 rcItem.right = rcItem.left + GetItemWidth(); | |
| 803 return rcItem; | |
| 804 } | |
| 805 | |
| 806 bool CListControlImpl::GetGroupHeaderRectAbs2(size_t atItem,CRect & p_rect) const { | |
| 807 | |
| 808 if (m_groupHeaders.count(atItem) == 0) return false; | |
| 809 | |
| 810 p_rect.bottom = GetItemOffsetAbs(atItem); | |
| 811 p_rect.top = p_rect.bottom - GetGroupHeaderHeight(); | |
| 812 p_rect.left = 0; | |
| 813 p_rect.right = GetItemWidth(); | |
| 814 return true; | |
| 815 } | |
| 816 | |
| 817 CRect CListControlImpl::GetViewAreaRectAbs() const { | |
| 818 return CRect(0,0,GetViewAreaWidth(),GetViewAreaHeight()); | |
| 819 } | |
| 820 | |
| 821 CRect CListControlImpl::GetViewAreaRect() const { | |
| 822 CRect rc = GetViewAreaRectAbs(); | |
| 823 rc.OffsetRect( - GetViewOffset() ); | |
| 824 CRect ret; ret.IntersectRect(rc,GetClientRectHook()); | |
| 825 return ret; | |
| 826 } | |
| 827 | |
| 828 void CListControlImpl::UpdateGroupHeader2(size_t atItem) { | |
| 829 CRect rect; | |
| 830 if (GetGroupHeaderRect2(atItem,rect)) { | |
| 831 InvalidateRect(rect); | |
| 832 } | |
| 833 } | |
| 834 static void AddUpdateRect(HRGN p_rgn,CRect const & p_rect) { | |
| 835 CRgn temp; temp.CreateRectRgnIndirect(p_rect); | |
| 836 CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); | |
| 837 } | |
| 838 | |
| 839 void CListControlImpl::OnItemsReordered( const size_t * order, size_t count ) { | |
| 840 PFC_ASSERT(count == GetItemCount()); (void)count; | |
| 841 ReloadItems( pfc::bit_array_order_changed(order) ); | |
| 842 } | |
| 843 void CListControlImpl::UpdateItems(const pfc::bit_array & p_mask) { | |
| 844 t_size base,count; | |
| 845 if (GetItemRangeAbs(GetVisibleRectAbs(),base,count)) { | |
| 846 const t_size max = base+count; | |
| 847 CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); | |
| 848 bool found = false; | |
| 849 for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { | |
| 850 found = true; | |
| 851 AddUpdateRect(updateRgn,GetItemRect(walk)); | |
| 852 } | |
| 853 if (found) { | |
| 854 InvalidateRgn(updateRgn); | |
| 855 } | |
| 856 } | |
| 857 } | |
| 858 | |
| 859 std::pair<size_t, size_t> CListControlImpl::GetVisibleRange() const { | |
| 860 const size_t total = GetItemCount(); | |
| 861 CRect rcVisible = this->GetVisibleRectAbs(); | |
| 862 size_t lo = this->IndexFromPointAbs(rcVisible.top); | |
| 863 PFC_ASSERT(lo != SIZE_MAX); | |
| 864 if (lo == SIZE_MAX) lo = 0; // should not happen | |
| 865 size_t hi = this->IndexFromPointAbs(rcVisible.bottom); | |
| 866 if (hi < total) ++hi; | |
| 867 else hi = total; | |
| 868 return { lo, hi }; | |
| 869 } | |
| 870 | |
| 871 bool CListControlImpl::IsItemVisible(size_t which) const { | |
| 872 CRect rcVisible = this->GetVisibleRectAbs(); | |
| 873 CRect rcItem = this->GetItemRectAbs(which); | |
| 874 return rcItem.top >= rcVisible.top && rcItem.bottom <= rcVisible.bottom; | |
| 875 } | |
| 876 | |
| 877 void CListControlImpl::UpdateItemsAndHeaders(const pfc::bit_array & p_mask) { | |
| 878 t_size base,count; | |
| 879 groupID_t groupWalk = 0; | |
| 880 if (GetItemRangeAbsInclHeaders(GetVisibleRectAbs(),base,count)) { | |
| 881 const t_size max = base+count; | |
| 882 CRgn updateRgn; updateRgn.CreateRectRgn(0,0,0,0); | |
| 883 bool found = false; | |
| 884 for(t_size walk = p_mask.find_first(true,base,max); walk < max; walk = p_mask.find_next(true,walk,max)) { | |
| 885 found = true; | |
| 886 const groupID_t groupId = GetItemGroup(walk); | |
| 887 if (groupId != groupWalk) { | |
| 888 CRect rect; | |
| 889 if (GetGroupHeaderRect2(walk,rect)) { | |
| 890 AddUpdateRect(updateRgn,rect); | |
| 891 } | |
| 892 groupWalk = groupId; | |
| 893 } | |
| 894 AddUpdateRect(updateRgn,GetItemRect(walk)); | |
| 895 } | |
| 896 if (found) { | |
| 897 InvalidateRgn(updateRgn); | |
| 898 } | |
| 899 } | |
| 900 } | |
| 901 | |
| 902 | |
| 903 CRect CListControlImpl::GetValidViewOriginArea() const { | |
| 904 const CRect rcView = GetViewAreaRectAbs(); | |
| 905 const CRect rcClient = GetClientRectHook(); | |
| 906 CRect rcArea = rcView; | |
| 907 rcArea.right -= pfc::min_t(rcView.Width(),rcClient.Width()); | |
| 908 rcArea.bottom -= pfc::min_t(rcView.Height(),rcClient.Height()); | |
| 909 return rcArea; | |
| 910 } | |
| 911 | |
| 912 void CListControlImpl::OnViewAreaChanged(CPoint p_originOverride) { | |
| 913 const CPoint oldViewOrigin = m_viewOrigin; | |
| 914 | |
| 915 PrepLayoutCache(p_originOverride); | |
| 916 | |
| 917 m_viewOrigin = ClipPointToRect(p_originOverride,GetValidViewOriginArea()); | |
| 918 | |
| 919 if (m_viewOrigin != p_originOverride) { | |
| 920 // Did clip from the requested? | |
| 921 PrepLayoutCache(m_viewOrigin); | |
| 922 } | |
| 923 #if PrepLayoutCache_Debug | |
| 924 PFC_DEBUGLOG << "OnViewAreaChanged: m_viewOrigin=" << m_viewOrigin.x << "," << m_viewOrigin.y; | |
| 925 #endif | |
| 926 | |
| 927 RefreshSliders(); | |
| 928 | |
| 929 Invalidate(); | |
| 930 | |
| 931 if (oldViewOrigin != m_viewOrigin) { | |
| 932 OnViewOriginChange(m_viewOrigin - oldViewOrigin); | |
| 933 } | |
| 934 } | |
| 935 | |
| 936 size_t CListControlImpl::IndexFromPointAbs(CPoint pt) const { | |
| 937 if (pt.x < 0 || pt.x >= GetItemWidth()) return SIZE_MAX; | |
| 938 return IndexFromPointAbs(pt.y); | |
| 939 } | |
| 940 | |
| 941 size_t CListControlImpl::IndexFromPointAbs(int ptY) const { | |
| 942 const size_t count = GetItemCount(); | |
| 943 if (count == 0) return SIZE_MAX; | |
| 944 | |
| 945 class wrapper { | |
| 946 public: | |
| 947 wrapper(const CListControlImpl & o) : owner(o) {} | |
| 948 int operator[] (size_t idx) const { | |
| 949 // Return LAST line of this item | |
| 950 return owner.GetItemBottomOffsetAbs(idx)-1; | |
| 951 } | |
| 952 const CListControlImpl & owner; | |
| 953 }; | |
| 954 | |
| 955 wrapper w(*this); | |
| 956 size_t result = SIZE_MAX; | |
| 957 pfc::binarySearch<>::run(w, 0, count, ptY, result); | |
| 958 PFC_ASSERT(result != SIZE_MAX); | |
| 959 return result; | |
| 960 } | |
| 961 | |
| 962 bool CListControlImpl::ItemFromPointAbs(CPoint const & p_pt,t_size & p_item) const { | |
| 963 size_t idx = IndexFromPointAbs(p_pt); | |
| 964 if (idx >= GetItemCount()) return false; | |
| 965 CRect rc = this->GetItemRectAbs(idx); | |
| 966 if (!rc.PtInRect(p_pt)) return false; | |
| 967 p_item = idx; | |
| 968 return true; | |
| 969 } | |
| 970 | |
| 971 size_t CListControlImpl::ItemFromPointAbs(CPoint const& p_pt) const { | |
| 972 size_t ret = SIZE_MAX; | |
| 973 ItemFromPointAbs(p_pt, ret); | |
| 974 return ret; | |
| 975 } | |
| 976 | |
| 977 bool CListControlImpl::GroupHeaderFromPointAbs2(CPoint const & p_pt,size_t & atItem) const { | |
| 978 size_t idx = IndexFromPointAbs(p_pt); | |
| 979 if (idx == SIZE_MAX) return false; | |
| 980 CRect rc; | |
| 981 if (!this->GetGroupHeaderRectAbs2(idx, rc)) return false; | |
| 982 if (!rc.PtInRect(p_pt)) return false; | |
| 983 atItem = idx; | |
| 984 return true; | |
| 985 } | |
| 986 | |
| 987 void CListControlImpl::OnThemeChanged() { | |
| 988 m_themeCache.remove_all(); | |
| 989 } | |
| 990 | |
| 991 CTheme & CListControlImpl::themeFor(const char * what) { | |
| 992 bool bNew; | |
| 993 auto & ret = this->m_themeCache.find_or_add_ex( what, bNew ); | |
| 994 if (bNew) ret.OpenThemeData(*this, pfc::stringcvt::string_wide_from_utf8(what)); | |
| 995 return ret; | |
| 996 } | |
| 997 | |
| 998 void CListControlImpl::SetDarkMode(bool v) { | |
| 999 if (m_darkMode != v) { | |
| 1000 m_darkMode = v; | |
| 1001 RefreshDarkMode(); | |
| 1002 } | |
| 1003 } | |
| 1004 | |
| 1005 void CListControlImpl::RefreshDarkMode() { | |
| 1006 if (m_hWnd != NULL) { | |
| 1007 Invalidate(); | |
| 1008 | |
| 1009 // GOD DAMNIT: Should use ItemsView, but only Explorer fixes scrollbars | |
| 1010 DarkMode::ApplyDarkThemeCtrl(m_hWnd, m_darkMode, L"Explorer"); | |
| 1011 } | |
| 1012 } | |
| 1013 | |
| 1014 LRESULT CListControlImpl::OnCreatePassThru(UINT,WPARAM,LPARAM,BOOL& bHandled) { | |
| 1015 | |
| 1016 RefreshDarkMode(); | |
| 1017 | |
| 1018 | |
| 1019 OnViewAreaChanged(); | |
| 1020 | |
| 1021 if (m_gestureAPI.IsAvailable()) { | |
| 1022 GESTURECONFIG config = {GID_PAN, GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|GC_PAN_WITH_INERTIA, GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER}; | |
| 1023 m_gestureAPI.SetGestureConfig( *this, 0, 1, &config, sizeof(GESTURECONFIG)); | |
| 1024 } | |
| 1025 | |
| 1026 bHandled = FALSE; | |
| 1027 return 0; | |
| 1028 } | |
| 1029 bool CListControlImpl::IsSameItemOrHeaderAbs(const CPoint & p_point1, const CPoint & p_point2) const { | |
| 1030 t_size item1, item2; | |
| 1031 if (ItemFromPointAbs(p_point1, item1)) { | |
| 1032 if (ItemFromPointAbs(p_point2,item2)) { | |
| 1033 return item1 == item2; | |
| 1034 } else { | |
| 1035 return false; | |
| 1036 } | |
| 1037 } | |
| 1038 if (GroupHeaderFromPointAbs2(p_point1, item1)) { | |
| 1039 if (GroupHeaderFromPointAbs2(p_point2, item2)) { | |
| 1040 return item1 == item2; | |
| 1041 } else { | |
| 1042 return false; | |
| 1043 } | |
| 1044 } | |
| 1045 return false; | |
| 1046 } | |
| 1047 | |
| 1048 void CListControlImpl::OnSizeAsync_Trigger() { | |
| 1049 if (!m_sizeAsyncPending) { | |
| 1050 if (PostMessage(MSG_SIZE_ASYNC,0,0)) { | |
| 1051 m_sizeAsyncPending = true; | |
| 1052 } else { | |
| 1053 PFC_ASSERT(!"Shouldn't get here!"); | |
| 1054 //should not happen | |
| 1055 ListHandleResize(); | |
| 1056 } | |
| 1057 } | |
| 1058 } | |
| 1059 | |
| 1060 void CListControlImpl::ListHandleResize() { | |
| 1061 MoveViewOriginDelta(CPoint(0,0)); | |
| 1062 m_sizeAsyncPending = false; | |
| 1063 } | |
| 1064 | |
| 1065 void CListControlImpl::AddGroupHeaderToUpdateRgn2(HRGN p_rgn, size_t atItem) const { | |
| 1066 CRect rcHeader; | |
| 1067 if (GetGroupHeaderRect2(atItem,rcHeader)) AddUpdateRect(p_rgn,rcHeader); | |
| 1068 } | |
| 1069 void CListControlImpl::AddItemToUpdateRgn(HRGN p_rgn, t_size p_index) const { | |
| 1070 if (p_index < this->GetItemCount()) { | |
| 1071 AddUpdateRect(p_rgn,GetItemRect(p_index)); | |
| 1072 } | |
| 1073 } | |
| 1074 | |
| 1075 COLORREF CListControlImpl::GetSysColorHook(int colorIndex) const { | |
| 1076 if (m_darkMode) { | |
| 1077 return DarkMode::GetSysColor(colorIndex); | |
| 1078 } else { | |
| 1079 return GetSysColor(colorIndex); | |
| 1080 } | |
| 1081 } | |
| 1082 | |
| 1083 BOOL CListControlImpl::OnEraseBkgnd(CDCHandle dc) { | |
| 1084 | |
| 1085 if (paintInProgress()) return FALSE; | |
| 1086 | |
| 1087 CRect rcClient; WIN32_OP_D(GetClientRect(rcClient)); // SPECIAL CASE: No GetClientRectHook() here, fill physical client area, not logical | |
| 1088 PaintUtils::FillRectSimple(dc,rcClient,this->GetSysColorHook(COLOR_WINDOW)); | |
| 1089 | |
| 1090 return TRUE; | |
| 1091 } | |
| 1092 | |
| 1093 t_size CListControlImpl::InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const { | |
| 1094 bInside = false; | |
| 1095 int y_abs = pt.y + GetViewOffset().y; | |
| 1096 | |
| 1097 if (y_abs >= GetViewAreaHeight()) { | |
| 1098 return GetItemCount(); | |
| 1099 } | |
| 1100 size_t itemIdx = IndexFromPointAbs(y_abs); | |
| 1101 if (itemIdx == SIZE_MAX) return SIZE_MAX; | |
| 1102 | |
| 1103 { | |
| 1104 CRect rc; | |
| 1105 if (!this->GetGroupHeaderRectAbs2(itemIdx, rc)) { | |
| 1106 if (y_abs >= rc.top && y_abs <= rc.bottom) { | |
| 1107 bInside = false; | |
| 1108 return itemIdx; | |
| 1109 } | |
| 1110 } | |
| 1111 } | |
| 1112 if (itemIdx != SIZE_MAX) { | |
| 1113 const CRect rc = GetItemRectAbs(itemIdx); | |
| 1114 if (y_abs > rc.top + MulDiv(rc.Height(), 2, 3)) itemIdx++; | |
| 1115 else if (y_abs >= rc.top + MulDiv(rc.Height(), 1, 3)) bInside = true; | |
| 1116 return itemIdx; | |
| 1117 } | |
| 1118 return SIZE_MAX; | |
| 1119 } | |
| 1120 | |
| 1121 t_size CListControlImpl::InsertIndexFromPoint(const CPoint & pt) const { | |
| 1122 bool dummy; return InsertIndexFromPointEx(pt,dummy); | |
| 1123 } | |
| 1124 | |
| 1125 COLORREF CListControlImpl::BlendGridColor( COLORREF bk ) { | |
| 1126 return BlendGridColor( bk, PaintUtils::DetermineTextColor( bk ) ); | |
| 1127 } | |
| 1128 | |
| 1129 COLORREF CListControlImpl::BlendGridColor( COLORREF bk, COLORREF tx ) { | |
| 1130 return PaintUtils::BlendColor(bk, tx, 10); | |
| 1131 } | |
| 1132 | |
| 1133 COLORREF CListControlImpl::GridColor() { | |
| 1134 return BlendGridColor( GetSysColorHook(colorBackground), GetSysColorHook(colorText) ); | |
| 1135 } | |
| 1136 | |
| 1137 void CListControlImpl::RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,size_t p_item, uint32_t bkColor) { | |
| 1138 switch( this->m_rowStyle ) { | |
| 1139 case rowStylePlaylistDelimited: | |
| 1140 PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item,bkColor); | |
| 1141 { | |
| 1142 auto blend = BlendGridColor(bkColor); | |
| 1143 CDCPen pen(p_dc, blend); | |
| 1144 SelectObjectScope scope(p_dc, pen); | |
| 1145 | |
| 1146 p_dc.MoveTo( p_itemRect.right-1, p_itemRect.top ); | |
| 1147 p_dc.LineTo( p_itemRect.right-1, p_itemRect.bottom ); | |
| 1148 } | |
| 1149 break; | |
| 1150 case rowStylePlaylist: | |
| 1151 PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item,bkColor); | |
| 1152 break; | |
| 1153 case rowStyleGrid: | |
| 1154 PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); | |
| 1155 { | |
| 1156 auto blend = BlendGridColor(bkColor); | |
| 1157 CDCBrush brush(p_dc, blend); | |
| 1158 p_dc.FrameRect(&p_itemRect, brush); | |
| 1159 | |
| 1160 } | |
| 1161 break; | |
| 1162 case rowStyleFlat: | |
| 1163 PaintUtils::FillRectSimple(p_dc, p_itemRect, bkColor ); | |
| 1164 break; | |
| 1165 } | |
| 1166 } | |
| 1167 | |
| 1168 void CListControlImpl::RenderGroupHeaderBackground(CDCHandle p_dc,const CRect & p_headerRect,int p_group) { | |
| 1169 (void)p_group; | |
| 1170 const t_uint32 bkColor = GetSysColorHook(colorBackground); | |
| 1171 size_t pretendIndex = 0; | |
| 1172 switch( this->m_rowStyle ) { | |
| 1173 default: | |
| 1174 PaintUtils::FillRectSimple( p_dc, p_headerRect, bkColor ); | |
| 1175 break; | |
| 1176 case rowStylePlaylistDelimited: | |
| 1177 case rowStylePlaylist: | |
| 1178 PaintUtils::RenderItemBackground(p_dc,p_headerRect,pretendIndex,bkColor); | |
| 1179 break; | |
| 1180 } | |
| 1181 } | |
| 1182 | |
| 1183 void CListControlImpl::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) { | |
| 1184 this->RenderItemBackground(p_dc, p_itemRect, p_item, GetSysColorHook(colorBackground) ); | |
| 1185 | |
| 1186 DCStateScope backup(p_dc); | |
| 1187 p_dc.SetBkMode(TRANSPARENT); | |
| 1188 p_dc.SetBkColor(GetSysColorHook(colorBackground)); | |
| 1189 p_dc.SetTextColor(GetSysColorHook(colorText)); | |
| 1190 | |
| 1191 RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, true); | |
| 1192 } | |
| 1193 | |
| 1194 void CListControlImpl::RenderGroupHeader2(size_t baseItem,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) { | |
| 1195 this->RenderGroupHeaderBackground(p_dc, p_headerRect, 0 ); | |
| 1196 | |
| 1197 DCStateScope backup(p_dc); | |
| 1198 p_dc.SetBkMode(TRANSPARENT); | |
| 1199 p_dc.SetBkColor(GetSysColorHook(colorBackground)); | |
| 1200 p_dc.SetTextColor(GetSysColorHook(colorHighlight)); | |
| 1201 | |
| 1202 RenderGroupHeaderText2(baseItem,p_headerRect,p_updateRect,p_dc); | |
| 1203 } | |
| 1204 | |
| 1205 | |
| 1206 CListControlFontOps::CListControlFontOps() : m_font((HFONT)::GetStockObject(DEFAULT_GUI_FONT)), m_itemHeight(), m_groupHeaderHeight() { | |
| 1207 UpdateGroupHeaderFont(); | |
| 1208 CalculateHeights(); | |
| 1209 } | |
| 1210 | |
| 1211 void CListControlFontOps::UpdateGroupHeaderFont() { | |
| 1212 try { | |
| 1213 m_groupHeaderFont = NULL; | |
| 1214 LOGFONT lf = {}; | |
| 1215 WIN32_OP_D( m_font.GetLogFont(lf) ); | |
| 1216 lf.lfHeight = pfc::rint32( (double) lf.lfHeight * GroupHeaderFontScale() ); | |
| 1217 lf.lfWeight = GroupHeaderFontWeight(lf.lfWeight); | |
| 1218 WIN32_OP_D( m_groupHeaderFont.CreateFontIndirect(&lf) != NULL ); | |
| 1219 } catch(std::exception const & e) { | |
| 1220 (void) e; | |
| 1221 // console::print(e.what()); | |
| 1222 m_groupHeaderFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT); | |
| 1223 } | |
| 1224 } | |
| 1225 | |
| 1226 void CListControlFontOps::CalculateHeights() { | |
| 1227 const t_uint32 spacing = MulDiv(4, m_dpi.cy, 96); | |
| 1228 m_itemHeight = GetFontHeight( m_font ) + spacing; | |
| 1229 m_groupHeaderHeight = GetFontHeight( m_groupHeaderFont ) + spacing; | |
| 1230 } | |
| 1231 | |
| 1232 void CListControlFontOps::SetFont(HFONT font,bool bUpdateView) { | |
| 1233 m_font = font; | |
| 1234 UpdateGroupHeaderFont(); CalculateHeights(); | |
| 1235 OnSetFont(bUpdateView); | |
| 1236 if (bUpdateView && m_hWnd != NULL) OnViewAreaChanged(); | |
| 1237 | |
| 1238 } | |
| 1239 | |
| 1240 LRESULT CListControlFontOps::OnSetFont(UINT,WPARAM wp,LPARAM,BOOL&) { | |
| 1241 SetFont((HFONT)wp); | |
| 1242 return 0; | |
| 1243 } | |
| 1244 | |
| 1245 LRESULT CListControlFontOps::OnGetFont(UINT,WPARAM,LPARAM,BOOL&) { | |
| 1246 return (LRESULT)(HFONT)m_font; | |
| 1247 } | |
| 1248 | |
| 1249 LRESULT CListControlImpl::OnGetDlgCode(UINT, WPARAM wp, LPARAM) { | |
| 1250 switch(wp) { | |
| 1251 case VK_RETURN: | |
| 1252 return m_dlgWantEnter ? DLGC_WANTMESSAGE : 0; | |
| 1253 default: | |
| 1254 SetMsgHandled(FALSE); | |
| 1255 return 0; | |
| 1256 } | |
| 1257 } | |
| 1258 | |
| 1259 HWND CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID, CWindow lstReplace) { | |
| 1260 PFC_ASSERT(lstReplace != NULL); | |
| 1261 auto status = lstReplace.SendMessage(WM_GETDLGCODE, VK_RETURN); | |
| 1262 m_dlgWantEnter = (status & DLGC_WANTMESSAGE); | |
| 1263 CRect rc; | |
| 1264 CWindow wndPrev = wndDialog.GetNextDlgTabItem(lstReplace, TRUE); | |
| 1265 WIN32_OP_D(lstReplace.GetWindowRect(&rc)); | |
| 1266 WIN32_OP_D(wndDialog.ScreenToClient(rc)); | |
| 1267 WIN32_OP_D(lstReplace.DestroyWindow()); | |
| 1268 WIN32_OP_D(this->Create(wndDialog, &rc, 0, 0, WS_EX_STATICEDGE, replaceControlID)); | |
| 1269 if (wndPrev != NULL) this->SetWindowPos(wndPrev, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); | |
| 1270 // this->BringWindowToTop(); | |
| 1271 this->SetFont(wndDialog.GetFont()); | |
| 1272 return m_hWnd; | |
| 1273 } | |
| 1274 | |
| 1275 HWND CListControlImpl::CreateInDialog(CWindow wndDialog, UINT replaceControlID ) { | |
| 1276 return this->CreateInDialog(wndDialog, replaceControlID, wndDialog.GetDlgItem(replaceControlID)); | |
| 1277 } | |
| 1278 | |
| 1279 | |
| 1280 void CListControlImpl::defer(std::function<void() > f) { | |
| 1281 m_deferred.push_back( f ); | |
| 1282 if (!m_defferredMsgPending) { | |
| 1283 if ( PostMessage(MSG_EXEC_DEFERRED) ) m_defferredMsgPending = true; | |
| 1284 } | |
| 1285 } | |
| 1286 | |
| 1287 LRESULT CListControlImpl::OnExecDeferred(UINT, WPARAM, LPARAM) { | |
| 1288 | |
| 1289 for ( ;; ) { | |
| 1290 auto i = m_deferred.begin(); | |
| 1291 if ( i == m_deferred.end() ) break; | |
| 1292 auto op = std::move(*i); | |
| 1293 m_deferred.erase(i); // erase first, execute later - avoid erratic behavior if op alters the list | |
| 1294 op(); | |
| 1295 } | |
| 1296 | |
| 1297 m_defferredMsgPending = false; | |
| 1298 return 0; | |
| 1299 } | |
| 1300 | |
| 1301 // ======================================================================================== | |
| 1302 // Mouse wheel vs drag&drop hacks | |
| 1303 // Install MouseHookProc for the duration of DoDragDrop and handle the input from there | |
| 1304 // ======================================================================================== | |
| 1305 static HHOOK g_hook = NULL; | |
| 1306 static CListControlImpl * g_dragDropInstance = nullptr; | |
| 1307 LRESULT CALLBACK CListControlImpl::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) { | |
| 1308 if (nCode == HC_ACTION && g_dragDropInstance != nullptr) { | |
| 1309 switch (wParam) { | |
| 1310 case WM_MOUSEWHEEL: | |
| 1311 case WM_MOUSEHWHEEL: | |
| 1312 g_dragDropInstance->MouseWheelFromHook((UINT)wParam, lParam); | |
| 1313 break; | |
| 1314 } | |
| 1315 } | |
| 1316 return CallNextHookEx(g_hook, nCode, wParam, lParam); | |
| 1317 } | |
| 1318 | |
| 1319 bool CListControlImpl::MouseWheelFromHook(UINT msg, LPARAM data) { | |
| 1320 MOUSEHOOKSTRUCTEX const * mhs = reinterpret_cast<MOUSEHOOKSTRUCTEX const *> ( data ); | |
| 1321 if ( ::WindowFromPoint(mhs->pt) != m_hWnd ) return false; | |
| 1322 LRESULT dummyResult = 0; | |
| 1323 WPARAM wp = mhs->mouseData; | |
| 1324 LPARAM lp = MAKELPARAM( mhs->pt.x, mhs->pt.y ); | |
| 1325 // If we get here, m_suppressMouseWheel should be true per our DoDragDrop() | |
| 1326 pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, false); | |
| 1327 this->ProcessWindowMessage( m_hWnd, msg, wp, lp, dummyResult ); | |
| 1328 return true; | |
| 1329 } | |
| 1330 | |
| 1331 HRESULT CListControlImpl::DoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) { | |
| 1332 HRESULT ret = E_FAIL; | |
| 1333 // Should not get here with non null g_dragDropInstance - means we have a recursive call | |
| 1334 PFC_ASSERT(g_dragDropInstance == nullptr); | |
| 1335 if ( g_dragDropInstance == nullptr ) { | |
| 1336 // futureproofing: kill mouse wheel message processing if we get them delivered the regular way while this is in progress | |
| 1337 pfc::vartoggle_t<bool> scope(m_suppressMouseWheel, true); | |
| 1338 g_dragDropInstance = this; | |
| 1339 g_hook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, GetCurrentThreadId()); | |
| 1340 try { | |
| 1341 ret = ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect); | |
| 1342 } catch (...) { | |
| 1343 } | |
| 1344 g_dragDropInstance = nullptr; | |
| 1345 UnhookWindowsHookEx(pfc::replace_null_t(g_hook)); | |
| 1346 } | |
| 1347 return ret; | |
| 1348 } | |
| 1349 | |
| 1350 | |
| 1351 CPoint CListControlImpl::PointAbsToClient(CPoint pt) const { | |
| 1352 return pt - GetViewOffset(); | |
| 1353 } | |
| 1354 | |
| 1355 CPoint CListControlImpl::PointClientToAbs(CPoint pt) const { | |
| 1356 return pt + GetViewOffset(); | |
| 1357 } | |
| 1358 | |
| 1359 CRect CListControlImpl::RectAbsToClient(CRect rc) const { | |
| 1360 CRect ret; | |
| 1361 #if 1 | |
| 1362 ret = rc; | |
| 1363 ret.OffsetRect(-GetViewOffset()); | |
| 1364 #else | |
| 1365 ret.TopLeft() = PointAbsToClient(rc.TopLeft()); | |
| 1366 ret.BottomRight() = PointAbsToClient(rc.BottomRight()); | |
| 1367 #endif | |
| 1368 return ret; | |
| 1369 } | |
| 1370 | |
| 1371 CRect CListControlImpl::RectClientToAbs(CRect rc) const { | |
| 1372 CRect ret; | |
| 1373 #if 1 | |
| 1374 ret = rc; | |
| 1375 ret.OffsetRect(GetViewOffset()); | |
| 1376 #else | |
| 1377 ret.TopLeft() = PointClientToAbs(rc.TopLeft()); | |
| 1378 ret.BottomRight() = PointAbsToClient(rc.BottomRight()); | |
| 1379 #endif | |
| 1380 return ret; | |
| 1381 } | |
| 1382 | |
| 1383 size_t CListControlImpl::ItemFromPoint(CPoint const& pt) const { | |
| 1384 size_t ret = SIZE_MAX; | |
| 1385 if (!ItemFromPoint(pt, ret)) ret = SIZE_MAX; | |
| 1386 return ret; | |
| 1387 } | |
| 1388 | |
| 1389 bool CListControlImpl::ItemFromPoint(CPoint const & p_pt, t_size & p_item) const { | |
| 1390 return ItemFromPointAbs( PointClientToAbs( p_pt ), p_item); | |
| 1391 } | |
| 1392 | |
| 1393 void CListControlImpl::ReloadData() { | |
| 1394 this->m_varItemHeights.clear(); | |
| 1395 this->m_groupHeaders.clear(); | |
| 1396 OnViewAreaChanged(); | |
| 1397 } | |
| 1398 | |
| 1399 void CListControlImpl::ReloadItems(pfc::bit_array const & mask) { | |
| 1400 bool bReLayout = false; | |
| 1401 mask.for_each(true, 0, GetItemCount(), [this, &bReLayout] (size_t idx) { | |
| 1402 int hNew = this->GetItemHeight2(idx); | |
| 1403 int hOld = -1; | |
| 1404 { | |
| 1405 auto iter = m_varItemHeights.find(idx); | |
| 1406 if (iter != m_varItemHeights.end()) hOld = iter->second; | |
| 1407 } | |
| 1408 if (hNew != hOld) { | |
| 1409 m_varItemHeights[idx] = hNew; | |
| 1410 bReLayout = true; | |
| 1411 } | |
| 1412 }); | |
| 1413 if (bReLayout) { | |
| 1414 OnViewAreaChanged(); | |
| 1415 } else { | |
| 1416 UpdateItems(mask); | |
| 1417 } | |
| 1418 | |
| 1419 } | |
| 1420 | |
| 1421 void CListControlImpl::MinGroupHeight2Changed(size_t itemInGroup, bool reloadWhole) { | |
| 1422 size_t lo, hi; | |
| 1423 if (ResolveGroupRangeCached(itemInGroup, lo, hi)) { | |
| 1424 if (reloadWhole) { | |
| 1425 CRect rc; | |
| 1426 if (this->GetGroupOverlayRectAbs(itemInGroup, rc)) { | |
| 1427 this->InvalidateRect(this->RectAbsToClient(rc)); | |
| 1428 } | |
| 1429 { | |
| 1430 pfc::bit_array_range range(lo, hi-lo); | |
| 1431 this->ReloadItems(range); | |
| 1432 } | |
| 1433 } else { | |
| 1434 this->ReloadItem(hi - 1); | |
| 1435 } | |
| 1436 } | |
| 1437 } | |
| 1438 | |
| 1439 bool CListControlImpl::IsItemFirstInGroupCached(size_t item) const { | |
| 1440 return m_groupHeaders.count(item) > 0; | |
| 1441 } | |
| 1442 | |
| 1443 bool CListControlImpl::IsItemFirstInGroup(size_t item) const { | |
| 1444 if (item == 0) return true; | |
| 1445 return GetItemGroup(item) != GetItemGroup(item - 1); | |
| 1446 } | |
| 1447 bool CListControlImpl::IsItemLastInGroup(size_t item) const { | |
| 1448 size_t next = item + 1; | |
| 1449 if (next >= GetItemCount()) return true; | |
| 1450 return GetItemGroup(item) != GetItemGroup(next); | |
| 1451 } | |
| 1452 | |
| 1453 int CListControlImpl::GetItemHeight2(size_t which) const { | |
| 1454 if (!IsItemLastInGroup(which)) return -1; | |
| 1455 | |
| 1456 const int minGroupHeight = this->GetMinGroupHeight2(which); | |
| 1457 if (minGroupHeight <= 0) return -1; | |
| 1458 | |
| 1459 const int heightNormal = this->GetItemHeight(); | |
| 1460 | |
| 1461 const size_t base = FindGroupBase(which); | |
| 1462 | |
| 1463 const int groupHeightWithout = (which > base ? this->GetItemOffsetAbs2(base,which) : 0); | |
| 1464 | |
| 1465 const int minItemHeight = minGroupHeight - groupHeightWithout; // possibly negative | |
| 1466 | |
| 1467 if (minItemHeight > heightNormal) return minItemHeight; | |
| 1468 else return -1; // use normal | |
| 1469 } | |
| 1470 | |
| 1471 void CListControlImpl::wndSetDarkMode(CWindow wndListControl, bool bDark) { | |
| 1472 wndListControl.SendMessage(DarkMode::msgSetDarkMode(), bDark ? 1 : 0); | |
| 1473 } | |
| 1474 | |
| 1475 LRESULT CListControlImpl::OnSetDark(UINT, WPARAM wp, LPARAM) { | |
| 1476 switch (wp) { | |
| 1477 case 0: | |
| 1478 this->SetDarkMode(false); | |
| 1479 break; | |
| 1480 case 1: | |
| 1481 this->SetDarkMode(true); | |
| 1482 break; | |
| 1483 } | |
| 1484 return 1; | |
| 1485 } | |
| 1486 | |
| 1487 void CListControlImpl::OnItemRemoved(size_t which) { | |
| 1488 this->OnItemsRemoved(pfc::bit_array_one(which), GetItemCount() + 1); | |
| 1489 } | |
| 1490 | |
| 1491 | |
| 1492 UINT CListControlImpl::msgSetDarkMode() { | |
| 1493 return DarkMode::msgSetDarkMode(); | |
| 1494 } | |
| 1495 | |
| 1496 void CListControlImpl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { | |
| 1497 (void)nRepCnt; (void)nFlags; | |
| 1498 switch (nChar) { | |
| 1499 case VK_LEFT: | |
| 1500 MoveViewOriginDelta(CPoint(-MulDiv(16, m_dpi.cx, 96), 0)); | |
| 1501 break; | |
| 1502 case VK_RIGHT: | |
| 1503 MoveViewOriginDelta(CPoint(MulDiv(16, m_dpi.cx, 96), 0)); | |
| 1504 break; | |
| 1505 default: | |
| 1506 SetMsgHandled(FALSE); break; | |
| 1507 } | |
| 1508 } | |
| 1509 | |
| 1510 void CListControlImpl::OnItemsInserted(size_t at, size_t count, bool bSelect) { | |
| 1511 size_t newCount = this->GetItemCount(); | |
| 1512 this->OnItemsInsertedEx(pfc::bit_array_range(at, count, true), newCount - count, newCount, bSelect); | |
| 1513 } |
