componentPanel.py (15740B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\studio\componentPanel.py 2 import eveicon 3 import uthread2 4 from carbon.common.script.sys.serviceConst import ROLE_QA 5 from carbonui import Align, TextColor, uiconst 6 from carbonui.text.styles import TextBody 7 from carbonui.button.group import ButtonGroup, ButtonSizeMode 8 from carbonui.control.button import Button 9 from carbonui.control.buttonIcon import ButtonIcon 10 from carbonui.control.checkbox import Checkbox 11 from carbonui.control.scrollContainer import ScrollContainer 12 from carbonui.control.tabGroup import TabGroup 13 from carbonui.primitives.container import Container 14 from carbonui.primitives.flowcontainer import FlowContainer 15 from carbonui.primitives.frame import Frame 16 from carbonui.primitives.sprite import Sprite 17 from carbonui.uianimations import animations 18 from carbonui.uiconst import VK_DOWN, VK_UP, VK_LEFT, VK_RIGHT, VK_PRIOR, VK_NEXT 19 from carbonui.uicore import uicore 20 from collections import defaultdict 21 from cosmetics.client.shipSkinComponentSvc import get_ship_skin_component_svc 22 from cosmetics.client.ships import ship_skin_signals 23 from cosmetics.client.ships.qa.settings import show_unpublished_components 24 from cosmetics.client.ships.skins.live_data import current_skin_design 25 from cosmetics.common.ships.skins.live_data.component_license_type import ComponentLicenseType 26 from cosmetics.common.ships.skins.static_data.component_finish import ComponentFinish 27 from cosmetics.common.ships.skins.static_data.slot import SlotsDataLoader 28 from eve.client.script.ui.control.toggleButtonGroup import ToggleButtonGroup 29 from eve.client.script.ui.cosmetics.ship.const import COMPONENT_CATEGORY_NAMES, SkinrPage, SLOT_ICONS 30 from eve.client.script.ui.cosmetics.ship.pages import current_page 31 from eve.client.script.ui.cosmetics.ship.pages.studio import studioSignals 32 from eve.client.script.ui.cosmetics.ship.pages.studio.componentLicenseEntry import ComponentEntry 33 from eve.client.script.ui.cosmetics.ship.pages.studio.studioSettings import only_shown_owned_design_elements_setting 34 from localization import GetByLabel 35 from signals import Signal 36 FINISH_ALL = 0 37 LABEL_BY_COMPONENT_FINISH = {ComponentFinish.MATTE: 'UI/Personalization/ShipSkins/SKINR/FinishMatte', 38 ComponentFinish.SATIN: 'UI/Personalization/ShipSkins/SKINR/FinishSatin', 39 ComponentFinish.GLOSS: 'UI/Personalization/ShipSkins/SKINR/FinishGloss'} 40 41 class ComponentPanel(Container): 42 default_state = uiconst.UI_DISABLED 43 default_width = 360 44 default_opacity = 0.0 45 RADIUS_OFFSET = 50 46 isTabStop = True 47 48 def __init__(self, slot_data, *args, **kwargs): 49 super(ComponentPanel, self).__init__(*args, **kwargs) 50 self.slot_data = slot_data 51 self.is_active = False 52 self.on_close_btn = Signal('on_close_btn') 53 self._reconstruct_thread = None 54 self._selected_category = None 55 self.construct_layout() 56 self.connect_signals() 57 58 def Close(self): 59 try: 60 self.kill_reconstruct_thread() 61 self.disconnect_signals() 62 finally: 63 super(ComponentPanel, self).Close() 64 65 def kill_reconstruct_thread(self): 66 if self._reconstruct_thread is not None: 67 self._reconstruct_thread.kill() 68 self._reconstruct_thread = None 69 70 def connect_signals(self): 71 studioSignals.on_scene_zoom.connect(self.on_scene_zoom) 72 ship_skin_signals.on_component_license_granted.connect(self._on_component_license_granted) 73 ship_skin_signals.on_component_license_cache_invalidated.connect(self._on_component_license_cache_invalidated) 74 ship_skin_signals.on_skin_sequencing_job_updated.connect(self.on_skin_sequencing_job_updated) 75 only_shown_owned_design_elements_setting.on_change.connect(self.on_only_show_owned_design_elements_setting) 76 show_unpublished_components.on_change.connect(self._on_qa_show_unpublished) 77 78 def disconnect_signals(self): 79 studioSignals.on_scene_zoom.disconnect(self.on_scene_zoom) 80 ship_skin_signals.on_component_license_granted.disconnect(self._on_component_license_granted) 81 ship_skin_signals.on_component_license_cache_invalidated.disconnect(self._on_component_license_cache_invalidated) 82 ship_skin_signals.on_skin_sequencing_job_updated.disconnect(self.on_skin_sequencing_job_updated) 83 only_shown_owned_design_elements_setting.on_change.disconnect(self.on_only_show_owned_design_elements_setting) 84 show_unpublished_components.on_change.disconnect(self._on_qa_show_unpublished) 85 86 def on_only_show_owned_design_elements_setting(self, *args): 87 self.reconstruct_entries() 88 89 def construct_layout(self): 90 self.content = Container(name='content', parent=self, padding=16) 91 self.construct_header() 92 self.category_toggle_button_group = ToggleButtonGroup(name='category_toggle_button_group', parent=self.content, align=Align.TOTOP, padTop=4, callback=self.on_category_toggle_buttons) 93 self.finish_tab_group = TabGroup(name='finish_tab_group', parent=self.content, callback=self.on_finish_tab_group, show_line=False, padTop=4, padBottom=-4) 94 Checkbox(name='only_ownned_checkbox', parent=self.content, align=Align.TOTOP, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/OnlyShowOwned'), setting=only_shown_owned_design_elements_setting, padTop=16) 95 self.button_group = ButtonGroup(parent=self.content, align=Align.TOBOTTOM, button_size_mode=ButtonSizeMode.STRETCH, padTop=4) 96 Button(name='buy_more_btn', parent=self.button_group, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/BuyMore'), func=self.on_buy_more_btn) 97 self.scroll_container = ScrollContainer(name='scroll_container', parent=self.content, align=Align.TOALL, padTop=8) 98 self.scroll_container.OnKeyDown = self.OnKeyDown 99 self.flow_container = FlowContainer(name='flow_container', parent=self.scroll_container, align=Align.TOTOP, contentSpacing=(4, 4)) 100 101 def construct_header(self): 102 header_cont = Container(name='header_cont', parent=self.content, align=Align.TOTOP, height=36) 103 icon_cont = Container(name='icon_cont', parent=header_cont, align=Align.TOLEFT, width=36) 104 Frame(bgParent=icon_cont, color=(1, 1, 1, 0.05)) 105 Sprite(parent=icon_cont, texturePath=SLOT_ICONS[self.slot_data.unique_id], pos=(0, 0, 32, 32), align=Align.CENTER, color=TextColor.HIGHLIGHT) 106 text_cont = Container(parent=header_cont, bgColor=(1, 1, 1, 0.05), clipChildren=True, padLeft=4) 107 TextBody(parent=text_cont, align=Align.VERTICALLY_CENTERED, text=self.slot_data.name, color=TextColor.HIGHLIGHT, autoFadeSides=10, padLeft=16, padRight=32) 108 ButtonIcon(parent=header_cont, align=Align.CENTERRIGHT, texturePath=eveicon.close, func=self.on_close_btn) 109 110 def on_finish_tab_group(self, *args): 111 self.reconstruct_entries() 112 113 def on_buy_more_btn(self, *args): 114 current_page.set_page_id(page_id=SkinrPage.STORE_COMPONENTS, page_args=self.get_selected_category()) 115 116 def _on_qa_show_unpublished(self, *args): 117 if session.role & ROLE_QA: 118 self.reconstruct_entries() 119 120 def get_selected_category(self): 121 return self.category_toggle_button_group.GetSelected() 122 123 def reconstruct_category_buttons(self): 124 self.kill_reconstruct_thread() 125 self._reconstruct_thread = uthread2.start_tasklet(self.reconstruct_category_buttons_async) 126 127 def reconstruct_category_buttons_async(self): 128 try: 129 self.category_toggle_button_group.Flush() 130 licenses_by_category = self.get_components_by_category() 131 categories = licenses_by_category.keys() 132 for category in categories: 133 category_name = GetByLabel(COMPONENT_CATEGORY_NAMES.get(category)) 134 self.category_toggle_button_group.AddButton(label=category_name, btnID=category) 135 136 self.category_toggle_button_group.display = len(categories) > 1 137 category_id = self.get_selected_category_id(licenses_by_category) 138 if category_id: 139 self.category_toggle_button_group.SelectByID(category_id) 140 finally: 141 self._reconstruct_thread = None 142 143 def get_components_by_category(self): 144 slot_id = self.slot_data.unique_id 145 components_by_category = defaultdict(list) 146 components = SlotsDataLoader.get_allowed_component(slot_id).values() 147 for component_data in components: 148 category = component_data.category 149 components_by_category[category].append(component_data) 150 151 return components_by_category 152 153 def check_tab_exists(self, finish): 154 if finish == FINISH_ALL: 155 finish_label = GetByLabel('UI/Common/All') 156 else: 157 finish_label = GetByLabel(LABEL_BY_COMPONENT_FINISH[finish]) 158 label, name = self.finish_tab_group.GetNameLabel(finish_label) 159 uiID = self.finish_tab_group.GetUIID(name, finish) 160 exist = list(filter(lambda x: x.parent == self.finish_tab_group.tabsCont and str(x.tabID) == str(uiID), self.finish_tab_group.mytabs)) 161 return len(exist) > 0 162 163 def reconstruct_finish_tabs(self): 164 selected_finish = self.finish_tab_group.GetSelectedID() 165 self.finish_tab_group.Flush() 166 components = self.get_components_to_show() 167 finishes = {c.finish for c in components} 168 if not self.check_tab_exists(FINISH_ALL): 169 self.finish_tab_group.AddTab(label=GetByLabel('UI/Common/All'), tabID=FINISH_ALL) 170 if len(finishes) > 1: 171 self.finish_tab_group.display = True 172 for finish in (ComponentFinish.MATTE, ComponentFinish.SATIN, ComponentFinish.GLOSS): 173 if finish not in finishes: 174 continue 175 if not self.check_tab_exists(finish): 176 self.finish_tab_group.AddTab(label=GetByLabel(LABEL_BY_COMPONENT_FINISH[finish]), tabID=finish) 177 178 else: 179 self.finish_tab_group.display = False 180 if selected_finish is None: 181 selected_finish = FINISH_ALL 182 self.finish_tab_group.SelectByID(selected_finish, useCallback=False) 183 184 def get_selected_category_id(self, licenses_by_category): 185 if self._selected_category: 186 return self._selected_category 187 component_instance = current_skin_design.get().slot_layout.slots[self.slot_data.unique_id] 188 if component_instance: 189 return component_instance.category 190 elif len(licenses_by_category.keys()) > 0: 191 return licenses_by_category.keys()[0] 192 else: 193 return None 194 195 def on_category_toggle_buttons(self, new_category, old_category): 196 self._selected_category = new_category 197 self.reconstruct_finish_tabs() 198 self.reconstruct_entries() 199 200 def reconstruct_entries(self): 201 self.flow_container.Flush() 202 components = self.get_components_to_show() 203 fitted_component = current_skin_design.get().slot_layout.slots.get(self.slot_data.unique_id) 204 fitted_component_id = fitted_component.component_id if fitted_component else None 205 for component_data in components: 206 owned_licenses_by_type = get_ship_skin_component_svc().get_owned_licenses_by_type(component_data.component_id) 207 is_selected = component_data.component_id == fitted_component_id 208 ComponentEntry(parent=self.flow_container, slot_id=self.slot_data.unique_id, component_data=component_data, is_selected=is_selected, limited_license=owned_licenses_by_type.get(ComponentLicenseType.LIMITED, None), unlimited_license=owned_licenses_by_type.get(ComponentLicenseType.UNLIMITED, None)) 209 210 def get_components_to_show(self): 211 components = SlotsDataLoader.get_allowed_component(self.slot_data.unique_id).values() 212 if only_shown_owned_design_elements_setting.is_enabled(): 213 owned_licenses = get_ship_skin_component_svc().get_all_owned_licenses() 214 owned_ids = {l.component_id for l in owned_licenses} 215 components = [ c for c in components if c.component_id in owned_ids ] 216 show_unpublished = show_unpublished_components.is_enabled() 217 if not session.role & ROLE_QA or not show_unpublished: 218 components = [ c for c in components if c.published ] 219 category_id = self.get_selected_category() 220 if category_id: 221 components = [ c for c in components if c.category == category_id ] 222 selected_finish = self.finish_tab_group.GetSelectedID() 223 if selected_finish: 224 components = [ c for c in components if c.finish == selected_finish ] 225 components = sorted(components, key=lambda component: component.color_shade_sort_index, reverse=False) 226 return components 227 228 def get_components_in_selected_category(self): 229 components_by_category = self.get_components_by_category() 230 components = components_by_category[self.get_selected_category()] 231 return components 232 233 def on_slot_selected(self, slot_id): 234 if slot_id == self.slot_data.unique_id: 235 self.reconstruct_tabs() 236 self.appear() 237 uicore.registry.SetFocus(self) 238 else: 239 self.disappear() 240 241 def reconstruct_tabs(self): 242 self.reconstruct_category_buttons() 243 self.reconstruct_finish_tabs() 244 245 def disappear(self): 246 self.is_active = False 247 self.state = uiconst.UI_DISABLED 248 self.StopAnimations() 249 self.opacity = 0.0 250 251 def appear(self): 252 if self.is_active: 253 return 254 self.is_active = True 255 self.state = uiconst.UI_PICKCHILDREN 256 animations.FadeTo(self, self.opacity, 1.0, duration=4 * uiconst.TIME_ENTRY) 257 258 def on_scene_zoom(self, is_zoomed): 259 if not self.is_active: 260 return 261 target_opacity = 0.0 if is_zoomed else 1.0 262 time_offset = 0.0 if is_zoomed else 0.15 263 animations.FadeTo(self, self.opacity, target_opacity, duration=0.15, timeOffset=time_offset) 264 265 def OnKeyDown(self, key, *args): 266 diff = None 267 if key == VK_LEFT: 268 diff = -1 269 elif key == VK_RIGHT: 270 diff = 1 271 elif key == VK_UP: 272 diff = -3 273 elif key == VK_DOWN: 274 diff = 3 275 elif key == VK_PRIOR: 276 self.scroll_container.ScrollByPage(up=True) 277 elif key == VK_NEXT: 278 self.scroll_container.ScrollByPage(up=False) 279 if diff: 280 self._select_new_component(diff) 281 282 def _select_new_component(self, diff): 283 selected = self.get_selected() 284 components = self.flow_container.children 285 if selected: 286 idx = components.index(selected) + diff 287 else: 288 idx = 0 289 if 0 <= idx < len(components): 290 components[idx].toggle_selected() 291 292 def get_selected(self): 293 for component in self.flow_container.children: 294 if component.is_selected: 295 return component 296 297 def select_component(self, component_instance): 298 for component in self.flow_container.children: 299 if component_instance is None: 300 component.is_selected = False 301 else: 302 component.is_selected = component.component_data.component_id == component_instance.component_id 303 304 def _on_component_license_granted(self, *args): 305 self._on_licenses_updated() 306 307 def _on_component_license_cache_invalidated(self, _licenses_flushed): 308 self._on_licenses_updated() 309 310 def on_skin_sequencing_job_updated(self, *args): 311 self._on_licenses_updated() 312 313 @uthread2.debounce(1.0) 314 def _on_licenses_updated(self): 315 self.reconstruct_tabs()