tab.py (21667B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\tab.py 2 import math 3 import telemetry 4 import trinity 5 import uthread 6 import uthread2 7 from carbon.client.script.environment.AudioUtil import PlaySound 8 from carbonui import uiconst, fontconst 9 from carbonui.primitives.container import Container 10 from carbonui.primitives.dragdrop import DragDropObject 11 from carbonui.primitives.fill import Fill 12 from carbonui.text.color import TextColor 13 from carbonui.uianimations import animations 14 from carbonui.uicore import uicore 15 from carbonui.util.various_unsorted import GetWindowAbove 16 from eve.client.script.ui import eveThemeColor 17 from eve.client.script.ui.control import eveLabel 18 from eve.client.script.ui.control.eveIcon import Icon 19 from eveexceptions import UserError, EatsExceptions 20 from logmodule import LogException 21 from carbonui.loggers.message_bus.tabMessenger import TabMessenger 22 OPACITY_DEFAULT = 1.0 23 OPACITY_DISABLED = 0.35 24 OPACITY_SELECTED = 1.0 25 26 class Tab(Container): 27 default_enabled = True 28 default_fontsize = fontconst.EVE_MEDIUM_FONTSIZE 29 default_fontStyle = None 30 default_fontFamily = None 31 default_fontPath = None 32 default_labelPadding = 0 33 selectedBG = None 34 35 @telemetry.ZONE_METHOD 36 def ApplyAttributes(self, attributes): 37 Container.ApplyAttributes(self, attributes) 38 self.enabled = attributes.get('enabled', self.default_enabled) 39 self.fontStyle = attributes.get('fontStyle', self.default_fontStyle) 40 self.fontFamily = attributes.get('fontFamily', self.default_fontFamily) 41 self.fontPath = attributes.get('fontPath', self.default_fontPath) 42 self.fontsize = attributes.get('fontsize', self.default_fontsize) 43 self.labelPadding = attributes.get('labelPadding', self.default_labelPadding) 44 self.tabID = None 45 self.selecting = 0 46 self.icon = None 47 self.headerIconCont = None 48 self.sr.LoadTabCallback = None 49 self.isTabStop = True 50 self._activity_indicator = None 51 self._hovered = False 52 self._gap_left = 0 53 self._gap_right = 0 54 self._detachallowed = False 55 self.ConstructLayout() 56 self.isTabStop = False 57 58 @property 59 def gap_left(self): 60 return self._gap_left 61 62 @gap_left.setter 63 def gap_left(self, value): 64 if self._gap_left != value: 65 self._gap_left = value 66 self._update_activity_indicator_padding() 67 68 @property 69 def gap_right(self): 70 return self._gap_right 71 72 @gap_right.setter 73 def gap_right(self, value): 74 if self._gap_right != value: 75 self._gap_right = value 76 self._update_activity_indicator_padding() 77 78 def _update_activity_indicator_padding(self): 79 self._activity_indicator.padding = self._get_activity_indicator_padding() 80 81 def _get_activity_indicator_padding(self): 82 pad_left = int(math.floor(max(self._gap_left - 1, 0) / 2.0)) 83 pad_right = int(math.ceil(max(self._gap_right - 1, 0) / 2.0)) 84 return (-pad_left, 85 0, 86 -pad_right, 87 0) 88 89 def GetHeight(self): 90 if self.icon: 91 return self.icon.height 92 return self.label.textheight 93 94 @telemetry.ZONE_METHOD 95 def ConstructLayout(self): 96 self._ConstructClipper() 97 self._ConstructLabel() 98 self._ConstructActivityIndicator() 99 self._ConstructUnderlay() 100 101 def _ConstructActivityIndicator(self): 102 self._activity_indicator = ActivityIndicator(parent=self, align=uiconst.TOALL, padding=self._get_activity_indicator_padding()) 103 104 def _ConstructClipper(self): 105 self.clipper = Container(name='labelClipper', parent=self, align=uiconst.TOALL, clipChildren=True, state=uiconst.UI_PICKCHILDREN) 106 107 def _ConstructLabel(self): 108 self.label = eveLabel.EveLabelMedium(parent=self.clipper, align=uiconst.CENTERLEFT, state=uiconst.UI_DISABLED, autoFadeSides=8, outputMode=uiconst.OUTPUT_COLOR_AND_GLOW, glowBrightness=0.0, offer_auxiliary_copy_option=False) 109 110 def _ConstructUnderlay(self): 111 pass 112 113 @telemetry.ZONE_METHOD 114 def Startup(self, tabgroup, data): 115 from carbonui.primitives.stretchspritehorizontal import StretchSpriteHorizontal 116 self.blinkDrop = StretchSpriteHorizontal(parent=self, align=uiconst.TOTOP_NOPUSH, color=(0.5, 0.5, 0.5, 0.0), name='blinkDrop', height=11, leftEdgeSize=6, rightEdgeSize=6, offset=-2, blendMode=trinity.TR2_SBM_ADD, texturePath='res:/UI/Texture/Lines/BLUR4.png') 117 self.sr.args = data.get('args', None) 118 self.tabID = self.sr.args 119 self.sr.grab = [0, 0] 120 self.tabgroup = tabgroup 121 self._selected = False 122 self.sr.panel = data.get('panel', None) 123 self.sr.panelparent = data.get('panelparent', None) 124 self.sr.code = data.get('code', None) 125 self.sr.LoadTabCallback = data.get('LoadTabCallback', None) 126 self.SetLabel(data.get('label', None)) 127 self.SetIcon(data.get('icon', None)) 128 self.Deselect(False) 129 self.hint = data.get('hint', None) 130 if hasattr(self.sr.code, 'GetTabMenu'): 131 self.GetMenu = lambda : self.sr.code.GetTabMenu(self) 132 if hasattr(self.sr.panel, 'sr'): 133 self.sr.panel.sr.tab = self 134 self.name = data.name or data.label 135 if not self.enabled: 136 self.Disable() 137 self.callback = data.callback 138 139 def GetPanel(self): 140 try: 141 return self.sr.panel 142 except AttributeError: 143 return None 144 145 def Confirm(self, *args): 146 self.OnClick() 147 148 def OnSetFocus(self, *args): 149 pass 150 151 def OnKillFocus(self, *etc): 152 pass 153 154 def SetLabel(self, label, hint = None): 155 if self.destroyed: 156 return 157 self.label.text = label 158 self.UpdateTabSize() 159 self.label.hint = hint 160 self.tabgroup.UpdateSizes() 161 162 def UpdateTabSize(self): 163 self.sr.width = self.label.left + self.label.width 164 165 def OnDropData(self, dragObj, nodes): 166 if not self.enabled: 167 return 168 if hasattr(self, 'OnTabDropData'): 169 if self.OnTabDropData(dragObj, nodes): 170 self.BlinkOnDrop() 171 elif isinstance(self.sr.panel, DragDropObject) and hasattr(self.sr.panel, 'OnDropData'): 172 if self.sr.panel.OnDropData(dragObj, nodes): 173 self.BlinkOnDrop() 174 175 def Blink(self, onoff = 1): 176 active = bool(onoff) 177 if active and not self.IsSelected(): 178 self._activity_indicator.mark_new(duration=5.0) 179 animations.MorphScalar(self.label, 'glowBrightness', startVal=0.0, endVal=1.0, curveType=uiconst.ANIM_WAVE, loops=3, duration=1.0) 180 else: 181 self._activity_indicator.activity_level = ActivityLevel.IDLE 182 self._update_label_color() 183 184 def BlinkOnDrop(self): 185 uicore.animations.FadeTo(self.blinkDrop, 0.0, 1.0, duration=0.25, curveType=uiconst.ANIM_WAVE, loops=2) 186 187 def SetUtilMenu(self, utilMenuFunc): 188 from eve.client.script.ui.control.utilMenu import UtilMenu 189 self.label.left = 14 190 utilMenuIcon = UtilMenu(menuAlign=uiconst.TOPLEFT, parent=self, align=uiconst.TOPLEFT, GetUtilMenu=utilMenuFunc, texturePath='res:/UI/Texture/Icons/73_16_50.png', pos=(uiconst.defaultPadding, 191 uiconst.defaultPadding, 192 14, 193 14)) 194 self.UpdateTabSize() 195 self.tabgroup.UpdateSizes() 196 return utilMenuIcon 197 198 def SetIcon(self, iconNo, shiftLabel = 14, hint = None, menufunc = None): 199 if self.icon: 200 self.icon.Close() 201 if self.headerIconCont: 202 self.headerIconCont.Close() 203 self.headerIconCont = None 204 self.sr.hint = hint 205 if iconNo is None: 206 if self.label: 207 self.label.left = 0 208 else: 209 self.headerIconCont = Container(name='headerIconCont', parent=self, pos=(2, 3, 16, 16), align=uiconst.RELATIVE, ignoreSize=True) 210 self.icon = Icon(icon=iconNo, parent=self.headerIconCont, pos=(0, 0, 16, 16), align=uiconst.TOPLEFT, idx=0, state=uiconst.UI_DISABLED) 211 if self.label: 212 self.label.left = shiftLabel 213 if menufunc: 214 self.icon.GetMenu = menufunc 215 self.icon.expandOnLeft = 1 216 self.icon.state = uiconst.UI_NORMAL 217 self.sr.hint = hint 218 self.UpdateTabSize() 219 self.tabgroup.UpdateSizes() 220 221 def OnClick(self, *args): 222 if not self.enabled: 223 return 224 if self.selecting: 225 return 226 self.tabgroup.state = uiconst.UI_DISABLED 227 PlaySound(uiconst.SOUND_BUTTON_CLICK) 228 try: 229 self.Select() 230 uicore.registry.SetFocus(self) 231 finally: 232 self.tabgroup.state = uiconst.UI_PICKCHILDREN 233 234 def IsSelected(self): 235 return self._selected 236 237 @telemetry.ZONE_METHOD 238 def Deselect(self, notify = True): 239 wasSelected = self._selected 240 self._selected = False 241 self.ShowDeselected_() 242 if self.sr.panel: 243 self.sr.panel.display = False 244 if notify: 245 if wasSelected and hasattr(self.sr.panel, 'OnSelectedTabDeselected'): 246 self.sr.panel.OnSelectedTabDeselected() 247 if hasattr(self.sr.panel, 'OnTabDeselect'): 248 self.sr.panel.OnTabDeselect() 249 elif hasattr(self.sr.panel, 'UnloadPanel'): 250 self.sr.panel.UnloadPanel() 251 if self.sr.panelparent: 252 self.sr.panelparent.display = False 253 if notify: 254 if wasSelected and hasattr(self.sr.panelparent, 'OnSelectedTabDeselected'): 255 self.sr.panelparent.OnSelectedTabDeselected() 256 if hasattr(self.sr.panelparent, 'OnTabDeselect'): 257 self.sr.panelparent.OnTabDeselect() 258 elif hasattr(self.sr.panelparent, 'UnloadPanel'): 259 self.sr.panelparent.UnloadPanel() 260 261 @telemetry.ZONE_METHOD 262 def ShowDeselected_(self): 263 if self.selectedBG: 264 self.selectedBG.Deselect() 265 self._update_label_color() 266 267 @telemetry.ZONE_METHOD 268 def ShowSelected_(self): 269 if self.selectedBG: 270 self.selectedBG.Select() 271 self._update_label_color() 272 273 @telemetry.ZONE_METHOD 274 def Select(self, silently = 0, useCallback = True, saveSelectedName = True): 275 if self.destroyed or self.selecting: 276 return 277 self.selecting = 1 278 self.Blink(0) 279 oldTabID = self.tabgroup.GetSelectedID() 280 if self is None or self.destroyed: 281 self.selecting = 0 282 self.tabgroup.state = uiconst.UI_PICKCHILDREN 283 return 284 if len(self.tabgroup.linkedrows): 285 for tabgroup in self.tabgroup.linkedrows: 286 if self in tabgroup.mytabs: 287 continue 288 tabgroup.SetOrder(0) 289 290 for each in self.tabgroup.sr.tabs: 291 if each.IsSelected(): 292 if hasattr(self.sr.code, 'UnloadTabPanel'): 293 self.sr.code.UnloadTabPanel(each.sr.args, each.sr.panel, each.tabgroup) 294 if each == self: 295 continue 296 notify = True 297 if each.sr.panel and each.sr.panel is self.sr.panel or each.sr.panelparent and each.sr.panelparent is self.sr.panelparent: 298 notify = False 299 each.Deselect(notify) 300 301 self._selected = True 302 self.tabgroup._set_selected_tab(self) 303 self.ShowSelected_() 304 if self.sr.panelparent: 305 self.sr.panelparent.display = True 306 if hasattr(self.sr.panelparent, 'OnTabSelect'): 307 self.sr.panelparent.OnTabSelect() 308 elif hasattr(self.sr.panelparent, 'LoadPanel'): 309 self.sr.panelparent.LoadPanel() 310 if self.sr.panel: 311 self.sr.panel.display = True 312 if hasattr(self.sr.panel, 'OnTabSelect'): 313 self.sr.panel.OnTabSelect() 314 elif hasattr(self.sr.panel, 'LoadPanel'): 315 self.sr.panel.LoadPanel() 316 if useCallback and self.tabgroup.callback: 317 self.tabgroup.callback(self.tabgroup.GetSelectedID(), oldTabID) 318 err = None 319 if self.sr.LoadTabCallback: 320 try: 321 self.sr.LoadTabCallback(self.sr.args, self.sr.panel, self.tabgroup) 322 finally: 323 self.selecting = 0 324 325 elif hasattr(self.sr.code, 'LoadTabPanel'): 326 try: 327 self.sr.code.LoadTabPanel(self.sr.args, self.sr.panel, self.tabgroup) 328 finally: 329 self.selecting = 0 330 331 elif getattr(self.sr.code, 'Load', None): 332 try: 333 self.sr.code.Load(self.sr.args) 334 except Exception as err: 335 LogException() 336 if self.destroyed: 337 return 338 wnd = GetWindowAbove(self) 339 if wnd and not wnd.destroyed: 340 wnd.HideLoad() 341 342 if not silently: 343 par = self.sr.panelparent or self.sr.panel 344 wnd = GetWindowAbove(self) 345 if par and wnd and wnd == uicore.registry.GetActive(): 346 uthread.new(uicore.registry.SetFocus, par) 347 if self.destroyed: 348 return 349 self.UpdateSelectedSettings(saveSelectedName) 350 if self and not self.destroyed: 351 self.tabgroup.UpdateSizes() 352 self.selecting = 0 353 if err and isinstance(err, UserError): 354 raise err 355 self._LogProtoEvent() 356 357 def UpdateSelectedSettings(self, saveSelectedName): 358 if self.tabgroup.settingsID: 359 self.SaveSelectedTabIndex() 360 if saveSelectedName: 361 self.SaveSelectedTabName() 362 363 def SaveSelectedTabIndex(self): 364 settings.user.tabgroups.Set(self.tabgroup.settingsID, self.tabgroup.sr.tabs.index(self)) 365 366 def SaveSelectedTabName(self): 367 settings.user.tabgroups.Set('%s_names' % self.tabgroup.settingsID, self.label.text if self.label else None) 368 369 def OnMouseDown(self, *args): 370 if not self.enabled: 371 return 372 self._detachallowed = 1 373 aL, aT, aW, aH = self.GetAbsolute() 374 self.sr.grab = [uicore.uilib.x - aL, uicore.uilib.y - aT] 375 if self.selectedBG: 376 self.selectedBG.OnMouseDown() 377 378 def OnMouseUp(self, *args): 379 if not self.enabled: 380 return 381 self._detachallowed = 0 382 if self.selectedBG: 383 self.selectedBG.OnMouseUp() 384 385 def OnMouseMove(self, *args): 386 if not self.enabled: 387 return 388 if self._detachallowed and uicore.uilib.mouseTravel > 24 and hasattr(self.sr.code, 'Detach'): 389 uthread.new(self.DoDetach) 390 391 def OnMouseEnter(self, *args): 392 if not self.enabled: 393 return 394 self._hovered = True 395 PlaySound(uiconst.SOUND_BUTTON_HOVER) 396 if self._selected: 397 return 398 if self.selectedBG: 399 self.selectedBG.OnMouseEnter() 400 self._update_label_color() 401 self._activity_indicator.mark_seen() 402 403 def OnMouseExit(self, *args): 404 if not self.enabled: 405 return 406 self._hovered = False 407 if self.selectedBG: 408 self.selectedBG.OnMouseExit() 409 self._update_label_color() 410 411 def DoDetach(self, *args): 412 if self is not None and not self.destroyed: 413 if self.sr.code.Detach(self.sr.panel, self.sr.grab): 414 if self is not None and not self.destroyed: 415 self.Close() 416 else: 417 self._detachallowed = 0 418 419 def GetHint(self): 420 if self.hint: 421 return self.hint 422 423 @EatsExceptions('protoClientLogs') 424 def _LogProtoEvent(self): 425 if self.tabgroup.analyticID: 426 message_bus = TabMessenger(sm.GetService('publicGatewaySvc')) 427 message_bus.tab_selected(self.analyticContext, self._GetAnalyticsTabID(), self.tabgroup.analyticID) 428 429 def _GetAnalyticsTabID(self): 430 tab_id = None 431 selected_id = self.tabgroup.GetSelectedID() 432 if isinstance(selected_id, tuple): 433 tab_id = selected_id[0] 434 elif isinstance(selected_id, str): 435 tab_id = selected_id 436 if not tab_id: 437 tab_id = self.tabgroup.GetSelectedIdx() 438 return str(tab_id) 439 440 def Enable(self, *args): 441 super(Tab, self).Enable(*args) 442 self.enabled = True 443 self.state = uiconst.UI_NORMAL 444 self.opacity = OPACITY_DEFAULT 445 446 def Disable(self, *args): 447 super(Tab, self).Disable(*args) 448 self.enabled = False 449 self.state = uiconst.UI_DISABLED 450 self.opacity = OPACITY_DISABLED 451 452 def _update_label_color(self): 453 if self._hovered: 454 duration = 0.1 455 else: 456 duration = 0.3 457 animations.SpColorMorphTo(self.label, endColor=self._get_label_color(), duration=duration) 458 459 def _get_label_color(self): 460 if self._selected or self._hovered: 461 return TextColor.HIGHLIGHT 462 elif self._activity_indicator.activity_level == ActivityLevel.NEW: 463 return TextColor.NORMAL 464 else: 465 return TextColor.SECONDARY 466 467 468 class ActivityLevel(object): 469 IDLE = 1 470 UNSEEN = 2 471 NEW = 3 472 473 474 class ActivityIndicator(Container): 475 UNDERLAY_OPACITY_ACTIVE = 0.3 476 INDICATOR_OPACITY_ACTIVE = 1.0 477 INDICATOR_WIDTH_NEW = 16 478 INDICATOR_WIDTH = 8 479 480 def __init__(self, parent = None, align = uiconst.TOALL, activity_level = ActivityLevel.IDLE, padding = 0): 481 self._activity_level = activity_level 482 self._underlay = None 483 self._indicator = None 484 self._mark_after_thread = None 485 super(ActivityIndicator, self).__init__(parent=parent, align=align, padding=padding) 486 self._update() 487 488 @property 489 def activity_level(self): 490 return self._activity_level 491 492 @activity_level.setter 493 def activity_level(self, value): 494 if self._activity_level != value: 495 previous_activity_level = self._activity_level 496 self._activity_level = value 497 self._update(previous_activity_level=previous_activity_level) 498 if self._mark_after_thread: 499 self._mark_after_thread.kill() 500 self._mark_after_thread = None 501 502 def mark_seen(self): 503 if self._activity_level == ActivityLevel.NEW: 504 self.activity_level = ActivityLevel.UNSEEN 505 506 def mark_new(self, duration = None): 507 self.activity_level = ActivityLevel.NEW 508 if duration is not None: 509 self._mark_after_thread = uthread2.StartTasklet(self._mark_after, ActivityLevel.UNSEEN, duration) 510 511 def _mark_after(self, activityLevel, duration): 512 uthread2.sleep(duration) 513 self.activity_level = activityLevel 514 515 def _update(self, previous_activity_level = None): 516 if self._activity_level in (ActivityLevel.IDLE, ActivityLevel.UNSEEN): 517 if self._underlay is not None: 518 if previous_activity_level is not None: 519 animations.FadeOut(self._underlay, duration=0.6) 520 else: 521 self._underlay.opacity = 0.0 522 elif self._activity_level == ActivityLevel.NEW: 523 if self._underlay is None: 524 self._create_underlay() 525 if previous_activity_level is not None: 526 animations.FadeIn(self._underlay, endVal=self.UNDERLAY_OPACITY_ACTIVE, duration=0.6) 527 else: 528 self._underlay.opacity = self.UNDERLAY_OPACITY_ACTIVE 529 if self._activity_level == ActivityLevel.IDLE: 530 if self._indicator is not None: 531 if previous_activity_level is not None: 532 animations.FadeOut(self._indicator, duration=0.3) 533 animations.MorphScalar(self._indicator, 'width', startVal=self._indicator.width, endVal=0, duration=0.6) 534 else: 535 self._indicator.opacity = 0.0 536 self._indicator.width = 0 537 elif self._activity_level in (ActivityLevel.UNSEEN, ActivityLevel.NEW): 538 if self._indicator is None: 539 self._create_indicator() 540 indicator_width = self._get_indicator_width() 541 if previous_activity_level is not None: 542 animations.FadeIn(self._indicator, duration=0.3) 543 animations.MorphScalar(self._indicator, 'width', startVal=self._indicator.width, endVal=indicator_width, duration=0.6) 544 else: 545 self._indicator.opacity = self.INDICATOR_OPACITY_ACTIVE 546 self._indicator.width = indicator_width 547 548 def _create_underlay(self): 549 if self._underlay is None: 550 self._underlay = Fill(parent=self, align=uiconst.TOALL, color=eveThemeColor.THEME_ALERT, opacity=0.0) 551 552 def _create_indicator(self): 553 if self._indicator is None: 554 self._indicator = Fill(parent=self, idx=0, align=uiconst.CENTERBOTTOM, width=self._get_indicator_width(), height=1, color=eveThemeColor.THEME_ALERT, outputMode=uiconst.OUTPUT_COLOR_AND_GLOW, opacity=0.0) 555 556 def _get_indicator_width(self): 557 if self.activity_level == ActivityLevel.NEW: 558 return self.INDICATOR_WIDTH_NEW 559 return self.INDICATOR_WIDTH 560 561 def OnColorThemeChanged(self): 562 if self._indicator is not None: 563 self._indicator.color = tuple(eveThemeColor.THEME_ALERT[:3]) + (self._indicator.opacity,) 564 if self._underlay is not None: 565 self._underlay.color = tuple(eveThemeColor.THEME_ALERT[:3]) + (self._underlay.opacity,)