cosmeticSlot.py (24578B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\studio\cosmeticSlot.py 2 import eveicon 3 import evetypes 4 import gametime 5 import math 6 from carbon.client.script.environment.AudioUtil import PlaySound 7 from carbon.common.script.sys.serviceConst import ROLE_GML 8 from carbonui import Align, uiconst, PickState, SpriteEffect 9 from carbonui.control.contextMenu.menuData import MenuData 10 from carbonui.loggers.buttonLogger import log_button_clicked 11 from carbonui.primitives.container import Container 12 from carbonui.primitives.containerAutoSize import ContainerAutoSize 13 from carbonui.primitives.sprite import Sprite 14 from carbonui.primitives.transform import Transform 15 from carbonui.uianimations import animations 16 from carbonui.uiconst import OutputMode 17 from cosmetics.client.shipSkinComponentSvc import get_ship_skin_component_svc 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.skins.live_data import current_skin_design_signals, current_skin_design 21 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 22 from cosmetics.common.ships.skins.static_data.slot_name import PATTERN_SLOT_IDS, PATTERN_SLOT_ID_BY_PATTERN_MATERIAL_ID 23 from eve.client.script.ui import eveThemeColor, eveColor 24 from eve.client.script.ui.cosmetics.ship import const as shipConst 25 from eve.client.script.ui.cosmetics.ship.pages.sequence import sequence_ui_signals 26 from eve.client.script.ui.cosmetics.ship.pages.studio import nebulaConsts 27 from eve.client.script.ui.cosmetics.ship.pages.studio.licenseSelector import LicenseSelector 28 from eve.client.script.ui.cosmetics.ship.pages.studio.radiallyAlignedMixin import RadiallyAlignedMixin 29 from eve.client.script.ui.cosmetics.ship.pages.studio.requiredComponentTooltipPanel import RequiredComponentTooltipPanel 30 from eveservices.menu import GetMenuService 31 from localization import GetByLabel 32 from signals import Signal 33 OPACITY_FRAME_IDLE = 0.1 34 OPACITY_FRAME_HOVER = 0.2 35 OPACITY_FRAME_SELECTED = 0.3 36 OPACITY_FRAME_IDLE_NEBULA = 0.4 37 OPACITY_FRAME_HOVER_NEBULA = 0.5 38 OPACITY_FRAME_SELECTED_NEBULA = 0.6 39 40 def get_frame_color(showing_warning): 41 if showing_warning: 42 return eveColor.DANGER_RED 43 else: 44 return eveThemeColor.THEME_FOCUSDARK 45 46 47 def get_side_frame_color(showing_warning): 48 return eveThemeColor.THEME_FOCUSDARK 49 50 51 def get_anim_time_offset(duration): 52 seconds = gametime.GetWallclockTimeNow() / float(gametime.SEC) 53 return duration - seconds % duration 54 55 56 class CosmeticSlotButton(Container, RadiallyAlignedMixin): 57 default_width = 64 58 default_height = 64 59 default_state = uiconst.UI_NORMAL 60 default_align = Align.CENTER 61 analyticID = None 62 63 def __init__(self, texture_path = None, icon_size = 32, is_selectable = True, **kw): 64 super(CosmeticSlotButton, self).__init__(**kw) 65 self.is_selectable = is_selectable 66 self.is_selected = False 67 self.is_hovered = False 68 self.showing_warning = False 69 self.construct_icon(texture_path, icon_size) 70 self.construct_hover_frame() 71 self.construct_frame() 72 self.on_click = Signal('on_click') 73 self.on_mouse_enter = Signal('on_mouse_enter') 74 self.on_mouse_exit = Signal('on_mouse_exit') 75 self.on_selected = Signal('on_selected') 76 self.on_deselected = Signal('on_deselected') 77 nebulaConsts.SELECTED_SKINR_BACKGROUND.on_change.connect(self.on_nebula_changed) 78 79 def Close(self): 80 try: 81 nebulaConsts.SELECTED_SKINR_BACKGROUND.on_change.disconnect(self.on_nebula_changed) 82 finally: 83 super(CosmeticSlotButton, self).Close() 84 85 def OnClick(self, *args): 86 self.on_click() 87 log_button_clicked(self) 88 89 def OnMouseEnter(self, *args): 90 self.is_hovered = True 91 self.update_state() 92 self.on_mouse_enter() 93 94 def OnMouseExit(self, *args): 95 self.is_hovered = False 96 self.update_state() 97 self.on_mouse_exit() 98 99 def construct_icon(self, texture_path, icon_size): 100 self.icon = Sprite(name='icon', parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath=texture_path, pos=(0, 101 0, 102 icon_size, 103 icon_size)) 104 Sprite(bgParent=self, texturePath='res:/UI/Texture/circle_full.png', padding=8, color=eveThemeColor.THEME_FOCUSDARK, opacity=0.2) 105 Sprite(bgParent=self, texturePath='res:/UI/Texture/circle_full.png', padding=8, color=eveColor.BLACK, opacity=0.4) 106 107 def construct_hover_frame(self): 108 self.hover_frame = Transform(parent=self, align=Align.TOALL, pickState=PickState.OFF, padding=4, opacity=0.0, scalingCenter=(0.5, 0.5)) 109 self.hover_frame_sprite = Sprite(name='hover_frame_sprite', bgParent=self.hover_frame, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/Pages/Studio/slot_hover_frame.png', outputMode=OutputMode.COLOR_AND_GLOW, glowBrightness=0.2, color=eveThemeColor.THEME_ACCENT) 110 111 def construct_frame(self): 112 self.frame = Transform(name='frame', parent=self, align=Align.TOALL, pickState=PickState.OFF, padding=-6, opacity=OPACITY_FRAME_IDLE if nebulaConsts.SELECTED_SKINR_BACKGROUND.get() is nebulaConsts.BackgroundEnum.Empty else OPACITY_FRAME_IDLE_NEBULA, scalingCenter=(0.5, 0.5)) 113 self.frame_bg = Sprite(bgParent=self.frame, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/Pages/Studio/slot_frame.png', outputMode=OutputMode.COLOR_AND_GLOW, glowBrightness=0.2, color=get_frame_color(self.showing_warning)) 114 115 def set_selected(self): 116 self.is_selected = True 117 self.on_selected() 118 119 def set_deselected(self): 120 self.is_selected = False 121 self.on_deselected() 122 123 def update_state(self, animate = True): 124 self._update_state_frame(animate) 125 self._update_state_hover_frame(animate) 126 127 def _update_state_hover_frame(self, animate): 128 opacity = 1.0 if self.is_selected else 0.0 129 if self.is_selected: 130 scale = (1.0, 1.0) 131 else: 132 scale = (0.7, 0.7) 133 if animate: 134 animations.FadeTo(self.hover_frame, self.hover_frame.opacity, opacity, duration=2 * uiconst.TIME_ENTRY) 135 animations.MorphVector2(self.hover_frame, 'scale', self.hover_frame.scale, scale, duration=2 * uiconst.TIME_ENTRY) 136 else: 137 self.hover_frame.opacity = opacity 138 self.hover_frame.scale = scale 139 140 def _update_state_frame(self, animate = True): 141 has_nebula_background = nebulaConsts.SELECTED_SKINR_BACKGROUND.get() is not nebulaConsts.BackgroundEnum.Empty 142 if self.is_selected: 143 opacity = OPACITY_FRAME_SELECTED if not has_nebula_background else OPACITY_FRAME_SELECTED_NEBULA 144 elif self.is_hovered: 145 opacity = OPACITY_FRAME_HOVER if not has_nebula_background else OPACITY_FRAME_HOVER_NEBULA 146 else: 147 opacity = OPACITY_FRAME_IDLE if not has_nebula_background else OPACITY_FRAME_IDLE_NEBULA 148 rotation = 0.0 if self.is_hovered or self.is_selected else -math.pi / 2 149 scale = (1.06, 1.06) if self.is_selected else (1.0, 1.0) 150 self.frame_bg.rgb = get_frame_color(self.showing_warning)[:3] 151 if animate: 152 animations.FadeTo(self.frame, self.frame.opacity, opacity, duration=uiconst.TIME_ENTRY) 153 animations.MorphScalar(self.frame, 'rotation', self.frame.rotation, rotation, duration=0.15) 154 animations.MorphVector2(self.frame, 'scale', self.frame.scale, scale, duration=0.15) 155 else: 156 self.frame.opacity = opacity 157 self.frame.rotation = rotation 158 self.frame.scale = scale 159 160 def on_nebula_changed(self, value): 161 has_nebula = value is not nebulaConsts.BackgroundEnum.Empty 162 if self.is_selected: 163 opacity = OPACITY_FRAME_SELECTED if has_nebula is False else OPACITY_FRAME_SELECTED_NEBULA 164 elif self.is_hovered: 165 opacity = OPACITY_FRAME_HOVER if has_nebula is False else OPACITY_FRAME_HOVER_NEBULA 166 else: 167 opacity = OPACITY_FRAME_IDLE if has_nebula is False else OPACITY_FRAME_IDLE_NEBULA 168 animations.FadeTo(self.frame, self.frame.opacity, opacity, duration=uiconst.TIME_ENTRY) 169 170 171 class CosmeticSlot(Container, RadiallyAlignedMixin): 172 default_width = 256 173 default_height = 256 174 default_align = Align.CENTER 175 default_pickState = PickState.CHILDREN 176 default_display = True 177 _radial_offset = 20 178 has_side_frame = True 179 showing_warning = False 180 181 def __init__(self, slot_data, component = None, selectable = True, license_selector_align = Align.CENTERBOTTOM, *args, **kwargs): 182 super(CosmeticSlot, self).__init__(*args, **kwargs) 183 self.slot_data = slot_data 184 self.is_selectable = selectable 185 self._selected_component = component 186 self._license_selector_align = license_selector_align 187 self.construct_layout() 188 self.connect_signals() 189 self.update_icon() 190 self.update_warning() 191 self._update_state(animate=False) 192 193 def Close(self): 194 try: 195 self.disconnect_signals() 196 finally: 197 super(CosmeticSlot, self).Close() 198 199 def connect_signals(self): 200 current_skin_design_signals.on_slot_selected.connect(self.on_slot_selected) 201 current_skin_design_signals.on_slot_fitting_changed.connect(self.on_slot_fitting_changed) 202 sequence_ui_signals.on_num_skins_changed.connect(self.on_num_skins_changed) 203 sequence_ui_signals.on_component_license_selection_changed.connect(self.on_component_license_selection_changed) 204 ship_skin_signals.on_component_license_granted.connect(self.on_component_license_granted) 205 ship_skin_signals.on_component_license_cache_invalidated.connect(self.on_component_license_cache_invalidated) 206 nebulaConsts.SELECTED_SKINR_BACKGROUND.on_change.connect(self.on_nebula_changed) 207 208 def disconnect_signals(self): 209 current_skin_design_signals.on_slot_selected.disconnect(self.on_slot_selected) 210 current_skin_design_signals.on_slot_fitting_changed.disconnect(self.on_slot_fitting_changed) 211 sequence_ui_signals.on_num_skins_changed.disconnect(self.on_num_skins_changed) 212 sequence_ui_signals.on_component_license_selection_changed.disconnect(self.on_component_license_selection_changed) 213 ship_skin_signals.on_component_license_granted.disconnect(self.on_component_license_granted) 214 ship_skin_signals.on_component_license_cache_invalidated.disconnect(self.on_component_license_cache_invalidated) 215 nebulaConsts.SELECTED_SKINR_BACKGROUND.on_change.disconnect(self.on_nebula_changed) 216 217 def on_num_skins_changed(self, num_skins): 218 self._check_show_not_owned_warning() 219 self.update_warning() 220 221 def on_component_license_selection_changed(self): 222 self._check_show_not_owned_warning() 223 self.update_warning() 224 225 def on_component_license_granted(self, *args): 226 self._check_show_not_owned_warning() 227 self.update_warning() 228 229 def on_component_license_cache_invalidated(self, _licenses_flushed): 230 self._check_show_not_owned_warning() 231 self.update_warning() 232 233 def construct_layout(self): 234 self.construct_button() 235 if self.has_side_frame: 236 self.construct_side_frame() 237 self.construct_license_selector() 238 self.construct_pulse_highlight() 239 240 def construct_button(self): 241 self.button = CosmeticSlotButton(parent=self, align=Align.CENTER, is_selectable=self.is_selectable) 242 if self.slot_data.unique_id in PATTERN_SLOT_IDS: 243 mask_texture = 'res:/UI/Texture/circle_full.png' 244 else: 245 mask_texture = 'res:/UI/Texture/classes/Cosmetics/Ship/pages/studio/nanocoating_mask.png' 246 self.button.icon.spriteEffect = SpriteEffect.MODULATE 247 self.button.icon.SetSecondaryTexturePath(mask_texture) 248 self.button.on_click.connect(self.OnSlotClick) 249 self.button.on_mouse_enter.connect(self.OnSlotMouseEnter) 250 self.button.on_mouse_exit.connect(self.OnSlotMouseExit) 251 self.button.on_selected.connect(self.on_button_selected) 252 self.button.on_deselected.connect(self.on_button_deselected) 253 self.button.GetMenu = self.GetSlotMenu 254 self.button.ConstructTooltipPanel = self.ConstructButtonTooltipPanel 255 256 def ConstructButtonTooltipPanel(self, *args): 257 return RequiredComponentTooltipPanel(component_instance=self.selected_component, title=self.slot_data.name, text=self.get_fitted_component_name(), show_warning=self.showing_warning) 258 259 def get_fitted_component_name(self): 260 if self.selected_component: 261 return self.selected_component.get_component_data().name 262 else: 263 return GetByLabel('UI/OrbitalSkyhook/ConfigWnd/NothingSelected') 264 265 def on_button_selected(self): 266 self._update_state() 267 268 def on_button_deselected(self): 269 self._update_state() 270 271 def construct_side_frame(self): 272 self.side_frame = Container(name='side_frame', parent=self, align=Align.CENTER, pickState=PickState.OFF, pos=(-54, 0, 39, 36)) 273 Sprite(parent=self.side_frame, pos=(-2, 0, 24, 24), align=Align.CENTER, texturePath=shipConst.SLOT_ICONS[self.slot_data.unique_id]) 274 self.side_frame_bg = Sprite(bgParent=self.side_frame, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/Pages/Studio/slot_side_frame.png', color=get_side_frame_color(self.showing_warning), glowBrightness=0.2, outputMode=uiconst.OUTPUT_COLOR_AND_GLOW) 275 276 def construct_license_selector(self): 277 if self._license_selector_align == Align.CENTERTOP: 278 align = Align.CENTER 279 left = 0 280 top = -53 281 elif self._license_selector_align == Align.CENTERBOTTOM: 282 align = Align.CENTER 283 left = 0 284 top = 53 285 elif self._license_selector_align == Align.CENTERLEFT: 286 align = Align.CENTERRIGHT 287 left = 174 288 top = 0 289 elif self._license_selector_align == Align.CENTERRIGHT: 290 align = Align.CENTERLEFT 291 left = 174 292 top = 0 293 self.license_selector_container = ContainerAutoSize(name='license_selector_container', parent=self, align=align, height=20, left=left, top=top, display=False) 294 self.license_selector = LicenseSelector(name='license_selector', parent=self.license_selector_container, component_instance=self.selected_component) 295 296 def construct_pulse_highlight(self): 297 self.pulse_highlight = Transform(name='pulse_highlight', parent=self, align=Align.CENTER, pickState=PickState.OFF, scalingCenter=(0.5, 0.5), pos=(0, 0, 120, 120), display=False) 298 self.pulse_highlight_bg = Sprite(bgParent=self.pulse_highlight, texturePath='res:/UI/Texture/Classes/Cosmetics/Ship/Pages/Studio/slot_pulse_highlight.png') 299 300 def show_pulse_highlight(self): 301 self.pulse_highlight.display = True 302 scale_contracted = (0.85, 0.85) 303 scale_expanded = (1.1, 1.1) 304 duration = 5.0 305 animation_curve = ((0.0, scale_contracted), 306 (0.5 / duration, scale_contracted), 307 (2.5 / duration, scale_expanded), 308 (3.0 / duration, scale_expanded), 309 (1.0, scale_contracted)) 310 animations.MorphVector2(obj=self.pulse_highlight, attrName='scale', duration=5.0, curveType=animation_curve, loops=uiconst.ANIM_REPEAT, timeOffset=get_anim_time_offset(5.0)) 311 312 def hide_pulse_highlight(self): 313 animations.StopAllAnimations(self.pulse_highlight_bg) 314 self.pulse_highlight.display = False 315 316 def show_warning(self): 317 self.showing_warning = True 318 self.button.showing_warning = True 319 self._update_state() 320 321 def hide_warning(self): 322 self.showing_warning = False 323 self.button.showing_warning = False 324 self._update_state() 325 326 def update_warning(self): 327 if not self.selected_component: 328 return 329 layout = current_skin_design.get().slot_layout 330 number_of_runs = get_ship_skin_sequencing_svc().get_num_runs() 331 num_required = number_of_runs * layout.get_amount_of_components_with_license_type(self.selected_component.component_id, ComponentLicenseType.LIMITED) 332 license = self.selected_component.component_license_to_use or get_ship_skin_component_svc().get_default_license_to_use(self.selected_component.component_id, self.selected_component.get_component_data().category) 333 if license is None: 334 has_sufficient_licenses = True 335 elif license.license_type == ComponentLicenseType.LIMITED: 336 has_sufficient_licenses = license is None or not license.has_enough_remaining_uses(num_required) 337 else: 338 has_sufficient_licenses = False 339 if has_sufficient_licenses: 340 self.show_warning() 341 else: 342 self.hide_warning() 343 344 def on_slot_selected(self, slot_id): 345 if slot_id and slot_id == self.slot_data.unique_id: 346 self.button.set_selected() 347 else: 348 self.button.set_deselected() 349 350 def _update_state(self, animate = True): 351 self.button.update_state(animate) 352 if self.has_side_frame: 353 self._update_state_side_frame(animate) 354 self._update_state_license_selector() 355 356 def _update_state_side_frame(self, animate): 357 has_nebula_background = nebulaConsts.SELECTED_SKINR_BACKGROUND.get() is not nebulaConsts.BackgroundEnum.Empty 358 if not self.is_fitted: 359 opacity = 0.0 360 elif self.is_selected: 361 opacity = OPACITY_FRAME_SELECTED if not has_nebula_background else OPACITY_FRAME_SELECTED_NEBULA 362 elif self.button.is_hovered: 363 opacity = OPACITY_FRAME_HOVER if not has_nebula_background else OPACITY_FRAME_HOVER_NEBULA 364 else: 365 opacity = OPACITY_FRAME_IDLE if not has_nebula_background else OPACITY_FRAME_IDLE_NEBULA 366 self.side_frame_bg.rgb = get_side_frame_color(self.showing_warning)[:3] 367 if animate: 368 animations.FadeTo(self.side_frame_bg, self.side_frame_bg.opacity, opacity, duration=2 * uiconst.TIME_ENTRY) 369 else: 370 self.side_frame_bg.opacity = opacity 371 opacity = 1.0 if opacity > 0 else 0.0 372 if animate: 373 animations.FadeTo(self.side_frame, self.side_frame.opacity, opacity, duration=2 * uiconst.TIME_ENTRY) 374 else: 375 self.side_frame.opacity = opacity 376 377 def _update_state_license_selector(self): 378 if self.license_selector: 379 self.license_selector.component_instance = self.selected_component 380 self.license_selector_container.display = self.selected_component is not None and self.is_selected 381 382 @property 383 def is_selected(self): 384 return current_skin_design.get_selected_slot_id() == self.slot_data.unique_id 385 386 def on_slot_fitting_changed(self, slot_id, component_instance): 387 self._check_show_not_owned_warning() 388 if slot_id != self.slot_data.unique_id: 389 return 390 self.selected_component = component_instance 391 self.update_icon() 392 self._update_state() 393 394 def _check_show_not_owned_warning(self): 395 if not self.selected_component: 396 self.hide_warning() 397 return 398 license = self.selected_component.component_license_to_use or get_ship_skin_component_svc().get_default_license_to_use(self.selected_component.component_id, self.selected_component.get_component_data().category) 399 if not license: 400 self.show_warning() 401 return 402 if license.license_type == ComponentLicenseType.UNLIMITED: 403 self.hide_warning() 404 return 405 layout = current_skin_design.get().slot_layout 406 number_of_runs = get_ship_skin_sequencing_svc().get_num_runs() 407 num_required = number_of_runs * layout.get_amount_of_components_with_license_type(self.selected_component.component_id, ComponentLicenseType.LIMITED) 408 if license.has_enough_remaining_uses(num_required): 409 self.hide_warning() 410 else: 411 self.show_warning() 412 413 def update_icon(self): 414 if self.selected_component: 415 self.button.icon.texturePath = self.selected_component.get_component_data().icon_file_path 416 self.button.icon.width = self.button.icon.height = 48 417 else: 418 self.button.icon.texturePath = shipConst.SLOT_ICONS[self.slot_data.unique_id] 419 self.button.icon.width = self.button.icon.height = 32 420 421 @property 422 def is_fitted(self): 423 return bool(self.selected_component) 424 425 def OnSlotClick(self, *args): 426 if not self.is_selectable: 427 return 428 PlaySound('nanocoating_button_push_primary_play') 429 selected_slot = None if self.is_selected else self.slot_data 430 current_skin_design.set_selected_slot_id(selected_slot.unique_id if selected_slot else None) 431 432 def OnSlotMouseEnter(self, *args): 433 if not self.is_selectable: 434 return 435 PlaySound('nanocoating_button_hover_play') 436 self._update_state() 437 438 def OnSlotMouseExit(self, *args): 439 if not self.is_selectable: 440 return 441 self._update_state() 442 443 @property 444 def selected_component(self): 445 return current_skin_design.get().get_fitted_component_instance(self.slot_data.unique_id) 446 447 @selected_component.setter 448 def selected_component(self, value): 449 self._selected_component = value 450 self._update_state() 451 if value: 452 animations.MorphScalar(self.button.hover_frame_sprite, 'glowBrightness', 2.0, 0.0, duration=1.2) 453 454 def GetSlotMenu(self): 455 if not self.selected_component: 456 return 457 component_data = self.selected_component.get_component_data() 458 m = MenuData() 459 for type_id in component_data.component_item_data_by_type_id.keys(): 460 m.AddEntry(evetypes.GetName(type_id), subMenuData=GetMenuService().GetMenuFromItemIDTypeID(itemID=None, typeID=type_id, includeMarketDetails=True)) 461 462 if self.is_fitted and self.slot_data.unique_id not in PATTERN_SLOT_ID_BY_PATTERN_MATERIAL_ID.keys(): 463 m.AddSeparator() 464 m.AddEntry(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/ResetSlot'), self.unfit_slot, texturePath=eveicon.close) 465 if self.slot_data.unique_id in PATTERN_SLOT_IDS: 466 component_instance = current_skin_design.get().get_fitted_component_instance(self.slot_data.unique_id) 467 if component_instance: 468 m.AddSeparator() 469 m.AddEntry(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/ResetPatternProjection'), lambda c = component_instance: self.reset_pattern_rotation_scale_and_offset(c), texturePath=eveicon.refresh) 470 if session.role & ROLE_GML: 471 m.AddSeparator() 472 component_id = self.selected_component.component_id if self.selected_component else None 473 m.AddEntry('QA: slot_id: {}, component_id: {}'.format(self.slot_data.unique_id, component_id)) 474 return m 475 476 def reset_pattern_rotation_scale_and_offset(self, component_instance): 477 component_instance.reset_rotation_scale_and_offset() 478 current_skin_design.add_to_undo_history() 479 480 def unfit_slot(self, *args): 481 current_skin_design.get().fit_slot(self.slot_data.unique_id, None) 482 current_skin_design.add_to_undo_history() 483 484 def show(self): 485 self.state = uiconst.UI_PICKCHILDREN 486 animations.FadeTo(self, self.opacity, 1.0, 0.2) 487 488 def hide(self): 489 self.state = uiconst.UI_DISABLED 490 animations.FadeTo(self, self.opacity, 0.0, 0.25) 491 492 def on_nebula_changed(self, value): 493 if not self.has_side_frame: 494 return 495 has_nebula = value is not nebulaConsts.BackgroundEnum.Empty 496 if self.is_selected: 497 opacity = OPACITY_FRAME_SELECTED if has_nebula is False else OPACITY_FRAME_SELECTED_NEBULA 498 elif self.button.is_hovered: 499 opacity = OPACITY_FRAME_HOVER if has_nebula is False else OPACITY_FRAME_HOVER_NEBULA 500 else: 501 opacity = OPACITY_FRAME_IDLE if has_nebula is False else OPACITY_FRAME_IDLE_NEBULA 502 animations.FadeTo(self.side_frame_bg, self.side_frame_bg.opacity, opacity, duration=2 * uiconst.TIME_ENTRY) 503 opacity = 1.0 if opacity > 0 else 0.0 504 animations.FadeTo(self.side_frame, self.side_frame.opacity, opacity, duration=2 * uiconst.TIME_ENTRY) 505 506 507 class PatternCosmeticSlot(CosmeticSlot): 508 has_side_frame = False