button.py (34279B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\button.py 2 import logging 3 import math 4 import weakref 5 import eveformat 6 import eveicon 7 import signals 8 import telemetry 9 import threadutils 10 import trinity 11 import uthread 12 from carbon.client.script.environment.AudioUtil import PlaySound 13 from carbonui import uiconst, fontconst, PickState 14 from carbonui.button import styling 15 from carbonui.button.const import ButtonFrameType, ButtonStyle, ButtonVariant, HEIGHT_NORMAL, InteractionState 16 from carbonui.control.contextMenu.menuEntryData import MenuEntryData 17 from carbonui.loggers.buttonLogger import log_button_clicked 18 from carbonui.primitives.container import Container 19 from carbonui.primitives.fill import Fill 20 from carbonui.primitives.frame import Frame 21 from carbonui.primitives.gradientSprite import GradientSprite 22 from carbonui.primitives.sprite import Sprite 23 from carbonui.uianimations import animations 24 from carbonui.uiconst import Density 25 from carbonui.util.various_unsorted import GetBrowser 26 from eve.client.script.ui.control.eveLabel import EveLabelMedium 27 from eve.client.script.ui.menuUtil import DESTRUCTIVEGROUP, RAGEQUITGROUP 28 log = logging.getLogger(__name__) 29 OPACITY_LABEL_IDLE = 1.0 30 OPACITY_LABEL_HOVER = 1.0 31 OPACITY_LABEL_MOUSEDOWN = 1.2 32 33 class Button(Container): 34 default_analyticID = None 35 default_align = uiconst.RELATIVE 36 default_label = '' 37 default_func = None 38 default_args = None 39 default_fixedwidth = None 40 default_fixedheight = None 41 default_state = uiconst.UI_NORMAL 42 default_enabled = True 43 default_busy = False 44 default_iconSize = 16 45 default_texturePath = None 46 default_color = None 47 default_iconPadding = 0 48 default_variant = ButtonVariant.NORMAL 49 default_style = ButtonStyle.NORMAL 50 default_density = Density.NORMAL 51 default_frame_type = ButtonFrameType.CUT_BOTTOM_RIGHT 52 default_width = 100 53 default_height = HEIGHT_NORMAL 54 default_maxWidth = 300 55 default_sidePadding = 10 56 default_soundHover = uiconst.SOUND_BUTTON_HOVER 57 default_soundClick = uiconst.SOUND_BUTTON_CLICK 58 default_fontsize = fontconst.EVE_SMALL_FONTSIZE 59 default_fontStyle = None 60 default_fontFamily = None 61 default_fontPath = None 62 default_uppercase = False 63 default_bold = False 64 ICON_SIZE = 16 65 ICON_MARGIN = 4 66 BUSY_ANIMATION_DURATION = 2.0 67 ARROWS_TEXTURE = 'res:/UI/Texture/Classes/Industry/CenterBar/arrows.png' 68 ARROWS_TEXTURE_SEGMENT_COUNT = 6 69 ARROWS_TEXTURE_MASK = 'res:/UI/Texture/Classes/Industry/CenterBar/arrowMask.png' 70 __busy_context = None 71 __on_enabled_changed = None 72 __on_hovered_changed = None 73 __ready = False 74 _arrows = None 75 _icon = None 76 _label = None 77 78 def __init__(self, get_menu_entry_data_func = None, **kwargs): 79 self._get_menu_entry_data_func = get_menu_entry_data_func 80 self._interaction_state = set() 81 super(Button, self).__init__(**kwargs) 82 83 def ApplyAttributes(self, attributes): 84 self._busy = attributes.get('busy', self.default_busy) 85 self._density = attributes.get('density', self.default_density) 86 self._texturePath = attributes.get('texturePath', self.default_texturePath) 87 self._variant = attributes.get('variant', None) or self.default_variant 88 self._style = attributes.get('style', None) or self.default_style 89 self._frame_type = attributes.get('frame_type', None) or self.default_frame_type 90 self._label_text = self._sanitize_label(attributes.get('label', self.default_label)) 91 enabled = attributes.get('enabled', self.default_enabled) 92 if not enabled: 93 self._interaction_state.add(InteractionState.disabled) 94 args = attributes.get('args', None) 95 if args == 'self': 96 attributes.args = self 97 self.fixedwidth = attributes.get('fixedwidth', self.default_fixedwidth) 98 self.fixedheight = attributes.get('fixedheight', self.default_fixedheight) 99 self.func = attributes.get('func', self.default_func) 100 self.args = attributes.get('args', self.default_args) 101 self.color = attributes.get('color', self.default_color) 102 self.fontStyle = attributes.get('fontStyle', self.default_fontStyle) 103 self.fontFamily = attributes.get('fontFamily', self.default_fontFamily) 104 self.fontPath = attributes.get('fontPath', self.default_fontPath) 105 self.fontsize = attributes.get('fontsize', self.default_fontsize) 106 self.soundClick = attributes.get('soundClick', self.default_soundClick) 107 self.soundHover = attributes.get('soundHover', self.default_soundHover) 108 self.minwidth = attributes.get('minwidth', self._GetDefaultMinWidth()) 109 self.maxwidth = attributes.get('maxwidth', self.default_maxWidth) 110 self.sidePadding = attributes.get('sidePadding', self.default_sidePadding) 111 self.analyticID = attributes.get('analyticID', self.default_analyticID) 112 self.bold = attributes.get('bold', self.default_bold) 113 self.color = attributes.get('color', self.default_color) 114 self.iconPadding = attributes.get('iconPadding', self.default_iconPadding) 115 self.iconSize = attributes.get('iconSize', self.default_iconSize) 116 self.uppercase = attributes.get('uppercase', self.default_uppercase) 117 attributes.pop('fontFamily', None) 118 attributes.fontsize = styling.get_fontsize(self.density) 119 super(Button, self).ApplyAttributes(attributes) 120 if self.default_fontsize is None: 121 self.default_fontsize = fontconst.DEFAULT_FONTSIZE 122 self.isTabStop = 1 123 self.resetTop = None 124 self.blinking = 0 125 for each in ('btn_modalresult', 'btn_default', 'btn_cancel'): 126 if each in attributes: 127 attr = attributes.get(each, None) 128 setattr(self, each, attr) 129 130 self.Prepare_() 131 self.__ready = True 132 self._update_busy(animate=False) 133 self.Update_Size_() 134 135 @property 136 def density(self): 137 return self._density 138 139 @density.setter 140 def density(self, value): 141 if self._density != value: 142 self._density = value 143 self.Update_Size_() 144 145 @property 146 def frame_type(self): 147 return self._frame_type 148 149 @frame_type.setter 150 def frame_type(self, value): 151 if self._frame_type != value: 152 self._frame_type = value 153 self._update_frame_type() 154 155 @property 156 def hovered(self): 157 return InteractionState.hovered in self._interaction_state 158 159 @property 160 def on_hovered_changed(self): 161 if self.__on_hovered_changed is None: 162 self.__on_hovered_changed = signals.Signal('{}.on_hovered_changed'.format(self.__class__.__name__)) 163 return self.__on_hovered_changed 164 165 @property 166 def variant(self): 167 return self._variant 168 169 @variant.setter 170 def variant(self, value): 171 if self._variant != value: 172 self._variant = value 173 self._OnVariantChanged() 174 175 @property 176 def style(self): 177 return self._style 178 179 @style.setter 180 def style(self, value): 181 if self._style != value: 182 self._style = value 183 self._OnStyleChanged() 184 185 @property 186 def icon(self): 187 return self.texturePath 188 189 @icon.setter 190 def icon(self, value): 191 self.texturePath = value 192 193 @property 194 def label(self): 195 return self._label_text 196 197 @label.setter 198 def label(self, value): 199 if self.destroyed: 200 return 201 value = self._sanitize_label(value) 202 if self._label_text != value: 203 self._label_text = value 204 if value: 205 if self._label is None: 206 self.PrepareLabel() 207 self._label.text = value 208 elif self._label is not None: 209 self._label.Close() 210 self._label = None 211 self.sr.label = None 212 self.Update_Size_() 213 214 @staticmethod 215 def _sanitize_label(label): 216 if label is not None: 217 stripped_label = eveformat.strip_tags(label, tags=['center']) 218 if stripped_label != label: 219 log.warning("Attempting to assign a button label containing a 'center' tag.") 220 return stripped_label 221 return label 222 223 @property 224 def text(self): 225 return self.label 226 227 @property 228 def texturePath(self): 229 return self._texturePath 230 231 @texturePath.setter 232 def texturePath(self, value): 233 if value != self._texturePath: 234 self._texturePath = value 235 self._UpdateIcon() 236 237 @property 238 def enabled(self): 239 return InteractionState.disabled not in self._interaction_state 240 241 @enabled.setter 242 def enabled(self, value): 243 if value and InteractionState.disabled in self._interaction_state: 244 self._discard_state(InteractionState.disabled) 245 self._emit_on_enabled_changed() 246 elif not value and InteractionState.disabled not in self._interaction_state: 247 self._add_state(InteractionState.disabled) 248 self._emit_on_enabled_changed() 249 250 @property 251 def disabled(self): 252 return InteractionState.disabled in self._interaction_state 253 254 @disabled.setter 255 def disabled(self, value): 256 if value and InteractionState.disabled not in self._interaction_state: 257 self._add_state(InteractionState.disabled) 258 self._emit_on_enabled_changed() 259 elif not value and InteractionState.disabled in self._interaction_state: 260 self._discard_state(InteractionState.disabled) 261 self._emit_on_enabled_changed() 262 263 @property 264 def on_enabled_changed(self): 265 if self.__on_enabled_changed is None: 266 self.__on_enabled_changed = signals.Signal() 267 return self.__on_enabled_changed 268 269 def _emit_on_enabled_changed(self): 270 if self.__on_enabled_changed is not None: 271 self.__on_enabled_changed(self) 272 273 @property 274 def pressed(self): 275 return InteractionState.pressed in self._interaction_state 276 277 @property 278 def busy(self): 279 return self._busy 280 281 @busy.setter 282 def busy(self, value): 283 if value != self._busy: 284 self._busy = value 285 self._update_busy() 286 287 @property 288 def busy_context(self): 289 if self.__busy_context is None: 290 self.__busy_context = BusyContext(self) 291 return self.__busy_context 292 293 def enable(self, animate = True): 294 if not self.enabled: 295 self._discard_state(InteractionState.disabled, animate) 296 297 def disable(self, animate = True): 298 if self.enabled: 299 self._add_state(InteractionState.disabled, animate) 300 301 def get_menu_entry_data(self): 302 if self._get_menu_entry_data_func is not None: 303 return self._get_menu_entry_data_func() 304 else: 305 return ButtonActionMenuEntryData(self) 306 307 def press(self, play_audio = True): 308 self._execute(play_audio=play_audio) 309 310 @telemetry.ZONE_METHOD 311 def Prepare_(self): 312 self.PrepareLabel() 313 self.PrepareIcon() 314 self.PrepareHilite() 315 self.PrepareActiveFrame() 316 self.PrepareUnderlay() 317 318 def PrepareLabel(self): 319 self._label = EveLabelMedium(name='label', parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, color=self._get_label_color(), shadowSpriteEffect=trinity.TR2_SFX_GLOW, text=self._label_text, idx=0) 320 self.sr.label = self._label 321 322 def PrepareIcon(self): 323 if self.texturePath is None: 324 return 325 icon_size = self._get_icon_size() 326 self._icon = Sprite(name='icon', parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=icon_size, height=icon_size, texturePath=self.texturePath, spriteEffect=self._get_icon_sprite_effect(), color=self._get_icon_color(), idx=0) 327 328 def _UpdateIcon(self): 329 if self._texturePath is None: 330 if self._icon is not None: 331 self._icon.Close() 332 self._icon = None 333 elif self._icon is not None: 334 self._icon.SetTexturePath(self._texturePath) 335 self._icon.spriteEffect = self._get_icon_sprite_effect() 336 else: 337 self.PrepareIcon() 338 self.Update_Size_() 339 340 def OnColorThemeChanged(self): 341 super(Button, self).OnColorThemeChanged() 342 self._OnStateChanged() 343 344 def _OnVariantChanged(self): 345 self.underlay.variant = self.variant 346 self._OnStateChanged() 347 348 def _OnStyleChanged(self): 349 self.underlay.style = self.style 350 self._OnStateChanged() 351 352 def _OnStateChanged(self, old_state = None, animate = True): 353 if self.destroyed: 354 return 355 if old_state is None: 356 old_state = self._interaction_state 357 self.underlay.set_interaction_state(self._interaction_state, animate) 358 self._update_label_color(animate) 359 self._update_icon_color(animate) 360 self._update_arrows_color(animate) 361 362 def _update_frame_type(self): 363 if self.underlay: 364 self.underlay.frame_type = self._frame_type 365 366 def _get_label_color(self): 367 return styling.get_foreground_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 368 369 def _update_label_color(self, animate = True): 370 if not self.sr.label: 371 return 372 if animate: 373 duration = 0.1 if self.pressed or self.hovered else 0.3 374 animations.SpColorMorphTo(self.sr.label, endColor=self._get_label_color(), duration=duration) 375 else: 376 self.sr.label.color = self._get_label_color() 377 378 def _get_icon_color(self): 379 return styling.get_icon_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 380 381 def _update_icon_color(self, animate = True): 382 if not self._icon: 383 return 384 if animate: 385 duration = 0.1 if self.pressed or self.hovered else 0.3 386 animations.SpColorMorphTo(self._icon, endColor=self._get_icon_color(), duration=duration) 387 else: 388 self._icon.color = self._get_icon_color() 389 390 def _get_icon_size(self): 391 if eveicon.is_icon_in_library(self._texturePath): 392 return self.ICON_SIZE 393 else: 394 return min(self.iconSize, styling.get_height(self.density) - self.ICON_MARGIN * 2) 395 396 def _get_icon_sprite_effect(self): 397 if eveicon.is_icon_in_library(self._texturePath): 398 return trinity.TR2_SFX_COPY 399 else: 400 return trinity.TR2_SFX_COLOROVERLAY 401 402 def _get_arrows_color(self): 403 color = styling.get_stroke_color(self._variant, self._style, self._interaction_state) 404 if not self.busy: 405 color = color[:3] + (0.0,) 406 return color 407 408 def _update_arrows_color(self, animate = True): 409 if not self._arrows: 410 return 411 if animate: 412 duration = 0.1 if self.pressed or self.hovered else 0.3 413 animations.SpColorMorphTo(self._arrows, endColor=self._get_arrows_color(), duration=duration) 414 else: 415 self._arrows.color = self._get_arrows_color() 416 417 def PrepareHilite(self): 418 self.sr.hilite = Fill(name='hilite', bgParent=self, color=(0.7, 0.7, 0.7, 0.5), state=uiconst.UI_HIDDEN) 419 420 def PrepareActiveFrame(self): 421 pass 422 423 def PrepareUnderlay(self): 424 self.underlay = ButtonUnderlay(name='underlay', parent=self, variant=self._variant, style=self._style, frame_type=self._frame_type, interaction_state=self._interaction_state) 425 426 def PrepareBusyArrows(self): 427 if self._arrows is None: 428 self._arrows = Sprite(name='arrows', bgParent=self, texturePath=self.ARROWS_TEXTURE_MASK, textureSecondaryPath=self.ARROWS_TEXTURE, spriteEffect=trinity.TR2_SFX_MODULATE, color=self._get_arrows_color(), idx=0) 429 self._arrows.tileXSecondary = True 430 x_translation = 1.0 / float(self.ARROWS_TEXTURE_SEGMENT_COUNT) 431 self._arrows.translationSecondary = (-x_translation, 0) 432 433 def _UpdateContentPosition(self): 434 if self._label is not None and self.text and self._icon is not None: 435 icon_offset = styling.get_icon_offset(self.density) 436 icon_size = self._get_icon_size() 437 gap_size = styling.get_icon_gap(self.density) 438 half_icon_size = int(round(icon_size / 2.0)) 439 half_icon_offset = int(round(icon_offset / 2.0)) 440 half_gap_size = int(round(gap_size / 2.0)) 441 half_text_width = int(round(self._label.textwidth / 2.0)) 442 self._label.left = half_icon_size + half_gap_size - half_icon_offset 443 self._icon.left = -half_text_width - half_gap_size - half_icon_offset 444 elif self._label is not None and self._label.text: 445 self._label.left = 0 446 elif self._icon is not None: 447 self._icon.left = 0 448 449 @telemetry.ZONE_METHOD 450 def Update_Size_(self): 451 if not self.__ready: 452 return 453 self._UpdateContentPosition() 454 self.width = self.get_intrinsic_width() 455 self.height = self.get_intrinsic_height() 456 457 def get_intrinsic_height(self): 458 height = styling.get_height(self.density) 459 if self.align in uiconst.ALIGNMENTS_WITH_INCLUDED_VERTICAL_PADDING: 460 height += self.padTop + self.padBottom 461 return height 462 463 def get_intrinsic_width(self): 464 width = styling.get_side_padding(self.density) * 2 465 if self._icon is not None and self._label is not None and self.text: 466 icon_offset = styling.get_icon_offset(self.density) 467 gap_size = styling.get_icon_gap(self.density) 468 text_width = self._label.textwidth 469 width += self._get_icon_size() + gap_size + text_width - icon_offset 470 elif self._icon is not None: 471 width = styling.get_height(self.density) 472 elif self._label is not None: 473 width += self._label.textwidth 474 if self.align in uiconst.ALIGNMENTS_WITH_INCLUDED_HORIZONTAL_PADDING: 475 width += self.padLeft + self.padRight 476 return max(styling.get_min_width(self.density), width) 477 478 def SetLabel(self, label): 479 self.label = label 480 481 def SetLabel_(self, label): 482 self.label = label 483 484 def OnSetFocus(self, *args): 485 self._add_state(InteractionState.focused) 486 if self.disabled: 487 return 488 if self and not self.destroyed and self.parent and self.parent.name == 'inlines': 489 if self.parent.parent and self.parent.parent.sr.node: 490 browser = GetBrowser(self) 491 if browser: 492 uthread.new(browser.ShowObject, self) 493 494 def OnKillFocus(self, *etc): 495 self._discard_state(InteractionState.focused) 496 497 def OnMouseEnter(self, *args): 498 if InteractionState.hovered not in self._interaction_state: 499 self._add_state(InteractionState.hovered) 500 if self.__on_hovered_changed is not None: 501 self.__on_hovered_changed(self) 502 self.Blink(False) 503 if not self.disabled: 504 self.underlay.OnMouseEnter() 505 PlaySound(self.soundHover) 506 507 def OnMouseExit(self, *args): 508 if InteractionState.hovered in self._interaction_state: 509 self._discard_state(InteractionState.hovered) 510 if self.__on_hovered_changed is not None: 511 self.__on_hovered_changed(self) 512 self.underlay.OnMouseExit() 513 514 def OnMouseDown(self, button, *args): 515 if button == uiconst.MOUSELEFT: 516 self._add_state(InteractionState.pressed) 517 518 def OnMouseUp(self, button, *args): 519 if button == uiconst.MOUSELEFT: 520 self._discard_state(InteractionState.pressed) 521 522 def Confirm(self, *args): 523 if self.IsVisible(): 524 uthread.new(self.OnClick, self) 525 526 @telemetry.ZONE_METHOD 527 def SetColor(self, color): 528 pass 529 530 @telemetry.ZONE_METHOD 531 def SetLabelColor(self, color): 532 pass 533 534 @telemetry.ZONE_METHOD 535 def SetActiveFrameColor(self, color): 536 pass 537 538 def Enable(self): 539 self.enabled = True 540 541 def Disable(self): 542 self.enabled = False 543 544 def Blink(self, on_off = 1, blinks = 1000, time = 800): 545 self.blinking = on_off 546 if on_off: 547 self.underlay.Blink(blinks) 548 else: 549 self.underlay.StopBlink() 550 551 def GetCursor(self): 552 if self.disabled: 553 return uiconst.UICURSOR_DEFAULT 554 555 def _add_state(self, state, animate = True): 556 old_state = frozenset(self._interaction_state) 557 self._interaction_state.add(state) 558 self._OnStateChanged(old_state, animate) 559 560 def _discard_state(self, state, animate = True): 561 old_state = frozenset(self._interaction_state) 562 self._interaction_state.discard(state) 563 self._OnStateChanged(old_state, animate) 564 565 def _loop_arrow_animation(self): 566 if not self._busy and not self.destroyed: 567 return 568 x_translation = 1.0 / float(self.ARROWS_TEXTURE_SEGMENT_COUNT) 569 animations.MorphVector2(self._arrows, 'translationSecondary', (x_translation, 0.0), (-x_translation, 0.0), duration=self.BUSY_ANIMATION_DURATION, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT) 570 571 @threadutils.threaded 572 def _start_arrow_animation(self): 573 if self.destroyed: 574 return 575 self._animate_reset_arrow() 576 if self._busy: 577 self._loop_arrow_animation() 578 579 @threadutils.threaded 580 def _stop_arrow_animation(self): 581 if self.destroyed: 582 return 583 self._animate_reset_arrow() 584 if not self._busy: 585 animations.StopAnimation(self._arrows, 'translationSecondary') 586 587 def _animate_reset_arrow(self): 588 x_translation = 1.0 / float(self.ARROWS_TEXTURE_SEGMENT_COUNT) 589 diff = math.fabs(-x_translation - self._arrows.translationSecondary[0]) 590 if diff > 0.001: 591 duration = diff / x_translation / 2.0 * self.BUSY_ANIMATION_DURATION 592 animations.MorphVector2(self._arrows, 'translationSecondary', self._arrows.translationSecondary, (-x_translation, 0.0), duration=duration, curveType=uiconst.ANIM_LINEAR, sleep=True) 593 594 def _update_busy(self, animate = True): 595 if self.destroyed: 596 return 597 if self._busy: 598 self.PrepareBusyArrows() 599 self._start_arrow_animation() 600 elif self._arrows is not None: 601 self._stop_arrow_animation() 602 self._OnStateChanged(animate=animate) 603 604 def IsEnabled(self): 605 return not self.disabled 606 607 def SetFunc(self, func): 608 self.func = func 609 610 def OnClick(self, *args): 611 if self: 612 self._execute() 613 614 def _execute(self, play_audio = True): 615 if not self.destroyed and self.enabled and self.func: 616 if play_audio: 617 PlaySound(self.soundClick) 618 log_button_clicked(self) 619 self._CallFunction() 620 621 def _CallFunction(self): 622 if type(self.args) == tuple: 623 self.func(*self.args) 624 else: 625 self.func(self.args or self) 626 627 def Hilite_(self, on): 628 if self.disabled: 629 return 630 if self.sr.hilite: 631 if on: 632 self.sr.hilite.state = uiconst.UI_DISABLED 633 else: 634 self.sr.hilite.state = uiconst.UI_HIDDEN 635 636 def _GetDefaultMinWidth(self): 637 return styling.get_default_min_width(self.density) 638 639 def OnGlobalFontSizeChanged(self): 640 super(Button, self).OnGlobalFontSizeChanged() 641 self.Update_Size_() 642 643 644 class ButtonUnderlay(Container): 645 BACKGROUND_TEXTURE_BY_FRAME_TYPE = {ButtonFrameType.CUT_BOTTOM_LEFT: 'res:/UI/Texture/classes/Button/background_cut_bottom_left.png', 646 ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT: 'res:/UI/Texture/classes/Button/background_cut_bottom_left_right.png', 647 ButtonFrameType.CUT_BOTTOM_RIGHT: 'res:/UI/Texture/classes/Button/background_cut_bottom_right.png', 648 ButtonFrameType.RECTANGLE: 'res:/UI/Texture/classes/Button/background_rectangle.png'} 649 FRAME_TEXTURE_BY_FRAME_TYPE = {ButtonFrameType.CUT_BOTTOM_LEFT: 'res:/UI/Texture/classes/Button/frame_cut_bottom_left.png', 650 ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT: 'res:/UI/Texture/classes/Button/frame_cut_bottom_left_right.png', 651 ButtonFrameType.CUT_BOTTOM_RIGHT: 'res:/UI/Texture/classes/Button/frame_cut_bottom_right.png', 652 ButtonFrameType.RECTANGLE: 'res:/UI/Texture/classes/Button/frame_rectangle.png'} 653 654 def __init__(self, parent = None, name = None, variant = ButtonVariant.NORMAL, style = ButtonStyle.NORMAL, frame_type = ButtonFrameType.CUT_BOTTOM_RIGHT, interaction_state = None): 655 self._ready = False 656 self._variant = variant 657 self._style = style 658 self._frame_type = frame_type 659 self._glow = None 660 self._frame = None 661 self._background = None 662 self._interaction_state = set() 663 if interaction_state is not None: 664 self._interaction_state = self._interaction_state.union(interaction_state) 665 super(ButtonUnderlay, self).__init__(name=name, parent=parent, align=uiconst.TOALL) 666 self._layout() 667 self._ready = True 668 669 @property 670 def variant(self): 671 return self._variant 672 673 @variant.setter 674 def variant(self, value): 675 if self._variant != value: 676 self._variant = value 677 self._update() 678 679 @property 680 def style(self): 681 return self._style 682 683 @style.setter 684 def style(self, value): 685 if self._style != value: 686 self._style = value 687 self._update() 688 689 @property 690 def frame_type(self): 691 return self._frame_type 692 693 @frame_type.setter 694 def frame_type(self, value): 695 if self._frame_type != value: 696 self._frame_type = value 697 self._update_frame_type() 698 699 @property 700 def interaction_state(self): 701 return self._interaction_state 702 703 @interaction_state.setter 704 def interaction_state(self, value): 705 self.set_interaction_state(value) 706 707 @property 708 def hovered(self): 709 return InteractionState.hovered in self._interaction_state 710 711 @property 712 def pressed(self): 713 return InteractionState.pressed in self._interaction_state 714 715 @property 716 def focused(self): 717 return InteractionState.focused in self._interaction_state 718 719 @property 720 def disabled(self): 721 return InteractionState.disabled in self._interaction_state 722 723 def set_interaction_state(self, state, animate = True): 724 if self._interaction_state != state: 725 self._interaction_state = frozenset(state) 726 self._update(animate) 727 728 def _layout(self): 729 frame_texture = self._get_frame_texture() 730 self._glow = Frame(parent=self, align=uiconst.TOALL, texturePath=frame_texture, cornerSize=9, color=self._get_glow_color(), outputMode=trinity.Tr2SpriteTarget.GLOW) 731 self._frame_top_effect = Sprite(parent=self, align=uiconst.CENTERTOP, state=uiconst.UI_DISABLED, texturePath=self._get_frame_top_effect_texture(), color=self._get_frame_top_effect_color(), opacity=self._get_frame_top_effect_opacity(), pos=self._get_frame_top_effect_pos()) 732 self._frame_top_line_gradient = GradientSprite(parent=self, align=uiconst.TOTOP_NOPUSH, pickState=PickState.OFF, rgbData=self._get_frame_top_line_rgb_data(), alphaData=self._get_frame_top_line_alpha_data(), height=1) 733 self._frame = Frame(parent=self, align=uiconst.TOALL, texturePath=frame_texture, cornerSize=9, color=self._get_frame_color()) 734 self._background = Frame(parent=self, align=uiconst.TOALL, texturePath=self._get_background_texture(), cornerSize=9, color=self._get_background_color(), outputMode=trinity.Tr2SpriteTarget.COLOR_AND_GLOW, glowBrightness=0.3) 735 736 def _update(self, animate = True): 737 if not self._ready or self.destroyed: 738 return 739 self._update_glow_color(animate) 740 self._update_frame_top_effect(animate) 741 self._update_frame_top_line(animate) 742 self._update_background_color(animate) 743 self._update_border_color(animate) 744 745 def _get_glow_color(self): 746 return styling.get_glow_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 747 748 def _update_glow_color(self, animate = True): 749 if animate: 750 duration = 0.1 if self.pressed or self.hovered else 0.3 751 animations.SpColorMorphTo(self._glow, endColor=self._get_glow_color(), duration=duration) 752 else: 753 self._glow.color = self._get_glow_color() 754 755 def _get_frame_top_effect_texture(self): 756 return styling.get_frame_top_effect_texture(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 757 758 def _get_frame_top_effect_color(self): 759 return styling.get_frame_top_effect_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 760 761 def _get_frame_top_effect_opacity(self): 762 return styling.get_frame_top_effect_opacity(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 763 764 def _get_frame_top_effect_pos(self): 765 return styling.get_frame_top_effect_pos(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 766 767 def _update_frame_top_effect(self, animate = True): 768 target_opacity = 0.0 if self.disabled else self._get_frame_top_effect_opacity() 769 if animate: 770 animations.FadeTo(obj=self._frame_top_effect, startVal=self._frame_top_effect.opacity, endVal=target_opacity, duration=0.1 if self.pressed or self.hovered else 0.3) 771 else: 772 self._frame_top_effect.opacity = target_opacity 773 774 def _get_frame_top_line_rgb_data(self): 775 return styling.get_frame_top_line_rgb_data(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 776 777 def _get_frame_top_line_alpha_data(self): 778 return styling.get_frame_top_line_alpha_data(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 779 780 def _update_frame_top_line(self, animate = True): 781 target_opacity = 0.0 if self.disabled else 1.0 782 if animate: 783 animations.FadeTo(obj=self._frame_top_line_gradient, startVal=self._frame_top_line_gradient.opacity, endVal=target_opacity, duration=0.1 if self.pressed or self.hovered else 0.3) 784 else: 785 self._frame_top_line_gradient.opacity = target_opacity 786 787 def _get_background_color(self): 788 return styling.get_background_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 789 790 def _update_background_color(self, animate = True): 791 if animate: 792 duration = 0.1 if self.pressed or self.hovered else 0.3 793 animations.SpColorMorphTo(self._background, endColor=self._get_background_color(), duration=duration) 794 else: 795 self._background.color = self._get_background_color() 796 797 def _get_frame_color(self): 798 return styling.get_stroke_color(button_variant=self._variant, button_style=self._style, interaction_states=self._interaction_state) 799 800 def _get_frame_texture(self): 801 return self.FRAME_TEXTURE_BY_FRAME_TYPE[self._frame_type] 802 803 def _get_background_texture(self): 804 return self.BACKGROUND_TEXTURE_BY_FRAME_TYPE[self._frame_type] 805 806 def _update_frame_type(self): 807 frame_texture = self._get_frame_texture() 808 if self._glow: 809 self._glow.texturePath = frame_texture 810 if self._frame: 811 self._frame.texturePath = frame_texture 812 if self._background: 813 self._background.texturePath = self._get_background_texture() 814 815 def _update_border_color(self, animate = True): 816 if animate: 817 duration = 0.1 if self.pressed or self.hovered else 0.3 818 animations.SpColorMorphTo(self._frame, endColor=self._get_frame_color(), duration=duration) 819 else: 820 self._frame.color = self._get_frame_color() 821 822 def OnMouseEnter(self): 823 pass 824 825 def OnMouseExit(self): 826 pass 827 828 def OnMouseDown(self): 829 pass 830 831 def OnMouseUp(self): 832 pass 833 834 def SetFixedColor(self, color): 835 pass 836 837 def SetEnabled(self): 838 pass 839 840 def SetDisabled(self): 841 pass 842 843 def Blink(self, blinkCount = 3): 844 pass 845 846 def StopBlink(self): 847 pass 848 849 def SetFocused(self): 850 pass 851 852 def SetUnfocused(self): 853 pass 854 855 def Select(self): 856 pass 857 858 def Deselect(self): 859 pass 860 861 def OnColorThemeChanged(self): 862 self._update() 863 864 865 class BusyContext(object): 866 867 def __init__(self, button): 868 self._count = 0 869 self._button_ref = weakref.ref(button) 870 871 def __enter__(self): 872 self._count += 1 873 self._set_busy(True) 874 875 def __exit__(self, exc_type, exc_val, exc_tb): 876 self._count -= 1 877 if self._count == 0: 878 self._set_busy(False) 879 880 def _set_busy(self, value): 881 button = self._button_ref() 882 if button: 883 button.busy = value 884 885 886 class ButtonActionMenuEntryData(MenuEntryData): 887 888 def __init__(self, button): 889 self._button_ref = weakref.ref(button) 890 text = button.label or button.hint 891 hint = button.hint if button.hint != text else None 892 super(ButtonActionMenuEntryData, self).__init__(text=text, hint=hint, func=self._execute, texturePath=button.icon, menuGroupID=get_menu_group_id_from_button_style(button.style), isEnabled=button.enabled) 893 button.on_enabled_changed.connect(self._on_button_enabled_changed) 894 895 def _execute(self): 896 button = self._button_ref() 897 if button: 898 button.press(play_audio=False) 899 900 def _on_button_enabled_changed(self, button): 901 self.isEnabled = button.enabled 902 903 904 def get_menu_group_id_from_button_style(style): 905 if style == ButtonStyle.DANGER: 906 return RAGEQUITGROUP 907 elif style == ButtonStyle.WARNING: 908 return DESTRUCTIVEGROUP 909 else: 910 return None