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

reminderCont.py (20911B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\loginRewards\reminderCont.py
      2 import gametime
      3 import uthread2
      4 from carbon.client.script.environment.AudioUtil import PlaySound
      5 from carbon.common.script.util.timerstuff import AutoTimer
      6 from carbonui.control.button import Button
      7 from carbonui.primitives.container import Container
      8 from carbonui.primitives.containerAutoSize import ContainerAutoSize
      9 from carbonui.primitives.fill import Fill
     10 from carbonui.uianimations import animations
     11 from carbonui.util.color import Color
     12 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelMediumBold
     13 from eve.client.script.ui.shared.loginRewards.panelControllers import SeasonalPanelController, LoginCampaignController
     14 from eve.client.script.ui.shared.loginRewards.rewardItemsGrid import RewardItemsGrid
     15 from eve.client.script.ui.shared.loginRewards.rewardPreviewItemEntry import TwoTrackPreviewItemEntryOmega, TwoTrackPreviewItemEntryAlpha, ReminderPreviewItemEntry
     16 from carbonui import uiconst
     17 from eve.client.script.ui.shared.loginRewards.rewardUiConst import ALPHA_SEASONAL_ENTRY, OMEGA_SEASONAL_ENTRY, BLUE_TEXT_COLOR, ReminderRewardPanelConst
     18 from eve.client.script.ui.shared.loginRewards.twoTrackRewardPanel import GetGiftsReceivedConst
     19 from eveui import ButtonIcon
     20 from localization import GetByLabel, GetByMessageID
     21 import eve.client.script.ui.shared.loginRewards.rewardUiConst as rewardUiConst
     22 from loginrewards.common.const import CLAIM_STATE_CLAIMABLE
     23 from utillib import KeyVal
     24 ENTRY_WIDTH = 270
     25 ENTRY_DIVIDER = 18
     26 animationDelay = 2.0
     27 
     28 class ReminderCont(ContainerAutoSize):
     29     default_alignMode = uiconst.TOTOP
     30     default_height = 350
     31     __notifyevents__ = ['OnRewardReminderClaimed', 'OnDailyRewardClaimed']
     32 
     33     def ApplyAttributes(self, attributes):
     34         ContainerAutoSize.ApplyAttributes(self, attributes)
     35         animations.FadeTo(self, duration=0.3)
     36         parentWidth = attributes.parentWidth
     37         self.loginRewardSvc = sm.GetService('loginCampaignService')
     38         self.grid = None
     39         self.currentIdx = 0
     40         sidePadding = 10
     41         self.headerText = EveLabelMedium(parent=self, align=uiconst.TOTOP)
     42         self.entryCont = ContainerAutoSize(name='entryCont', parent=self, align=uiconst.TOTOP, height=250, clipChildren=True, padding=4, alignMode=uiconst.TOTOP)
     43         self.breadcrumbsParent = Container(name='breadcrumbsParent', parent=self.entryCont, align=uiconst.TOTOP, height=20)
     44         self.fullWidth = parentWidth - self.entryCont.padLeft - self.entryCont.padRight
     45         self.AddBrowseBtns()
     46         self.Load()
     47         sm.RegisterNotify(self)
     48 
     49     def Load(self):
     50         self.UpdateHeaderText()
     51         rewardsData = self.loginRewardSvc.get_next_rewards_to_claim_both_campaigns()
     52         rewardDataWithRewardInfo = [ x for x in rewardsData if x.rewardInfo is not None ]
     53         numToDisplay = len(rewardDataWithRewardInfo)
     54         gridParent = ContainerAutoSize(name='gridParent', parent=self.entryCont, align=uiconst.TOTOP, height=180)
     55         self.breadcrumbsParent.display = len(rewardDataWithRewardInfo) > 1
     56         self.breadcrumbsParent.Flush()
     57         breadcrumbsPadding = (self.fullWidth - ReminderRewardPanelConst.ENTRY_WIDTH) / 2 + ReminderRewardPanelConst.ENTRY_SIDE_PADDING
     58         self.breadcrumbsCont = BreadcrumbCont(parent=self.breadcrumbsParent, align=uiconst.TOBOTTOM, numRewards=len(rewardDataWithRewardInfo), padLeft=breadcrumbsPadding)
     59         self.grid = RewardItemsGrid(name='rewardGrid', parent=gridParent, align=uiconst.CENTERLEFT, columns=numToDisplay, numAllEntries=numToDisplay, panelConst=ReminderRewardPanelConst, offset=int((self.fullWidth - ReminderRewardPanelConst.ENTRY_WIDTH) / 2.0))
     60         entryInfoForGrid = []
     61         firstClaimable = None
     62         for i, eachReward in enumerate(rewardDataWithRewardInfo):
     63             if eachReward.rewardInfo.claimState == CLAIM_STATE_CLAIMABLE and firstClaimable is None:
     64                 firstClaimable = i
     65             contClass, _ = GetContAndEntryClassFromType(eachReward.entryType)
     66             entryInfoForGrid.append({'entryClass': contClass,
     67              'rewardData': eachReward,
     68              'contIdx': i})
     69 
     70         self.grid.LoadEntries(entryInfoForGrid)
     71         self.SetCurrentSelectedAndUpdateUI(firstClaimable or 0, 0)
     72         self.grid.UpdateEntryState(self.currentIdx, KeyVal(fadeTime=0))
     73 
     74     def UpdateHeaderText(self):
     75         rewardsData = self.loginRewardSvc.get_next_rewards_to_claim_both_campaigns()
     76         rewardInfos = [ x.rewardInfo for x in rewardsData if x.rewardInfo is not None ]
     77         numCanClaimNow = len([ x for x in rewardInfos if x.claimState == CLAIM_STATE_CLAIMABLE ])
     78         if not numCanClaimNow:
     79             self.headerText.opacity = 0.0
     80         else:
     81             self.headerText.opacity = 1.0
     82             self.headerText.display = True
     83             text = GetByLabel('UI/LoginRewards/NumUnclaimedItems', numItems=numCanClaimNow)
     84             self.headerText.text = '<center><b>%s</b></center>' % text
     85 
     86     def MoveToNextClaimable(self):
     87         nextClaimable = None
     88         entries = self.grid.entries.items()
     89         entries = sorted(entries, key=lambda x: x[0])
     90         for i, entry in entries:
     91             if entry.rewardItemCont.currentRewardInfo is None:
     92                 continue
     93             if entry.rewardItemCont.currentRewardInfo.claimState == CLAIM_STATE_CLAIMABLE:
     94                 nextClaimable = i
     95                 break
     96 
     97         nextClaimable = nextClaimable or self.currentIdx
     98         self.SetCurrentSelectedAndUpdateUI(nextClaimable, 0.5)
     99 
    100     def SetCurrentSelectedAndUpdateUI(self, newCurrentIdx, animateTime):
    101         self.currentIdx = newCurrentIdx
    102         self.grid.GoToEntry(self.currentIdx, animateTime=animateTime)
    103         self.grid.UpdateEntryState(self.currentIdx, KeyVal(fadeTime=animateTime))
    104         self.breadcrumbsCont.SetDotSelectionState(self.currentIdx, bool(animateTime))
    105         self.UpdateBrowseBtns()
    106 
    107     def UpdateBrowseBtns(self):
    108         self.backBtn.display = self.currentIdx > 0
    109         if not self.grid:
    110             return
    111         maxIdx = self.grid.columns - 1
    112         if maxIdx > 0 and maxIdx > self.currentIdx:
    113             self.fwdBtn.display = True
    114         else:
    115             self.fwdBtn.display = False
    116 
    117     def AddBrowseBtns(self):
    118         self.backBtn = ButtonIcon(parent=self, align=uiconst.CENTERLEFT, left=20, top=-12, texture_path='res:/UI/Texture/Shared/DarkStyle/backward.png', size=16, on_click=self.GoBack, opacity_enabled=0.6, opacity_disabled=0.2)
    119         self.fwdBtn = ButtonIcon(parent=self, align=uiconst.CENTERRIGHT, left=20, top=-12, texture_path='res:/UI/Texture/Shared/DarkStyle/forward.png', size=16, on_click=self.GoFwd, opacity_enabled=0.6, opacity_disabled=0.2)
    120 
    121     def GoBack(self, *args):
    122         self.OnBrowse(-1)
    123 
    124     def GoFwd(self, *args):
    125         self.OnBrowse(1)
    126 
    127     def OnBrowse(self, direction, *args):
    128         newDay = self.grid.OnBrowse(direction, self.currentIdx)
    129         if newDay is not None:
    130             self.currentIdx = newDay
    131             self.breadcrumbsCont.SetDotSelectionState(newDay, True)
    132             self.grid.UpdateEntryState(newDay, KeyVal(fadeTime=0.2))
    133         self.UpdateBrowseBtns()
    134 
    135     def OnDailyRewardClaimed(self, todays_alpha_rewards, todays_omega_rewards, retroactive_rewards):
    136         giftsReceivedConst = GetGiftsReceivedConst(todays_alpha_rewards, todays_omega_rewards, retroactive_rewards)
    137         triggerIdx = self.currentIdx
    138         sm.ScatterEvent('OnDailyRewardClaimedLocal', triggerIdx)
    139         if giftsReceivedConst in (rewardUiConst.ONLY_ALPHA_GIFT_REWARDED, rewardUiConst.ONLY_OMEGA_GIFT_REWARDED):
    140             PlaySound('daily_login_gift_single_play')
    141             uthread2.call_after_wallclocktime_delay(self.UpdateInfo, animationDelay)
    142         else:
    143             PlaySound('daily_login_gift_play')
    144             nextIdx = max([ i for i, x in self.grid.entries.iteritems() if getattr(x, 'OnDailyRewardClaimedLocal', None) and i != triggerIdx ] or [0])
    145             uthread2.call_after_wallclocktime_delay(self.SetCurrentSelectedAndUpdateUI, animationDelay, nextIdx, 0.5)
    146             uthread2.call_after_wallclocktime_delay(self.UpdateInfo, 2 * animationDelay)
    147 
    148     def OnRewardReminderClaimed(self, *args, **kwargs):
    149         self.UpdateInfo()
    150 
    151     def UpdateInfo(self):
    152         self.UpdateHeaderText()
    153         self.MoveToNextClaimable()
    154 
    155 
    156 class BaseRewardReminderCont(ContainerAutoSize):
    157     default_align = uiconst.TOPLEFT
    158     default_alignMode = uiconst.TOTOP
    159     default_height = 500
    160     default_name = 'RewardReminderCont'
    161 
    162     def ApplyAttributes(self, attributes):
    163         ContainerAutoSize.ApplyAttributes(self, attributes)
    164         self.panelController = self.GetPanelController()
    165         self.rewardData = attributes.rewardData
    166         self.oldRewardData = self.rewardData
    167         self.contIdx = attributes.contIdx
    168         self.entryType = self.rewardData.entryType
    169         self.countdownThread = None
    170         labelPadding = ReminderRewardPanelConst.ENTRY_SIDE_PADDING
    171         labelTop = 6
    172         campaignName = GetByMessageID(self.rewardData.campaignNameID)
    173         self.campaignNameLabel = EveLabelMedium(name='campaignNameLabel', parent=self, align=uiconst.TOTOP, top=labelTop, left=labelPadding, text=campaignName)
    174         self.campaignProgressLabel = EveLabelMedium(name='campaignProgressLabel', parent=self, align=uiconst.TOPRIGHT, left=labelPadding, top=labelTop)
    175         self.SetProgressText()
    176         self.campaignNameLabel.padRight = self.campaignProgressLabel.left + self.campaignProgressLabel.width + 20
    177         self.ConstructRewardItemCont(self.rewardData)
    178         self.bottomCont = Container(name='bottomCont', parent=self, align=uiconst.TOTOP, height=70)
    179         self.rewardItemCont.LoadRewardInfo(self.rewardData.rewardInfo)
    180         self.width = ReminderRewardPanelConst.ENTRY_WIDTH
    181         self.height = self.rewardItemCont.height + self.rewardItemCont.top + self.bottomCont.height
    182         self.ConstructClaimBtnAndLabel()
    183         sm.RegisterNotify(self)
    184 
    185     def GetPanelController(self):
    186         raise NotImplementedError('panel controller not specified for BaseRewardReminderCont')
    187 
    188     def SetProgressText(self, addToNumClaimed = False):
    189         if self.rewardData:
    190             numClaimed = self.rewardData.numClaimed
    191             totalRewards = self.rewardData.totalRewards
    192             progressText = GetProgressText(numClaimed, totalRewards)
    193         elif self.oldRewardData and addToNumClaimed:
    194             numClaimed = self.oldRewardData.numClaimed + 1
    195             totalRewards = self.oldRewardData.totalRewards
    196             progressText = GetProgressText(numClaimed, totalRewards)
    197         else:
    198             progressText = ''
    199         self.campaignProgressLabel.text = progressText
    200 
    201     def ConstructRewardItemCont(self, rewardData):
    202         itemParent = Container(name='itemParent', parent=self, align=uiconst.TOTOP)
    203         _, entryClass = GetContAndEntryClassFromType(rewardData.entryType)
    204         self.rewardItemCont = entryClass(parent=itemParent, align=uiconst.CENTERTOP, panelController=self.panelController, width=ReminderRewardPanelConst.DAY_WIDTH, bgColor=(1.0, 1.0, 1.0, 0.05))
    205         self.rewardItemCont.GetMenu = None
    206         itemParent.height = self.rewardItemCont.height
    207 
    208     def ConstructClaimBtnAndLabel(self):
    209         self.claimBtn = Button(parent=self.bottomCont, align=uiconst.CENTERTOP, label=GetByLabel('UI/LoginRewards/ClaimButtonText'), func=self.OnClaimBtnClicked, top=16)
    210         self.nextAvailableLabel = EveLabelMediumBold(name='nextAvailableLabel', parent=self.bottomCont, align=uiconst.TOTOP_NOPUSH, top=10, color=BLUE_TEXT_COLOR)
    211         redeemText = '<center>%s</center>' % GetByLabel('UI/LoginRewards/ClaimExplanationText', linkStart='<color=%s><b>' % Color.RGBtoHex(*BLUE_TEXT_COLOR), linkEnd='</b></color>')
    212         redeemPadding = (ReminderRewardPanelConst.ENTRY_SIDE_PADDING,
    213          0,
    214          ReminderRewardPanelConst.ENTRY_SIDE_PADDING,
    215          0)
    216         self.redeemLabel = EveLabelMedium(name='redeemLabel', parent=self.bottomCont, align=uiconst.TOTOP_NOPUSH, top=30, text=redeemText, padding=redeemPadding)
    217         self.redeemLabel.display = False
    218         self.UpdateBottom()
    219 
    220     def UpdateEntryState(self, entryIdx, selectedIdx, updateKeyVal):
    221         fadeTime = getattr(updateKeyVal, 'fadeTime', 0)
    222         isCurrent = entryIdx == selectedIdx
    223         if fadeTime:
    224             self.campaignNameLabel.display = True
    225             self.campaignProgressLabel.display = True
    226             if isCurrent:
    227                 animations.FadeTo(self.campaignNameLabel, self.campaignNameLabel.opacity, 1.0, duration=fadeTime)
    228                 animations.FadeTo(self.campaignProgressLabel, self.campaignNameLabel.opacity, 1.0, duration=fadeTime)
    229             else:
    230                 animations.FadeTo(self.campaignNameLabel, self.campaignNameLabel.opacity, 0.0, duration=fadeTime / 2.0)
    231                 animations.FadeTo(self.campaignProgressLabel, self.campaignNameLabel.opacity, 0.0, duration=fadeTime / 2.0)
    232         else:
    233             self.campaignNameLabel.opacity = 1.0 if isCurrent else 0.0
    234             self.campaignProgressLabel.opacity = 1.0 if isCurrent else 0.0
    235 
    236     def OnClaimBtnClicked(self, *args):
    237         self.panelController.ClaimReward()
    238 
    239     def UpdateBottom(self, withTransition = False):
    240         pass
    241 
    242     def _UpdateBottom(self, nextTime, withTransition = False):
    243         if self.countdownThread:
    244             self.countdownThread.KillTimer()
    245             self.countdownThread = None
    246         canClaimNow = nextTime < gametime.GetWallclockTime() if nextTime is not None else False
    247         if canClaimNow or nextTime is None:
    248             self.nextAvailableLabel.text = ''
    249         else:
    250             self.StartCountDown(nextTime)
    251         if withTransition:
    252             if canClaimNow:
    253                 self.claimBtn.display = True
    254                 animations.FadeOut(self.nextAvailableLabel, duration=0.1)
    255                 animations.FadeOut(self.redeemLabel, duration=0.1)
    256                 animations.FadeTo(self.claimBtn, startVal=0.0, duration=0.5)
    257             else:
    258                 self.nextAvailableLabel.display = True
    259                 self.redeemLabel.display = True
    260                 animations.FadeOut(self.claimBtn, duration=0.1)
    261                 animations.FadeTo(self.nextAvailableLabel, startVal=0.0, duration=0.5)
    262                 animations.FadeTo(self.redeemLabel, startVal=0.0, duration=0.5)
    263         else:
    264             self.claimBtn.display = canClaimNow
    265             self.nextAvailableLabel.display = not canClaimNow
    266             self.redeemLabel.display = not canClaimNow
    267 
    268     def StartCountDown(self, nextRewardTime):
    269         self.UpdateCountDown_thread(nextRewardTime)
    270         self.countdownThread = AutoTimer(500, self.UpdateCountDown_thread, nextRewardTime)
    271 
    272     def UpdateCountDown_thread(self, nextRewardTime):
    273         if not self or self.destroyed:
    274             self.countdownThread = None
    275             return
    276         text = GetCountdownTextToDisplay('UI/LoginRewards/NextGiftWithTimer', nextRewardTime)
    277         self.nextAvailableLabel.text = text
    278 
    279 
    280 class RewardReminderContRookie(BaseRewardReminderCont):
    281     __notifyevents__ = ['OnDailyCampaignAwardClaimed']
    282 
    283     def GetPanelController(self):
    284         return LoginCampaignController(sm.GetService('loginCampaignService'), rewardUiConst.ReminderRewardPanelConst)
    285 
    286     def UpdateBottom(self, withTransition = False):
    287         nextRewardData = self.panelController.GetNextRewardData()
    288         nextTime = self.panelController.GetTimestampWhenRewardCanBeClaimed() if nextRewardData and nextRewardData.rewardInfo else None
    289         self._UpdateBottom(nextTime, withTransition)
    290 
    291     def SetProgressText(self, addToNumClaimed = False):
    292         pConst = self.panelController.GetPanelConstants()
    293         if pConst.SHOW_REWARD_COUNTER and self.panelController.IsRookieCampaign():
    294             BaseRewardReminderCont.SetProgressText(self, addToNumClaimed)
    295 
    296     def OnDailyCampaignAwardClaimed(self, itemReward, updatedItemProgress, receivedBucketTypeID, updatedBucketInfo):
    297         if updatedItemProgress or updatedBucketInfo:
    298             PlaySound('daily_login_rewards_play')
    299             self.UpdateBottom(withTransition=True)
    300             nextRewardData = self.panelController.GetNextRewardData()
    301             self.oldRewardData = self.rewardData
    302             self.rewardData = nextRewardData
    303             self.SetProgressText(addToNumClaimed=True)
    304             rewardInfo = self.rewardData.rewardInfo if self.rewardData else None
    305             self.rewardItemCont.LoadNewReward(rewardInfo)
    306             sm.ScatterEvent('OnRewardReminderClaimed')
    307 
    308 
    309 class RewardReminderContSeasons(BaseRewardReminderCont):
    310     default_align = uiconst.TOPLEFT
    311     __notifyevents__ = ['OnDailyRewardClaimedLocal']
    312 
    313     def GetPanelController(self):
    314         seasonalLoginCampaignService = sm.GetService('seasonalLoginCampaignService')
    315         isOmega = seasonalLoginCampaignService.is_omega()
    316         return SeasonalPanelController(seasonalLoginCampaignService, rewardUiConst.ReminderRewardPanelConst, isOmega)
    317 
    318     def UpdateBottom(self, withTransition = False):
    319         isEnding = self.panelController.IsCampaignEndingBeforeNextReward()
    320         campaingCompleted = self.panelController.HasActiveCampaignBeenCompleted()
    321         if isEnding or campaingCompleted:
    322             nextTime = None
    323         else:
    324             nextTime = self.panelController.GetTimestampWhenRewardCanBeClaimed()
    325         self._UpdateBottom(nextTime, withTransition)
    326 
    327     def OnDailyRewardClaimedLocal(self, claimedIdx):
    328         if claimedIdx != self.contIdx:
    329             uthread2.Sleep(animationDelay)
    330         isCampaignSuccessfullyCompleted = self.panelController.HasActiveCampaignBeenCompleted()
    331         isCampaignEnding = self.panelController.IsCampaignEndingBeforeNextReward()
    332         self.UpdateBottom(withTransition=True)
    333         if isCampaignSuccessfullyCompleted:
    334             campaignStatus = rewardUiConst.CAMPAIGN_STATUS_SUCCESSFULLY_COMPLETED
    335         elif isCampaignEnding:
    336             campaignStatus = rewardUiConst.CAMPAIGN_STATUS_ENDING
    337         else:
    338             campaignStatus = rewardUiConst.CAMPAIGN_STATUS_ONGOING
    339         entryType = self.entryType
    340         rewardData = self.panelController.GetNextRewardDataForEntryType(entryType)
    341         if not rewardData:
    342             return
    343         self.oldRewardData = self.rewardData
    344         self.rewardData = rewardData
    345         if campaignStatus in (rewardUiConst.CAMPAIGN_STATUS_ENDING, rewardUiConst.CAMPAIGN_STATUS_SUCCESSFULLY_COMPLETED):
    346             self.rewardItemCont.FadeOutAndPlayVideo()
    347             self.rewardItemCont.LoadCompletedScreen(False)
    348         else:
    349             rewardInfo = self.rewardData.rewardInfo if self.rewardData else None
    350             self.rewardItemCont.LoadNewReward(rewardInfo)
    351         self.SetProgressText(addToNumClaimed=True)
    352 
    353 
    354 def GetCountdownTextToDisplay(labelPath, nextRewardTime):
    355     if nextRewardTime is None:
    356         return ''
    357     endTime = max(0, nextRewardTime - gametime.GetWallclockTime())
    358     text = '<center>%s</center>' % GetByLabel(labelPath, endTime=endTime, formattingStart='<color=white>', formattingEnd='</color>')
    359     return text
    360 
    361 
    362 def GetContAndEntryClassFromType(entryType):
    363     if entryType == ALPHA_SEASONAL_ENTRY:
    364         return (RewardReminderContSeasons, TwoTrackPreviewItemEntryAlpha)
    365     if entryType == OMEGA_SEASONAL_ENTRY:
    366         return (RewardReminderContSeasons, TwoTrackPreviewItemEntryOmega)
    367     return (RewardReminderContRookie, ReminderPreviewItemEntry)
    368 
    369 
    370 def GetProgressText(numRewardsClaimed, numRewardsTotal):
    371     hexColor = Color.RGBtoHex(*BLUE_TEXT_COLOR)
    372     startTags = '<color=%s><b>' % hexColor
    373     endTags = '</b></color>'
    374     return GetByLabel('UI/LoginRewards/CampaignProgress', numRewardsCollected=numRewardsClaimed, numRewardsTotal=numRewardsTotal, startTags=startTags, endTags=endTags)
    375 
    376 
    377 class BreadcrumbCont(Container):
    378     default_height = 10
    379     IDLE_COLOR = (0.75, 0.75, 0.75, 0.3)
    380     SELECTED_COLOR = (1, 1, 1, 1)
    381 
    382     def ApplyAttributes(self, attributes):
    383         Container.ApplyAttributes(self, attributes)
    384         numRewards = attributes.numRewards
    385         self.dots = []
    386         for i in xrange(numRewards):
    387             c = Container(parent=self, align=uiconst.TOLEFT, width=10)
    388             f = Fill(parent=c, align=uiconst.CENTERLEFT, pos=(0, 0, 6, 6))
    389             self.dots.append(f)
    390 
    391     def SetDotSelectionState(self, idx, animate = False):
    392         for i, dot in enumerate(self.dots):
    393             if i == idx:
    394                 color = self.SELECTED_COLOR
    395                 animations.SpColorMorphTo(dot, endColor=self.SELECTED_COLOR)
    396             else:
    397                 color = self.IDLE_COLOR
    398             if animate:
    399                 animations.SpColorMorphTo(dot, endColor=color, duration=0.2)
    400                 animations.FadeTo(dot, dot.opacity, color[3], duration=0.2)
    401             else:
    402                 dot.SetRGBA(*self.IDLE_COLOR)