licenseSelector.py (14601B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\studio\licenseSelector.py 2 import eveicon 3 import uthread2 4 from carbonui import Align, TextColor, uiconst 5 from carbonui.text.styles import TextBody 6 from carbonui.primitives.container import Container 7 from carbonui.primitives.containerAutoSize import ContainerAutoSize 8 from carbonui.primitives.sprite import Sprite 9 from carbonui.primitives.stretchspritehorizontal import StretchSpriteHorizontal 10 from carbonui.uianimations import animations 11 from cosmetics.client.ships import ship_skin_signals 12 from cosmetics.client.shipSkinComponentSvc import get_ship_skin_component_svc 13 from cosmetics.client.ships.skins.live_data import current_skin_design_signals 14 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 15 from eve.client.script.ui import eveColor 16 from player_messaging.client.ui_message import message_player 17 from eve.client.script.ui.cosmetics.ship.pages.sequence import sequence_ui_signals 18 from localization import GetByLabel 19 from publicGateway.grpc.exceptions import GenericException 20 from stackless_response_router.exceptions import TimeoutException 21 22 class LicenseSelector(ContainerAutoSize): 23 default_height = 20 24 25 def __init__(self, component_instance, *args, **kwargs): 26 super(LicenseSelector, self).__init__(*args, **kwargs) 27 self._component_instance = component_instance 28 self.bound_license = None 29 self.unbound_license = None 30 self._update_thread = None 31 self._update_component_license_granted_thread = None 32 self.construct_layout() 33 self.update_async() 34 self.connect_signals() 35 36 def Close(self): 37 try: 38 self.kill_threads() 39 self.disconnect_signals() 40 finally: 41 super(LicenseSelector, self).Close() 42 43 def kill_threads(self): 44 self.kill_update_thread() 45 self.kill_update_component_license_granted_thread() 46 47 def kill_update_thread(self): 48 if self._update_thread is not None: 49 self._update_thread.kill() 50 self._update_thread = None 51 52 def kill_update_component_license_granted_thread(self): 53 if self._update_component_license_granted_thread is not None: 54 self._update_component_license_granted_thread.kill() 55 self._update_component_license_granted_thread = None 56 57 def connect_signals(self): 58 current_skin_design_signals.on_component_instance_license_to_use_changed.connect(self.on_component_instance_license_to_use_changed) 59 ship_skin_signals.on_component_license_granted.connect(self.on_component_license_granted) 60 61 def disconnect_signals(self): 62 current_skin_design_signals.on_component_instance_license_to_use_changed.disconnect(self.on_component_instance_license_to_use_changed) 63 ship_skin_signals.on_component_license_granted.disconnect(self.on_component_license_granted) 64 65 def on_component_instance_license_to_use_changed(self, component_id, license): 66 if self.component_instance and self.component_instance.component_id == component_id: 67 self.update_async() 68 69 def on_component_license_granted(self, component_id, license_type, quantity): 70 self.update_component_license_granted_async(component_id, license_type) 71 72 def update_component_license_granted_async(self, component_id, license_type): 73 self.kill_update_component_license_granted_thread() 74 self._update_component_license_granted_thread = uthread2.start_tasklet(self.update_component_license_granted, component_id, license_type) 75 76 def update_component_license_granted(self, component_id, license_type): 77 if self.component_instance and self.component_instance.component_id == component_id: 78 self.load_license_status() 79 if not self.has_available_license: 80 return 81 if license_type == ComponentLicenseType.UNLIMITED and self.unbound_license: 82 self.component_instance.component_license_to_use = self.unbound_license 83 elif license_type == ComponentLicenseType.LIMITED and self.bound_license: 84 self.component_instance.component_license_to_use = self.bound_license 85 86 def construct_layout(self): 87 self.construct_unbound_indicator() 88 self.construct_slash() 89 self.construct_bound_indicator() 90 91 def construct_unbound_indicator(self): 92 self.unbound_indicator = UnboundLicenseIndicator(name='unbound_indicator', parent=self, align=Align.TOLEFT) 93 self.unbound_indicator.OnClick = self.on_unbound_click 94 95 def construct_slash(self): 96 self.slash = SlashDivider(name='slash', parent=self, align=Align.TOLEFT, padTop=1) 97 98 def construct_bound_indicator(self): 99 self.bound_indicator = BoundLicenseIndicator(name='bound_indicator', parent=self, align=Align.TOLEFT) 100 self.bound_indicator.OnClick = self.on_bound_click 101 102 def update_async(self): 103 self.kill_update_thread() 104 self._update_thread = uthread2.start_tasklet(self.update) 105 106 def update(self): 107 self.load_license_status() 108 self.display = self.has_available_license 109 self.update_unbound_indicator() 110 self.update_bound_indicator() 111 self.update_selection() 112 self.update_slash() 113 if self.component_instance and self.component_instance.component_license_to_use: 114 self.show() 115 else: 116 self.hide() 117 118 def load_license_status(self): 119 self.bound_license = self.unbound_license = None 120 if not self.component_instance: 121 return 122 try: 123 self.bound_license = get_ship_skin_component_svc().get_bound_license(component_id=self.component_instance.component_id, component_type=self.component_instance.get_component_data().category) 124 self.unbound_license = get_ship_skin_component_svc().get_unbound_license(component_id=self.component_instance.component_id, component_type=self.component_instance.get_component_data().category) 125 except (GenericException, TimeoutException): 126 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 127 128 def update_unbound_indicator(self): 129 self.unbound_indicator.component_license = self.unbound_license 130 self.unbound_indicator.component_instance = self.component_instance 131 132 def update_bound_indicator(self): 133 self.bound_indicator.component_license = self.bound_license 134 self.bound_indicator.component_instance = self.component_instance 135 136 def update_selection(self): 137 self.bound_indicator.is_selected = False 138 self.unbound_indicator.is_selected = False 139 if not self.component_instance: 140 return 141 license = self.component_instance.get_component_license() 142 if not license: 143 return 144 if license.license_type == ComponentLicenseType.UNLIMITED: 145 self.unbound_indicator.is_selected = True 146 elif license.license_type == ComponentLicenseType.LIMITED: 147 self.bound_indicator.is_selected = self.has_bound_uses_remaining 148 149 def update_slash(self): 150 self.slash.display = self.unbound_license is not None and self.bound_license is not None 151 if self.bound_indicator.is_selected: 152 self.slash.close() 153 else: 154 self.slash.open() 155 156 def show(self, animate = True): 157 self.state = uiconst.UI_PICKCHILDREN 158 if animate: 159 animations.FadeTo(self, self.opacity, 1.0, duration=0.2) 160 else: 161 self.opacity = 1.0 162 163 def hide(self, animate = True): 164 self.state = uiconst.UI_DISABLED 165 if animate: 166 animations.FadeTo(self, self.opacity, 0.0, duration=0.2) 167 else: 168 self.opacity = 0.0 169 170 def on_unbound_click(self, *args): 171 self.unbound_indicator.is_selected = True 172 self.bound_indicator.is_selected = False 173 self.component_instance.component_license_to_use = self.unbound_license 174 sequence_ui_signals.on_component_license_selection_changed() 175 176 def on_bound_click(self, *args): 177 self.bound_indicator.is_selected = True 178 self.unbound_indicator.is_selected = False 179 self.component_instance.component_license_to_use = self.bound_license 180 sequence_ui_signals.on_component_license_selection_changed() 181 182 @property 183 def has_available_license(self): 184 return self.bound_license is not None or self.unbound_license is not None 185 186 @property 187 def has_bound_uses_remaining(self): 188 if self.bound_license: 189 return self.bound_license.remaining_license_uses and self.bound_license.remaining_license_uses > 0 190 return False 191 192 @property 193 def component_instance(self): 194 return self._component_instance 195 196 @component_instance.setter 197 def component_instance(self, value): 198 if self._component_instance == value: 199 return 200 self._component_instance = value 201 self.update_async() 202 203 204 class BaseLicenseIndicator(Container): 205 default_width = 28 206 default_height = 20 207 default_state = uiconst.UI_NORMAL 208 209 def __init__(self, component_license = None, component_instance = None, *args, **kwargs): 210 super(BaseLicenseIndicator, self).__init__(*args, **kwargs) 211 self._component_license = component_license 212 self._component_instance = component_instance 213 self._is_selected = False 214 self.construct_layout() 215 self.update() 216 217 def construct_layout(self): 218 self.construct_background() 219 220 def construct_background(self): 221 self.background_sprite = StretchSpriteHorizontal(name='background_sprite', parent=self, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/license_indicator_bg.png', color=eveColor.SMOKE_BLUE, opacity=0.3, leftEdgeSize=8, rightEdgeSize=8) 222 223 def update(self): 224 self.update_display() 225 if not self.display: 226 return 227 self.update_width() 228 self.update_background() 229 230 def update_display(self): 231 self.display = self.component_license is not None 232 233 def update_width(self): 234 self.width = self.default_width 235 236 def update_background(self): 237 self.background_sprite.pos = (0, 238 0, 239 self.width, 240 self.default_height) 241 target_opacity = 1.0 if self.is_selected else 0.3 242 animations.FadeTo(self.background_sprite, self.background_sprite.opacity, target_opacity, duration=0.2) 243 244 @property 245 def is_selected(self): 246 return self._is_selected 247 248 @is_selected.setter 249 def is_selected(self, value): 250 if self._is_selected == value: 251 return 252 self._is_selected = value 253 self.update() 254 255 @property 256 def component_license(self): 257 return self._component_license 258 259 @component_license.setter 260 def component_license(self, value): 261 if self._component_license == value: 262 return 263 self._component_license = value 264 self.update() 265 266 @property 267 def component_instance(self): 268 return self._component_instance 269 270 @component_instance.setter 271 def component_instance(self, value): 272 if self._component_instance == value: 273 return 274 self._component_instance = value 275 self.update() 276 277 278 class BoundLicenseIndicator(BaseLicenseIndicator): 279 280 def construct_layout(self): 281 self.label = TextBody(name='label', parent=self, align=Align.CENTER, opacity=0.5) 282 super(BoundLicenseIndicator, self).construct_layout() 283 284 def update(self): 285 super(BoundLicenseIndicator, self).update() 286 self.update_label() 287 288 def update_display(self): 289 self.display = self.has_uses_remaining 290 291 def update_width(self): 292 text_width, _ = TextBody.MeasureTextSize(self.label_text) 293 self.width = max(self.default_width, text_width + 16) 294 295 def update_label(self): 296 self.label.text = self.label_text 297 target_opacity = 1.0 if self.is_selected else 0.5 298 animations.FadeTo(self.label, self.label.opacity, target_opacity, duration=0.2) 299 300 @property 301 def has_uses_remaining(self): 302 if self.component_license: 303 return self.component_license.remaining_license_uses and self.component_license.remaining_license_uses > 0 304 return False 305 306 @property 307 def label_text(self): 308 if self.component_license: 309 return self.component_license.remaining_license_uses 310 return '' 311 312 def GetHint(self): 313 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/UseLimitedDesignElement') + '\n' + GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/NumLimitedDesignElementsAvailable', num_available=self.component_license.remaining_license_uses) 314 315 316 class UnboundLicenseIndicator(BaseLicenseIndicator): 317 318 def construct_layout(self): 319 self.icon = Sprite(name='icon', parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath=eveicon.infinity, color=TextColor.NORMAL, opacity=0.5, pos=(0, 0, 16, 16)) 320 super(UnboundLicenseIndicator, self).construct_layout() 321 322 def update(self): 323 super(UnboundLicenseIndicator, self).update() 324 self.update_icon() 325 326 def update_icon(self): 327 target_opacity = 1.0 if self.is_selected else 0.5 328 animations.FadeTo(self.icon, self.icon.opacity, target_opacity, duration=0.2) 329 330 def GetHint(self): 331 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/UnlimitedDesignElementRequirement', type_id=self.component_instance.sequence_binder_type_id, amount=self.component_instance.sequence_binder_amount_required_if_bound) 332 333 334 class SlashDivider(Container): 335 default_width = 13 336 default_height = 18 337 338 def __init__(self, *args, **kwargs): 339 super(SlashDivider, self).__init__(*args, **kwargs) 340 self.construct_layout() 341 342 def construct_layout(self): 343 self.open_sprite = Sprite(name='open_sprite', parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/slash_open.png', color=eveColor.FOCUS_BLUE, pos=(0, 0, 33, 38), display=False) 344 self.closed_sprite = Sprite(name='closed_sprite', parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/slash_closed.png', color=eveColor.FOCUS_BLUE, pos=(0, 0, 27, 26)) 345 346 def open(self): 347 self.open_sprite.display = True 348 self.closed_sprite.display = False 349 350 def close(self): 351 self.open_sprite.display = False 352 self.closed_sprite.display = True