NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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