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

craftingWindow.py (55194B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\dynamicItem\craftingWindow.py
      2 from __future__ import absolute_import
      3 import __builtin__
      4 import math
      5 import dogma.data as dogma_data
      6 from carbon.client.script.environment.AudioUtil import PlaySound
      7 from carbonui import uiconst
      8 from carbonui.control.contextMenu.menuData import MenuData
      9 from carbonui.control.scrollContainer import ScrollContainer
     10 from carbonui.primitives.container import Container
     11 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     12 from carbonui.primitives.fill import Fill
     13 from carbonui.primitives.sprite import Sprite
     14 from clientsettings.setting import CharSettingBool
     15 from carbonui.uianimations import animations
     16 from carbonui.uicore import uicore
     17 from carbonui.util.color import Color
     18 from carbonui.util.various_unsorted import IsUnder
     19 from eve.client.script.ui.control import eveLabel
     20 from eve.client.script.ui.control.entries import item as item_entry
     21 from eve.client.script.ui.control.entries.space import Space
     22 from eve.client.script.ui.control.entries.text import Text
     23 from eve.client.script.ui.control.entries.util import GetFromClass
     24 from eve.client.script.ui.control.eveIcon import Icon
     25 from eve.client.script.ui.control.eveScroll import Scroll
     26 from carbonui.control.window import Window
     27 from eve.client.script.ui.control.itemIcon import ItemIcon
     28 from eve.client.script.ui.control.themeColored import FillThemeColored, SpriteThemeColored
     29 from eve.client.script.ui.control.utilMenu import UtilMenu
     30 from eve.client.script.ui.shared.dynamicItem.const import COLOR_DECAYED, COLOR_GRAVID, COLOR_NEGATIVE, COLOR_POSITIVE, COLOR_UNSTABLE, GetAbnormalTypes, GetDecayedTypes, GetGravidTypes, GetUnstableTypes
     31 from eve.client.script.ui.shared.dynamicItem.info import FormatAttributeBonusRange
     32 from eve.client.script.ui.shared.industry.submitButton import PrimaryButton
     33 from eve.client.script.ui.shared.industry.views.errorFrame import ErrorFrame
     34 from eve.client.script.ui.shared.info.attribute import Attribute, MutatedAttribute
     35 from eve.client.script.ui.util import uix
     36 from eve.common.lib import appConst as const
     37 import blue
     38 import dynamicitemattributes as dynamicitems
     39 import eveexceptions
     40 import evetypes
     41 import localization
     42 import log
     43 import menucheckers
     44 import signals
     45 import trinity
     46 import uthread
     47 import utillib
     48 from localization import GetByLabel
     49 MUTATOR_FAST_ANIMATIONS_SETTING = CharSettingBool('MutatorFastAnimations', False)
     50 RES_BAR_FILL = 'res:/UI/Texture/Classes/DynamicItem/arrows.png'
     51 RES_ERROR_ICON = 'res:/UI/Texture/Icons/icons111_07.png'
     52 RES_INPUT_BACKGROUND = 'res:/UI/Texture/classes/DynamicItem/inputBackground.png'
     53 RES_INPUT_FRAME = 'res:/UI/Texture/classes/DynamicItem/inputFrame.png'
     54 RES_INPUT_RING = 'res:/UI/Texture/classes/DynamicItem/bgCircle.png'
     55 RES_RING_GLOW_MASK = 'res:/UI/Texture/classes/DynamicItem/bgGlowRing.png'
     56 RES_SWIRL = 'res:/UI/Texture/classes/DynamicItem/swirl.png'
     57 RES_WINDOW_ICON = 'res:/ui/Texture/WindowIcons/mutation.png'
     58 
     59 def Item(typeID, itemID = None, locationID = None, flagID = None, ownerID = None):
     60     return utillib.KeyVal(typeID=typeID, itemID=itemID, locationID=locationID, flagID=flagID, ownerID=ownerID)
     61 
     62 
     63 class CraftingController(object):
     64     __notifyevents__ = ['OnItemChange', 'OnSessionChanged']
     65 
     66     def __init__(self, mutator, source = None):
     67         self.onChange = signals.Signal(signalName='onChange')
     68         self.onDragEnter = signals.Signal(signalName='onDragEnter')
     69         self.onDragExit = signals.Signal(signalName='onDragExit')
     70         self.onError = signals.Signal(signalName='onError')
     71         self._craftingResult = None
     72         self._isCrafting = False
     73         self._isResultPresented = False
     74         self._mutator = mutator
     75         self._mutatorTypes = None
     76         self._resultItemID = None
     77         self._settings = __builtin__.settings
     78         self._source = source
     79         sm.RegisterNotify(self)
     80 
     81     @property
     82     def isFastAnimations(self):
     83         return MUTATOR_FAST_ANIMATIONS_SETTING.is_enabled()
     84 
     85     @isFastAnimations.setter
     86     def isFastAnimations(self, isFastAnimations):
     87         MUTATOR_FAST_ANIMATIONS_SETTING.set(isFastAnimations)
     88 
     89     @property
     90     def isResultPresented(self):
     91         return self._isResultPresented
     92 
     93     @property
     94     def mutatorItemID(self):
     95         return self._mutator.itemID
     96 
     97     @property
     98     def mutatorTypeID(self):
     99         return self._mutator.typeID
    100 
    101     @property
    102     def resultItemID(self):
    103         return self._resultItemID
    104 
    105     @property
    106     def sourceTypeID(self):
    107         if self._source is None:
    108             return
    109         return self._source.typeID
    110 
    111     @property
    112     def sourceItemID(self):
    113         if self._source is None:
    114             return
    115         return self._source.itemID
    116 
    117     @property
    118     def resultTypeID(self):
    119         if self._source is None:
    120             return
    121         return dynamicitems.GetResultType(self._mutator.typeID, self._source.typeID)
    122 
    123     def GetAttribute(self, attributeID):
    124         if not self.IsSourceItemSelected():
    125             return Attribute(attributeID, None)
    126         value = None
    127         if self.IsCraftingResultAvailable():
    128             value = self._craftingResult[attributeID]
    129         static = sm.GetService('clientDogmaStaticSvc')
    130         base = static.GetTypeAttribute(self._source.typeID, attributeID)
    131         bonusLow, bonusHigh = self.GetAttributeBonusRange(attributeID)
    132         return MutatedAttribute(attribute=Attribute(attributeID, value), sourceValue=base, minValue=base * bonusLow, maxValue=base * bonusHigh, highIsGood=self._GetHighIsGood(attributeID))
    133 
    134     def _GetHighIsGood(self, attributeID):
    135         attributes = dynamicitems.GetMutatorAttributes(self.mutatorTypeID)
    136         attribute = attributes[attributeID]
    137         return getattr(attribute, 'highIsGood', None)
    138 
    139     def GetApplicableTypes(self):
    140         return dynamicitems.GetApplicableTypes(self.mutatorTypeID)
    141 
    142     def GetRelatedMutatorTypes(self):
    143         if self._mutatorTypes is None:
    144             resultTypes = dynamicitems.GetResultTypes(self.mutatorTypeID)
    145             mutatorTypes = dynamicitems.GetMutatorTypesByResultTypes(resultTypes)
    146             self._mutatorTypes = mutatorTypes
    147         return self._mutatorTypes
    148 
    149     def GetModifiedAttributeIDs(self):
    150         return dynamicitems.GetMutatorAttributes(self.mutatorTypeID).keys()
    151 
    152     def GetAttributeBonusRange(self, attributeID):
    153         attributes = dynamicitems.GetMutatorAttributes(self.mutatorTypeID)
    154         attribute = attributes[attributeID]
    155         return (attribute.min, attribute.max)
    156 
    157     def SelectSourceItem(self, item):
    158         if self._isCrafting or self.IsCraftingResultAvailable():
    159             return
    160         if item is None and self._source is not None:
    161             self._source = None
    162             self.onChange()
    163             return
    164         if item.typeID not in self.GetApplicableTypes():
    165             raise RuntimeError('Invalid source item type')
    166         if item.itemID is None:
    167             item = self._FindItem(item.typeID)
    168         if self.sourceItemID is not None and self.sourceItemID == item.itemID:
    169             return
    170         self._source = item
    171         self.onChange()
    172 
    173     def SelectMutator(self, item):
    174         if self._isCrafting or self.IsCraftingResultAvailable():
    175             return
    176         if item is None:
    177             return
    178         if item.typeID not in dynamicitems.GetAllMutatorTypes():
    179             raise RuntimeError('Invalid mutator item type')
    180         if item.itemID is None:
    181             item = self._FindItem(item.typeID)
    182         if item.itemID is not None and self._mutator.itemID == item.itemID:
    183             return
    184         if self.mutatorTypeID == item.typeID and self.mutatorItemID == item.itemID:
    185             return
    186         if item.typeID not in self.GetRelatedMutatorTypes():
    187             self._source = None
    188             self._mutatorTypes = None
    189         self._mutator = item
    190         if self.IsSourceItemSelected() and not self.IsSourceItemAvailable():
    191             self._source = self._FindItem(self._source.typeID)
    192         self.onChange()
    193 
    194     def IsItemAvailable(self, typeID):
    195         item = self._FindItem(typeID)
    196         return item.itemID is not None
    197 
    198     def _FindItem(self, typeID):
    199         checker = menucheckers.ItemChecker(self._mutator)
    200         if not checker.IsInPilotLocation():
    201             return Item(typeID=typeID, locationID=self._mutator.locationID, flagID=self._mutator.flagID)
    202         invCache = sm.GetService('invCache')
    203         if self._mutator.locationID == session.stationid:
    204             inventory = invCache.GetInventory(const.containerHangar)
    205         else:
    206             inventory = invCache.GetInventoryFromId(self._mutator.locationID)
    207         items = inventory.List(self._mutator.flagID)
    208         for item in items:
    209             if item.typeID == typeID:
    210                 return Item(typeID=typeID, itemID=item.itemID, locationID=self._mutator.locationID, flagID=self._mutator.flagID, ownerID=item.ownerID)
    211 
    212         return Item(typeID=typeID, locationID=self._mutator.locationID, flagID=self._mutator.flagID)
    213 
    214     def IsSourceItemSelected(self):
    215         return self._source is not None
    216 
    217     def IsSourceItemAvailable(self):
    218         if not self.IsSourceItemSelected():
    219             return False
    220         checker = menucheckers.ItemChecker(self._source)
    221         return self._source.itemID is not None and checker.IsInPilotLocation()
    222 
    223     def IsMutatorAvailable(self):
    224         checker = menucheckers.ItemChecker(self._mutator)
    225         return self._mutator.itemID is not None and checker.IsInPilotLocation()
    226 
    227     def ExecuteCrafting(self):
    228         self._isCrafting = True
    229         try:
    230             if not self.IsMutatorAvailable():
    231                 raise RuntimeError('The selected mutator is not available')
    232             if not self.IsSourceItemAvailable():
    233                 raise RuntimeError('Invalid source item selected')
    234             dynamicItemSvc = sm.GetService('dynamicItemSvc')
    235             try:
    236                 itemID = dynamicItemSvc.CreateDynamicItem(self.mutatorItemID, self._source.itemID)
    237             except eveexceptions.UserError:
    238                 raise
    239             except Exception:
    240                 log.LogException()
    241                 message = localization.GetByLabel('UI/DynamicItem/UnknownErrorBody')
    242                 self.onError(message)
    243             else:
    244                 self._craftingResult = dynamicItemSvc.GetMutatedAttributes(itemID)
    245                 self._resultItemID = itemID
    246                 self.onChange()
    247 
    248         finally:
    249             self._isCrafting = False
    250 
    251     def SetResultPresented(self):
    252         self._isResultPresented = True
    253         self.onChange()
    254 
    255     def IsCraftingResultAvailable(self):
    256         return self._craftingResult is not None
    257 
    258     def Close(self):
    259         sm.UnregisterNotify(self)
    260         self.onChange.clear()
    261         self.onDragEnter.clear()
    262         self.onDragExit.clear()
    263         self.onError.clear()
    264 
    265     def AcceptItemDropData(self, dropData):
    266         for entry in dropData:
    267             item = getattr(entry, 'item', None)
    268             if item is None:
    269                 continue
    270             if self.IsAcceptedSourceItem(item):
    271                 self.SelectSourceItem(item)
    272                 break
    273             if self.IsAcceptedMutatorItem(item):
    274                 self.SelectMutator(item)
    275                 break
    276 
    277     def IsAcceptedSourceItem(self, item):
    278         if self._isCrafting or self.IsCraftingResultAvailable():
    279             return False
    280         if not self._IsItemReachable(item):
    281             return False
    282         if item.typeID not in self.GetApplicableTypes():
    283             return False
    284         if self._source is None:
    285             return True
    286         if self._source.itemID is None and item.itemID is not None:
    287             return True
    288         if self._source.itemID != item.itemID:
    289             return True
    290         if self._source.typeID != item.typeID:
    291             return True
    292         return False
    293 
    294     def IsAcceptedMutatorItem(self, item):
    295         if self._isCrafting or self.IsCraftingResultAvailable():
    296             return False
    297         if not self._IsItemReachable(item):
    298             return False
    299         if item.typeID not in dynamicitems.GetAllMutatorTypes():
    300             return False
    301         if self._mutator.itemID is None and item.itemID is not None:
    302             return True
    303         if self._mutator.itemID != item.itemID:
    304             return True
    305         if self._mutator.typeID != item.typeID:
    306             return True
    307         return False
    308 
    309     def _IsItemReachable(self, item):
    310         if item.ownerID != session.charid:
    311             return False
    312         if item.flagID in const.fittingFlags:
    313             return False
    314         return True
    315 
    316     def GetResultDragData(self):
    317         item = utillib.KeyVal(typeID=self.resultTypeID, itemID=self._resultItemID, locationID=self._mutator.locationID, flagID=self._mutator.flagID, ownerID=self._mutator.ownerID, stacksize=1, categoryID=evetypes.GetCategoryID(self.resultTypeID), groupID=evetypes.GetGroupID(self.resultTypeID), singleton=True)
    318         return utillib.KeyVal(__guid__='listentry.InvItem', typeID=self.resultTypeID, item=item, rec=item, label=evetypes.GetName(self.resultTypeID), name=evetypes.GetName(self.resultTypeID))
    319 
    320     def Reset(self):
    321         self._resultItemID = None
    322         self._craftingResult = None
    323         self._isResultPresented = False
    324         self._mutator = self._FindItem(self._mutator.typeID)
    325         if self.IsSourceItemSelected():
    326             self._source = self._FindItem(self._source.typeID)
    327         self.onChange()
    328 
    329     def OnItemChange(self, item, change, location):
    330         itemMovedKeys = (const.ixLocationID, const.ixOwnerID, const.ixFlag)
    331         if not any((key in change for key in itemMovedKeys)):
    332             return
    333         if item.itemID is None:
    334             return
    335         if item.itemID == self.resultItemID:
    336             self.Reset()
    337         elif item.itemID == self.mutatorItemID:
    338             self.SelectMutator(Item(typeID=item.typeID))
    339         elif item.typeID == self.mutatorTypeID and not self.IsMutatorAvailable():
    340             self.SelectMutator(Item(typeID=item.typeID))
    341         elif item.itemID == self.sourceItemID or item.typeID == self.sourceTypeID and not self.IsSourceItemAvailable():
    342             self.SelectSourceItem(Item(typeID=item.typeID))
    343 
    344     def OnSessionChanged(self, isRemote, session, changes):
    345         monitoredChanges = ('locationid', 'stationid', 'structureid', 'shipid')
    346         if any((key in changes for key in monitoredChanges)):
    347             self.Reset()
    348 
    349     def OnDragEnter(self, item):
    350         self.onDragEnter(item)
    351 
    352     def OnDragExit(self):
    353         self.onDragExit()
    354 
    355 
    356 class CraftingWindow(Window):
    357     default_fixedWidth = 750
    358     default_fixedHeight = 478
    359     default_windowID = 'CraftingWindow'
    360     default_caption = GetByLabel('UI/DynamicItem/MutateItem')
    361     default_isCollapseable = False
    362     default_isLightBackgroundConfigurable = False
    363     default_isLockable = False
    364     default_isOverlayable = False
    365     default_isStackable = False
    366     default_iconNum = RES_WINDOW_ICON
    367 
    368     @classmethod
    369     def Open(cls, **attributes):
    370         window = cls.GetIfOpen()
    371         if not window:
    372             return super(CraftingWindow, cls).Open(**attributes)
    373         mutator = attributes.get('mutator', None)
    374         if mutator and mutator.itemID == window.controller.mutatorItemID:
    375             window.Maximize()
    376             return window
    377         window.CloseByUser()
    378         return super(CraftingWindow, cls).Open(**attributes)
    379 
    380     def ApplyAttributes(self, attributes):
    381         super(CraftingWindow, self).ApplyAttributes(attributes)
    382         self.controller = CraftingController(attributes.mutator)
    383         self._dragCookie = None
    384         self._isDragHintActive = False
    385         self.Layout()
    386         self.controller.onDragEnter.connect(self.OnItemDragEnter)
    387         self.controller.onDragExit.connect(self.OnItemDragExit)
    388         self.controller.onError.connect(self.OnError)
    389 
    390     def Layout(self):
    391         buildCont = Container(name='buildCont', parent=self.GetMainArea(), align=uiconst.TOLEFT, width=400)
    392         buttonCont = ContainerAutoSize(name='buttonCont', parent=buildCont, align=uiconst.TOBOTTOM)
    393         BuildButton(parent=buttonCont, align=uiconst.CENTERTOP, controller=self.controller)
    394         inputCont = Container(name='inputCont', parent=buildCont, align=uiconst.TOALL)
    395         self.sourceItemSlot = SourceItemSlot(parent=inputCont, align=uiconst.CENTER, top=-10, controller=self.controller)
    396         self.sourceItemSlot.OnDropData = self.OnDropData
    397         attributeCont = Container(name='attributeCont', parent=self.GetMainArea(), align=uiconst.TOALL)
    398         mutatorItemSlot = MutatorItemSlot(parent=attributeCont, align=uiconst.TOTOP, controller=self.controller)
    399         mutatorItemSlot.OnDropData = self.OnDropData
    400         attributeScroll = ScrollContainer(parent=attributeCont, align=uiconst.TOALL, padTop=5, padBottom=5)
    401         attributeListCont = ContainerAutoSize(parent=attributeScroll, align=uiconst.TOPLEFT)
    402         AttributeList(parent=attributeListCont, align=uiconst.TOPLEFT, controller=self.controller, width=300)
    403 
    404     def UpdateState(self):
    405         name = evetypes.GetName(self.controller.mutatorTypeID)
    406         self.SetCaption(name)
    407 
    408     def Close(self, *args, **kwargs):
    409         self.controller.Close()
    410         if self._dragCookie is not None:
    411             uicore.uilib.UnregisterForTriuiEvents(self._dragCookie)
    412             self._dragCookie = None
    413         super(CraftingWindow, self).Close(*args, **kwargs)
    414 
    415     def GetUtilMenu(self, menu):
    416         menu.AddCheckBox(text=localization.GetByLabel('UI/DynamicItem/OptionFastAnimations'), checked=MUTATOR_FAST_ANIMATIONS_SETTING.is_enabled(), callback=MUTATOR_FAST_ANIMATIONS_SETTING.toggle)
    417 
    418     def GetMenuMoreOptions(self):
    419         m = MenuData()
    420         m.AddCheckbox(localization.GetByLabel('UI/DynamicItem/OptionFastAnimations'), setting=MUTATOR_FAST_ANIMATIONS_SETTING)
    421         return m
    422 
    423     def OnItemDragEnter(self, item):
    424         if self.controller.IsAcceptedSourceItem(item):
    425             self._isDragHintActive = True
    426             uthread.new(self.AnimDragEnter)
    427 
    428     def OnItemDragExit(self):
    429         if self._isDragHintActive:
    430             uthread.new(self.AnimDragExit)
    431 
    432     def AnimDragEnter(self):
    433         self.sourceItemSlot.AnimShowDragHint()
    434 
    435     def AnimDragExit(self):
    436         self.sourceItemSlot.AnimHideDragHint()
    437 
    438     def OnError(self, message):
    439         error = ErrorMessage(parent=self.GetMainArea(), align=uiconst.CENTER, width=750, height=450, idx=0)
    440         error.DelegateEvents(self)
    441         error.SetMessage(message)
    442         uthread.new(error.AnimShow)
    443         self.controller.Close()
    444 
    445     def OnDropData(self, source, data):
    446         self.controller.AcceptItemDropData(data)
    447         if self._dragCookie is not None:
    448             uicore.uilib.UnregisterForTriuiEvents(self._dragCookie)
    449             self._dragCookie = None
    450             uthread.new(self.AnimDragExit)
    451 
    452     def OnGlobalDrag(self, *args):
    453         target = uicore.uilib.mouseOver
    454         if uicore.IsDragging() and (IsUnder(target, self) or target == self):
    455             return True
    456         else:
    457             self.controller.OnDragExit()
    458             self._dragCookie = None
    459             return False
    460 
    461     def OnDragEnter(self, source, data):
    462         if self._dragCookie is not None:
    463             return
    464         for entry in data:
    465             item = getattr(entry, 'item', None)
    466             if item is None:
    467                 continue
    468             self._dragCookie = uicore.uilib.RegisterForTriuiEvents(uiconst.UI_MOUSEMOVEDRAG, self.OnGlobalDrag)
    469             self.controller.OnDragEnter(item)
    470             break
    471 
    472 
    473 class ErrorMessage(Container):
    474     default_state = uiconst.UI_NORMAL
    475 
    476     def ApplyAttributes(self, attributes):
    477         super(ErrorMessage, self).ApplyAttributes(attributes)
    478         self.Layout()
    479 
    480     def Layout(self):
    481         Fill(bgParent=self, color=Color.BLACK, opacity=0.75)
    482         dialogCont = ContainerAutoSize(parent=self, align=uiconst.CENTER, alignMode=uiconst.TOPLEFT, width=440)
    483         Sprite(parent=dialogCont, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, pos=(0, 0, 64, 64), texturePath=RES_ERROR_ICON)
    484         messageCont = ContainerAutoSize(parent=dialogCont, align=uiconst.TOPLEFT, left=80, width=360)
    485         FillThemeColored(bgParent=messageCont, colorType=uiconst.COLORTYPE_UIBASECONTRAST, opacity=0.9)
    486         eveLabel.EveCaptionLarge(parent=messageCont, align=uiconst.TOTOP, padding=(16, 16, 16, 8), text=localization.GetByLabel('UI/DynamicItem/UnknownErrorHeader'))
    487         self.label = eveLabel.EveLabelLarge(parent=messageCont, align=uiconst.TOTOP, padding=(16, 0, 16, 16))
    488 
    489     def SetMessage(self, message):
    490         self.label.SetText(message)
    491 
    492     def AnimShow(self):
    493         animations.FadeTo(self, duration=0.8, curveType=uiconst.ANIM_OVERSHOT)
    494 
    495 
    496 class AttributeList(ContainerAutoSize):
    497 
    498     def ApplyAttributes(self, attributes):
    499         super(AttributeList, self).ApplyAttributes(attributes)
    500         self.controller = attributes.controller
    501         self._mutatorTypes = self.controller.GetRelatedMutatorTypes()
    502         uthread.new(self.Load)
    503         self.controller.onChange.connect(self.OnChange)
    504 
    505     def Load(self):
    506         entries = []
    507         for attributeID in self.controller.GetModifiedAttributeIDs():
    508             entry = AttributeEntry(align=uiconst.TOTOP, top=8, controller=self.controller, attributeID=attributeID)
    509             entries.append(entry)
    510 
    511         sortedEntries = sorted(entries, key=lambda e: e.label)
    512         for i, entry in enumerate(sortedEntries):
    513             entry.SetParent(self)
    514             entry.SetAnimationIndex(i)
    515 
    516     def Reload(self):
    517         self.Flush()
    518         self.Load()
    519         for child in self.children:
    520             uthread.new(child.AnimEnter)
    521 
    522     def OnChange(self):
    523         if self.controller.mutatorTypeID not in self._mutatorTypes:
    524             self._mutatorTypes = self.controller.GetRelatedMutatorTypes()
    525             uthread.new(self.Reload)
    526         else:
    527             for child in self.children:
    528                 child.OnChange()
    529 
    530 
    531 class MutatorItemSlot(Container):
    532     default_height = 80
    533     default_state = uiconst.UI_NORMAL
    534 
    535     def ApplyAttributes(self, attributes):
    536         super(MutatorItemSlot, self).ApplyAttributes(attributes)
    537         self.controller = attributes.controller
    538         self._selectedTypeID = self.controller.mutatorTypeID
    539         self._isAvailable = self.controller.IsMutatorAvailable()
    540         self.Layout()
    541         self.UpdateState()
    542         self.controller.onChange.connect(self.OnChange)
    543         self.controller.onDragEnter.connect(self.OnItemDragEnter)
    544         self.controller.onDragExit.connect(self.OnItemDragExit)
    545 
    546     def Layout(self):
    547         self.bgFill = FillThemeColored(bgParent=self, colorType=uiconst.COLORTYPE_UIHILIGHT)
    548         self.errorFrame = ErrorFrame(bgParent=self, opacity=0.0, opacityHigh=0.2, opacityLow=0.1, color=COLOR_NEGATIVE)
    549         mutatorIconCont = Container(parent=self, align=uiconst.TOLEFT, width=80)
    550         self.mutatorIcon = ItemIcon(parent=mutatorIconCont, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=64, height=64, showOmegaOverlay=False)
    551         mutatorLabelCont = Container(parent=self, align=uiconst.TOALL)
    552         floatLabelCont = ContainerAutoSize(parent=mutatorLabelCont, align=uiconst.CENTERLEFT, width=206)
    553         self.mutatorLabel = eveLabel.EveLabelLargeBold(parent=floatLabelCont, align=uiconst.TOTOP)
    554         eveLabel.EveLabelMedium(parent=floatLabelCont, align=uiconst.TOTOP, text=localization.GetByLabel('UI/DynamicItem/MutationEffects'), opacity=0.6)
    555 
    556     def UpdateState(self):
    557         self._selectedTypeID = self.controller.mutatorTypeID
    558         self._isAvailable = self.controller.IsMutatorAvailable()
    559         name = evetypes.GetName(self._selectedTypeID)
    560         self.mutatorLabel.SetText(name)
    561         self.mutatorIcon.SetTypeID(self._selectedTypeID)
    562         if self._isAvailable:
    563             self.errorFrame.Hide()
    564             self.mutatorIcon.opacity = 1.0
    565             self.mutatorLabel.opacity = 1.0
    566         else:
    567             self.errorFrame.Show()
    568             self.mutatorIcon.opacity = 0.5
    569             self.mutatorLabel.opacity = 0.5
    570 
    571     def AnimUpdateItemIcon(self):
    572         animations.FadeTo(self.mutatorIcon, endVal=self.mutatorIcon.opacity, duration=0.3, curveType=uiconst.ANIM_OVERSHOT, timeOffset=0.1)
    573         animations.Tr2DScaleIn(self.mutatorIcon.icon, scaleCenter=(0.5, 0.5), duration=0.2)
    574         animations.FadeIn(self.mutatorLabel, endVal=self.mutatorLabel.opacity, duration=0.3)
    575         if uicore.uilib.mouseOver != self:
    576             self.AnimDeactivate()
    577 
    578     def LoadTooltipPanel(self, panel, owner):
    579         if not self.controller.IsCraftingResultAvailable():
    580             panel.SetState(uiconst.UI_NORMAL)
    581             panel.margin = (8, 8, 8, 8)
    582             MutatorItemScrollList(parent=panel, align=uiconst.TOPLEFT, width=284, height=110, controller=self.controller)
    583 
    584     def GetTooltipPositionFallbacks(self):
    585         return [uiconst.POINT_BOTTOM_2,
    586          uiconst.POINT_TOP_2,
    587          uiconst.POINT_LEFT_2,
    588          uiconst.POINT_RIGHT_2]
    589 
    590     def AnimActivate(self):
    591         animations.FadeTo(self.bgFill, startVal=self.bgFill.opacity, endVal=0.35, duration=0.1)
    592 
    593     def AnimDeactivate(self):
    594         animations.FadeTo(self.bgFill, startVal=self.bgFill.opacity, endVal=0.25, duration=0.3)
    595 
    596     def OnMouseEnter(self, *args):
    597         if self.controller.IsCraftingResultAvailable():
    598             return
    599         PlaySound(uiconst.SOUND_BUTTON_HOVER)
    600         uthread.new(self.AnimActivate)
    601 
    602     def OnMouseExit(self, *args):
    603         uthread.new(self.AnimDeactivate)
    604 
    605     def OnItemDragEnter(self, item):
    606         if self.controller.IsAcceptedMutatorItem(item):
    607             uthread.new(self.AnimActivate)
    608 
    609     def OnItemDragExit(self):
    610         uthread.new(self.AnimDeactivate)
    611 
    612     def OnChange(self):
    613         if self._selectedTypeID != self.controller.mutatorTypeID or self._isAvailable != self.controller.IsMutatorAvailable():
    614             self.UpdateState()
    615             uthread.new(self.AnimUpdateItemIcon)
    616 
    617 
    618 class MutatorItemScrollList(Scroll):
    619     default_multiSelect = False
    620 
    621     def ApplyAttributes(self, attributes):
    622         super(MutatorItemScrollList, self).ApplyAttributes(attributes)
    623         self.controller = attributes.controller
    624         uthread.new(self.LoadMutatorItemList)
    625 
    626     def LoadMutatorItemList(self):
    627         self.ShowLoading()
    628         try:
    629             entries = {}
    630             for typeID in self.controller.GetRelatedMutatorTypes():
    631                 key = evetypes.GetName(typeID)
    632                 entry = GetFromClass(SourceItemEntry, {'typeID': typeID,
    633                  'label': evetypes.GetName(typeID),
    634                  'getIcon': True,
    635                  'isSelected': typeID == self.controller.sourceTypeID,
    636                  'isItemOwned': self.controller.IsItemAvailable(typeID)})
    637                 entries[key] = entry
    638 
    639         finally:
    640             self.HideLoading()
    641 
    642         sortedEntries = []
    643         if entries:
    644             text = localization.GetByLabel('UI/DynamicItem/MutaplasmidListHeader')
    645             entry = GetFromClass(HeaderEntry, {'text': text})
    646             sortedEntries.append(entry)
    647             sortedEntries.extend(sortedValuesByKey(entries))
    648         self.Load(contentList=sortedEntries)
    649 
    650     def OnSelectionChange(self, nodes):
    651         if not nodes:
    652             return
    653         item = Item(typeID=nodes[0].typeID, itemID=None)
    654         self.controller.SelectMutator(item)
    655 
    656     def Prepare_Underlay_(self):
    657         pass
    658 
    659 
    660 class SourceItemSlot(Container):
    661     default_width = 70
    662     default_height = 70
    663     default_state = uiconst.UI_NORMAL
    664     default_clipChildren = False
    665     isDragObject = True
    666 
    667     def ApplyAttributes(self, attributes):
    668         super(SourceItemSlot, self).ApplyAttributes(attributes)
    669         self.controller = attributes.controller
    670         self._selectedMutatorID = self.controller.mutatorTypeID
    671         self._selectedTypeID = self.controller.sourceTypeID
    672         self._isAvailable = self.controller.IsSourceItemAvailable()
    673         self.Layout()
    674         self.Load()
    675         self.controller.onChange.connect(self.OnChange)
    676 
    677     def Layout(self):
    678         swirlCont = Container(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, pos=(0, 0, 64, 64), clipChildren=False)
    679         self.swirlPrimary = Sprite(parent=swirlCont, align=uiconst.CENTER, pos=(0, 0, 500, 500), texturePath=RES_SWIRL, color=self.GetSwirlColor())
    680         self.swirlSecondary = Sprite(parent=swirlCont, align=uiconst.CENTER, pos=(0, 0, 500, 500), texturePath=RES_SWIRL, textureSecondaryPath=RES_SWIRL, spriteEffect=trinity.TR2_SFX_MODULATE, blendMode=trinity.TR2_SBM_ADDX2, color=self.GetSwirlColor(), rotation=math.pi)
    681         self.swirlTiny = Sprite(parent=swirlCont, align=uiconst.CENTER, pos=(0, 0, 200, 200), texturePath=RES_SWIRL, textureSecondaryPath=RES_SWIRL, spriteEffect=trinity.TR2_SFX_MODULATE, blendMode=trinity.TR2_SBM_ADDX2, color=self.GetSwirlColor(), opacity=0.0)
    682         animations.MorphScalar(self.swirlPrimary, 'rotation', startVal=0.0, endVal=-2.0 * math.pi, duration=80.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    683         animations.SpSecondaryTextureRotate(self.swirlSecondary, endVal=-2.0 * math.pi, duration=40.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    684         animations.SpSecondaryTextureScale(self.swirlSecondary, startVal=(0.8, 0.8), endVal=(1.2, 1.2), duration=35.0, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    685         self.itemIcon = ItemIcon(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=64, height=64, showOmegaOverlay=False)
    686         self.box = SpriteThemeColored(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, pos=(0, 0, 70, 70), texturePath=RES_INPUT_FRAME, opacity=0.5, colorType=uiconst.COLORTYPE_UIHILIGHTGLOW)
    687         self.errorFrame = ErrorFrame(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, pos=(0, 0, 64, 64), opacity=0.0, opacityHigh=0.2, opacityLow=0.1, color=COLOR_NEGATIVE)
    688         self.bgGrid = Sprite(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, pos=(0, 0, 64, 64), texturePath=RES_INPUT_BACKGROUND, opacity=0.0)
    689         Fill(parent=self, align=uiconst.CENTER, pos=(0, 0, 64, 64), color=Color.BLACK, opacity=0.2)
    690         self.bgCircle = SpriteThemeColored(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=400, height=400, texturePath=RES_INPUT_RING, textureSecondaryPath=RES_RING_GLOW_MASK, spriteEffect=trinity.TR2_SFX_MODULATE, opacity=0.0, colorType=uiconst.COLORTYPE_UIHILIGHTGLOW)
    691         self.bgCircleUnderlay = SpriteThemeColored(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=400, height=400, texturePath=RES_INPUT_RING, opacity=0.6, colorType=uiconst.COLORTYPE_UIHILIGHTGLOW)
    692 
    693     def Load(self):
    694         if self._selectedTypeID is not None:
    695             self.itemIcon.SetTypeID(self._selectedTypeID)
    696             if self._isAvailable:
    697                 self.itemIcon.opacity = 1.0
    698                 self.errorFrame.Hide()
    699             else:
    700                 self.itemIcon.opacity = 0.5
    701                 self.errorFrame.Show()
    702 
    703     def AnimUpdateItemIcon(self):
    704         animations.FadeOut(self.bgCircle, duration=0.3, callback=self.bgCircle.StopAnimations)
    705         if self._selectedTypeID is None:
    706             self.itemIcon.opacity = 0.0
    707             self.errorFrame.Hide()
    708             animations.FadeOut(self.bgGrid, duration=0.3)
    709         else:
    710             self.itemIcon.SetTypeID(self._selectedTypeID)
    711             if self._isAvailable:
    712                 opacity = 1.0
    713                 self.errorFrame.Hide()
    714             else:
    715                 opacity = 0.5
    716                 self.errorFrame.Show()
    717             animations.FadeTo(self.itemIcon, endVal=opacity, duration=0.3, curveType=uiconst.ANIM_OVERSHOT5, timeOffset=0.1)
    718             animations.Tr2DScaleIn(self.itemIcon.icon, scaleCenter=(0.5, 0.5), duration=0.2)
    719             animations.FadeTo(self.bgGrid, startVal=self.bgGrid.opacity, endVal=0.8, duration=0.2)
    720 
    721     def AnimShowDragHint(self):
    722         animations.FadeTo(self.box, startVal=self.box.opacity, endVal=1.0, duration=0.3)
    723         animations.FadeTo(self.bgCircle, startVal=0.0, endVal=10.0, duration=1.6, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    724         animations.MorphVector2(self.bgCircle, 'scaleSecondary', startVal=(1.1, 1.1), endVal=(2.5, 2.5), duration=1.6, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    725 
    726     def AnimHideDragHint(self):
    727         self.bgCircle.StopAnimations()
    728         self.box.StopAnimations()
    729         animations.FadeTo(self.bgCircle, startVal=self.bgCircle.opacity, endVal=0.0, duration=0.6)
    730         animations.FadeTo(self.box, startVal=self.box.opacity, endVal=0.5, duration=0.3)
    731 
    732     def AnimCraft(self):
    733         self.Disable()
    734         animations.FadeOut(self.itemIcon, duration=0.3)
    735         animations.MorphScalar(self.itemIcon, 'width', startVal=64, endVal=55, duration=0.5)
    736         animations.MorphScalar(self.itemIcon, 'height', startVal=64, endVal=55, duration=0.5)
    737         animations.FadeTo(self.swirlPrimary, startVal=self.swirlPrimary.opacity, endVal=1.6, duration=2.0, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    738         animations.FadeTo(self.swirlSecondary, startVal=self.swirlSecondary.opacity, endVal=4.0, duration=1.0)
    739         animations.SpSecondaryTextureRotate(self.swirlSecondary, startVal=self.swirlSecondary.rotationSecondary, endVal=self.swirlSecondary.rotationSecondary - 2.0 * math.pi, duration=22.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    740         animations.FadeIn(self.swirlTiny, duration=0.8, curveType=uiconst.ANIM_OVERSHOT5)
    741         animations.SpSecondaryTextureRotate(self.swirlTiny, endVal=-2.0 * math.pi, duration=7.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    742         attributeCount = len(self.controller.GetModifiedAttributeIDs())
    743         animationFactor = 1.0 if not MUTATOR_FAST_ANIMATIONS_SETTING.is_enabled() else 10.0
    744         duration = 1.5 + (attributeCount - 1) * 1.2 / animationFactor
    745         blue.pyos.synchro.SleepWallclock(duration * 1000)
    746         if self.destroyed:
    747             return
    748         self.itemIcon.SetTypeID(self.controller.resultTypeID)
    749         self.Enable()
    750         animations.FadeTo(self.bgCircle, startVal=0.0, endVal=10.0, duration=1.6, curveType=uiconst.ANIM_WAVE, loops=uiconst.ANIM_REPEAT)
    751         animations.MorphVector2(self.bgCircle, 'scaleSecondary', startVal=(2.5, 2.5), endVal=(1.1, 1.1), duration=1.6, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    752         animations.FadeTo(self.itemIcon, duration=0.3, curveType=uiconst.ANIM_OVERSHOT5, timeOffset=0.1)
    753         animations.MorphScalar(self.itemIcon, 'width', startVal=55, endVal=64, duration=0.4)
    754         animations.MorphScalar(self.itemIcon, 'height', startVal=55, endVal=64, duration=0.4, callback=self.controller.SetResultPresented)
    755         animations.FadeOut(self.swirlTiny, duration=0.5)
    756         animations.FadeTo(self.swirlPrimary, startVal=self.swirlPrimary.opacity, endVal=1.0, duration=3.0)
    757         animations.FadeTo(self.swirlSecondary, startVal=self.swirlSecondary.opacity, endVal=0.0, duration=3.0, sleep=True)
    758         if self.destroyed:
    759             return
    760         animations.SpSecondaryTextureRotate(self.swirlSecondary, startVal=self.swirlSecondary.rotationSecondary, endVal=self.swirlSecondary.rotationSecondary - 2.0 * math.pi, duration=40.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT)
    761         animations.FadeIn(self.swirlSecondary, duration=3.0)
    762 
    763     def AnimUpdateSwirl(self):
    764         newColor = self.GetSwirlColor()
    765         animations.SpColorMorphTo(self.swirlPrimary, endColor=newColor, duration=0.5)
    766         animations.SpColorMorphTo(self.swirlSecondary, endColor=newColor, duration=0.5)
    767         animations.SpColorMorphTo(self.swirlTiny, endColor=newColor, duration=0.5)
    768 
    769     def LoadTooltipPanel(self, panel, owner):
    770         panel.SetState(uiconst.UI_NORMAL)
    771         panel.margin = (8, 8, 8, 8)
    772         if self.controller.IsCraftingResultAvailable():
    773             name = evetypes.GetName(self.controller.resultTypeID)
    774             panel.AddLabelMedium(text=name)
    775         else:
    776             SourceItemScrollList(parent=panel, align=uiconst.TOPLEFT, width=250, height=200, controller=self.controller)
    777 
    778     def OnChange(self):
    779         if self.controller.isResultPresented:
    780             return
    781         if self.controller.IsCraftingResultAvailable():
    782             self._selectedTypeID = self.controller.resultTypeID
    783             self._isAvailable = True
    784             uthread.new(self.AnimCraft)
    785         elif self._selectedTypeID != self.controller.sourceTypeID or self._isAvailable != self.controller.IsSourceItemAvailable():
    786             self._selectedTypeID = self.controller.sourceTypeID
    787             self._isAvailable = self.controller.IsSourceItemAvailable()
    788             uthread.new(self.AnimUpdateItemIcon)
    789         if self._selectedMutatorID != self.controller.mutatorTypeID:
    790             self._selectedMutatorID = self.controller.mutatorTypeID
    791             uthread.new(self.AnimUpdateSwirl)
    792 
    793     def OnMouseEnter(self, *args):
    794         PlaySound(uiconst.SOUND_BUTTON_HOVER)
    795         animations.FadeTo(self.box, startVal=self.box.opacity, endVal=1.0, duration=0.2)
    796 
    797     def OnMouseExit(self, *args):
    798         animations.FadeTo(self.box, startVal=self.box.opacity, endVal=0.5, duration=0.3)
    799 
    800     def GetDragData(self, *args):
    801         if self.controller.IsCraftingResultAvailable():
    802             return [self.controller.GetResultDragData()]
    803         else:
    804             return []
    805 
    806     def GetSwirlColor(self):
    807         typeID = self.controller.mutatorTypeID
    808         if typeID in GetDecayedTypes():
    809             return COLOR_DECAYED
    810         elif typeID in GetGravidTypes():
    811             return COLOR_GRAVID
    812         elif typeID in GetUnstableTypes() or typeID in GetAbnormalTypes():
    813             return COLOR_UNSTABLE
    814         else:
    815             return Color.WHITE
    816 
    817 
    818 class SourceItemScrollList(Scroll):
    819     default_multiSelect = False
    820 
    821     def ApplyAttributes(self, attributes):
    822         super(SourceItemScrollList, self).ApplyAttributes(attributes)
    823         self.controller = attributes.controller
    824         uthread.new(self.LoadSourceItemList)
    825 
    826     def LoadSourceItemList(self):
    827         self.ShowLoading()
    828         try:
    829             owned = {}
    830             unowned = {}
    831             for typeID in self.controller.GetApplicableTypes():
    832                 if typeID == self.controller.sourceTypeID:
    833                     isOwned = self.controller.IsSourceItemAvailable()
    834                     itemID = self.controller.sourceItemID
    835                 else:
    836                     isOwned = self.controller.IsItemAvailable(typeID)
    837                     itemID = None
    838                 key = (evetypes.GetMetaLevel(typeID), evetypes.GetName(typeID))
    839                 entry = GetFromClass(SourceItemEntry, {'typeID': typeID,
    840                  'itemID': itemID,
    841                  'label': evetypes.GetName(typeID),
    842                  'getIcon': True,
    843                  'isSelected': typeID == self.controller.sourceTypeID,
    844                  'isItemOwned': isOwned})
    845                 if isOwned:
    846                     owned[key] = entry
    847                 else:
    848                     unowned[key] = entry
    849 
    850         finally:
    851             self.HideLoading()
    852 
    853         entries = []
    854         if owned or unowned:
    855             text = localization.GetByLabel('UI/DynamicItem/SourceItemListHeader')
    856             entry = GetFromClass(HeaderEntry, {'text': text})
    857             entries.append(entry)
    858         if owned:
    859             entries.extend(sortedValuesByKey(owned))
    860         if unowned:
    861             if owned:
    862                 entries.append(GetFromClass(Space, {'height': 8}))
    863             entries.extend(sortedValuesByKey(unowned))
    864         self.Load(contentList=entries)
    865         self.ScrollToSelectedNode()
    866 
    867     def OnSelectionChange(self, nodes):
    868         if not nodes:
    869             return
    870         data = nodes[0]
    871         item = Item(typeID=data.typeID, itemID=data.itemID)
    872         self.controller.SelectSourceItem(item)
    873 
    874     def Prepare_Underlay_(self):
    875         pass
    876 
    877 
    878 def sortedValuesByKey(dictionary):
    879     for _, entry in sorted(dictionary.items(), key=lambda x: x[0]):
    880         yield entry
    881 
    882 
    883 class HeaderEntry(Text):
    884 
    885     def Startup(self, *args):
    886         super(HeaderEntry, self).Startup(*args)
    887         self.sr.text.align = uiconst.TOPLEFT
    888         self.sr.text.padLeft = -8
    889         self.sr.text.state = uiconst.UI_DISABLED
    890 
    891     def GetHeight(self, *args):
    892         node, width = args
    893         textHeight = uix.GetTextHeight(node.text, maxLines=1)
    894         node.height = 4 + textHeight
    895         return node.height
    896 
    897     def GetMenu(self):
    898         return []
    899 
    900 
    901 class SourceItemEntry(item_entry.Item):
    902 
    903     def Load(self, node):
    904         super(SourceItemEntry, self).Load(node)
    905         if not node.isItemOwned:
    906             self.sr.icon.opacity = self.sr.label.opacity = 0.25
    907 
    908     def UpdateAlignment(self, *args):
    909         budget = super(SourceItemEntry, self).UpdateAlignment(*args)
    910         budgetLeft, budgetTop, budgetWidth, budgetHeight, sizeChange = budget
    911         self.sr.label.SetRightAlphaFade(budgetWidth - 54, 16)
    912         return budget
    913 
    914 
    915 class AttributeEntry(Container):
    916     default_clipChildren = False
    917     default_height = 48
    918 
    919     def ApplyAttributes(self, attributes):
    920         super(AttributeEntry, self).ApplyAttributes(attributes)
    921         self.attributeID = attributes.attributeID
    922         self.controller = attributes.controller
    923         self._animIndex = attributes.get('animationIndex', 0)
    924         self._lastSourceTypeID = self.controller.sourceTypeID
    925         self._lastMutatorTypeID = self.controller.mutatorTypeID
    926         self.Layout()
    927         uthread.new(self.Load)
    928 
    929     @property
    930     def label(self):
    931         return dogma_data.get_attribute_display_name(self.attributeID)
    932 
    933     def Layout(self):
    934         self.valueLabel = None
    935         self.bgFill = FillThemeColored(bgParent=self, colorType=uiconst.COLORTYPE_UIHILIGHT)
    936         iconCont = Container(parent=self, align=uiconst.TOLEFT, width=46)
    937         self.icon = Icon(parent=iconCont, align=uiconst.CENTER, state=uiconst.UI_DISABLED, size=32)
    938         self.nameLabel = eveLabel.EveLabelMedium(parent=self, align=uiconst.TOTOP, state=uiconst.UI_NORMAL, top=6, singleline=True, opacity=self.GetNameLabelOpacity())
    939         self.nameLabel.SetRightAlphaFade(fadeEnd=200, maxFadeWidth=10)
    940         self.mutationBar = MutationBar(parent=self, align=uiconst.TOBOTTOM, idx=0, controller=self.controller, attributeID=self.attributeID, animationIndex=self._animIndex)
    941 
    942     def Load(self):
    943         self.icon.LoadIcon(dogma_data.get_attribute_icon_id(self.attributeID))
    944         self.nameLabel.SetText(dogma_data.get_attribute_display_name(self.attributeID))
    945         self.nameLabel.SetHint(dogma_data.get_attribute_display_name(self.attributeID))
    946         self.UpdateAttributeValue()
    947 
    948     def SetAnimationIndex(self, index):
    949         self._animIndex = index
    950         self.mutationBar.SetAnimationIndex(index)
    951 
    952     def ConstructValueLabel(self):
    953         attribute = self.controller.GetAttribute(self.attributeID)
    954         if self.controller.IsCraftingResultAvailable():
    955             return FinalAttributeValueLabel(parent=self, align=uiconst.TOTOP_NOPUSH, height=16, controller=self.controller, attributeID=self.attributeID, value=attribute.value)
    956         if self.controller.IsSourceItemSelected():
    957             text = self._FormatAttributeRange(attribute)
    958         else:
    959             low, high = self.controller.GetAttributeBonusRange(self.attributeID)
    960             text = FormatAttributeBonusRange(self.attributeID, low, high)
    961         return eveLabel.EveLabelMedium(parent=self, align=uiconst.TOTOP_NOPUSH, text=text)
    962 
    963     def GetNameLabelOpacity(self):
    964         if self.controller.IsCraftingResultAvailable():
    965             return 0.6
    966         else:
    967             return 1.0
    968 
    969     def UpdateAttributeValue(self):
    970         if self.valueLabel:
    971             self.valueLabel.Close()
    972         self.valueLabel = self.ConstructValueLabel()
    973 
    974     def AnimEnter(self):
    975         animations.FadeTo(self, duration=0.3, timeOffset=self._animIndex * 0.05)
    976 
    977     def AnimUpdateAttributeValue(self):
    978         if self.valueLabel:
    979             animations.FadeOut(self.valueLabel, duration=0.1, sleep=True)
    980         if self.destroyed:
    981             return
    982         self.UpdateAttributeValue()
    983         animations.FadeTo(self.valueLabel, duration=0.25, curveType=uiconst.ANIM_OVERSHOT2, timeOffset=self._animIndex * 0.05)
    984         animations.FadeTo(self.nameLabel, startVal=self.nameLabel.opacity, endVal=self.GetNameLabelOpacity(), duration=0.3)
    985 
    986     def AnimShowFinalValue(self):
    987         animations.FadeOut(self.valueLabel, duration=0.05, sleep=True)
    988         if self.destroyed:
    989             return
    990         self.UpdateAttributeValue()
    991         attribute = self.controller.GetAttribute(self.attributeID)
    992         self.valueLabel.value = attribute.sourceValue
    993         animationFactor = 1.0 if not MUTATOR_FAST_ANIMATIONS_SETTING.is_enabled() else 10.0
    994         uthread.new(self.valueLabel.AnimSetValue, attribute.value, duration=1.5, timeOffset=self._animIndex * 1.2 / animationFactor)
    995         animations.FadeTo(self.nameLabel, startVal=self.nameLabel.opacity, endVal=self.GetNameLabelOpacity(), duration=0.3, timeOffset=self._animIndex * 1.2 / animationFactor)
    996         animations.FadeTo(self.valueLabel, startVal=0.0, endVal=1.0, duration=1.5, curveType=uiconst.ANIM_OVERSHOT2, timeOffset=self._animIndex * 1.2 / animationFactor)
    997         animations.MorphScalar(self.valueLabel, 'left', startVal=-10, endVal=0, duration=0.2, timeOffset=self._animIndex * 1.2 / animationFactor)
    998         animations.FadeTo(self.bgFill, startVal=self.bgFill.opacity, endVal=self.bgFill.opacity + 0.2, duration=0.25, curveType=uiconst.ANIM_WAVE, timeOffset=1.5 + self._animIndex * 1.2 / animationFactor)
    999 
   1000     def _FormatAttributeRange(self, attribute):
   1001         negativeColor = Color(*COLOR_NEGATIVE).GetHex()
   1002         positiveColor = Color(*COLOR_POSITIVE).GetHex()
   1003         if not attribute.highIsGood:
   1004             negativeColor, positiveColor = positiveColor, negativeColor
   1005         if attribute.mutationLow < attribute.sourceValue:
   1006             lowColor = negativeColor
   1007         else:
   1008             lowColor = positiveColor
   1009         if attribute.mutationHigh < attribute.sourceValue:
   1010             highColor = negativeColor
   1011         else:
   1012             highColor = positiveColor
   1013         template = u'<color={colorLow}>{low}</color> - <color={colorHigh}>{high}</color>'
   1014         return template.format(low=attribute.displayMutationLow, high=attribute.displayMutationHigh, colorLow=lowColor, colorHigh=highColor)
   1015 
   1016     def OnChange(self):
   1017         if self.controller.isResultPresented:
   1018             return
   1019         if self.controller.IsCraftingResultAvailable():
   1020             self._lastSourceTypeID = None
   1021             self._lastMutatorTypeID = None
   1022             uthread.new(self.AnimShowFinalValue)
   1023         elif self._lastSourceTypeID != self.controller.sourceTypeID or self._lastMutatorTypeID != self.controller.mutatorTypeID:
   1024             uthread.new(self.AnimUpdateAttributeValue)
   1025             self._lastSourceTypeID = self.controller.sourceTypeID
   1026             self._lastMutatorTypeID = self.controller.mutatorTypeID
   1027 
   1028 
   1029 class FinalAttributeValueLabel(Container):
   1030     default_clipChildren = True
   1031 
   1032     def ApplyAttributes(self, attributes):
   1033         super(FinalAttributeValueLabel, self).ApplyAttributes(attributes)
   1034         self.controller = attributes.controller
   1035         self.attributeID = attributes.attributeID
   1036         self._value = attributes.value
   1037         self.Layout()
   1038         self.UpdateValueLabel()
   1039 
   1040     def Layout(self):
   1041         self.valueLabel = eveLabel.EveLabelLargeBold(parent=self, align=uiconst.BOTTOMLEFT, autoFitToText=True)
   1042         self.diffLabel = eveLabel.EveLabelSmall(parent=self, align=uiconst.BOTTOMLEFT, top=1, opacity=0.0)
   1043 
   1044     @property
   1045     def value(self):
   1046         return self._value
   1047 
   1048     @value.setter
   1049     def value(self, value):
   1050         self._value = value
   1051         self.UpdateValueLabel()
   1052 
   1053     def AnimSetValue(self, value, duration = 0.5, timeOffset = 0.0):
   1054         base = self.value or 0.0
   1055         animations.FadeOut(self.diffLabel, duration=0.05)
   1056         animations.MorphScalar(self, 'value', startVal=base, endVal=value, duration=duration, timeOffset=timeOffset, sleep=True)
   1057         if self.destroyed:
   1058             return
   1059         self.value = value
   1060         self.SetDiff(value - base)
   1061         animations.FadeIn(self.diffLabel, duration=0.2)
   1062 
   1063     def SetDiff(self, diff):
   1064         attribute = self.controller.GetAttribute(self.attributeID)
   1065         diffStr = attribute.FormatDiff(diff)
   1066         if attribute.isMutationPositive:
   1067             color = Color(*COLOR_POSITIVE).GetHex()
   1068         else:
   1069             color = Color(*COLOR_NEGATIVE).GetHex()
   1070         text = u'<color={color}>({diff})</color>'.format(color=color, diff=diffStr)
   1071         self.diffLabel.SetText(text)
   1072         self.diffLabel.left = self.valueLabel.textwidth + 8
   1073 
   1074     def UpdateValueLabel(self):
   1075         attribute = self.controller.GetAttribute(self.attributeID)
   1076         self.valueLabel.SetText(attribute.Format(self.value))
   1077 
   1078 
   1079 class MutationBar(Container):
   1080     default_height = 4
   1081     default_clipChildren = False
   1082 
   1083     def ApplyAttributes(self, attributes):
   1084         super(MutationBar, self).ApplyAttributes(attributes)
   1085         self.controller = attributes.controller
   1086         self.attributeID = attributes.attributeID
   1087         self._lastAttribute = self.controller.GetAttribute(self.attributeID)
   1088         self._animationIndex = attributes.get('animationIndex', 0)
   1089         self.Layout()
   1090         self.controller.onChange.connect(self.OnChange)
   1091 
   1092     def Layout(self):
   1093         FillThemeColored(bgParent=self, colorType=uiconst.COLORTYPE_UIHILIGHT)
   1094         self.barPin = Container(parent=self, align=uiconst.TOPLEFT_PROP, left=0.5, height=1.0, width=1.0, opacity=0.0)
   1095         self.barPip = Fill(parent=self.barPin, align=uiconst.TOPLEFT, height=self.height, width=1, color=(0.7, 0.7, 0.7, 1.0))
   1096         self.barFill = Sprite(parent=self, align=uiconst.TOLEFT_PROP, left=0.5, width=0.0, texturePath=RES_BAR_FILL, tileX=True)
   1097 
   1098     def SetAnimationIndex(self, index):
   1099         self._animationIndex = index
   1100 
   1101     def AnimUpdateBar(self):
   1102         attribute = self.controller.GetAttribute(self.attributeID)
   1103         attributeValuesDiffer = self._lastAttribute is not None and self._lastAttribute.value is not None and self._lastAttribute.value != attribute.value
   1104         if attributeValuesDiffer:
   1105             self.AnimHideBar()
   1106         if self.destroyed:
   1107             return
   1108         if attribute is not None and attribute.value is not None:
   1109             self.AnimShowBar()
   1110         self._lastAttribute = attribute
   1111 
   1112     def AnimHideBar(self):
   1113         animations.MorphScalar(self.barFill, 'width', startVal=self.barFill.width, endVal=0.0, duration=0.1)
   1114         animations.MorphScalar(self.barPin, 'left', startVal=self.barPin.left, endVal=0.5, duration=0.1)
   1115         animations.FadeOut(self.barPin, duration=0.05, sleep=True, timeOffset=0.1)
   1116 
   1117     def AnimShowBar(self):
   1118         attribute = self.controller.GetAttribute(self.attributeID)
   1119         trueSource = max(attribute.sourceValue, attribute.mutationMin)
   1120         if attribute.isMutationPositive:
   1121             self.barFill.align = uiconst.TOLEFT_PROP
   1122             self.barFill.SetRGB(*COLOR_POSITIVE)
   1123             self.barFill.rotation = 0.0
   1124             rangeWidth = abs(attribute.mutationHigh - trueSource)
   1125         else:
   1126             self.barFill.align = uiconst.TORIGHT_PROP
   1127             self.barFill.SetRGB(*COLOR_NEGATIVE)
   1128             self.barFill.rotation = math.pi
   1129             rangeWidth = abs(attribute.mutationLow - trueSource)
   1130         animationFactor = 1.0 if not MUTATOR_FAST_ANIMATIONS_SETTING.is_enabled() else 10.0
   1131         animations.FadeTo(self.barPin, startVal=0.0, endVal=1.0, duration=0.05, timeOffset=self._animationIndex * 1.2 / animationFactor)
   1132         animations.FadeTo(self.barPip, startVal=self.barPip.opacity, endVal=2.0, duration=0.2, curveType=uiconst.ANIM_WAVE, timeOffset=1.5 + self._animationIndex * 1.2 / animationFactor)
   1133         if rangeWidth > 0.0:
   1134             width = abs(attribute.value - trueSource) / rangeWidth
   1135         else:
   1136             width = 0.0
   1137         sign = 1.0 if attribute.isMutationPositive else -1.0
   1138         animations.MorphScalar(self.barPin, 'left', startVal=0.5, endVal=0.5 + sign * (width / 2.0), duration=1.5, timeOffset=self._animationIndex * 1.2 / animationFactor)
   1139         animations.MorphScalar(self.barFill, 'width', startVal=0.0, endVal=width / 2.0, duration=1.5, timeOffset=self._animationIndex * 1.2 / animationFactor)
   1140 
   1141     def OnChange(self):
   1142         if self.controller.isResultPresented:
   1143             return
   1144         uthread.new(self.AnimUpdateBar)
   1145 
   1146 
   1147 class BuildButton(PrimaryButton):
   1148 
   1149     def ApplyAttributes(self, attributes):
   1150         super(BuildButton, self).ApplyAttributes(attributes)
   1151         self.controller = attributes.controller
   1152         self.UpdateState()
   1153         self.controller.onChange.connect(self.UpdateState)
   1154 
   1155     def ClickFunc(self, *args):
   1156         if self.controller.isResultPresented:
   1157             self.controller.Reset()
   1158         else:
   1159             self.controller.ExecuteCrafting()
   1160 
   1161     def GetColor(self):
   1162         return sm.GetService('uiColor').GetUIColor(uiconst.COLORTYPE_UIHILIGHT)
   1163 
   1164     def GetText(self):
   1165         if self.controller.isResultPresented:
   1166             return localization.GetByLabel('UI/DynamicItem/Continue')
   1167         else:
   1168             return localization.GetByLabel('UI/DynamicItem/Mutate')
   1169 
   1170     def IsActive(self):
   1171         if self.controller.isResultPresented:
   1172             return True
   1173         elif self.controller.IsCraftingResultAvailable():
   1174             return False
   1175         elif not self.controller.IsMutatorAvailable():
   1176             return False
   1177         elif self.controller.IsSourceItemAvailable():
   1178             return True
   1179         else:
   1180             return False
   1181 
   1182     def IsBlinking(self):
   1183         return False
   1184 
   1185     def IsErrorPresent(self):
   1186         if not self.controller.IsMutatorAvailable():
   1187             return True
   1188         if not self.controller.IsSourceItemSelected():
   1189             return False
   1190         if not self.controller.IsSourceItemAvailable():
   1191             return True
   1192         return False
   1193 
   1194     def UpdateIsEnabledByState(self):
   1195         if self.IsActive():
   1196             self.Enable()
   1197         else:
   1198             self.Disable()
   1199 
   1200     def UpdateBlinkByState(self):
   1201         pass
   1202 
   1203     def OnColorThemeChanged(self):
   1204         self.UpdateState()
   1205 
   1206     def LoadTooltipPanel(self, panel, owner):
   1207         panel.margin = (8, 8, 8, 0)
   1208         if not self.controller.IsMutatorAvailable():
   1209             panel.AddLabelMedium(text=localization.GetByLabel('UI/DynamicItem/ErrorMutatorNotAvailable'), width=200, padBottom=8)
   1210         if not self.controller.IsSourceItemSelected():
   1211             panel.AddLabelMedium(text=localization.GetByLabel('UI/DynamicItem/ErrorSourceItemNotSelected'), width=200, padBottom=8)
   1212         elif not self.controller.IsSourceItemAvailable():
   1213             panel.AddLabelMedium(text=localization.GetByLabel('UI/DynamicItem/ErrorSourceItemNotAvailable'), width=200, padBottom=8)
   1214 
   1215 
   1216 def __SakeReloadHook():
   1217     try:
   1218         instance = CraftingWindow.GetIfOpen()
   1219         if instance:
   1220             CraftingWindow.Reload(instance)
   1221     except Exception:
   1222         import log
   1223         log.LogException()