eveImagePicker.py (18692B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\control\eveImagePicker.py 2 import math 3 import blue 4 import charactercreator.const as ccConst 5 import localization 6 import uthread 7 from carbon.client.script.environment.AudioUtil import PlaySound 8 from carbon.common.script.sys import serviceConst 9 from carbon.common.script.util import logUtil as log, timerstuff 10 from carbonui import uiconst 11 from carbonui.primitives.container import Container 12 from carbonui.primitives.fill import Fill 13 from carbonui.primitives.frame import Frame 14 from carbonui.primitives.sprite import Sprite 15 from carbonui.uicore import uicore 16 from eve.client.script.ui.login.charcreation.label import CCLabel 17 from eveprefs import prefs 18 HILITE = ('ui_105_32_27', 15, -3) 19 20 class ImagePicker(Container): 21 default_align = uiconst.CENTER 22 default_left = 0 23 default_top = 0 24 default_width = 768 25 default_height = 128 26 default_name = 'ImagePicker' 27 default_state = uiconst.UI_NORMAL 28 29 def ApplyAttributes(self, attributes): 30 for each in uicore.layer.main.children: 31 if each.name == self.default_name: 32 each.Close() 33 34 Container.ApplyAttributes(self, attributes) 35 if attributes.parent is None: 36 uicore.layer.main.children.append(self) 37 self.onPreSetValueCallback = None 38 self.onSetValueCallback = None 39 self.onChangeFocusCallback = None 40 self._activeData = None 41 self._lastBrowseDiff = False 42 self._lastSpringValue = None 43 self._initMouseDownX = None 44 self._doBlock = False 45 self._availableTypeIDs = set() 46 if session.role & serviceConst.ROLE_CONTENT: 47 invCache = sm.GetService('invCache') 48 if session.stationid or session.structureid: 49 try: 50 inv = invCache.GetInventory(const.containerHangar) 51 self._availableTypeIDs.update({i.typeID for i in inv.List(const.flagHangar) if i.categoryID == const.categoryApparel}) 52 except Exception as e: 53 log.LogWarn() 54 55 if session.charid: 56 try: 57 inv = invCache.GetInventoryFromId(session.charid) 58 self._availableTypeIDs.update({i.typeID for i in inv.List() if i.flagID == const.flagWardrobe}) 59 except Exception as e: 60 log.LogWarn() 61 62 self.zoomFactor = attributes.get('zoomFactor', 6.0) 63 self.imageWidth = attributes.get('imageWidth', 128) 64 self.imageHeight = attributes.get('imageHeight', 128) 65 self.browserWidth = attributes.get('browserWidth', 400) 66 self.numSpecialItems = attributes.get('numSpecialItems', 0) 67 l, t, w, h = self.parent.GetAbsolute() 68 self.sr.counterParent = Container(parent=self.parent, align=uiconst.TOPLEFT, name='counterParent', state=uiconst.UI_NORMAL, hint=localization.GetByLabel('UI/CharacterCustomization/ShowActive'), pos=(7, 6, 13, 9), opacity=0.5, idx=0) 69 self.sr.counterParent.OnMouseEnter = (self.OnGenericMouseEnter, self.sr.counterParent) 70 self.sr.counterParent.OnMouseExit = (self.OnGenericMouseExit, self.sr.counterParent) 71 self.sr.counterParent.OnClick = self.ShowActiveSlot 72 self.sr.counterParent.sr.icon = Frame(frameConst=(ccConst.ICON_FOCUSFRAME, 15, -11), parent=self.sr.counterParent, color=ccConst.COLOR50) 73 self.sr.counterParent.sr.label = CCLabel(text='', parent=self.sr.counterParent, color=ccConst.COLOR50, align=uiconst.CENTER, left=1) 74 if self.DebugRoleCheck(): 75 self.sr.debugLabel = CCLabel(text='', parent=self, align=uiconst.CENTER, top=self.imageHeight / 2 + 8, letterspace=0, fontsize=9, color=(1.0, 1.0, 1.0, 0.5), state=uiconst.UI_NORMAL, idx=0) 76 self.sr.debugLabel.GetMenu = self.GetDebugMenu 77 self.BuildStrip(attributes.images) 78 self.UpdateCounter() 79 self.onSetValueCallback = attributes.get('OnSetValue', None) 80 self.onChangeFocusCallback = attributes.get('OnChangeFocus', None) 81 self.onPreSetValueCallback = attributes.get('OnPreSetValue', None) 82 83 def ShowActiveSlot(self, *args): 84 active = self.GetActiveData() 85 if active: 86 self.SetActiveData(active) 87 88 def GetDebugMenu(self): 89 if self.DebugRoleCheck(): 90 return [('Copy Focus Data', self.CopyFocusData)] 91 92 def CopyFocusData(self): 93 fd = self.GetFocusData() 94 if fd: 95 blue.pyos.SetClipboardData(repr(fd[1])) 96 97 def BuildStrip(self, images, *args): 98 if self.sr.slots: 99 self.sr.slots.Close() 100 self.sr.slots = Container(parent=self, align=uiconst.CENTERLEFT) 101 self.images = images 102 self.slots = [] 103 self.height = self.imageHeight + 64 104 self.width = self.browserWidth 105 self.focusSlot = None 106 if len(images) > 0: 107 for idx, imageData in enumerate(images): 108 slot = self.AddSlot(idx, imageData) 109 110 else: 111 slot = self.AddSlot(0, (None, [None, None], None)) 112 log.LogTraceback('ImagePicker - Building strip without images') 113 self.sr.slots.width = self.sr.slots.children[-1].left + self.sr.slots.children[-1].width 114 self.maxSlotLeft = int(self.width / 2.0 - self.imageWidth / 4.0) 115 self.minSlotLeft = -int(self.sr.slots.width - self.width / 2.0) 116 self.sr.slots.height = self.imageHeight 117 self.FixPosition(instant=True) 118 self.UpdateSlotZoom() 119 120 def AddSlot(self, idx, imageData): 121 step = int(self.imageWidth * 0.75) 122 self._slotGap = step 123 specialItemPadding = 0 124 if self.numSpecialItems and idx >= self.numSpecialItems: 125 specialItemPadding = int(step * 0.5) 126 slotpar = Container(name='slotPar_%s' % idx, parent=self.sr.slots, align=uiconst.TOPLEFT, state=uiconst.UI_PICKCHILDREN, pos=(-self.imageWidth / 2 + (idx + 1) * step + specialItemPadding, 127 0, 128 self.imageWidth, 129 self.imageHeight)) 130 slotpar.idx = idx 131 slotpar.imageData = imageData 132 slot = Container(parent=slotpar, align=uiconst.CENTER, state=uiconst.UI_NORMAL, pos=(0, 133 0, 134 self.imageWidth / 2, 135 self.imageHeight / 2)) 136 slot.OnMouseDown = (self.OnSlotMouseDown, slot) 137 slot.OnMouseEnter = self.OnSlotMouseEnter 138 slot.OnMouseUp = (self.OnSlotMouseUp, slot) 139 slot.imageData = imageData 140 slot.sr.hilite = Frame(parent=slot, align=uiconst.TOALL, frameConst=HILITE, state=uiconst.UI_HIDDEN, color=(1.0, 1.0, 1.0, 1.0)) 141 slot.sr.hilite.padding = -6 142 slot.sr.thumbnail = Sprite(parent=slot, texturePath=None, align=uiconst.TOALL, padding=(2, 2, 2, 2), state=uiconst.UI_DISABLED) 143 Fill(parent=slot, padding=(2, 2, 2, 2), color=(0.0, 0.0, 0.0, 0.75)) 144 return slot 145 146 def OnSlotMouseEnter(self): 147 PlaySound('ui_icc_button_mouse_over_play') 148 149 def GetActiveData(self): 150 return self._activeData 151 152 def GetOptions(self): 153 return self.images 154 155 def GetFocusData(self): 156 if self.focusSlot: 157 return self.focusSlot.imageData 158 159 def SetActiveDataFromValue(self, toValue, doCallbacks = True, focusOnSlot = True, doYield = True): 160 toValueSet = set([toValue]) 161 if isinstance(toValue, tuple): 162 if toValue[1] == '': 163 v0TypeData = (toValue[0], 'v0', toValue[2]) 164 toValueSet.add(v0TypeData) 165 elif toValue[1] == 'v0': 166 emptyStringTypeData = (toValue[0], '', toValue[2]) 167 toValueSet.add(emptyStringTypeData) 168 for i, imageData in enumerate(self.images): 169 resPath, data, hiliteResPath = imageData 170 if data[1] in toValueSet: 171 self.SetActiveData(imageData, focusOnSlot=focusOnSlot, doCallbacks=doCallbacks, doYield=doYield) 172 break 173 174 def SetActiveData(self, data, focusOnSlot = True, doCallbacks = True, doYield = True): 175 newData = self._activeData != data 176 if doCallbacks and self.onPreSetValueCallback and newData: 177 self.onPreSetValueCallback(data) 178 self._activeData = data 179 self.UpdateCounter() 180 if focusOnSlot: 181 uthread.new(self.SetFocusData, data, doYield) 182 if doCallbacks and self.onSetValueCallback and newData: 183 self.onSetValueCallback(data) 184 185 def SetFocusData(self, data, doYield = True): 186 if self.destroyed: 187 return 188 slotParent = self.GetSlotParentByData(data) 189 if slotParent: 190 l, t, w, h = self.GetAbsolute() 191 newleft = w / 2 - (slotParent.left + slotParent.width / 2) 192 if newleft == self.sr.slots.left: 193 return 194 uicore.effect.MorphUIMassSpringDamper(self.sr.slots, 'left', newleft, newthread=not doYield, initVal=self.sr.slots.left, float=False, frequency=15.0, dampRatio=0.75, callback=self.UpdateSlotZoom) 195 self.state = uiconst.UI_NORMAL 196 197 def UpdateCounter(self): 198 if self.sr.counterParent: 199 if self._activeData is not None: 200 slotParent = self.GetSlotParentByData(self._activeData) 201 self.sr.counterParent.sr.label.text = '%s/%s' % (slotParent.idx + 1, len(self.images)) 202 else: 203 self.sr.counterParent.sr.label.text = '-/%s' % len(self.images) 204 self.sr.counterParent.width = self.sr.counterParent.sr.label.textwidth + 8 205 206 def GetSlotParentByData(self, data): 207 for slot in self.sr.slots.children: 208 if getattr(slot, 'imageData', None) == data: 209 return slot 210 211 def GetValue(self, *args): 212 if self.focusSlot: 213 return self.focusSlot.imageData 214 215 def SpringFeed(self, item, value): 216 if self.destroyed: 217 return 218 lastSpringValue = self._lastSpringValue or 0 219 self.RotateImages(lastSpringValue - value, clampValue=False) 220 self._lastSpringValue = value 221 222 def CheckMouseOver(self, slot): 223 if slot.destroyed: 224 slot.sr.mouseOverTimer = None 225 return 226 if uicore.uilib.leftbtn or uicore.uilib.rightbtn: 227 return 228 if self._activeData == slot.imageData: 229 slot.sr.hilite.state = uiconst.UI_DISABLED 230 slot.sr.hilite.SetAlpha(1.0) 231 elif uicore.uilib.mouseOver is slot: 232 slot.sr.hilite.state = uiconst.UI_DISABLED 233 slot.sr.hilite.SetAlpha(0.25) 234 else: 235 slot.sr.hilite.state = uiconst.UI_HIDDEN 236 237 def OnSlotMouseDown(self, slot, mouseBtn, *args): 238 self.OnMouseDown(mouseBtn) 239 240 def OnSlotMouseUp(self, slot, mouseBtn, *args): 241 self.OnMouseUpAlt(slot, mouseBtn) 242 243 def CheckIfSlotLoaded(self, slotParent): 244 if getattr(slotParent, '_loaded', False): 245 return 246 slotParent._loaded = True 247 slot = slotParent.children[0] 248 resPath, data, hiliteResPath = slot.imageData 249 if not resPath or not blue.paths.exists(resPath): 250 resPath = 'res:/UI/Asset/missingThumbnail.dds' 251 slot.sr.thumbnail.LoadTexture(resPath) 252 if len(data) > 2 and data[2] is not None and session.role & serviceConst.ROLE_CONTENT: 253 flag = Container(parent=slot, align=uiconst.TOPLEFT, padding=(2, 2, 2, 2), pos=(0, 0, 20, 20), state=uiconst.UI_DISABLED, idx=0) 254 fill = Fill(parent=flag, color=(0, 1.0, 0, 0.5)) 255 if data[2] not in self._availableTypeIDs: 256 fill.SetRGB(1.0, 0, 0) 257 slot.GetMenu = (self.GetRightClickMenu, data[2]) 258 if getattr(uicore.layer.charactercreation.controller, 'mode', -1) == ccConst.MODE_FULLINITIAL_CUSTOMIZATION: 259 Fill(parent=flag, color=(0, 0.0, 0, 1.0), padding=5, idx=0) 260 if self._activeData == slot.imageData: 261 slot.sr.hilite.state = uiconst.UI_DISABLED 262 slot.sr.hilite.SetAlpha(1.0) 263 elif slot.sr.hilite: 264 slot.sr.hilite.state = uiconst.UI_HIDDEN 265 slot.sr.mouseOverTimer = timerstuff.AutoTimer(33, self.CheckMouseOver, slot) 266 267 def OnMouseDown(self, mouseBtn = 0, *args): 268 if mouseBtn != uiconst.MOUSELEFT: 269 return 270 PlaySound('ui_icc_button_mouse_down_play') 271 self._lastBrowseDiff = None 272 self._lastMouseX = uicore.uilib.x 273 self._initMouseDownX = uicore.uilib.x 274 self._initPosX = self.sr.slots.left 275 self.sr.scrollTimer = timerstuff.AutoTimer(10, self.UpdateScroll) 276 uthread.new(self.OnScrollStart) 277 278 def OnMouseUp(self, mouseBtn, *args): 279 self.OnMouseUpAlt(mouseBtn=mouseBtn) 280 281 def OnMouseUpAlt(self, slot = None, mouseBtn = 0): 282 if mouseBtn != uiconst.MOUSELEFT: 283 return 284 self.sr.scrollTimer = None 285 if self._lastBrowseDiff: 286 uthread.new(self.Throw, self._lastBrowseDiff) 287 elif self.sr.slots.left != getattr(self, '_initPosX', 0): 288 self._initMouseDownX = None 289 uthread.new(self.FixPosition) 290 elif slot: 291 self.SetActiveData(slot.imageData) 292 293 def OnMouseWheel(self, *args): 294 uthread.new(self.MouseWheel_thread).context = 'ImagePicker::Throw' 295 return 1 296 297 def MouseWheel_thread(self, *args): 298 self.Browse(uicore.uilib.dz / 3) 299 if self.sr.settleWheelThread: 300 self.sr.settleWheelThread.kill() 301 self.sr.settleWheelThread = None 302 self.sr.settleWheelThread = uthread.new(self.SettleAfterWheel_thread) 303 304 def SettleAfterWheel_thread(self, *args): 305 blue.pyos.synchro.SleepWallclock(250) 306 if not self or self.destroyed: 307 return 308 self.sr.wheelSettleTimer = None 309 self.FixPosition() 310 311 def FixPosition(self, instant = False): 312 l, t, w, h = self.GetAbsolute() 313 newleft = self.sr.slots.left 314 rounding = (newleft - w / 2) % self._slotGap 315 if rounding > self._slotGap / 2: 316 newleft += self._slotGap - rounding 317 elif rounding < self._slotGap / 2: 318 newleft -= rounding 319 else: 320 return 321 newleft = max(-self.sr.slots.width + (w + self.imageWidth) / 2, min(newleft, w / 2 - self._slotGap)) 322 if instant: 323 self.sr.slots.left = newleft 324 else: 325 uicore.effect.MorphUIMassSpringDamper(self.sr.slots, 'left', newleft, newthread=0, initVal=self.sr.slots.left, float=False, frequency=15.0, dampRatio=0.75, callback=self.UpdateSlotZoom) 326 self.state = uiconst.UI_NORMAL 327 328 def UpdateScroll(self, *args): 329 newLeft = self._initPosX + (uicore.uilib.x - self._initMouseDownX) 330 if self.maxSlotLeft > newLeft and newLeft > self.minSlotLeft: 331 self.sr.slots.left = newLeft 332 self._lastBrowseDiff = uicore.uilib.x - self._lastMouseX 333 self._lastMouseX = uicore.uilib.x 334 self.UpdateSlotZoom() 335 336 def UpdateSlotZoom(self, *args): 337 if not hasattr(self, 'sr'): 338 return 339 l, t, w, h = self.GetAbsolute() 340 centerX = l + w / 2 341 slotsInside = [] 342 centerSlot = None 343 minDistFromCenter = 1000 344 for slot in self.sr.slots.children: 345 if not hasattr(slot, 'children'): 346 continue 347 sl, st, sw, sh = slot.GetAbsolute() 348 slotCenter = sl + sw / 2 349 if slotCenter >= l and slotCenter <= l + w: 350 slotsInside.append((abs(centerX - slotCenter), slotCenter, slot)) 351 else: 352 slot.children[0].width = self.imageWidth / 2 353 slot.children[0].height = self.imageHeight / 2 354 distFromCenter = abs(centerX - slotCenter) 355 if distFromCenter < minDistFromCenter: 356 minDistFromCenter = distFromCenter 357 centerSlot = slot 358 359 slotsInside.sort() 360 slotsInside.reverse() 361 maxDist = w / 2 362 for diff, slotCenter, slot in slotsInside: 363 if diff: 364 sizeFactor = 1.0 - abs(diff) / float(maxDist) 365 else: 366 sizeFactor = 1.0 367 slot.children[0].width = self.imageWidth / 2 + int(self.imageWidth / 2 * sizeFactor) 368 slot.children[0].height = self.imageHeight / 2 + int(self.imageHeight / 2 * sizeFactor) 369 slot.SetOrder(0) 370 uthread.new(self.CheckIfSlotLoaded, slot) 371 372 if slotsInside: 373 self.focusSlot = slotsInside[-1][-1] 374 if self.onChangeFocusCallback: 375 self.onChangeFocusCallback(self.focusSlot.imageData) 376 if self.DebugRoleCheck(): 377 resPath, data, hiliteResPath = self.focusSlot.imageData 378 self.sr.debugLabel.text = repr(data[1]) 379 380 def DebugRoleCheck(self): 381 mask = serviceConst.ROLE_CONTENT | serviceConst.ROLE_QA | serviceConst.ROLE_PROGRAMMER | serviceConst.ROLE_GML 382 if session.role & mask and not prefs.GetValue('CCHideAssetDebugText', False): 383 return True 384 else: 385 return False 386 387 def Browse(self, direction): 388 if not len(self.sr.slots.children): 389 return 390 self.sr.slots.left = self.sr.slots.left - direction 391 self.UpdateSlotZoom() 392 393 def Throw(self, distance): 394 steps = 20 395 maxExp = math.exp(steps / 10.0) 396 l, t, w, h = self.GetAbsolute() 397 self._initMouseDownX = None 398 for i in xrange(1, steps + 1): 399 if self.sr.slots.left > self.maxSlotLeft: 400 break 401 if self.sr.slots.left < self.minSlotLeft: 402 break 403 iExp = math.exp(i / 10.0) 404 self.Browse(-(distance - int(distance * (iExp / maxExp)))) 405 blue.pyos.synchro.SleepWallclock(10) 406 if self.destroyed: 407 return 408 if self._initMouseDownX: 409 break 410 411 self.FixPosition() 412 413 def OnScrollStart(self, *args): 414 pass 415 416 def OnGenericMouseEnter(self, btn, *args): 417 btn.SetOpacity(1.0) 418 419 def OnGenericMouseExit(self, btn, *args): 420 btn.SetOpacity(0.5) 421 422 def OnGenericMouseDown(self, btn, *args): 423 btn.sr.icon.top += 1 424 425 def GetRightClickMenu(self, typeID, *args): 426 if not session.role & serviceConst.ROLE_WORLDMOD: 427 return [] 428 return [('WM: /create this type', self.GMCreateItem, (typeID,))] 429 430 def GMCreateItem(self, typeID, *args): 431 try: 432 sm.RemoteSvc('slash').SlashCmd('/create %s %s %s' % (typeID, 1, session.stationid)) 433 uicore.layer.charactercreation.controller.sr.step.GoToAssetMode(forcedMode=1) 434 sm.GetService('cc').ClearMyAvailabelTypeIDs() 435 except Exception as e: 436 log.LogWarn()