sequencingJobCard.py (14829B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\cards\sequencingJobCard.py 2 import blue 3 import carbonui 4 import eveicon 5 import evetypes 6 import uthread2 7 from carbon.common.script.sys.serviceConst import ROLE_QA 8 from carbonui import Align, TextAlign, TextColor, uiconst, PickState, SpriteEffect, ButtonStyle 9 from carbonui.text.styles import TextBody 10 from carbonui.control.button import Button 11 from carbonui.control.contextMenu.menuData import MenuData 12 from carbonui.primitives.container import Container 13 from carbonui.primitives.containerAutoSize import ContainerAutoSize 14 from carbonui.primitives.layoutGrid import LayoutGrid 15 from carbonui.primitives.sprite import Sprite 16 from carbonui.primitives.stretchspritehorizontal import StretchSpriteHorizontal 17 from carbonui.uianimations import animations 18 from cosmetics.client.liveIconRenderer import LiveIconSprite 19 from cosmetics.client.shipSkinSequencingSvc import get_ship_skin_sequencing_svc 20 from cosmetics.client.ships.skins.errors import NoTimeRemainingException, get_sequencing_error_text 21 from cosmetics.client.ships.skins.live_data.sequencing_job import SequencingJobState 22 from eve.client.script.ui import eveColor, eveThemeColor 23 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel 24 from eve.client.script.ui.control.gauge import Gauge 25 from eve.client.script.ui.cosmetics.ship.pages.cards.cardConst import CardBackground, CardMask 26 from eve.client.script.ui.shared.neocom.wallet.buyMissingPlexDialog import BuyMissingPlexDialog 27 from eveservices.menu import GetMenuService 28 from localization import GetByLabel 29 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog 30 from plex.client.api.repository import get_repository 31 from plex.client.api.signal import on_balance_changed 32 from publicGateway.grpc.exceptions import GenericException 33 from signals import Signal 34 from stackless_response_router.exceptions import TimeoutException 35 ANIM_DURATION = 0.2 36 ICON_SIZE = 128 37 38 class SequencingJobCard(ContainerAutoSize): 39 default_state = uiconst.UI_NORMAL 40 default_width = 168 41 default_height = 308 42 43 def __init__(self, sequencing_job, skin_design, *args, **kwargs): 44 super(SequencingJobCard, self).__init__(*args, **kwargs) 45 self.sequencing_job = sequencing_job 46 self.skin_design = skin_design 47 self._is_selected = False 48 self.is_hovered = False 49 self.live_icon = None 50 self.finished_loading = False 51 self.time_label_update_thread = None 52 self.expedite_sequencing_thread = None 53 self.update_complete_now_thread = None 54 self.on_click = Signal('on_click') 55 self.construct_layout() 56 self.update_live_icon() 57 self.update() 58 self.connect_signals() 59 self.time_label_update_thread = uthread2.start_tasklet(self.update_time_left_label) 60 61 def Close(self): 62 try: 63 self.kill_threads() 64 self.disconnect_signals() 65 finally: 66 super(SequencingJobCard, self).Close() 67 68 def kill_threads(self): 69 self.kill_time_label_update_thread() 70 self.kill_expedite_sequencing_thread() 71 self.kill_update_complete_now_thread() 72 73 def kill_time_label_update_thread(self): 74 if self.time_label_update_thread is not None: 75 self.time_label_update_thread.kill() 76 self.time_label_update_thread = None 77 78 def kill_expedite_sequencing_thread(self): 79 if self.expedite_sequencing_thread is not None: 80 self.expedite_sequencing_thread.kill() 81 self.expedite_sequencing_thread = None 82 83 def kill_update_complete_now_thread(self): 84 if self.update_complete_now_thread is not None: 85 self.update_complete_now_thread.kill() 86 self.update_complete_now_thread = None 87 88 def connect_signals(self): 89 on_balance_changed.connect(self.on_plex_amount_changed) 90 91 def disconnect_signals(self): 92 on_balance_changed.disconnect(self.on_plex_amount_changed) 93 94 def construct_layout(self): 95 self.construct_top_cont() 96 self.construct_tier_indicator() 97 self.construct_num_runs_label() 98 self.construct_label_and_gauge() 99 self.construct_complete_now() 100 self.construct_ship_icon() 101 102 def on_plex_amount_changed(self, *args): 103 self.update() 104 105 def on_live_rendered_icon_ready(self): 106 self.finished_loading = True 107 self.loading_sprite.display = False 108 animations.FadeIn(self.live_icon) 109 self.live_icon.on_rendered.disconnect(self.on_live_rendered_icon_ready) 110 111 def on_loading_sprite_fadeout_complete(self): 112 self.loading_sprite.display = False 113 114 def construct_label_and_gauge(self): 115 self.name_label = TextBody(name='name_label', parent=self, align=Align.TOTOP, color=TextColor.HIGHLIGHT, padTop=8, bold=True) 116 self.gauge = Gauge(parent=self, align=Align.TOTOP, gaugeHeight=4, padTop=4, color=eveThemeColor.THEME_FOCUS) 117 self.time_left_label = TextBody(name='time_left_label', parent=self, align=Align.TOTOP, padTop=2) 118 119 def construct_complete_now(self): 120 self.complete_now_cont = Container(name='complete_cont', parent=self, align=Align.TOTOP, height=Button.default_height, padBottom=16, padTop=8) 121 self.complete_now_label_cont = LayoutGrid(name='complete_now_label_cont', parent=self.complete_now_cont, align=Align.CENTER, cellSpacing=(4, 0)) 122 Sprite(parent=self.complete_now_label_cont, align=Align.CENTERLEFT, texturePath=eveicon.plex, color=eveColor.PLEX_YELLOW, pos=(0, 0, 16, 16)) 123 self.complete_now_label = TextBody(parent=self.complete_now_label_cont, align=Align.CENTERLEFT) 124 self.complete_button = Button(name='complete_button', parent=self.complete_now_cont, align=Align.TOTOP, display=False, pickState=PickState.ON, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/CompleteJobNow'), func=self.on_complete_button) 125 self.complete_button.LoadTooltipPanel = self.LoadCompleteButtonTooltipPanel 126 127 def LoadCompleteButtonTooltipPanel(self, tooltipPanel, *args): 128 if self.has_enough_plex: 129 return 130 tooltipPanel.columns = 1 131 tooltipPanel.LoadStandardSpacing() 132 tooltipPanel.pickState = PickState.ON 133 TextBody(parent=tooltipPanel, align=Align.CENTERLEFT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/InsufficientPLEX', plex_balance=int(get_repository().get_balance()) or 0), color=TextColor.DANGER, maxWidth=220) 134 Button(parent=tooltipPanel, label=GetByLabel('UI/Wallet/BuyMore'), func=self.on_buy_more_plex_button, style=ButtonStyle.MONETIZATION, align=Align.TOTOP) 135 136 def on_buy_more_plex_button(self, *args): 137 buy_missing_plex_dialog = BuyMissingPlexDialog(required_amount=self.get_early_completion_plex_cost()) 138 buy_missing_plex_dialog.ShowModal() 139 140 def on_complete_button(self, *args): 141 if not self.has_enough_plex: 142 return 143 try: 144 self.complete_button.busy = True 145 self.complete_button.disable() 146 self.show_expedite_sequencing_popup_async() 147 except (GenericException, TimeoutException): 148 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 149 finally: 150 self.complete_button.busy = False 151 self.complete_button.enable() 152 153 def show_expedite_sequencing_popup_async(self): 154 self.kill_expedite_sequencing_thread() 155 self.expedite_sequencing_thread = uthread2.start_tasklet(self.show_expedite_sequencing_popup) 156 157 def show_expedite_sequencing_popup(self): 158 params = {'plex_cost': self.get_early_completion_plex_cost()} 159 icon = 'res:/UI/texture/Icons/SKINR.png' 160 if prompt_player_fsd_dialog('CompleteSequencingNow', params, uiconst.YESNO, icon) == uiconst.ID_YES: 161 error = get_ship_skin_sequencing_svc().expedite_sequencing(self.sequencing_job.job_id) 162 if error is not None: 163 message_player(GetByLabel(get_sequencing_error_text(error))) 164 165 def construct_top_cont(self): 166 self.top_cont = Container(name='top_cont', parent=self, align=Align.TOTOP, height=198) 167 168 def construct_num_runs_label(self): 169 num_runs_cont = ContainerAutoSize(name='num_runs_Cont', parent=self.top_cont, align=Align.BOTTOMRIGHT, height=46) 170 StretchSpriteHorizontal(bgParent=num_runs_cont, texturePath='res:/UI/Texture/classes/Cosmetics/Ship/cards/sequencing/numruns_background.png', leftEdgeSize=34, rightEdgeSize=30) 171 label_cont = ContainerAutoSize(name='label_cont', parent=num_runs_cont, align=Align.TOLEFT, padLeft=40, padRight=26) 172 self.num_runs_label = TextBody(align=Align.CENTERLEFT, top=4, parent=label_cont) 173 174 def construct_ship_icon(self): 175 icon_cont = Container(name='icon_cont', parent=self.top_cont, align=Align.CENTER, pos=(0, 0, 168, 198), state=uiconst.UI_DISABLED) 176 self.live_icon = LiveIconSprite(name='live_icon', parent=icon_cont, opacity=0.0, viewportWidth=168, viewportHeight=198, bg_texture_path=CardBackground.SEQUENCING, mask_texture_path=CardMask.SEQUENCING) 177 self.fallback_background = Sprite(name='fallback_background', bgParent=icon_cont, spriteEffect=SpriteEffect.MASK, textureSecondaryPath=CardMask.SEQUENCING, texturePath=CardBackground.SEQUENCING) 178 self.finished_loading = False 179 self.live_icon.on_rendered.connect(self.on_live_rendered_icon_ready) 180 self.loading_sprite = LoadingWheel(parent=self.top_cont, align=uiconst.CENTER) 181 182 def construct_tier_indicator(self): 183 if not self.show_tier_indicator(): 184 return 185 self.tier_label = TextBody(name='tier_label', parent=self.top_cont, align=Align.TOTOP_NOPUSH, textAlign=TextAlign.CENTER, text='{tier}'.format(tier=self.get_skin_tier_level()), color=eveColor.BLACK, shadowOffset=(0, 0), bold=True, padTop=6) 186 self.tier_container = Container(name='tier_container', parent=self.top_cont, align=Align.TOTOP_NOPUSH, opacity=0.5, width=self.default_width, height=29) 187 self.tier_background = Sprite(name='tier_background', parent=self.tier_container, align=Align.TOTOP_NOPUSH, state=uiconst.UI_DISABLED, texturePath=self.get_skin_tier_texture(), outputMode=uiconst.OutputMode.COLOR_AND_GLOW, glowBrightness=0.4, width=self.default_width, height=29) 188 189 def show_tier_indicator(self): 190 return True 191 192 def get_skin_tier_level(self): 193 return self.skin_design.tier_level 194 195 def get_skin_tier_texture(self): 196 tier_level = self.get_skin_tier_level() 197 return 'res:/UI/Texture/classes/Cosmetics/Ship/cards/tier/tier_{level}.png'.format(level=tier_level) 198 199 def update(self): 200 self.num_runs_label.text = str(self.sequencing_job.nb_runs) 201 self.name_label.text = self.skin_design.name or '' 202 self.update_tier_indicator() 203 self.fallback_background.texturePath = CardBackground.SEQUENCING_SELECTED if self.is_selected else CardBackground.SEQUENCING 204 self.update_complete_now_async() 205 206 def update_complete_now_async(self): 207 self.kill_update_complete_now_thread() 208 self.update_complete_now_thread = uthread2.start_tasklet(self.update_complete_now) 209 210 def update_complete_now(self): 211 if not self.complete_now_cont or self.complete_now_cont.destroyed: 212 return 213 try: 214 plex_cost = self.get_early_completion_plex_cost() 215 if plex_cost: 216 self.complete_now_cont.display = True 217 self.complete_now_label.text = plex_cost 218 has_enough_plex = get_repository().get_balance() >= plex_cost 219 self.complete_button.style = ButtonStyle.NORMAL if has_enough_plex else ButtonStyle.DANGER 220 else: 221 self.complete_now_cont.display = False 222 except NoTimeRemainingException: 223 self.complete_now_cont.display = False 224 225 def get_early_completion_plex_cost(self): 226 try: 227 plex_cost = get_ship_skin_sequencing_svc().get_early_completion_cost(self.sequencing_job.job_id) 228 except (GenericException, TimeoutException): 229 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 230 return None 231 232 return plex_cost 233 234 def update_time_left_label(self): 235 while not self.destroyed: 236 job_state = self.sequencing_job.state 237 if job_state == SequencingJobState.STARTED: 238 self.time_left_label.text = self.sequencing_job.get_time_remaining_text() 239 self.gauge.SetValue(self.sequencing_job.get_completed_ratio(), animate=False) 240 elif job_state == SequencingJobState.COMPLETED: 241 self.time_left_label.text = GetByLabel('UI/Generic/Completed') 242 self.gauge.SetValue(1.0) 243 uthread2.Sleep(0.5) 244 245 def update_tier_indicator(self): 246 if not self.show_tier_indicator(): 247 return 248 animations.FadeTo(self.tier_container, self.tier_container.opacity, 0.5, ANIM_DURATION) 249 250 def update_live_icon(self, skip_queue = False): 251 bg_texture_path = CardBackground.SEQUENCING_SELECTED if self.is_selected else CardBackground.SEQUENCING 252 self.live_icon.apply_skin_design(self.skin_design, bg_texture_path, skip_queue) 253 254 @property 255 def has_enough_plex(self): 256 return get_repository().get_balance() >= self.get_early_completion_plex_cost() 257 258 @property 259 def is_selected(self): 260 return self._is_selected 261 262 @is_selected.setter 263 def is_selected(self, value): 264 should_update = value != self._is_selected 265 self._is_selected = value 266 if should_update: 267 self.update() 268 self.update_live_icon(skip_queue=True) 269 270 def OnMouseEnter(self, *args): 271 self.is_hovered = True 272 if self.complete_now_cont.display: 273 self.complete_button.display = True 274 self.complete_now_label_cont.display = False 275 self.update() 276 277 def OnMouseExit(self, *args): 278 self.is_hovered = False 279 if self.complete_now_cont.display: 280 self.complete_button.display = False 281 self.complete_now_label_cont.display = True 282 self.update() 283 284 def OnClick(self, *args): 285 self.is_selected = not self.is_selected 286 self.on_click(self) 287 288 def GetMenu(self): 289 menu = MenuData() 290 type_id = self.skin_design.ship_type_id 291 menu.AddEntry(text=evetypes.GetName(type_id), subMenuData=lambda : GetMenuService().GetMenuFromItemIDTypeID(itemID=None, typeID=type_id, includeMarketDetails=True)) 292 if session.role & ROLE_QA: 293 menu.AddEntry(text='Job UUID: {job_uuid}'.format(job_uuid=self.sequencing_job.job_id), func=lambda *args: blue.pyos.SetClipboardData(str(self.sequencing_job.job_id))) 294 return menu