scrollContainer.py (21747B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\scrollContainer.py 2 import logging 3 import mathext 4 import signals 5 import uthread2 6 from carbonui import TextAlign, uiconst, Align 7 from carbonui.control.baseScrollContEntry import LazyLoadVerticalMixin 8 from carbonui.control.scrollbar import Scrollbar 9 from carbonui.decorative.panelUnderlay import PanelUnderlay 10 from carbonui.primitives.container import Container 11 from carbonui.primitives.containerAutoSize import ContainerAutoSize 12 from carbonui.uianimations import animations 13 from carbonui.uiconst import Axis 14 from carbonui.uicore import uicore 15 from eve.client.script.ui.control.eveLabel import EveCaptionLarge 16 logger = logging.getLogger(__name__) 17 18 class ScrollContainer(Container): 19 __guid__ = 'uicls.ScrollContainer' 20 default_name = 'scrollContainer' 21 default_pushContent = True 22 default_state = uiconst.UI_NORMAL 23 default_showUnderlay = False 24 default_scrollBarColor = None 25 default_alignMode = None 26 default_scrollBarPadding = 0 27 default_innerPadding = None 28 default_centerContent = False 29 default_scrollBarDisabled = False 30 dragHoverScrollAreaSize = 30 31 dragHoverScrollSpeed = 60.0 32 isTabStop = True 33 _scrollbar_vertical = None 34 _scrollbar_horizontal = None 35 36 def ApplyAttributes(self, attributes): 37 super(ScrollContainer, self).ApplyAttributes(attributes) 38 pushContent = attributes.get('pushContent', self.default_pushContent) 39 showUnderlay = attributes.Get('showUnderlay', self.default_showUnderlay) 40 self.fixedColor = attributes.get('scrollBarColor', self.default_scrollBarColor) 41 alignMode = attributes.get('alignMode', self.default_alignMode) 42 self.scrollBarPadding = attributes.get('scrollBarPadding', self.default_scrollBarPadding) 43 self._innerPadding = attributes.get('innerPadding', self.default_innerPadding) 44 self._centerContent = attributes.get('centerContent', self.default_centerContent) 45 self.scrollbarsDisabled = self.default_scrollBarDisabled 46 self.scrollToVerticalPending = None 47 self.scrollToHorizontalPending = None 48 self.noResultsContainer = None 49 self.noResultsLabel = None 50 self.onDownButtonSignal = signals.Signal(__name__ + '_onDownButtonSignal') 51 self.onUpButtonSignal = signals.Signal(__name__ + '_onUpButtonSignal') 52 self.onDropDataSignal = signals.Signal(__name__ + '_onDropDataSignal') 53 self.onDragEnterSignal = signals.Signal(__name__ + '_onDragEnterSignal') 54 self.onDragExitSignal = signals.Signal(__name__ + '_onDragExitSignal') 55 self.onSizeChangeSignal = signals.Signal(__name__ + '_onSizeChangeSignal') 56 self._ConstructVerticalScrollBar(pushContent) 57 self._ConstructHorizontalScrollBar(pushContent) 58 self.clipCont = Container(name='clipCont', parent=self, clipChildren=True) 59 self.mainCont = ContainerAutoSize(name='mainCont', parent=self.clipCont, state=uiconst.UI_NORMAL, alignMode=alignMode, align=alignMode or ContainerAutoSize.default_align, padding=self._innerPadding) 60 self.mainCont._OnSizeChange_NoBlock = self._OnMainContSizeChange 61 self.mainCont.OnDropData = self.OnDropData 62 self.children.insert = self._InsertChild 63 self.children.append = self._AppendChild 64 self.children.remove = self._RemoveChild 65 self._mouseHoverCookie = uicore.uilib.RegisterForTriuiEvents(uiconst.UI_MOUSEHOVER, self.OnGlobalMouseHover) 66 self.CheckConstructUnderlay(showUnderlay) 67 68 def _update_children_alignment(self, sizeChange, forceUpdate): 69 super(ScrollContainer, self)._update_children_alignment(sizeChange, forceUpdate) 70 self._check_lazy_load_entries() 71 72 @property 73 def innerPadding(self): 74 return self._innerPadding 75 76 @innerPadding.setter 77 def innerPadding(self, value): 78 if self._innerPadding != value: 79 self._innerPadding = value 80 self.mainCont.padding = self._innerPadding if self._innerPadding is not None else 0 81 82 def _ConstructHorizontalScrollBar(self, pushContent): 83 self._scrollbar_horizontal = Scrollbar(parent=self, align=uiconst.TOBOTTOM, axis=Axis.HORIZONTAL, padTop=8 if pushContent else 0, on_scroll_fraction_changed=self._on_scrollbar_scroll_fraction_changed) 84 85 def _ConstructVerticalScrollBar(self, pushContent): 86 self._scrollbar_vertical = Scrollbar(parent=self, align=uiconst.TORIGHT, axis=Axis.VERTICAL, padLeft=8 if pushContent else 0, on_scroll_fraction_changed=self._on_scrollbar_scroll_fraction_changed) 87 88 def _on_scrollbar_scroll_fraction_changed(self, scrollbar): 89 if scrollbar.axis == Axis.VERTICAL: 90 self._OnVerticalScrollBar(scrollbar.scroll_fraction) 91 elif scrollbar.axis == Axis.HORIZONTAL: 92 self._OnHorizontalScrollBar(scrollbar.scroll_fraction) 93 94 def CheckConstructUnderlay(self, showUnderlay): 95 if showUnderlay: 96 self.underlay = PanelUnderlay(bgParent=self) 97 else: 98 self.underlay = None 99 100 def Close(self, *args): 101 try: 102 uicore.uilib.UnregisterForTriuiEvents(self._mouseHoverCookie) 103 except Exception as e: 104 logger.exception(e) 105 finally: 106 Container.Close(self, *args) 107 108 try: 109 del self.mainCont._OnSizeChange_NoBlock 110 except AttributeError: 111 pass 112 113 def _InsertChild(self, idx, obj): 114 self.mainCont.children.insert(idx, obj) 115 if not self.mainCont.alignMode: 116 self.mainCont.align = obj.align 117 118 def _AppendChild(self, obj): 119 self.mainCont.children.append(obj) 120 if not self.mainCont.alignMode: 121 self.mainCont.align = obj.align 122 123 def _RemoveChild(self, obj): 124 self.mainCont.children.remove(obj) 125 126 def OnGlobalMouseHover(self, obj, *args): 127 if uicore.IsDragging() and (obj == self or obj.IsUnder(self.mainCont)): 128 l, t, w, h = self.GetAbsolute() 129 if self.IsVerticalScrollBarVisible() and h > 0: 130 fraction = self.dragHoverScrollSpeed / float(h) 131 y = uicore.uilib.y - t 132 if y <= self.dragHoverScrollAreaSize: 133 self.ScrollMoveVertical(-fraction) 134 elif y > h - self.dragHoverScrollAreaSize: 135 self.ScrollMoveVertical(fraction) 136 if self.IsHorizontalScrollBarVisible() and w > 0: 137 fraction = self.dragHoverScrollSpeed / float(w) 138 x = uicore.uilib.x - l 139 if x <= self.dragHoverScrollAreaSize: 140 self.ScrollMoveHorizontal(-fraction) 141 elif x > w - self.dragHoverScrollAreaSize: 142 self.ScrollMoveHorizontal(fraction) 143 return True 144 145 def _OnSizeChange_NoBlock(self, width, height): 146 main_cont_width, main_cont_height = self.mainCont.GetCurrentAbsoluteSize() 147 self._retain_scroll_position_on_resize(main_cont_width, main_cont_height) 148 self._UpdateHandleSizesAndPosition(width, height, updatePos=False) 149 150 def _OnMainContSizeChange(self, width, height): 151 self._retain_scroll_position_on_resize(width, height) 152 self._UpdateScrollbars() 153 self.onSizeChangeSignal(width, height) 154 155 def _check_lazy_load_entries(self): 156 any_lazy_loaded = False 157 for entry in self.mainCont.children: 158 if isinstance(entry, LazyLoadVerticalMixin): 159 if entry.check_lazy_load(self.clipCont): 160 any_lazy_loaded = True 161 elif any_lazy_loaded: 162 return 163 164 def _retain_scroll_position_on_resize(self, width, height): 165 clip_width, clip_height = self.clipCont.GetAbsoluteSize() 166 scrollable_size_vertical = height - clip_height 167 if scrollable_size_vertical <= 0.0: 168 scroll_fraction = 0.0 169 else: 170 offset = 0 171 if self.mainCont.align in (uiconst.CENTER, uiconst.CENTERLEFT, uiconst.CENTERRIGHT): 172 offset = scrollable_size_vertical / 2.0 173 scroll_fraction = mathext.clamp(value=float(-(self.mainCont.top - offset)) / scrollable_size_vertical, low=0.0, high=1.0) 174 if mathext.is_close(scroll_fraction, self._scrollbar_vertical.scroll_fraction): 175 self._OnVerticalScrollBar(scroll_fraction) 176 else: 177 self._scrollbar_vertical.scroll_fraction = scroll_fraction 178 scrollable_size_horizontal = width - clip_width 179 if scrollable_size_horizontal <= 0.0: 180 scroll_fraction = 0.0 181 else: 182 offset = 0 183 if self.mainCont.align in (uiconst.CENTER, uiconst.CENTERTOP, uiconst.CENTERBOTTOM): 184 offset = scrollable_size_horizontal / 2.0 185 scroll_fraction = mathext.clamp(value=float(-(self.mainCont.left - offset)) / scrollable_size_horizontal, low=0.0, high=1.0) 186 if mathext.is_close(scroll_fraction, self._scrollbar_horizontal.scroll_fraction): 187 self._OnHorizontalScrollBar(scroll_fraction) 188 else: 189 self._scrollbar_horizontal.scroll_fraction = scroll_fraction 190 191 def OnDropData(self, dragSource, dragData): 192 self.onDropDataSignal(dragSource, dragData) 193 194 def OnDragEnter(self, dragSource, dragData): 195 self.onDragEnterSignal(dragSource, dragData) 196 197 def OnDragExit(self, dragSource, dragData): 198 self.onDragExitSignal(dragSource, dragData) 199 200 def Flush(self): 201 self.mainCont.Flush() 202 self.HideNoContentHint() 203 204 def DisableScrollbars(self): 205 self.scrollbarsDisabled = True 206 self._UpdateScrollbars() 207 208 def EnableScrollbars(self): 209 self.scrollbarsDisabled = False 210 self._UpdateScrollbars() 211 212 def _UpdateScrollbars(self): 213 w, h = self.GetAbsoluteSize() 214 self._UpdateHandleSizesAndPosition(w, h) 215 216 def _GetMainContWidth(self): 217 width, _ = self.mainCont.GetCurrentAbsoluteSize() 218 return width + self.mainCont.padLeft + self.mainCont.padRight 219 220 def _GetMainContHeight(self): 221 _, height = self.mainCont.GetCurrentAbsoluteSize() 222 return height + self.mainCont.padTop + self.mainCont.padBottom 223 224 def _UpdateHandleSizesAndPosition(self, width, height, updatePos = True): 225 mainWidth = self._GetMainContWidth() 226 mainHeight = self._GetMainContHeight() 227 if mainHeight > 0 and not self.scrollbarsDisabled: 228 size = float(height) / mainHeight 229 else: 230 size = 1.0 231 size = mathext.clamp(size, 0.0, 1.0) 232 self._scrollbar_vertical.handle_size_fraction = size 233 self._scrollbar_vertical.display = not mathext.is_close(size, 1.0) 234 if mainWidth != 0 and not self.scrollbarsDisabled: 235 size = float(width) / mainWidth 236 else: 237 size = 1.0 238 size = mathext.clamp(size, 0.0, 1.0) 239 self._scrollbar_horizontal.handle_size_fraction = size 240 self._scrollbar_horizontal.display = not mathext.is_close(size, 1.0) 241 if self.IsHorizontalScrollBarVisible() and self.IsVerticalScrollBarVisible(): 242 self._scrollbar_vertical.padBottom = self._scrollbar_horizontal.height 243 else: 244 self._scrollbar_vertical.padBottom = 0 245 246 def _OnVerticalScrollBar(self, posFraction): 247 w, h = self.clipCont.GetAbsoluteSize() 248 posFraction = max(0.0, min(posFraction, 1.0)) 249 mainHeight = self._GetMainContHeight() 250 offset = 0 251 if self.mainCont.align in (uiconst.CENTER, uiconst.CENTERLEFT, uiconst.CENTERRIGHT): 252 offset = (mainHeight - h) / 2.0 253 if mainHeight <= h: 254 if self._centerContent: 255 self.mainCont.top = int(round((h - mainHeight) / 2.0)) + offset 256 else: 257 self.mainCont.top = 0 258 else: 259 self.mainCont.top = -posFraction * (mainHeight - h) + offset 260 self._check_update_sticky_children() 261 self.OnScrolledVertical(posFraction) 262 263 def _check_update_sticky_children(self): 264 for c in self.mainCont.children: 265 if c.align == Align.TOTOP_STICKY: 266 c.FlagAlignmentDirty() 267 268 uthread2.start_tasklet(self._check_lazy_load_entries) 269 270 def _OnHorizontalScrollBar(self, posFraction): 271 w, h = self.clipCont.GetAbsoluteSize() 272 posFraction = max(0.0, min(posFraction, 1.0)) 273 mainWidth = self._GetMainContWidth() 274 offset = 0 275 if self.mainCont.align in (uiconst.CENTER, uiconst.CENTERTOP, uiconst.CENTERBOTTOM): 276 offset = (mainWidth - w) / 2.0 277 if mainWidth <= w: 278 if self._centerContent: 279 self.mainCont.left = int(round((w - mainWidth) / 2.0)) + offset 280 else: 281 self.mainCont.left = 0 282 else: 283 self.mainCont.left = -posFraction * (mainWidth - w) + offset 284 uthread2.start_tasklet(self._check_lazy_load_entries) 285 self.OnScrolledHorizontal(posFraction) 286 287 def OnScrolledHorizontal(self, posFraction): 288 pass 289 290 def OnScrolledVertical(self, posFraction): 291 pass 292 293 def ScrollToVertical(self, posFraction): 294 if posFraction is None: 295 return 296 if self._alignmentDirty: 297 self.scrollToVerticalPending = posFraction 298 elif self.IsVerticalScrollBarVisible(): 299 self._scrollbar_vertical.scroll_to_fraction(posFraction) 300 301 def ScrollToHorizontal(self, posFraction): 302 if posFraction is None: 303 return 304 if self._alignmentDirty: 305 self.scrollToHorizontalPending = posFraction 306 elif self.IsHorizontalScrollBarVisible(): 307 self._scrollbar_horizontal.scroll_to_fraction(posFraction) 308 309 def GetPositionVertical(self): 310 return self._scrollbar_vertical.scroll_fraction 311 312 def GetPositionHorizontal(self): 313 return self._scrollbar_horizontal.scroll_fraction 314 315 def UpdateAlignment(self, *args, **kwds): 316 ret = Container.UpdateAlignment(self, *args, **kwds) 317 if self.scrollToVerticalPending: 318 self._scrollbar_vertical.scroll_fraction = self.scrollToVerticalPending 319 self.scrollToVerticalPending = None 320 if self.scrollToHorizontalPending: 321 self._scrollbar_horizontal.scroll_fraction = self.scrollToHorizontalPending 322 self.scrollToHorizontalPending = None 323 return ret 324 325 def ScrollMoveVertical(self, moveFraction): 326 self._scrollbar_vertical.scroll_to_fraction(mathext.clamp(self._scrollbar_vertical.scroll_fraction + moveFraction, 0.0, 1.0)) 327 328 def ScrollMoveHorizontal(self, moveFraction): 329 self._scrollbar_horizontal.scroll_to_fraction(mathext.clamp(self._scrollbar_horizontal.scroll_fraction + moveFraction, 0.0, 1.0)) 330 331 def OnMouseWheel(self, dz, *args): 332 scroll_pixels = -dz * 0.42 333 wasScrolled = self._scroll_by_pixels(scroll_pixels) 334 return wasScrolled 335 336 def _scroll_by_pixels(self, scroll_pixels): 337 if len(self.mainCont.children) == 0: 338 return False 339 if self.IsVerticalScrollBarVisible(): 340 scrollable_height = self._get_scrollable_height() 341 scroll_proportion = scroll_pixels / float(scrollable_height) 342 self.ScrollMoveVertical(mathext.clamp(scroll_proportion, -1.0, 1.0)) 343 return True 344 if self.IsHorizontalScrollBarVisible(): 345 scrollable_width = self._get_scrollable_width() 346 scroll_proportion = scroll_pixels / float(scrollable_width) 347 self.ScrollMoveHorizontal(mathext.clamp(scroll_proportion, -1.0, 1.0)) 348 return True 349 return False 350 351 def _get_scrollable_height(self): 352 _, clip_height = self.clipCont.GetAbsoluteSize() 353 return max(0, self._GetMainContHeight() - clip_height) 354 355 def _get_scrollable_width(self): 356 clip_width, _ = self.clipCont.GetAbsoluteSize() 357 return max(0, self._GetMainContWidth() - clip_width) 358 359 def IsHorizontalScrollBarVisible(self): 360 return self._scrollbar_horizontal and self._scrollbar_horizontal.display 361 362 def IsVerticalScrollBarVisible(self): 363 return self._scrollbar_vertical and self._scrollbar_vertical.display 364 365 def OnKeyDown(self, key, flag): 366 if key == uiconst.VK_PRIOR: 367 self.ScrollByPage(up=True) 368 elif key == uiconst.VK_NEXT: 369 self.ScrollByPage(up=False) 370 371 def ScrollByPage(self, up = True): 372 if not self.IsVerticalScrollBarVisible(): 373 return 374 _, h = self.clipCont.GetAbsoluteSize() 375 if up: 376 h *= -1 377 self._scroll_by_pixels(h) 378 379 def ShowNoContentHint(self, hint): 380 if not hint: 381 if self.noResultsContainer: 382 self.noResultsContainer.Hide() 383 return 384 isNew = self.noResultsLabel is None or self.noResultsLabel.GetText() != hint 385 if self.noResultsContainer is None: 386 self.ConstructNoContentContainer() 387 self.noResultsLabel.SetText(hint) 388 self.noResultsContainer.SetState(uiconst.UI_DISABLED) 389 if isNew: 390 animations.FadeTo(self.noResultsContainer, 0.0, 1.0, duration=0.3) 391 392 def HideNoContentHint(self): 393 if self.noResultsContainer: 394 self.noResultsContainer.Close() 395 self.noResultsContainer = None 396 self.noResultsLabel = None 397 398 def ConstructNoContentContainer(self): 399 self.noResultsContainer = ContainerAutoSize(name='noResultsContainer', parent=self.clipCont, align=uiconst.VERTICALLY_CENTERED) 400 self.noResultsLabel = EveCaptionLarge(name='noResultsLabel', parent=self.noResultsContainer, align=uiconst.TOTOP, textAlign=TextAlign.CENTER, padding=16, opacity=0.3) 401 402 def OnUp(self): 403 self.onUpButtonSignal() 404 405 def OnDown(self): 406 self.onDownButtonSignal() 407 408 def ScrollToRevealChildHorizontal(self, child): 409 child_left = 0 410 for c in self.mainCont.children: 411 child_left += c.padLeft 412 if c is child: 413 break 414 w, _ = c.GetCurrentAbsoluteSize() 415 child_left += w + c.padRight 416 417 clip_width, _ = self.clipCont.GetCurrentAbsoluteSize() 418 overflow_width = self._GetMainContWidth() - clip_width 419 if overflow_width == 0: 420 return 421 child_width, _ = child.GetCurrentAbsoluteSize() 422 fraction_scroll_left = abs(self.mainCont.left) / float(overflow_width) 423 fraction_child_left = child_left / float(overflow_width) 424 fraction_child_bottom = max(0.0, (child_left + child_width - clip_width) / float(overflow_width)) 425 if fraction_child_left <= fraction_scroll_left: 426 self.ScrollToVertical(fraction_child_left) 427 elif fraction_child_bottom > fraction_scroll_left: 428 self.ScrollToVertical(fraction_child_bottom) 429 430 def ScrollToRevealChildVertical(self, child): 431 _, clip_height, overflow_height = self._GetClipHeights() 432 if overflow_height == 0: 433 return 434 child_top, child_bottom, scroll_top = self._GetChildPositionFractionsInsideScroll(child, clip_height, overflow_height) 435 if child_top <= scroll_top: 436 self.ScrollToVertical(child_top) 437 elif child_bottom > scroll_top: 438 self.ScrollToVertical(child_bottom) 439 440 def _GetChildPositionFractionsInsideScroll(self, child, clip_height, overflow_height): 441 child_top, child_bottom = self._GetChildPositionsInsideScroll(child) 442 fraction_child_top = child_top / float(overflow_height) 443 fraction_child_bottom = max(0.0, (child_bottom - clip_height) / float(overflow_height)) 444 fraction_scroll_top = abs(self.mainCont.top) / float(overflow_height) 445 return (fraction_child_top, fraction_child_bottom, fraction_scroll_top) 446 447 def _GetChildPositionsInsideScroll(self, child): 448 child_top = 0 449 for c in self.mainCont.children: 450 child_top += c.padTop 451 if c is child: 452 break 453 _, h = c.GetCurrentAbsoluteSize() 454 child_top += h + c.padBottom 455 456 _, child_height = child.GetCurrentAbsoluteSize() 457 child_bottom = child_top + child_height 458 return (child_top, child_bottom) 459 460 def _GetClipHeights(self): 461 main_height = self._GetMainContHeight() 462 _, clip_height = self.clipCont.GetCurrentAbsoluteSize() 463 overflow_height = main_height - clip_height 464 return (main_height, clip_height, overflow_height) 465 466 def IsChildVisible(self, child): 467 if not child.display: 468 return False 469 main_height, clip_height, overflow_height = self._GetClipHeights() 470 if main_height == 0: 471 return False 472 if overflow_height == 0: 473 return True 474 child_top, child_bottom = self._GetChildPositionsInsideScroll(child) 475 scroll_top = abs(self.mainCont.top) 476 scroll_bottom = scroll_top + clip_height 477 return child_top < scroll_bottom and child_bottom > scroll_top 478 479 def GetPositionVerticalPixels(self): 480 return self._scrollbar_vertical.scroll_fraction * self._get_scrollable_height() 481 482 def GetPositionHorizontalPixels(self): 483 return self._scrollbar_horizontal.scroll_fraction * self._get_scrollable_width() 484 485 def ScrollToVerticalPixels(self, position): 486 scrollable_height = self._get_scrollable_height() 487 if scrollable_height > 0: 488 fraction = mathext.clamp(position / float(scrollable_height), 0.0, 1.0) 489 self.ScrollToVertical(fraction) 490 491 def ScrollToHorizontalPixels(self, position): 492 scrollable_width = self._get_scrollable_width() 493 if scrollable_width > 0: 494 fraction = mathext.clamp(position / float(scrollable_width), 0.0, 1.0) 495 self.ScrollToHorizontal(fraction)