spiffy.py (15394B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\devtools\script\spiffy.py 2 from __future__ import print_function 3 import sys 4 import log 5 import types 6 import uthread 7 from carbon.client.script.environment.AudioUtil import PlaySound 8 from carbon.common.script.util import timerstuff 9 from carbonui import uiconst 10 from carbonui.control.contextMenu.menuUtil import ClearMenuLayer 11 from carbonui.window.underlay import WindowUnderlay 12 from carbonui.primitives.container import Container 13 from carbonui.primitives.fill import Fill 14 from carbonui.primitives.line import Line 15 from carbonui.primitives.sprite import Sprite 16 from carbonui.uicore import uicore 17 from eve.client.script.ui.control import eveIcon, eveLabel 18 from eve.client.script.ui.util import uix 19 ENTRY_HASSUBMENU = 1 20 ENTRY_ISDYNAMIC = 2 21 ENTRY_VADJUST = 4 22 ADDTOP = 2 23 MENUWIDTH = 64 24 BARWIDTH = 24 25 TRACEWIDTH = 7 26 HILITEALPHA = 0.25 27 GHOSTALPHA = 0.5 28 SPACER = 4 29 30 def MenuHeader(label, **kw): 31 entry = {'label': label, 32 'flags': ENTRY_VADJUST, 33 'height': 16, 34 'hilited': 2, 35 'disabled': True} 36 entry.update(kw) 37 return entry 38 39 40 class MenuEntry(Container): 41 default_height = 14 42 default_name = 'entry' 43 default_align = uiconst.TOTOP 44 default_state = uiconst.UI_NORMAL 45 46 def ApplyAttributes(self, attributes): 47 Container.ApplyAttributes(self, attributes) 48 triangleRight = Sprite(parent=self, name='TriangleRight', pos=(3, 3, 10, 10), align=uiconst.TOPRIGHT, state=uiconst.UI_HIDDEN, texturePath='res:/UI/Texture/Shared/triangleRight.png') 49 50 def Setup(self, entry, menuobj = None, fontsize = None, addleft = 3): 51 self.hilited = self.ghosted = self.disabled = False 52 self.flags = 0 53 self.action = self.args = self.iconmap = None 54 self.iconmapscaling = 0.25 55 self.fontsize = fontsize 56 self.menuobj = menuobj 57 if type(entry) == types.DictType: 58 for k, v in entry.iteritems(): 59 setattr(self, k, v) 60 61 elif type(entry) == types.TupleType: 62 label = entry[0] 63 if type(label) == types.TupleType: 64 self.label = label[0] 65 if len(label) > 1: 66 self.iconmap = label[1] 67 if len(label) > 2: 68 self.iconmapscaling = label[2] 69 else: 70 self.iconmap = 'ui_9_64_14' 71 else: 72 self.label = label 73 self.iconmap = None 74 action = entry[1] 75 if type(action) == types.TupleType: 76 if action[0] == 'isDynamic': 77 self.flags |= ENTRY_HASSUBMENU | ENTRY_ISDYNAMIC 78 self.action = action[1] 79 if len(action) > 2: 80 self.args = action[2] 81 else: 82 raise TypeError('Unsupported submenu type') 83 elif type(action) == types.ListType: 84 self.flags |= ENTRY_HASSUBMENU 85 self.action = action 86 elif callable(action) and not self.disabled: 87 self.action = action 88 if len(entry) >= 3: 89 self.args = entry[2] 90 else: 91 self.action = None 92 else: 93 raise TypeError('Unsupported entry definition type') 94 if self.fontsize: 95 self.hspace = 0 96 elif self.height <= 16: 97 self.fontsize = 10 98 self.hspace = 1 99 else: 100 self.fontsize = 12 101 self.hspace = 1 102 self.name = self.label 103 if not self.action: 104 self.disabled = True 105 if self.disabled and not self.hilited: 106 self.ghosted = True 107 if hasattr(self, 'OnPreCreate'): 108 self.OnPreCreate(self) 109 self.sr.leftedge = Container(name='push', parent=self, width=addleft, state=uiconst.UI_DISABLED, align=uiconst.TOLEFT) 110 if self.iconmap: 111 self.sr.icon = icon = eveIcon.Icon(parent=self, align=uiconst.TOLEFT, state=uiconst.UI_DISABLED) 112 icon.LoadIcon(self.iconmap, ignoreSize=True) 113 icon.SetSize(self.height, self.height) 114 icon.left = addleft 115 if self.ghosted: 116 icon.opacity = GHOSTALPHA 117 SPACER = 4 118 Container(name='push', parent=self, width=SPACER, state=uiconst.UI_DISABLED, align=uiconst.TOLEFT) 119 indent = self.sr.leftedge.width + (self.height, 0)[not self.iconmap] + SPACER 120 self.sr.label = eveLabel.Label(text=self.label, parent=self, fontsize=self.fontsize, letterspace=self.hspace, state=uiconst.UI_DISABLED, left=indent) 121 self.sr.label.top = (self.height - self.sr.label.textheight) / 2 + 1 122 if self.ghosted: 123 self.sr.label.opacity = GHOSTALPHA 124 self.width = indent + self.sr.label.textwidth + SPACER + SPACER + 3 125 if self.flags & ENTRY_HASSUBMENU: 126 self.triangle = triangle = self.GetChild('TriangleRight') 127 triangle.state = uiconst.UI_DISABLED 128 triangle.top = (self.height - 10) / 2 129 triangle.left += 2 130 self.children[1].state = uiconst.UI_HIDDEN 131 self.sr.selection2 = self.children[2] 132 if self.hilited == 2: 133 c = (0.0, 0.0, 0.0, 0.2) 134 else: 135 c = (1.0, 1.0, 1.0, 0.25) 136 self.sr.selection = Fill(parent=self, idx=0, color=c) 137 self.sr.selection.state = uiconst.UI_HIDDEN 138 if self.iconmap: 139 self.sr.iconbg = Fill(parent=self, idx=-1, color=(0, 0, 0, 0.2), left=addleft, top=0, width=self.height, height=self.height, align=uiconst.RELATIVE) 140 if hasattr(self, 'OnPostCreate'): 141 self.OnPostCreate(self) 142 if self.hilited: 143 self.OnMouseEnter() 144 145 def OnMouseEnter(self, *args): 146 if self.hilited or not self.disabled: 147 self.sr.selection.state = uiconst.UI_DISABLED 148 if self.iconmap: 149 self.sr.iconbg.state = uiconst.UI_HIDDEN 150 151 def OnMouseExit(self, *args): 152 if not self.hilited: 153 self.sr.selection.state = uiconst.UI_HIDDEN 154 if self.iconmap: 155 self.sr.iconbg.state = uiconst.UI_DISABLED 156 157 def OnMouseUp(self, *args): 158 if self.disabled: 159 return 160 if not self.flags & ENTRY_HASSUBMENU: 161 uthread.new(ClearMenuLayer) 162 if self.args: 163 uthread.new(self.action, *self.args) 164 else: 165 uthread.new(self.action) 166 167 def GetSubContent(self): 168 if self.flags & ENTRY_ISDYNAMIC: 169 try: 170 if self.args: 171 m = self.action(*self.args) 172 else: 173 m = self.action() 174 except Exception as e: 175 log.LogException() 176 print(str(e)) 177 m = [{'label': 'ERROR', 178 'disabled': True, 179 'ghosted': True, 180 'action': None, 181 'iconmap': '44_8', 182 'iconmapscaling': 0.125}] 183 sys.exc_clear() 184 185 else: 186 m = self.action 187 return m 188 189 190 class Menu(Container): 191 default_name = 'contextualmenu' 192 default_width = 80 193 default_height = 64 194 default_align = uiconst.TOPLEFT 195 default_state = uiconst.UI_NORMAL 196 197 def ApplyAttributes(self, attributes): 198 Container.ApplyAttributes(self, attributes) 199 self.submenu = None 200 self.hilitedentry = None 201 entries = Container(parent=self, name='entries', padding=(2, 2, 2, 2), align=uiconst.TOALL, state=uiconst.UI_PICKCHILDREN) 202 underlay = WindowUnderlay(parent=self) 203 204 def Setup(self, menu, entryheight = 16, position = None, origin = 'topleft', caption = None, fromentry = None, reverse = False): 205 self.reverse = reverse 206 self.origin = origin 207 self.entryheight = entryheight 208 self.fromentry = fromentry 209 if fromentry: 210 self.parentmenu = self.fromentry.menuobj 211 self.state = uiconst.UI_HIDDEN 212 self.align = uiconst.RELATIVE 213 container = Container(name='SpiffyEntryList', parent=self, idx=0, pos=(0, 0, 0, 0)) 214 height = 0 215 width = 0 216 Container(name='push', parent=container, align=uiconst.TOTOP, height=2) 217 allflags = 0 218 wasline = 1 219 if caption or fromentry: 220 margin = (0, 1)[not fromentry] 221 else: 222 margin = 5 223 if not menu: 224 menu = [{'label': 'No items', 225 'disabled': True, 226 'ghosted': True, 227 'action': None, 228 'iconmap': '44_8', 229 'iconmapscaling': 0.125}] 230 while menu[-1] is None: 231 menu.pop() 232 233 addtop = 0 234 for entry in menu: 235 if entry: 236 item = MenuEntry(parent=container, height=entryheight) 237 item.Setup(entry, addleft=margin, menuobj=self) 238 item.OnMouseEnter = (self.OnEntryEnter, item) 239 item.OnMouseExit = (self.OnEntryExit, item) 240 width = max(width, item.width) 241 if item.flags & ENTRY_VADJUST and fromentry and not height: 242 addtop = item.height 243 allflags |= item.flags 244 wasline = 0 245 else: 246 if wasline: 247 continue 248 item = Line(parent=container, align=uiconst.TOTOP) 249 wasline = 1 250 height += item.height 251 252 if fromentry and item and item.flags & ENTRY_VADJUST: 253 subbottom = item.height 254 else: 255 subbottom = 0 256 if allflags & ENTRY_HASSUBMENU: 257 addwidth = 8 258 else: 259 addwidth = 0 260 self.height = ADDTOP + height + ADDTOP 261 if fromentry and not caption: 262 addwidth += TRACEWIDTH 263 edge = Container(name='SpiffyBar', parent=container, align=uiconst.TOLEFT, width=TRACEWIDTH, idx=0) 264 self.sr.trace = side = Fill(parent=self, align=uiconst.RELATIVE, left=0, top=0, width=TRACEWIDTH, idx=0) 265 else: 266 edge = None 267 if caption: 268 raise RuntimeError('SpiffyBar is still broken and needs fixing') 269 addwidth += BARWIDTH 270 side = uix.GetFill(name='SpiffyBar', parent=container, align=uiconst.TOLEFT, width=BARWIDTH, idx=0) 271 t = eveLabel.Label(text=caption, parent=side, width=self.height, height=BARWIDTH, state=uiconst.UI_NORMAL) 272 sm.GetService('textmetr').RefreshHeight(t) 273 t.left = (BARWIDTH - self.height + t.height) / 2 + 1 274 t.height = BARWIDTH 275 t.top = (self.height - BARWIDTH) / 2 - 1 - 12 276 t.rotation = 90.0 277 t.control.model.areas[0].areaMaterials[0].diffuse.a = 0.66 278 else: 279 side = None 280 if side: 281 side.SetRGBA(1.0, 1.0, 1.0, HILITEALPHA) 282 self.width = max(MENUWIDTH, width) + addwidth 283 if fromentry: 284 p = self.parentmenu 285 while True: 286 if self.reverse: 287 left = p.absoluteLeft - self.width + 3 288 else: 289 left = p.absoluteRight - 5 290 if left < 0 or left + self.width > uicore.desktop.absoluteRight: 291 self.reverse = not self.reverse 292 else: 293 break 294 295 fl, ft, fw, fh = fromentry.GetAbsolute() 296 top = ft - ADDTOP - addtop 297 if top + self.height > uicore.desktop.absoluteBottom: 298 top = ft + fh + ADDTOP + subbottom - self.height 299 if self.reverse and side: 300 side.width = 10 301 side.left = self.width - side.width 302 else: 303 if position: 304 x, y = position 305 else: 306 x, y = uicore.uilib.x, uicore.uilib.y 307 if origin == 'topleft': 308 left = x 309 top = y - addtop 310 elif origin == 'bottomleft': 311 left = x 312 top = y - self.height + subbottom 313 elif origin == 'bottomright': 314 left = x - self.width 315 top = y - self.height + subbottom 316 elif origin == 'topright': 317 left = x - self.width 318 top = y - addtop 319 else: 320 raise ValueError("origin must be one of 'topleft', 'topright', 'bottomleft', 'bottomright'") 321 if left < 0: 322 left = 0 323 elif left + self.width > uicore.desktop.absoluteRight: 324 left = uicore.desktop.absoluteRight - self.width 325 if top < 0: 326 top = 0 327 elif top + self.height > uicore.desktop.absoluteBottom: 328 top = uicore.desktop.absoluteBottom - self.height 329 if edge: 330 if left < p.left: 331 edge.width = 4 332 self.left = left 333 self.top = top 334 self.state = uiconst.UI_NORMAL 335 336 def OnEntryEnter(self, entry): 337 MenuEntry.OnMouseEnter(entry) 338 PlaySound(uiconst.SOUND_ENTRY_HOVER) 339 if self.fromentry: 340 self.DoEntryTrace(entry) 341 if entry != self.hilitedentry: 342 self.hilitedentry = entry 343 if self.submenu: 344 self.DoSubmenuCollapse() 345 if entry.flags & ENTRY_HASSUBMENU: 346 self.submenuTimer = timerstuff.AutoTimer(settings.user.ui.Get('menuExpandTime', 10), self.DoSubmenuExpand, entry) 347 348 def OnEntryExit(self, entry): 349 if not (self.submenu and self.submenu.fromentry == entry): 350 MenuEntry.OnMouseExit(entry) 351 if self.fromentry: 352 if self.submenu: 353 self.DoEntryTrace(entry) 354 else: 355 self.sr.trace.top = 99999 356 357 def DoSubmenuExpand(self, entry): 358 self.submenuTimer = None 359 self.DoSubmenuCollapse() 360 if entry == self.hilitedentry: 361 entry.OnMouseEnter() 362 if self.fromentry: 363 self.DoEntryTrace(entry) 364 self.submenu = Menu(parent=uicore.layer.menu, idx=0) 365 self.submenu.Setup(entry.GetSubContent(), fromentry=entry, reverse=self.reverse, entryheight=self.entryheight) 366 367 def DoSubmenuCollapse(self): 368 if self.submenu: 369 self.submenu.KillMe() 370 self.submenu = None 371 372 def DoEntryTrace(self, entry): 373 fl, ft, fw, fh = self.fromentry.GetAbsolute() 374 el, et, ew, eh = entry.GetAbsolute() 375 if et + eh / 2 > ft + fh / 2: 376 top = ft 377 if self.reverse: 378 bottom = et 379 else: 380 bottom = et + eh 381 else: 382 bottom = ft + fh 383 if self.reverse: 384 top = et + eh 385 else: 386 top = et 387 self.sr.trace.top = top - self.absoluteTop 388 self.sr.trace.height = bottom - top 389 390 def KillMe(self): 391 self.submenuTimer = None 392 if self.submenu: 393 self.submenu.KillMe() 394 self.submenu = None 395 if self.fromentry: 396 self.fromentry.OnMouseExit() 397 if not self.destroyed: 398 self.Close() 399 400 401 def CreateMenu(menu = None, *args, **kwargs): 402 if not menu: 403 return None 404 ClearMenuLayer() 405 obj = Menu(parent=uicore.layer.menu) 406 obj.Setup(menu, *args, **kwargs) 407 return obj