NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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__