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

skinPanel.py (36646B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\skins\skinPanel.py
      2 from __future__ import absolute_import
      3 import blue
      4 import carbonui.text.styles
      5 import contextlib
      6 import eveformat
      7 import evetypes
      8 import gametime
      9 import locks
     10 import log
     11 import logging
     12 import math
     13 import numbers
     14 import sys
     15 import uthread
     16 import uthread2
     17 from carbon.common.script.sys.serviceConst import ROLE_GML
     18 import carbonui
     19 from carbonui import ButtonStyle, ButtonVariant, const as uiconst, Density
     20 from carbonui.control.button import Button
     21 from carbonui.control.buttonIconHoverSprite import ButtonIconHoverSprite
     22 from carbonui.control.scrollContainer import ScrollContainer
     23 from carbonui.primitives.container import Container
     24 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     25 from carbonui.primitives.frame import Frame
     26 from carbonui.primitives.sprite import Sprite
     27 from carbonui.primitives.line import Line
     28 from carbonui.uianimations import animations
     29 from .defaultSkinSettings import get_default_skin_id_for_hull, set_default_skin_for_hull, clear_default_skin_for_hull
     30 from eve.client.script.ui import eveColor
     31 from eve.client.script.ui.control.infoIcon import MoreInfoIcon
     32 from eve.client.script.ui.control.themeColored import FillThemeColored, GradientThemeColored
     33 from eve.client.script.ui.plex.textures import PLEX_12_SOLID_WHITE
     34 from eve.client.script.ui.shared.skins.controller import SkinNotAvailableForType
     35 from eve.client.script.ui.shared.skins.devMenuFunctions import GiveSkin, GivePermanentSkin, GiveLimitedSkin, RemoveSkin, GetSkinUsedWithTypeAndMaterial, GiveExpiredSkin
     36 from eve.client.script.ui.shared.skins.uiutil import OPEN_SKINR_BUTTON_ANALYTIC_ID
     37 from eve.client.script.ui.shared.skins.event import LogBuySkinIsk, LogBuySkinAur
     38 from eve.client.script.ui.util.uiComponents import Component, ButtonEffect
     39 from cosmetics.common.ships.skins.static_data.skin_type import ShipSkinType
     40 from inventorycommon.const import typeSkinMaterial
     41 from cosmetics.common.ships.skins.static_data.slot_configuration import is_skinnable_ship
     42 from localization import GetByLabel
     43 logger = logging.getLogger(__name__)
     44 ENTRY_DEFAULT_HEIGHT = 106
     45 ENTRY_COMPACT_HEIGHT = 80
     46 COMPACT_THRESHOLD = 350
     47 
     48 class SkinPanel(ScrollContainer):
     49     default_settingsPrefix = 'SkinPanel'
     50 
     51     def ApplyAttributes(self, attributes):
     52         super(SkinPanel, self).ApplyAttributes(attributes)
     53         self.controller = attributes.controller
     54         self.settingsPrefix = attributes.settingsPrefix or self.default_settingsPrefix
     55         self._logContext = attributes.get('logContext', None)
     56         self.whatchlist = []
     57         self.loaded = False
     58         self._loadingLock = locks.Lock()
     59         self.controller.onSkinsChange.connect(self.OnSkinsChange)
     60         self.controller.on_position_changed.connect(self.OnPositionChanged)
     61         self._licensedSkinGroup = None
     62         self._unlicensedSkinGroup = None
     63         self._previewType = None
     64         self._forcedCompact = attributes.get('compact', False)
     65         self._compactMode = False
     66         self._paragonHubSection = None
     67         sm.RegisterForNotifyEvent(self, 'OnDefaultSkinSet')
     68 
     69     def Reload(self):
     70         with self._loadingLock:
     71             self.loaded = False
     72             self.Flush()
     73         self.Load()
     74 
     75     def Load(self, selectedSkin = None):
     76         if self.loaded:
     77             return
     78         self._compactMode = self._forcedCompact or self.GetAbsoluteSize()[0] < COMPACT_THRESHOLD
     79         with self._loadingLock:
     80             self.Flush()
     81             now = gametime.GetWallclockTime()
     82             default_skin_id = get_default_skin_id_for_hull(self.controller.typeID)
     83             skins = sorted(self.controller.skins, key=lambda skin: skin.name.lower() and skin.skinID != default_skin_id)
     84             self._paragonHubSection = OpenParagonHubSection(parent=self, align=uiconst.TOTOP, padBottom=8, display=False)
     85             licensedLabel = GetByLabel('UI/ShipCosmetics/LicensedSkins', shipTypeName='')
     86             settingName = '%s_LicensedSkins' % self.settingsPrefix
     87             licensed = [ skin for skin in skins if skin.licensed and (skin.expires is None or skin.expires > now) ]
     88             self._licensedSkinGroup = self.AddSkinGroup(licensedLabel, licensed, settingName=settingName, selectedSkin=selectedSkin)
     89             unlicensed = [ skin for skin in skins if not skin.licensed or skin.expires and skin.expires < now ]
     90             unlicensedLabel = GetByLabel('UI/ShipCosmetics/UnlicensedSkins', shipTypeName='')
     91             settingName = '%s_UnlicensedSkins' % self.settingsPrefix
     92             self._unlicensedSkinGroup = self.AddSkinGroup(unlicensedLabel, unlicensed, settingName=settingName, selectedSkin=selectedSkin)
     93             for i, entry in enumerate(self.mainCont.children):
     94                 if hasattr(entry, 'AnimShow') and entry.display:
     95                     entry.AnimShow(delay=i * 0.05)
     96 
     97             self.loaded = True
     98             self.SetPreviewType(self._previewType)
     99 
    100     def AddSkinGroup(self, label, skins, settingName = None, selectedSkin = None):
    101         group = SkinGroupEntry(parent=self, text=label, collapsedSettingName=settingName)
    102         if len(skins) == 0:
    103             message = carbonui.text.styles.TextHeader(parent=self, align=uiconst.TOTOP, padding=(25, 12, 0, 20), text=GetByLabel('UI/Skins/NoSkins'), color=carbonui.TextColor.SECONDARY)
    104             group.AddEntry(message)
    105         for skin in skins:
    106             entry = SkinEntrySlot(parent=self, align=uiconst.TOTOP, padTop=8, controller=self.controller, skin=skin, logContext=self._logContext, selectedSkin=selectedSkin)
    107             if self._compactMode:
    108                 entry.SetCompact()
    109             group.AddEntry(entry)
    110 
    111         return group
    112 
    113     def SetPreviewType(self, typeID):
    114         self._previewType = typeID
    115         if self.loaded and self._previewType:
    116             self._SetPreviewText()
    117             self._paragonHubSection.display = is_skinnable_ship(typeID)
    118             self._paragonHubSection.shipTypeID = typeID
    119 
    120     def SetCompactMode(self, compact):
    121         self._forcedCompact = compact
    122         if self._forcedCompact:
    123             self._SetCompact()
    124             return
    125         width = self.GetAbsoluteSize()[0]
    126         compactMode = width < COMPACT_THRESHOLD
    127         if self._compactMode != compactMode:
    128             if width < COMPACT_THRESHOLD:
    129                 self._SetCompact()
    130             else:
    131                 self._SetExpanded()
    132 
    133     def _SetPreviewText(self):
    134         shipTypeName = evetypes.GetName(self._previewType)
    135         licensedLabel = GetByLabel('UI/ShipCosmetics/LicensedSkins', shipTypeName=shipTypeName)
    136         unlicensedLabel = GetByLabel('UI/ShipCosmetics/UnlicensedSkins', shipTypeName=shipTypeName)
    137         self._licensedSkinGroup.SetLabelText(licensedLabel)
    138         self._unlicensedSkinGroup.SetLabelText(unlicensedLabel)
    139 
    140     def OnSkinsChange(self):
    141         self.Reload()
    142 
    143     def OnPositionChanged(self):
    144         skinPreviewed = self.controller.previewed
    145         groupEntry, skinEntrySlot = self._FindGroupAndSkinElements(skinPreviewed)
    146         if groupEntry and hasattr(groupEntry, 'collapsed') and groupEntry.collapsed:
    147             groupEntry.ToggleCollapse()
    148         if skinEntrySlot:
    149             clipperTop = self.clipCont.GetAbsoluteTop()
    150             _, clipperHeight = self.clipCont.GetAbsoluteSize()
    151             skinEntryTop = skinEntrySlot.GetAbsoluteTop()
    152             if skinEntryTop > clipperTop + clipperHeight:
    153                 rel = skinEntryTop - clipperTop
    154                 totalHeight = self.mainCont.height
    155                 pos = min(1.0, max(0, rel / float(totalHeight)))
    156                 self.ScrollToVertical(pos)
    157 
    158     def _FindGroupAndSkinElements(self, skinPreviewed):
    159         for eachChild in self.mainCont.children:
    160             if not isinstance(eachChild, SkinGroupEntry):
    161                 continue
    162             for eachEntry in eachChild._entries:
    163                 if not isinstance(eachEntry, SkinEntrySlot):
    164                     continue
    165                 if eachEntry.entry.skin == skinPreviewed:
    166                     return (eachChild, eachEntry)
    167 
    168         return (None, None)
    169 
    170     def Close(self):
    171         super(SkinPanel, self).Close()
    172         self.controller.Close()
    173         sm.UnregisterForNotifyEvent(self, 'OnDefaultSkinSet')
    174 
    175     def _OnSizeChange_NoBlock(self, width, height):
    176         super(SkinPanel, self)._OnSizeChange_NoBlock(width, height)
    177         if self._forcedCompact:
    178             return
    179         if width < COMPACT_THRESHOLD:
    180             self._SetCompact()
    181         else:
    182             self._SetExpanded()
    183 
    184     def _SetCompact(self):
    185         if self._compactMode:
    186             return
    187         self._compactMode = True
    188         for eachChild in self.mainCont.children:
    189             if not isinstance(eachChild, SkinGroupEntry):
    190                 continue
    191             for eachEntry in eachChild._entries:
    192                 if not isinstance(eachEntry, SkinEntrySlot):
    193                     continue
    194                 eachEntry.SetCompact()
    195 
    196     def _SetExpanded(self):
    197         if not self._compactMode:
    198             return
    199         self._compactMode = False
    200         for eachChild in self.mainCont.children:
    201             if not isinstance(eachChild, SkinGroupEntry):
    202                 continue
    203             for eachEntry in eachChild._entries:
    204                 if not isinstance(eachEntry, SkinEntrySlot):
    205                     continue
    206                 eachEntry.SetExpanded()
    207 
    208     def OnDefaultSkinSet(self, skin_id):
    209         self.Reload()
    210 
    211 
    212 class EntryMixin(object):
    213 
    214     def AnimShow(self, delay = 0.0):
    215         animations.FadeTo(self, duration=0.2, timeOffset=delay)
    216         animations.MoveInFromTop(self, curveType=uiconst.ANIM_OVERSHOT, duration=0.3, timeOffset=delay)
    217 
    218 
    219 class SkinEntrySlot(Container, EntryMixin):
    220     default_name = 'SkinEntrySlot'
    221     default_alignMode = uiconst.TOTOP
    222     default_height = ENTRY_DEFAULT_HEIGHT
    223 
    224     def ApplyAttributes(self, attributes):
    225         super(SkinEntrySlot, self).ApplyAttributes(attributes)
    226         selectedSkin = attributes.get('selectedSkin', None)
    227         controller = attributes.controller
    228         skin = attributes.skin
    229         logContext = attributes.get('logContext', None)
    230         if skin.skin_type == ShipSkinType.THIRD_PARTY_SKIN:
    231             self.entry = ThirdPartySkinEntry(parent=self, controller=controller, skin=skin, logContext=logContext, name='SkinEntry_{}'.format(skin.name.encode('utf-8')))
    232         elif skin.skin_type == ShipSkinType.FIRST_PARTY_SKIN:
    233             self.entry = SkinEntry(parent=self, controller=controller, skin=skin, logContext=logContext, name='SkinEntry_{}'.format(skin.materialID))
    234             if selectedSkin and selectedSkin.skin_type == ShipSkinType.FIRST_PARTY_SKIN:
    235                 if selectedSkin.materialID == skin.materialID:
    236                     self.entry.SetSkinState(BaseSkinEntry.STATE_PREVIEWED)
    237         if skin.licensed:
    238             skin_id = get_default_skin_id_for_hull(self.entry.controller.typeID)
    239             if skin_id is not None and skin_id == self.entry.skin.skinID:
    240                 self.entry.removeDefaultButtonIcon.opacity = 1.0
    241                 self.entry.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    242                 self.entry.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_ON
    243         else:
    244             self.entry.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    245             self.entry.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    246 
    247     def GetAbsoluteScrollPosition(self):
    248         _, top, _, height = self.GetAbsolute()
    249         return (top, top + height)
    250 
    251     def AnimShow(self, delay = 0.0):
    252         super(SkinEntrySlot, self).AnimShow(delay=delay)
    253         if self.entry.parent == self:
    254             self.entry.AnimShow(delay=delay)
    255 
    256     def SetCompact(self):
    257         self.entry.SetCompact()
    258         self.height = ENTRY_COMPACT_HEIGHT
    259 
    260     def SetExpanded(self):
    261         self.entry.SetExpanded()
    262         self.height = ENTRY_DEFAULT_HEIGHT
    263 
    264 
    265 @Component(ButtonEffect(opacityIdle=0.0, opacityHover=0.2, opacityMouseDown=0.3, bgElementFunc=lambda parent, _: parent.blinkFill, audioOnEntry=uiconst.SOUND_ENTRY_HOVER, audioOnClick='fitting_window_skin_select_play'))
    266 
    267 class BaseSkinEntry(Container):
    268     default_align = uiconst.TOALL
    269     default_bgColor = (0.0, 0.0, 0.0, 0.25)
    270     default_state = uiconst.UI_NORMAL
    271     STATE_IDLE = 1
    272     STATE_APPLIED = 2
    273     STATE_PENDING = 3
    274     STATE_PREVIEWED = 4
    275 
    276     def ApplyAttributes(self, attributes):
    277         super(BaseSkinEntry, self).ApplyAttributes(attributes)
    278         self.cosmeticsSvc = sm.GetService('cosmeticsSvc')
    279         self.controller = attributes.controller
    280         self.skin = attributes.skin
    281         self.skinstate = self.STATE_IDLE
    282         self._logContext = attributes.get('logContext', None)
    283         self.warningSprite = None
    284         self.Layout()
    285         self.UpdateSkinState(0)
    286         self.controller.onChange.connect(self.UpdateSkinState)
    287         sm.RegisterForNotifyEvent(self, 'OnDefaultSkinSet')
    288 
    289     def Close(self):
    290         super(BaseSkinEntry, self).Close()
    291         sm.UnregisterForNotifyEvent(self, 'OnDefaultSkinSet')
    292 
    293     @property
    294     def iconTexturePath(self):
    295         return self.skin.iconTexturePath
    296 
    297     def Layout(self):
    298         self._ConstructSelectionCont()
    299         self._ConstructIcon()
    300         self._ConstructMain()
    301         self._ConstructState()
    302         self._ConstructFrame()
    303 
    304     def _ConstructSelectionCont(self):
    305         self.selectionFrameCont = Container(parent=self, align=uiconst.TOALL)
    306 
    307     def _ConstructIcon(self):
    308         self.iconContainer = ContainerAutoSize(name='iconContainer', parent=self, align=uiconst.TOLEFT, state=uiconst.UI_DISABLED, padding=(32, 0, 32, 0))
    309         self.iconInnerContainer = Container(name='iconInnerContainer', parent=self.iconContainer, align=uiconst.CENTER, width=45, height=45)
    310         self.iconGlow = Sprite(name='iconGlow', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath='res:/UI/Texture/classes/skins/skin-icon-glow.png', opacity=0.0)
    311         Sprite(name='iconTexture', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath=self.iconTexturePath)
    312         self.iconShadow = Sprite(name='iconShadow', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath='res:/UI/Texture/classes/skins/icon-shadow.png')
    313         self.iconBG = Sprite(name='iconBackground', parent=self.iconContainer, align=uiconst.CENTER, texturePath='res:/UI/Texture/classes/skins/skin_background.png', color=eveColor.SAND_YELLOW, opacity=0.05, width=80, height=80)
    314 
    315     def _ConstructMain(self):
    316         self.textCont = VerticalCenteredContainer(name='textCont', parent=self, align=uiconst.TOALL, left=0, padRight=10)
    317         carbonui.text.styles.TextBody(name='title', parent=self.textCont, align=uiconst.TOTOP, text=self.skin.name, color=carbonui.TextColor.HIGHLIGHT, maxLines=2)
    318 
    319     def _ConstructState(self):
    320         self.pendingIcon = Sprite(name='pendingIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/classes/skins/pending.png', opacity=0.0)
    321         animations.MorphScalar(self.pendingIcon, 'rotation', startVal=0.0, endVal=-2 * math.pi, duration=1.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    322         self.appliedIcon = Sprite(name='appliedIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(0, 0, 34, 34), texturePath='res:/UI/Texture/classes/SkillPlan/completedIcon.png', opacity=0.0)
    323         self.defaultButtonIcon = ButtonIconHoverSprite(name='defaultIcon', parent=self, align=uiconst.TOPRIGHT, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/eveicon/system_icons/star_hollow_16px.png', hoverTexture='res:/UI/Texture/eveicon/system_icons/star_16px.png', func=self.SetDefaultSkin, hint=GetByLabel('UI/Skins/SetDefault'))
    324         self.defaultButtonIcon.opacity = 0.0
    325         self.removeDefaultButtonIcon = ButtonIconHoverSprite(name='removeDefaultIcon', parent=self, align=uiconst.TOPRIGHT, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/eveicon/system_icons/star_16px.png', hoverTexture='res:/UI/Texture/eveicon/system_icons/star_slash_16px.png', func=self.RemoveDefaultSkin, hint=GetByLabel('UI/Skins/RemoveDefault'))
    326         self.removeDefaultButtonIcon.opacity = 0.0
    327         self.previewedIcon = Sprite(name='previewedIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(9, 9, 18, 16), texturePath='res:/UI/Texture/classes/skins/previewed.png', opacity=0.0)
    328         self.stateIconLight = GradientThemeColored(name='stateIconLight', parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED, rotation=0, alphaData=[(0.0, 0.0),
    329          (0.25, 0.01),
    330          (0.5, 0.1),
    331          (0.6, 0.2),
    332          (0.7, 0.4),
    333          (0.8, 0.8),
    334          (0.9, 1.0)], colorType=uiconst.COLORTYPE_UIHILIGHTGLOW, opacity=0.0)
    335 
    336     def _ConstructFrame(self):
    337         self.selectionFrame = Frame(name='selectionFrame', parent=self.selectionFrameCont, align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/Fitting/Heraldry/selectionFrame.png', color=eveColor.SUCCESS_GREEN, cornerSize=10, uiScaleVertices=True, fillCenter=False, padding=(-8, -8, -8, -8), opacity=0.0)
    338         self.selectionGradient = FillThemeColored(name='selectionGradient', bgParent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED, colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=0.0)
    339         self.blinkFill = FillThemeColored(name='blinkFill', bgParent=self, colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=0.0)
    340 
    341     def UpdateSkinState(self, delay = None):
    342         if self.controller.applied == self.skin:
    343             delay = delay if delay is not None else self.controller.GetTimeUntilNextAllowedSkinChange()
    344             skinState = self.STATE_APPLIED
    345         elif self.controller.pending == self.skin:
    346             skinState = self.STATE_PENDING
    347         elif self.controller.previewed == self.skin:
    348             skinState = self.STATE_PREVIEWED
    349         else:
    350             skinState = self.STATE_IDLE
    351         self.SetSkinState(skinState, delay)
    352 
    353     def SetSkinState(self, state, delay = None):
    354         if state == self.skinstate:
    355             return
    356         if state == self.STATE_IDLE:
    357             self.AnimCollapse()
    358         elif self.skinstate == self.STATE_IDLE:
    359             self.AnimExpand()
    360         if state == self.STATE_APPLIED:
    361             if self.warningSprite:
    362                 self.warningSprite.display = False
    363             self.AnimStateApplied(delay)
    364         elif state == self.STATE_PENDING:
    365             self.AnimStatePending()
    366         elif state == self.STATE_PREVIEWED:
    367             self.AnimStatePreviewed()
    368         elif state == self.STATE_IDLE:
    369             self.AnimStateIdle()
    370         self.UpdateDefaultIconAlign(is_idle=state == self.STATE_IDLE)
    371         self.skinstate = state
    372 
    373     def AnimShow(self, delay = 0.0):
    374         animations.FadeIn(self.blinkFill, endVal=0.4, curveType=uiconst.ANIM_WAVE, duration=0.4, timeOffset=delay * 1.4 + 0.4)
    375 
    376     def AnimExpand(self):
    377         animations.FadeIn(self.selectionFrame, endVal=1.0, duration=0.1, curveType=uiconst.ANIM_OVERSHOT)
    378         animations.FadeIn(self.selectionGradient, endVal=0.15, duration=0.1, curveType=uiconst.ANIM_OVERSHOT)
    379         self._AnimShowIconGlow()
    380 
    381     def AnimCollapse(self):
    382         animations.FadeOut(self.selectionFrame, duration=0.1)
    383         animations.FadeOut(self.selectionGradient, duration=0.1)
    384         self._AnimHideIconGlow()
    385         self._FadeOutSmallIcons()
    386 
    387     def _FadeOutSmallIcons(self):
    388         for eachIcon in (self.pendingIcon, self.previewedIcon, self.appliedIcon):
    389             animations.FadeOut(eachIcon, duration=0.05)
    390 
    391     def _AnimShowIconGlow(self):
    392         self.iconGlow.opacity = 0.05
    393         animations.SpMaskIn(self.iconGlow, duration=0.4)
    394 
    395     def _AnimHideIconGlow(self):
    396         animations.FadeOut(self.iconGlow, duration=0.3)
    397 
    398     def AnimStateIdle(self):
    399         for icon in (self.pendingIcon, self.previewedIcon, self.pendingIcon):
    400             animations.StopAllAnimations(icon)
    401             animations.FadeOut(icon, duration=0.15)
    402 
    403     def AnimStateApplied(self, delay = None):
    404         if delay <= 0:
    405             self._AnimStateApplied()
    406         else:
    407             uthread2.call_after_wallclocktime_delay(self._AnimStateApplied, delay)
    408 
    409     def _AnimStateApplied(self):
    410         animations.FadeOut(self.pendingIcon, duration=0.15)
    411         animations.FadeOut(self.previewedIcon, duration=0.15)
    412         self._AnimShowStateIcon(self.appliedIcon)
    413 
    414     def AnimStatePending(self):
    415         animations.FadeOut(self.appliedIcon, duration=0.15)
    416         animations.FadeOut(self.previewedIcon, duration=0.15)
    417         animations.FadeIn(self.pendingIcon, duration=0.15)
    418 
    419     def AnimStatePreviewed(self):
    420         animations.FadeOut(self.appliedIcon, duration=0.15)
    421         animations.FadeOut(self.pendingIcon, duration=0.15)
    422         self._AnimShowStateIcon(self.previewedIcon)
    423 
    424     def UpdateDefaultIconAlign(self, is_idle):
    425         left = 9 if is_idle else 33
    426         for icon in (self.defaultButtonIcon, self.removeDefaultButtonIcon):
    427             icon.left = left
    428 
    429     def _AnimShowStateIcon(self, icon):
    430 
    431         def continue_glowExpand():
    432             animations.MorphScalar(icon, 'glowExpand', startVal=0.0, endVal=20.0, duration=0.5, curveType=uiconst.ANIM_SMOOTH)
    433             animations.SpColorMorphTo(icon, attrName='glowColor', startColor=(1.0, 1.0, 1.0, 0.5), endColor=(0.0, 0.0, 0.0, 0.0), duration=0.25, curveType=uiconst.ANIM_SMOOTH)
    434 
    435         animations.FadeIn(icon, duration=0.15, timeOffset=0.15)
    436         animations.SpColorMorphTo(icon, attrName='glowColor', startColor=(0.3, 0.3, 0.3, 0.0), endColor=(1.0, 1.0, 1.0, 0.5), duration=0.15, timeOffset=0.2, curveType=uiconst.ANIM_SMOOTH)
    437         animations.MorphScalar(icon, 'glowExpand', startVal=30.0, endVal=0.0, duration=0.2, timeOffset=0.15, curveType=uiconst.ANIM_SMOOTH, callback=continue_glowExpand)
    438         animations.FadeTo(self.stateIconLight, startVal=0.0, endVal=0.15, duration=0.55, timeOffset=0.15, curveType=uiconst.ANIM_BOUNCE)
    439 
    440     def Pick(self):
    441         try:
    442             self.controller.PickSkin(self.skin)
    443         except SkinNotAvailableForType:
    444             logger.warning('PickSkin failed with SkinNotAvailableForType. Most likely due to the shipID changing in the controller before the UI is torn down.')
    445             sys.exc_clear()
    446 
    447     def OnClick(self, *args):
    448         self.Pick()
    449 
    450     def SetCompact(self):
    451         self.iconContainer.padding = (12, 0, 8, 0)
    452         self.iconInnerContainer.SetSize(24, 24)
    453         self.iconBG.SetSize(40, 40)
    454         self.textCont.padRight = 30
    455 
    456     def SetExpanded(self):
    457         self.iconContainer.padding = (32, 0, 32, 0)
    458         self.iconInnerContainer.SetSize(46, 46)
    459         self.iconBG.SetSize(80, 80)
    460         self.textCont.padRight = 10
    461 
    462     def SetDefaultSkin(self):
    463         self.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    464         self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_ON
    465         self.defaultButtonIcon.opacity = 0.0
    466         self.removeDefaultButtonIcon.opacity = 1.0
    467         set_default_skin_for_hull(hull_id=self.controller.typeID, skin_id=self.skin.skinID)
    468 
    469     def RemoveDefaultSkin(self):
    470         self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    471         self.defaultButtonIcon.pickState = uiconst.TR2_SPS_ON
    472         self.removeDefaultButtonIcon.opacity = 0.0
    473         self.defaultButtonIcon.opacity = 1.0
    474         clear_default_skin_for_hull(hull_id=self.controller.typeID)
    475 
    476     def OnMouseEnter(self, *args):
    477         if self.skin.licensed:
    478             skin_id = get_default_skin_id_for_hull(self.controller.typeID)
    479             if skin_id is not None and skin_id == self.skin.skinID:
    480                 animations.FadeIn(self.removeDefaultButtonIcon, duration=0.15)
    481             else:
    482                 animations.FadeIn(self.defaultButtonIcon, duration=0.15)
    483 
    484     def OnMouseExit(self, *args):
    485         skin_id = get_default_skin_id_for_hull(self.controller.typeID)
    486         if skin_id != self.skin.skinID:
    487             animations.FadeOut(self.removeDefaultButtonIcon, duration=0.15)
    488             animations.FadeOut(self.defaultButtonIcon, duration=0.15)
    489 
    490     def OnDefaultSkinSet(self, skin_id):
    491         if skin_id != self.skin.skinID:
    492             animations.FadeOut(self.removeDefaultButtonIcon, duration=0.15)
    493             self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF
    494             self.defaultButtonIcon.pickState = uiconst.TR2_SPS_ON
    495 
    496 
    497 class SkinEntry(BaseSkinEntry):
    498 
    499     def _ConstructMain(self):
    500         super(SkinEntry, self)._ConstructMain()
    501         self.warningSprite = Sprite(parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_NORMAL, pos=(2, 2, 16, 16), texturePath='res:/ui/texture/icons/44_32_7.png', hint=GetByLabel('UI/Skins/WarningNoPermissionToChangeSkin'))
    502         self.warningSprite.display = False
    503         if self.skin.licensed and not self.skin.expired:
    504             SkinDurationLabel(name='durationText', parent=self.textCont, align=uiconst.TOTOP, skin=self.skin, padTop=3)
    505             if self.controller.IsSkinAccessRestricted():
    506                 self.warningSprite.hint = GetByLabel('UI/Skins/WarningNoPermissionToChangeSkin')
    507                 self.warningSprite.display = True
    508             elif self.controller.IsDisabledDueToDamage():
    509                 self.warningSprite.display = True
    510                 self.warningSprite.hint = GetByLabel('UI/Skins/WarningTooDamagedToChangeSkin')
    511         else:
    512             buttonCont = Container(name='buttonCont', parent=self.textCont, align=uiconst.TOTOP, height=24, padTop=8)
    513             is_single_use = self._AreAllLicensesSingleUse()
    514             SkinBuyButtonPlex(parent=buttonCont, align=uiconst.TOLEFT, typeID=self.controller.typeID, materialID=self.skin.materialID, logContext=self._logContext, padRight=4, display=False)
    515             SkinBuyButtonIsk(parent=buttonCont, align=uiconst.TOLEFT, typeID=self.controller.typeID, materialID=self.skin.materialID, logContext=self._logContext, state=uiconst.UI_HIDDEN if is_single_use else uiconst.UI_NORMAL, padRight=4)
    516             if is_single_use:
    517                 MoreInfoIcon(name='SingleUseSkinsInfoIcon', parent=buttonCont, align=uiconst.TOLEFT, hint=GetByLabel('UI/Skins/SingleUseHint'), padRight=4)
    518 
    519     def _AreAllLicensesSingleUse(self):
    520         return self.cosmeticsSvc.static.AreAllLicensesSingleUse(self.controller.typeID, self.skin.materialID)
    521 
    522     def GetMenu(self):
    523         entries = []
    524         gmEntries = self._GetGMMenuEntries()
    525         if len(gmEntries) > 0:
    526             entries.extend(gmEntries)
    527             entries.append(None)
    528         entries.extend([[GetByLabel('UI/Commands/ShowInfo'), self.ShowMaterialInfo], None, [GetByLabel('UI/Inventory/ItemActions/ViewTypesMarketDetails'), self.OpenMarketWindow]])
    529         return entries
    530 
    531     def _GetGMMenuEntries(self):
    532         if session.role & ROLE_GML != ROLE_GML:
    533             return []
    534         elif self.skin.skinID is None:
    535             skinInfo = GetSkinUsedWithTypeAndMaterial(self.skin.materialID, self.controller.typeID)
    536             ownerText = 'my corp' if skinInfo.isStructureSkin else 'me'
    537             m = [['GM: Give %s time-limited' % ownerText, GiveSkin, (self.skin.materialID, self.controller.typeID)], ['GM: Give %s single-use' % ownerText, GiveLimitedSkin, (self.skin.materialID, self.controller.typeID)], ['GM: Give %s permanently' % ownerText, GivePermanentSkin, (self.skin.materialID, self.controller.typeID)]]
    538             m += [['GM: skinID: %s' % skinInfo.skinID, blue.pyos.SetClipboardData, (str(skinInfo.skinID),)]]
    539             return m
    540         else:
    541             m = [['GM: Remove SKIN', RemoveSkin, (self.skin.skinID,)]]
    542             m += [['GM: Set SKIN as expired (processed on next login)', GiveExpiredSkin, (self.skin.skinID,)]]
    543             m += [['GM: skinID: %s' % self.skin.skinID, blue.pyos.SetClipboardData, (str(self.skin.skinID),)]]
    544             return m
    545 
    546     def OpenMarketWindow(self):
    547         self.cosmeticsSvc.OpenMarketForTypeWithMaterial(self.controller.typeID, self.skin.materialID)
    548         LogBuySkinIsk(self.controller.typeID, self.skin.materialID, self._logContext)
    549 
    550     def ShowMaterialInfo(self):
    551         sm.GetService('info').ShowInfo(typeSkinMaterial, itemID=self.skin.materialID)
    552 
    553 
    554 class ThirdPartySkinEntry(BaseSkinEntry):
    555 
    556     def _ConstructIcon(self):
    557         super(ThirdPartySkinEntry, self)._ConstructIcon()
    558         self.iconShadow.display = False
    559 
    560     def _ConstructMain(self):
    561         super(ThirdPartySkinEntry, self)._ConstructMain()
    562         prefixText = eveformat.color(GetByLabel('Achievements/UI/active'), carbonui.TextColor.NORMAL)
    563         expiresText = eveformat.color(GetByLabel('UI/Skins/SkinDurationPermanent'), carbonui.TextColor.SECONDARY)
    564         carbonui.text.styles.TextDetail(name='durationText', parent=self.textCont, align=uiconst.TOTOP, text='%s: %s' % (prefixText, expiresText), padTop=3)
    565         try:
    566             creatorName = cfg.eveowners.Get(self.skin.skin_data.creator_character_id).ownerName
    567         except:
    568             creatorName = None
    569 
    570         if creatorName:
    571             prefixText = eveformat.color(GetByLabel('UI/Personalization/ShipSkins/SKINR/MadeBy'), carbonui.TextColor.NORMAL)
    572             creatorText = eveformat.color(creatorName, carbonui.TextColor.SECONDARY)
    573             carbonui.text.styles.TextDetail(name='madeBy', parent=self.textCont, align=uiconst.TOTOP, text='%s: %s' % (prefixText, creatorText), padTop=3)
    574 
    575 
    576 class SkinGroupEntry(ContainerAutoSize, EntryMixin):
    577     default_align = uiconst.TOTOP
    578     default_state = uiconst.UI_PICKCHILDREN
    579 
    580     def ApplyAttributes(self, attributes):
    581         super(SkinGroupEntry, self).ApplyAttributes(attributes)
    582         self.text = attributes.text
    583         self._entries = set()
    584         self.Layout()
    585 
    586     def AddEntry(self, entry):
    587         self._entries.add(entry)
    588 
    589     def Layout(self):
    590         panel = ContainerAutoSize(name='panel', parent=self, align=uiconst.TOTOP, state=uiconst.UI_NORMAL, padTop=10)
    591         panel.DelegateEvents(self)
    592         self.label = carbonui.text.styles.TextBody(parent=panel, align=uiconst.TOTOP, text=self.text, maxlines=2, bold=True)
    593 
    594     def SetLabelText(self, text):
    595         self.label.text = text
    596 
    597     def _GetEntrySortKey(self, entry):
    598         return entry.parent.children.index(entry)
    599 
    600 
    601 class SkinDurationLabel(carbonui.text.styles.TextDetail):
    602 
    603     def ApplyAttributes(self, attributes):
    604         super(SkinDurationLabel, self).ApplyAttributes(attributes)
    605         self.skin = attributes.skin
    606         self.text = self._GetExpiryText()
    607         if not self.skin.permanent:
    608             uthread.new(self._UpdateLabelThread)
    609 
    610     def _UpdateLabelThread(self):
    611         while True:
    612             blue.synchro.SleepWallclock(1000)
    613             if self.destroyed:
    614                 break
    615             self.text = self._GetExpiryText()
    616 
    617     def _GetExpiryText(self):
    618         prefixText = eveformat.color(GetByLabel('Achievements/UI/active'), carbonui.TextColor.NORMAL)
    619         expiresText = eveformat.color(self.skin.GetExpiresLabel(), carbonui.TextColor.SECONDARY)
    620         return '%s: %s' % (prefixText, expiresText)
    621 
    622 
    623 class SkinBuyButton(Button):
    624     default_iconSize = 12
    625     default_density = Density.COMPACT
    626 
    627     def ApplyAttributes(self, attributes):
    628         super(SkinBuyButton, self).ApplyAttributes(attributes)
    629         self.typeID = attributes.typeID
    630         self.materialID = attributes.materialID
    631         self.logContext = attributes.logContext
    632 
    633 
    634 class SkinBuyButtonIsk(SkinBuyButton):
    635     default_name = 'SkinBuyButtonIsk'
    636     default_variant = ButtonVariant.PRIMARY
    637     default_label = GetByLabel('UI/Skins/BuyWithIsk')
    638     default_texturePath = 'res:/UI/Texture/classes/skins/isk_12.png'
    639 
    640     def OnClick(self, *args):
    641         if not self.enabled:
    642             return
    643         sm.GetService('cosmeticsSvc').OpenMarketForTypeWithMaterial(self.typeID, self.materialID)
    644         LogBuySkinIsk(self.typeID, self.materialID, self.logContext)
    645 
    646 
    647 class SkinBuyButtonPlex(SkinBuyButton):
    648     default_name = 'SkinBuyButtonPlex'
    649     default_style = ButtonStyle.MONETIZATION
    650     default_label = GetByLabel('UI/Skins/BuyWithAur')
    651     default_texturePath = PLEX_12_SOLID_WHITE
    652     default_pickState = carbonui.PickState.ON
    653 
    654     def ApplyAttributes(self, attributes):
    655         super(SkinBuyButtonPlex, self).ApplyAttributes(attributes)
    656         self.types = self.GetLicenseTypesForMaterial()
    657         self.offers = None
    658         if callable(self.types):
    659             self.types = self.types()
    660         if isinstance(self.types, numbers.Number):
    661             self.types = [self.types]
    662         if self.types is not None:
    663             uthread.new(self.FindOffersAndReveal)
    664 
    665     def FindOffersAndReveal(self):
    666         try:
    667             store = sm.GetService('vgsService').GetStore()
    668             self.offers = store.SearchOffersByTypeIDs(self.types)
    669             self.display = len(self.offers) > 0
    670         except Exception as e:
    671             if len(e.args) >= 1 and e.args[0] == 'tokenMissing':
    672                 log.LogInfo('Failed to search the NES for offers due to missing token')
    673             else:
    674                 log.LogException('Failed to search the NES for offers')
    675 
    676     def GetLicenseTypesForMaterial(self):
    677         licenses = sm.GetService('cosmeticsSvc').static.GetLicensesForTypeWithMaterial(self.typeID, self.materialID)
    678         return [ license.licenseTypeID for license in licenses ]
    679 
    680     def OnClick(self, *args):
    681         if not self.enabled:
    682             return
    683         sm.GetService('vgsService').OpenStore(typeIds=self.types)
    684         LogBuySkinAur(self.typeID, self.materialID, self.logContext)
    685 
    686 
    687 class VerticalCenteredContainer(ContainerAutoSize):
    688     default_alignMode = uiconst.TOTOP
    689 
    690     def __init__(self, **kwargs):
    691         kwargs['callback'] = self._on_size_change
    692         super(VerticalCenteredContainer, self).__init__(**kwargs)
    693 
    694     @contextlib.contextmanager
    695     def auto_size_disabled(self):
    696         was_enabled = self.isAutoSizeEnabled
    697         self.DisableAutoSize()
    698         try:
    699             yield self
    700         finally:
    701             if was_enabled:
    702                 self.EnableAutoSize()
    703 
    704     def _on_size_change(self):
    705         to_top_aligned_children = filter(lambda child: child.align == uiconst.TOTOP, self.children)
    706         if to_top_aligned_children:
    707             content_height = 0
    708             for i, child in enumerate(to_top_aligned_children):
    709                 content_height += child.height + child.padTop + child.padBottom
    710                 if i > 0:
    711                     content_height += child.top
    712 
    713             if content_height <= 0:
    714                 return
    715             _, height = self.GetCurrentAbsoluteSize()
    716             adjusted_top = max(0.0, math.floor((height - content_height) / 2.0))
    717             to_top_aligned_children[0].top = adjusted_top
    718 
    719     def SetSizeAutomatically(self):
    720         if self.align == uiconst.TOALL:
    721             self._on_size_change()
    722         else:
    723             super(VerticalCenteredContainer, self).SetSizeAutomatically()
    724 
    725 
    726 class OpenParagonHubSection(ContainerAutoSize):
    727     default_align = carbonui.Align.TOTOP
    728     default_alignMode = carbonui.Align.TOTOP
    729 
    730     def __init__(self, shipTypeID = None, *args, **kwargs):
    731         super(OpenParagonHubSection, self).__init__(*args, **kwargs)
    732         self.shipTypeID = shipTypeID
    733         Line(parent=self, align=carbonui.Align.TOTOP, weight=2, color=eveColor.PARAGON_BLUE, outputMode=uiconst.OUTPUT_COLOR_AND_GLOW, glowBrightness=0.3, opacity=0.6)
    734         container = ContainerAutoSize(parent=self, align=carbonui.Align.TOTOP, alignMode=carbonui.Align.TOTOP, bgColor=eveColor.Color(eveColor.PARAGON_BLUE).SetOpacity(0.1).GetRGBA())
    735         Sprite(parent=container, state=carbonui.uiconst.UI_DISABLED, align=carbonui.Align.CENTERLEFT, texturePath='res:/UI/texture/classes/paintTool/paragon_logo.png', height=32, width=34, left=8)
    736         carbonui.text.styles.TextBody(parent=container, align=carbonui.Align.TOTOP, padding=(50, 16, 8, 0), text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/Title'))
    737         carbonui.text.styles.TextDetail(parent=container, align=carbonui.Align.TOTOP, padding=(50, 0, 8, 16), text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/ShortDescription'))
    738         open_store_btn = Button(parent=self, align=carbonui.Align.TOTOP, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/ViewStoreButton'), frame_type=carbonui.ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT, func=self._open_store)
    739         open_store_btn.analyticID = OPEN_SKINR_BUTTON_ANALYTIC_ID
    740 
    741     def _open_store(self, *args, **kwargs):
    742         from eve.client.script.ui.cosmetics.ship.shipSKINRWindow import ShipSKINRWindow
    743         from eve.client.script.ui.cosmetics.ship.const import SkinrPage
    744         ShipSKINRWindow.open_on_paragon_hub(ship_type_id=self.shipTypeID)