key_select.py (14249B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\dynamicresources\client\ess\bracket\reserve_bank\key_select.py 2 import datetime 3 import datetimeutils 4 import eveformat 5 import evetypes 6 import eveui 7 import gametime 8 import localization 9 import locks 10 import signals 11 import threadutils 12 import uthread2 13 from carbon.common.script.sys.serviceManager import ServiceManager 14 from carbonui import uiconst 15 from carbonui.control.button import Button 16 from carbonui.primitives.containerAutoSize import ContainerAutoSize 17 from carbonui.primitives.frame import Frame 18 from carbonui.primitives.layoutGrid import LayoutGrid 19 from carbonui.primitives.transform import Transform 20 from carbonui.uianimations import animations 21 from carbonui.uicore import uicore 22 from dynamicresources.client import color, text 23 from dynamicresources.client.ess.bracket.label import Header 24 from dynamicresources.client.ess.bracket.panel import get_absolute_bounds 25 from dynamicresources.client.ess.bracket.state_machine import State, Transition 26 from dynamicresources.client.service import get_dynamic_resource_service 27 from dynamicresources.common.ess.const import LINK_ERROR_ALREADY_LINKED 28 from eve.client.script.ui.control import eveLabel 29 from eve.client.script.ui.control.itemIcon import ItemIcon 30 from player_messaging.client.ui_message import prompt_player_fsd_dialog 31 32 class KeySelect(State): 33 34 def __init__(self, parent, on_cancel = None): 35 super(KeySelect, self).__init__() 36 self._parent = parent 37 self._selection_controller = None 38 self._content = None 39 self._label = None 40 self._key_entries = [] 41 self._button = None 42 self._cancelled = False 43 self.on_cancel = signals.Signal() 44 if on_cancel: 45 self.on_cancel.connect(on_cancel) 46 47 @property 48 def focus_bounds(self): 49 if self._content is None: 50 return 51 content_bounds = get_absolute_bounds(self._content) 52 return content_bounds.inflate(100) 53 54 def enter(self): 55 self._content = LayoutGrid(parent=self._parent, align=uiconst.TOPLEFT, columns=1, cellSpacing=(0, 8)) 56 self._label = Header(parent=self._content, align=uiconst.TOPLEFT, text=text.title_select_key()) 57 animations.FadeTo(self._label, duration=0.15) 58 animations.MorphScalar(self._label, 'left', startVal=-10, endVal=0, duration=0.15) 59 service = get_dynamic_resource_service() 60 keys = service.GetESSReserveBankKeysForCurrentSystem() 61 self._selection_controller = KeySelectController(keys) 62 key_list = LayoutGrid(name='key_list', parent=self._content, align=uiconst.TOPLEFT, cellSpacing=(8, 0), columns=len(keys)) 63 for i, key in enumerate(self._selection_controller.keys): 64 entry = KeyEntry(parent=key_list, align=uiconst.TOPLEFT, key=key, on_confirm=self._selection_controller.use, on_disallowed=self.on_cancel) 65 self._key_entries.append(entry) 66 animations.FadeTo(entry, duration=0.15, timeOffset=i * 0.1) 67 animations.MorphScalar(entry, 'left', startVal=-30, endVal=0, duration=0.15, timeOffset=i * 0.1) 68 69 uicore.uilib.RegisterForTriuiEvents([uiconst.UI_MOUSEDOWN], self._on_global_mouse_down) 70 self._start_cursor_cancel_loop() 71 72 def exit(self): 73 transition = Exit(self._content, self._label, self._key_entries) 74 self._content = None 75 return transition 76 77 def close(self): 78 if self._content: 79 self._content.Close() 80 81 def cancel(self): 82 if not self._cancelled: 83 self._cancelled = True 84 self.on_cancel() 85 86 def _on_global_mouse_down(self, target, *args, **kwargs): 87 if target is None: 88 return True 89 focus_bounds = self.focus_bounds 90 if focus_bounds is None: 91 return True 92 click_captured = focus_bounds.contains(*eveui.Mouse.position()) 93 if not click_captured: 94 self.cancel() 95 return False 96 return True 97 98 @threadutils.threaded 99 def _start_cursor_cancel_loop(self): 100 cursor_last_within_bounds_at = None 101 cursor_distance_grace_period = datetime.timedelta(seconds=2) 102 while self._content is not None and not self._content.destroyed: 103 cursor_distance = self.focus_bounds.distance_from_point(*eveui.Mouse.position()) 104 if cursor_distance > 0: 105 if cursor_last_within_bounds_at is None: 106 cursor_last_within_bounds_at = gametime.now() 107 dt = gametime.now() - cursor_last_within_bounds_at 108 if dt >= cursor_distance_grace_period: 109 self.cancel() 110 break 111 else: 112 cursor_last_within_bounds_at = gametime.now() 113 eveui.wait_for_next_frame() 114 115 116 class Exit(Transition): 117 118 def __init__(self, content, label, key_entries): 119 super(Exit, self).__init__() 120 self._content = content 121 self._label = label 122 self._key_entries = key_entries 123 124 def animation(self): 125 animations.FadeOut(self._label, duration=0.2) 126 for i, entry in enumerate(reversed(self._key_entries)): 127 animations.FadeOut(entry, duration=0.2, timeOffset=i * 0.1) 128 129 self.sleep(max(0.2, 0.2 + (len(self._key_entries) - 1) * 0.1)) 130 131 def on_close(self): 132 self._content.Close() 133 134 135 class KeySelectController(object): 136 __notifyevents__ = ['OnItemChange'] 137 138 def __init__(self, keys_static_data): 139 self._use_request_outstanding = False 140 self._key_types_ids = set((key.type_id for key in keys_static_data)) 141 count = count_items_in_cargo(self._key_types_ids) 142 self._keys = [ Key(static_data, count[static_data.type_id]) for static_data in keys_static_data ] 143 ServiceManager.Instance().RegisterNotify(self) 144 145 @property 146 def keys(self): 147 return self._keys 148 149 def use(self, key): 150 if key.type_id not in self._key_types_ids: 151 raise ValueError('Unknown key type %s', key.type_id) 152 if self._use_request_outstanding: 153 return 154 self._use_request_outstanding = True 155 try: 156 service = get_dynamic_resource_service() 157 service.RequestUnlockReserveBank(key.type_id) 158 service.RequestESSReserveBankLink() 159 finally: 160 self._use_request_outstanding = False 161 162 @uthread2.debounce(wait=0.3) 163 def _update_key_count(self): 164 count = count_items_in_cargo(self._key_types_ids) 165 for key in self._keys: 166 key.count = count[key.type_id] 167 168 def OnItemChange(self, item, change, location): 169 if item.typeID in self._key_types_ids: 170 self._update_key_count() 171 172 173 class Key(object): 174 175 def __init__(self, key_static_data, count): 176 self._static_data = key_static_data 177 self._count = count 178 self.on_count_changed = signals.Signal() 179 180 @property 181 def type_id(self): 182 return self._static_data.type_id 183 184 @property 185 def name(self): 186 return evetypes.GetName(self.type_id) 187 188 @property 189 def total_pulses(self): 190 return self._static_data.total_pulses 191 192 @property 193 def duration(self): 194 ess_settings = get_dynamic_resource_service().ess_settings 195 pulse_interval = ess_settings.reserve_bank_pulse_interval.total_seconds() 196 return datetime.timedelta(seconds=self.total_pulses * pulse_interval) 197 198 @property 199 def available(self): 200 return self._count > 0 201 202 @property 203 def count(self): 204 return self._count 205 206 @count.setter 207 def count(self, count): 208 if count != self._count: 209 self._count = count 210 self.on_count_changed() 211 212 213 def count_items_in_cargo(type_ids): 214 count = {type_id:0 for type_id in type_ids} 215 if not session.shipid: 216 return count 217 inv_cache = ServiceManager.Instance().GetService('invCache') 218 cargo = inv_cache.GetInventoryFromId(session.shipid) 219 for item in cargo.List(): 220 if item.typeID in count: 221 count[item.typeID] += item.quantity 222 223 return count 224 225 226 class KeyEntry(ContainerAutoSize): 227 228 def __init__(self, parent, align, key, on_confirm = None, on_disallowed = None): 229 super(KeyEntry, self).__init__(parent=parent, align=align, alignMode=uiconst.TOPLEFT, state=uiconst.UI_NORMAL) 230 self.key = key 231 self._item = None 232 self._button = None 233 self._hovered = False 234 self._busy = False 235 self.on_confirm = signals.Signal() 236 if on_confirm: 237 self.on_confirm.connect(on_confirm) 238 self.on_disallowed = signals.Signal() 239 if on_disallowed: 240 self.on_disallowed.connect(on_disallowed) 241 self._layout() 242 self._update(animate=False) 243 self.key.on_count_changed.connect(self._update) 244 245 def _layout(self): 246 self._item = KeyEntryItem(parent=self, align=uiconst.TOPLEFT, key=self.key) 247 self._button_cont = ContainerAutoSize(parent=self, align=uiconst.TOPLEFT, top=self._item.height - 10, opacity=0.0) 248 self._button = Button(parent=self._button_cont, align=uiconst.TOPLEFT, label=text.action_use_key(), func=self._confirm, args=()) 249 250 def _update(self, animate = True): 251 if not self._item: 252 return 253 if self._hovered or self._busy: 254 self._item.hovered = True 255 if not self._busy and self.key.available: 256 self._button.Enable() 257 else: 258 self._button.Disable() 259 if self.key.available: 260 if animate: 261 animations.FadeIn(self._button_cont, duration=0.2) 262 if self._button_cont.top == 0: 263 self._button_cont.top = self._item.height - 10 264 animations.MorphScalar(self._button_cont, 'top', startVal=self._button_cont.top, endVal=self._item.height + 8, duration=0.2) 265 else: 266 self._button_cont.opacity = 1.0 267 self._button_cont.top = self._item.height + 8 268 else: 269 self._item.hovered = False 270 self._button.Disable() 271 if animate: 272 273 def move_button(): 274 self._button_cont.top = 0 275 276 animations.FadeOut(self._button_cont, duration=0.1, callback=move_button) 277 else: 278 self._button_cont.opacity = 0.0 279 self._button_cont.top = 0 280 281 @locks.SingletonCall 282 @threadutils.threaded 283 def _confirm(self): 284 self._busy = True 285 self._update(animate=False) 286 service = get_dynamic_resource_service() 287 main_bank_link = service.ess_state_provider.state.main_bank.link 288 if main_bank_link and main_bank_link.character_id == session.charid: 289 message = text.link_error_description(LINK_ERROR_ALREADY_LINKED) 290 if message is not None: 291 prompt_player_fsd_dialog('CustomNotify', {'notify': message}) 292 self.on_disallowed() 293 return 294 try: 295 self.on_confirm(self.key) 296 finally: 297 uthread2.sleep(1.0) 298 self._busy = False 299 self._update(animate=False) 300 301 def OnMouseEnter(self, *args): 302 eveui.Sound.button_hover.play() 303 self._hovered = True 304 self._update() 305 306 def OnMouseExit(self, *args): 307 self._hovered = False 308 self._update() 309 310 311 class KeyEntryItem(ContainerAutoSize): 312 313 def __init__(self, parent, align, key): 314 super(KeyEntryItem, self).__init__(parent=parent, align=align, state=uiconst.UI_NORMAL, width=80, alignMode=uiconst.TOTOP) 315 self.key = key 316 self._hovered = False 317 self._icon = None 318 self._label = None 319 self._underlay = None 320 self._layout() 321 self._update() 322 self.key.on_count_changed.connect(self._update) 323 324 @property 325 def hovered(self): 326 return self._hovered 327 328 @hovered.setter 329 def hovered(self, hovered): 330 if hovered != self._hovered: 331 self._hovered = hovered 332 self._update() 333 334 def _layout(self): 335 iconTransform = Transform(parent=ContainerAutoSize(parent=self, align=uiconst.TOTOP, top=10), align=uiconst.TOTOP, state=uiconst.UI_DISABLED, width=42, height=42) 336 self._icon = ItemIcon(parent=iconTransform, align=uiconst.CENTER, state=uiconst.UI_DISABLED, width=42, height=42, typeID=self.key.type_id) 337 label_cont = ContainerAutoSize(parent=self, align=uiconst.TOTOP, top=8, padding=(8, 0, 8, 3)) 338 self._label = eveLabel.EveLabelSmall(parent=label_cont, align=uiconst.TOTOP, text=eveformat.center(self.key.name), color=(1.0, 1.0, 1.0, 0.8)) 339 self._underlay = Frame(bgParent=self, texturePath='res:/UI/Texture/Shared/DarkStyle/panel2Corner_Solid.png', cornerSize=9, color=(0.0, 0.0, 0.0, 0.5)) 340 341 def _update(self): 342 if self.key.available: 343 self._icon.opacity = 1.0 344 self._label.opacity = 0.8 345 else: 346 self._icon.opacity = 0.5 347 self._label.opacity = 0.3 348 if self._hovered and self.key.available: 349 underlay_color = color.smoke_blue.with_alpha(0.6).rgba 350 elif not self.key.available: 351 underlay_color = color.mordus_legion.with_alpha(0.2).rgba 352 else: 353 underlay_color = color.black.with_alpha(0.5).rgba 354 animations.SpColorMorphTo(self._underlay, endColor=underlay_color, duration=0.15) 355 356 def GetHint(self): 357 duration = datetimeutils.timedelta_to_filetime_delta(self.key.duration) 358 duration_string = localization.formatters.FormatTimeIntervalWritten(duration) 359 hint = u'{}<br>{}'.format(eveformat.bold(self.key.name), text.hint_key_info(pulses=self.key.total_pulses, duration=eveformat.color(duration_string, color.focus))) 360 if not self.key.available: 361 hint = u'{}<br><br>{}'.format(hint, eveformat.color(text.hint_key_unavailable(), color.warning)) 362 return hint 363 364 def GetMenu(self): 365 menu_service = ServiceManager.Instance().GetService('menu') 366 return menu_service.GetMenuFromItemIDTypeID(typeID=self.key.type_id, itemID=None, includeMarketDetails=True) 367 368 369 from dynamicresources.client.ess.bracket.debug import __reload_update__