startSequencingPanel.py (20841B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\sequence\startSequencingPanel.py 2 import eveicon 3 import uthread2 4 from carbonui import Align, AxisAlignment, Density, PickState 5 from carbonui import ButtonVariant, ButtonStyle 6 from carbonui import TextColor 7 from carbonui.text.styles import TextHeader, TextBody 8 from carbonui.button.group import ButtonGroup, ButtonSizeMode 9 from carbonui.control.button import Button 10 from carbonui.control.scrollContainer import ScrollContainer 11 from carbonui.control.singlelineedits.singleLineEditInteger import SingleLineEditInteger 12 from carbonui.primitives.container import Container 13 from carbonui.primitives.containerAutoSize import ContainerAutoSize 14 from carbonui.primitives.fill import Fill 15 from carbonui.primitives.sprite import Sprite 16 from carbonui.uianimations import animations 17 from characterskills.client import skill_signals 18 from cosmetics.client.shipSkinSequencingSvc import get_ship_skin_sequencing_svc 19 from cosmetics.client.ships import ship_skin_signals 20 from cosmetics.client.ships.qa.settings import is_sequencing_button_always_enabled 21 from cosmetics.client.ships.skins.errors import SEQUENCING_ERROR_TEXT_BY_CODE 22 from cosmetics.client.ships.skins.live_data import current_skin_design, current_skin_design_signals 23 from cosmetics.common.cosmeticsConst import SEQUENCE_BINDER_TYPES 24 from cosmetics.common.ships.skins import sequencing_util 25 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 26 from cosmetics.common.ships.skins.util import Currency 27 from eve.client.script.ui import eveColor, eveThemeColor 28 from player_messaging.client.ui_message import message_player 29 from eve.client.script.ui.cosmetics.ship.const import SkinrPage 30 from eve.client.script.ui.cosmetics.ship.controls.costInfo import SequencingPanelCostInfo 31 from eve.client.script.ui.cosmetics.ship.controls.decoratedLine import DecoratedLine 32 from eve.client.script.ui.cosmetics.ship.controls.sequencingTime import SequencingTime 33 from eve.client.script.ui.cosmetics.ship.pages import current_page 34 from eve.client.script.ui.cosmetics.ship.pages.sequence import sequence_ui_signals 35 from eve.client.script.ui.cosmetics.ship.pages.sequence.sequenceBinderEntries import SequenceBinderCostEntry, UnlimitedComponentLicenseEntry, LimitedComponentLicenseEntry 36 from eve.client.script.ui.cosmetics.ship.pages.sequence.tooltipInfoIcons import NumRunsInfoIcon 37 from localization import GetByLabel 38 from plex.client.api.signal import on_balance_changed 39 from publicGateway.grpc.exceptions import GenericException 40 from skills.client.util import get_skill_service 41 from stackless_response_router.exceptions import TimeoutException 42 43 class StartSequencingPanel(Container): 44 default_width = 350 45 46 def __init__(self, **kw): 47 super(StartSequencingPanel, self).__init__(**kw) 48 self.unlimited_sequence_binder_entries = [] 49 self.limited_sequence_binder_entries = [] 50 self.sequencing_time = None 51 self.cost_info = None 52 self.num_skins_edit = None 53 self.start_btn = None 54 self.unlimited_use_entry_container = None 55 self.limited_use_entry_container = None 56 self._update_values_thread = None 57 self._set_num_runs_thread = None 58 self.construct_bottom_buttons() 59 self.construct_cost_info_container() 60 self.construct_sequencing_time_container() 61 self.construct_content_containers() 62 self.connnect_signals() 63 64 def Close(self): 65 try: 66 self.kill_threads() 67 self.disconnect_signals() 68 finally: 69 super(StartSequencingPanel, self).Close() 70 71 def kill_threads(self): 72 self.kill_update_values_thread() 73 self.kill_set_num_runs_thread() 74 75 def kill_update_values_thread(self): 76 if self._update_values_thread: 77 self._update_values_thread.kill() 78 self._update_values_thread = None 79 80 def kill_set_num_runs_thread(self): 81 if self._set_num_runs_thread: 82 self._set_num_runs_thread.kill() 83 self._set_num_runs_thread = None 84 85 def connnect_signals(self): 86 on_balance_changed.connect(self._on_plex_balance_changed) 87 skill_signals.on_skill_levels_trained.connect(self.on_skill_levels_trained) 88 sequence_ui_signals.on_num_skins_changed.connect(self.on_num_skins_changed) 89 sequence_ui_signals.on_sequence_binders_changed.connect(self.on_sequence_binders_changed) 90 current_skin_design_signals.on_name_changed.connect(self.on_current_skin_design_name_changed) 91 current_skin_design_signals.on_component_instance_license_to_use_changed.connect(self.on_component_instance_license_to_use_changed) 92 ship_skin_signals.on_component_license_granted.connect(self.on_component_license_granted) 93 ship_skin_signals.on_skin_sequencing_cache_invalidated.connect(self.on_skin_sequencing_cache_invalidated) 94 is_sequencing_button_always_enabled.on_change.connect(self._on_mock_sequencing_button_changed) 95 self.on_size_changed.connect(self.on_size_changed_signal) 96 97 def disconnect_signals(self): 98 on_balance_changed.disconnect(self._on_plex_balance_changed) 99 skill_signals.on_skill_levels_trained.disconnect(self.on_skill_levels_trained) 100 sequence_ui_signals.on_num_skins_changed.disconnect(self.on_num_skins_changed) 101 sequence_ui_signals.on_sequence_binders_changed.disconnect(self.on_sequence_binders_changed) 102 current_skin_design_signals.on_name_changed.disconnect(self.on_current_skin_design_name_changed) 103 current_skin_design_signals.on_component_instance_license_to_use_changed.disconnect(self.on_component_instance_license_to_use_changed) 104 ship_skin_signals.on_component_license_granted.disconnect(self.on_component_license_granted) 105 ship_skin_signals.on_skin_sequencing_cache_invalidated.disconnect(self.on_skin_sequencing_cache_invalidated) 106 is_sequencing_button_always_enabled.on_change.disconnect(self._on_mock_sequencing_button_changed) 107 self.on_size_changed.disconnect(self.on_size_changed_signal) 108 109 def load_panel(self): 110 current_skin_design.get().clean_up_skin() 111 self.reconstruct_layout() 112 self.update_values() 113 self.anim_entry() 114 115 def anim_entry(self): 116 self.content.opacity = 0.0 117 animations.MorphScalar(self, 'left', -200, 0, duration=0.6) 118 animations.FadeTo(self.content, 0.0, 1.0, duration=0.3, timeOffset=0.6) 119 120 def reconstruct_layout(self): 121 self.content.Flush() 122 self.construct_layout() 123 self.on_size_changed_signal() 124 125 def construct_content_containers(self): 126 outer_container = Container(name='outer_container', parent=self, align=Align.TOALL, padding=(0, 8, 0, 16)) 127 self.scroll_divider_top = DecoratedLine(name='scroll_divider_top', parent=outer_container, align=Align.TOTOP, color=eveThemeColor.THEME_ALERT) 128 self.scroll_container = ScrollContainer(name='scroll_container', parent=outer_container, align=Align.TOALL, padding=(0, 16, 0, 16)) 129 self.content = ContainerAutoSize(name='content', parent=self.scroll_container, align=Align.CENTER, width=self.default_width - 16) 130 self.scroll_divider_bottom = DecoratedLine(name='scroll_divider_bottom', parent=outer_container, align=Align.TOBOTTOM, color=eveThemeColor.THEME_ALERT) 131 132 def construct_layout(self): 133 self.construct_num_runs_input() 134 self.construct_sections_header() 135 self.construct_unlimited_use_section() 136 self.construct_limited_use_section() 137 self.construct_sequencing_time() 138 self.construct_cost_info() 139 140 def construct_num_runs_input(self): 141 num_skins_cont = ContainerAutoSize(name='num_skins_cont', parent=self.content, align=Align.TOTOP) 142 label_cont = ContainerAutoSize(name='label_cont', parent=num_skins_cont, align=Align.CENTERTOP, height=20) 143 TextBody(parent=ContainerAutoSize(parent=label_cont, align=Align.TOLEFT), text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/SkinAmount'), align=Align.CENTERLEFT) 144 NumRunsInfoIcon(parent=ContainerAutoSize(parent=label_cont, align=Align.TOLEFT, padLeft=4), align=Align.CENTERLEFT) 145 self.num_skins_edit = SingleLineEditInteger(name='num_skins_edit', parent=num_skins_cont, align=Align.CENTERTOP, density=Density.EXPANDED, top=32, width=96, minValue=1, maxValue=sequencing_util.get_maximum_runs_per_job(get_skill_service().GetMyLevel), setvalue=get_ship_skin_sequencing_svc().get_num_runs(), OnChange=self.on_num_skins_edit) 146 147 def construct_sections_header(self): 148 sections_header_container = Container(name='sections_header_container', parent=self.content, align=Align.TOTOP, height=32, padTop=32) 149 TextBody(parent=sections_header_container, align=Align.CENTERLEFT, color=TextColor.SECONDARY, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/ConsumablesRequired')) 150 151 def construct_unlimited_use_section(self): 152 unlimited_use_section_container = ContainerAutoSize(name='unlimited_use_section_container', parent=self.content, align=Align.TOTOP, padTop=8) 153 ComponentTypeHeader(name='unlimited_use_section_header', parent=unlimited_use_section_container, align=Align.TOTOP, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/UnlimitedDesignElement'), icon=eveicon.infinity) 154 self.unlimited_use_entry_container = ContainerAutoSize(name='unlimited_use_entry_container', parent=unlimited_use_section_container, align=Align.TOTOP) 155 self.refresh_unlimited_use_entries() 156 157 def refresh_unlimited_use_entries(self): 158 if not self.unlimited_use_entry_container: 159 return 160 self.unlimited_use_entry_container.Flush() 161 self.unlimited_sequence_binder_entries = [] 162 for type_id in SEQUENCE_BINDER_TYPES: 163 entry = SequenceBinderCostEntry(parent=self.unlimited_use_entry_container, align=Align.TOTOP, type_id=type_id, pickState=PickState.ON, padTop=8) 164 self.unlimited_sequence_binder_entries.append(entry) 165 fitted_components = current_skin_design.get().get_fitted_components() 166 for component in fitted_components: 167 if type_id != component.sequence_binder_type_id: 168 continue 169 component_license = component.get_component_license() 170 if component_license and component_license.license_type != ComponentLicenseType.UNLIMITED: 171 continue 172 if not component_license: 173 available_licenses = component.get_component_data().component_item_data_by_type_id.values() 174 limited_licenses = [ l for l in available_licenses if l.license_type == ComponentLicenseType.LIMITED ] 175 if len(limited_licenses) > 0: 176 continue 177 UnlimitedComponentLicenseEntry(parent=self.unlimited_use_entry_container, align=Align.TOTOP, type_id=type_id, component=component, padTop=8) 178 179 if type_id != SEQUENCE_BINDER_TYPES[-1]: 180 DecoratedLine(parent=self.unlimited_use_entry_container, padTop=16, align=Align.TOTOP) 181 182 def construct_limited_use_section(self): 183 self.limited_use_section_container = ContainerAutoSize(name='limited_use_section_container', parent=self.content, align=Align.TOTOP, padTop=32) 184 ComponentTypeHeader(name='limited_use_section_header', parent=self.limited_use_section_container, align=Align.TOTOP, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/LimitedDesignElement'), icon=eveicon.limited) 185 self.limited_use_entry_container = ContainerAutoSize(name='limited_use_entry_container', parent=self.limited_use_section_container, align=Align.TOTOP) 186 self.refresh_limited_use_entries() 187 188 def refresh_limited_use_entries(self): 189 if not self.limited_use_entry_container: 190 return 191 self.limited_use_entry_container.Flush() 192 self.limited_sequence_binder_entries = [] 193 fitted_components = current_skin_design.get().get_fitted_components() 194 if len(fitted_components) > 0: 195 self.limited_use_section_container.display = True 196 for component in fitted_components: 197 component_license = component.get_component_license() 198 if component_license and component_license.license_type != ComponentLicenseType.LIMITED: 199 continue 200 if not component_license: 201 available_licenses = component.get_component_data().component_item_data_by_type_id.values() 202 limited_licenses = [ l for l in available_licenses if l.license_type == ComponentLicenseType.LIMITED ] 203 if len(limited_licenses) == 0: 204 continue 205 if any([ e.component.component_id == component.component_id for e in self.limited_sequence_binder_entries ]): 206 continue 207 entry = LimitedComponentLicenseEntry(parent=self.limited_use_entry_container, align=Align.TOTOP, component=component, padTop=8) 208 self.limited_sequence_binder_entries.append(entry) 209 210 else: 211 self.limited_use_section_container.display = False 212 213 def get_components_by_sequence_binder_type(self, license_type): 214 components_by_type = {} 215 fitted_components = current_skin_design.get().get_fitted_components() 216 for component in fitted_components: 217 component_license = component.get_component_license() 218 if component_license is None or component_license.license_type != license_type: 219 continue 220 type_id = component.get_component_data().sequence_binder_type_id 221 components_by_type[type_id] = components_by_type.get(type_id, component) 222 223 return components_by_type 224 225 def construct_sequencing_time_container(self): 226 self.sequencing_time_container = Container(name='sequencing_time_container', parent=self, align=Align.TOBOTTOM, height=32, padBottom=4) 227 228 def construct_sequencing_time(self): 229 self.sequencing_time_container.Flush() 230 self.sequencing_time = SequencingTime(name='sequencing_time', parent=self.sequencing_time_container, align=Align.TOTOP, minWidth=self.start_btn.width - 32) 231 232 def construct_cost_info_container(self): 233 self.cost_info_container = ContainerAutoSize(name='cost_info_container', parent=self, align=Align.TOBOTTOM, padBottom=4) 234 235 def construct_cost_info(self): 236 self.cost_info_container.Flush() 237 self.cost_info = SequencingPanelCostInfo(name='cost_info', parent=self.cost_info_container, align=Align.TOBOTTOM, currency=Currency.PLEX) 238 239 def construct_bottom_buttons(self): 240 button_group = ButtonGroup(parent=self, align=Align.TOBOTTOM, button_alignment=AxisAlignment.CENTER, button_size_mode=ButtonSizeMode.STRETCH, density=Density.EXPANDED) 241 self.start_btn = StartSequencingBtn(label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/CreateSKIN'), func=self.on_start_sequencing_btn, variant=ButtonVariant.PRIMARY) 242 button_group.add_button(self.start_btn) 243 244 @uthread2.debounce(0.2) 245 def update_values(self): 246 self.kill_update_values_thread() 247 self._update_values_thread = uthread2.start_tasklet(self._update_values_async) 248 249 def _update_values_async(self): 250 if self.destroyed: 251 return 252 svc = get_ship_skin_sequencing_svc() 253 if self.sequencing_time: 254 self.sequencing_time.update() 255 if self.cost_info: 256 try: 257 plex_cost, discount = svc.get_sequencing_plex_price(current_skin_design.get(), svc.get_num_runs()) 258 except (GenericException, TimeoutException): 259 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 260 plex_cost = 0 261 262 self.cost_info.set_cost(plex_cost, discount) 263 if self.num_skins_edit: 264 self.num_skins_edit.SetMaxValue(sequencing_util.get_maximum_runs_per_job(get_skill_service().GetMyLevel)) 265 self.refresh_unlimited_use_entries() 266 self.refresh_limited_use_entries() 267 if self.start_btn: 268 self.start_btn.update() 269 270 @uthread2.debounce(0.5) 271 def on_num_skins_edit(self, *args): 272 self.kill_set_num_runs_thread() 273 self._set_num_runs_thread = uthread2.start_tasklet(get_ship_skin_sequencing_svc().set_num_runs, self.number_of_skins) 274 275 def on_num_skins_changed(self, *args): 276 self.update_values() 277 278 def on_sequence_binders_changed(self, *args): 279 self.update_values() 280 281 def on_component_instance_license_to_use_changed(self, *args): 282 self.update_values() 283 284 def on_component_license_granted(self, *args): 285 self.update_values() 286 287 def on_skin_sequencing_cache_invalidated(self, *args): 288 self.update_values() 289 290 def on_current_skin_design_name_changed(self, *args): 291 self.update_values() 292 293 def on_skill_levels_trained(self, *args): 294 self.update_values() 295 296 def on_size_changed_signal(self, *args): 297 if self.scroll_container: 298 is_scrolling = self.scroll_container.IsVerticalScrollBarVisible() 299 self.scroll_divider_top.display = is_scrolling 300 self.scroll_divider_bottom.display = is_scrolling 301 302 def _on_plex_balance_changed(self, *args): 303 self.update_values() 304 305 def on_start_sequencing_btn(self, *args): 306 self.on_sequencing_attempt() 307 try: 308 error = get_ship_skin_sequencing_svc().sequence_design(current_skin_design.get()) 309 if not error: 310 current_skin_design.create_blank_design() 311 current_page.reset_history() 312 current_page.set_page_id(SkinrPage.STUDIO) 313 else: 314 label = SEQUENCING_ERROR_TEXT_BY_CODE.get(error, None) 315 if label: 316 message_player(GetByLabel(label)) 317 finally: 318 self.on_after_sequencing_attempt() 319 320 def on_sequencing_attempt(self): 321 if self.start_btn: 322 self.start_btn.busy = True 323 self.start_btn.disabled = True 324 if self.num_skins_edit: 325 self.num_skins_edit.Disable() 326 327 def on_after_sequencing_attempt(self): 328 if self.start_btn: 329 self.start_btn.disabled = False 330 self.start_btn.busy = False 331 self.start_btn.update() 332 if self.num_skins_edit: 333 self.num_skins_edit.Enable() 334 335 def _on_mock_sequencing_button_changed(self, *args): 336 self.on_after_sequencing_attempt() 337 338 @property 339 def number_of_skins(self): 340 return self.num_skins_edit.GetValue() 341 342 343 class StartSequencingBtn(Button): 344 __notifyevents__ = ['OnSubscriptionChanged'] 345 errors = None 346 347 def __init__(self, **kwargs): 348 super(StartSequencingBtn, self).__init__(**kwargs) 349 sm.RegisterNotify(self) 350 351 def update(self): 352 if current_page.get_page_id() != SkinrPage.STUDIO_SEQUENCING: 353 return 354 if is_sequencing_button_always_enabled.is_enabled(): 355 self.errors = [] 356 else: 357 self.errors = get_ship_skin_sequencing_svc().validate_design_for_sequencing(current_skin_design.get()) 358 if self.errors: 359 self.style = ButtonStyle.DANGER 360 else: 361 self.style = ButtonStyle.NORMAL 362 363 def OnSubscriptionChanged(self): 364 self.update() 365 366 def LoadTooltipPanel(self, tooltipPanel, *args): 367 if not self.errors: 368 return 369 if self.busy: 370 return 371 tooltipPanel.LoadStandardSpacing() 372 tooltipPanel.columns = 1 373 tooltipPanel.AddMediumHeader(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/UnableToStartSequencingJob'), wrapWidth=300) 374 for error in self.errors: 375 label = SEQUENCING_ERROR_TEXT_BY_CODE.get(error, None) 376 if label: 377 tooltipPanel.AddLabelMedium(text=GetByLabel(label), wrapWidth=300, color=eveColor.DANGER_RED) 378 379 def _CallFunction(self): 380 if self.errors: 381 return 382 else: 383 return super(StartSequencingBtn, self)._CallFunction() 384 385 386 class ComponentTypeHeader(Container): 387 default_height = 29 388 389 def __init__(self, text, icon, *args, **kwargs): 390 super(ComponentTypeHeader, self).__init__(*args, **kwargs) 391 self.text = text 392 self.icon = icon 393 self.construct_layout() 394 395 def construct_layout(self): 396 Fill(bgParent=self, color=eveColor.TUNGSTEN_GREY, opacity=0.1) 397 TextHeader(parent=self, align=Align.CENTERLEFT, text=self.text, padLeft=8) 398 icon_container = Container(parent=self, align=Align.TORIGHT, width=16, padRight=8) 399 Sprite(parent=icon_container, align=Align.CENTER, texturePath=self.icon, pos=(0, 0, 16, 16))