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

dockPanel.py (32275B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\mapView\dockPanel.py
      2 import logging
      3 import blue
      4 import eveicon
      5 from carbonui.button.menu import MenuButtonIcon
      6 from carbonui.control.buttonIcon import ButtonIcon
      7 from mathext import clamp
      8 import localization
      9 import uthread
     10 from carbon.client.script.environment.AudioUtil import PlaySound
     11 from carbonui import uiconst, Align
     12 from carbonui.control.window import Window
     13 from carbonui.primitives.base import Base
     14 from carbonui.primitives.container import Container
     15 from carbonui.primitives.fill import Fill
     16 from carbonui.primitives.layoutGrid import LayoutGrid, LayoutGridRow
     17 from carbonui.uicore import uicore
     18 from carbonui.window.resizer import Side
     19 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelMediumBold
     20 from carbonui.loggers.message_bus.windowMessenger import WindowMessenger, DOCKING_MODES_CONVERSION
     21 from eve.client.script.ui.control.themeColored import FillThemeColored, StretchSpriteHorizontalThemeColored
     22 from eve.client.script.ui.shared.infoPanels.const import infoPanelUIConst
     23 from eve.client.script.ui.shared.infoPanels.infoPanelContainer import InfoPanelContainer
     24 from eve.client.script.ui.shared.mapView.dockPanelUtil import GetDockPanelManager, GetPanelSettings, RegisterPanelSettings
     25 from eveexceptions import EatsExceptions
     26 logger = logging.getLogger(__name__)
     27 TOOLBARWIDTH_FULLSCREEN = 400
     28 TOOLBARHEIGHT = 26
     29 BUTTON_FULLSCREEN = 1
     30 BUTTON_DOCKLEFT = 2
     31 BUTTON_DOCKRIGHT = 3
     32 BUTTON_FLOAT = 4
     33 BUTTON_DATA_BY_ID = {BUTTON_FULLSCREEN: ('res:/UI/Texture/classes/DockPanel/fullscreenButton.png', 'UI/Control/DockPanel/Fullscreen'),
     34  BUTTON_DOCKLEFT: ('res:/UI/Texture/classes/DockPanel/dockLeftButton.png', 'UI/Control/DockPanel/DockLeft'),
     35  BUTTON_DOCKRIGHT: ('res:/UI/Texture/classes/DockPanel/dockRightButton.png', 'UI/Control/DockPanel/DockRight'),
     36  BUTTON_FLOAT: ('res:/UI/Texture/classes/DockPanel/floatButton.png', 'UI/Control/DockPanel/Floating')}
     37 SNAP_RELEASE_THRESHOLD = 5
     38 SNAP_INDICATOR_SIZE = 2
     39 
     40 class DockablePanelHeaderButton(ButtonIcon):
     41     default_width = 16
     42     default_height = 16
     43     default_minSize = (300, 300)
     44 
     45     def GetTooltipPointer(self):
     46         return uiconst.POINT_TOP_1
     47 
     48     def GetTooltipDelay(self):
     49         return 5
     50 
     51     def GetTooltipPositionFallbacks(self, *args, **kwds):
     52         return [uiconst.POINT_TOP_2,
     53          uiconst.POINT_TOP_1,
     54          uiconst.POINT_TOP_3,
     55          uiconst.POINT_BOTTOM_2,
     56          uiconst.POINT_BOTTOM_1,
     57          uiconst.POINT_BOTTOM_3]
     58 
     59 
     60 class DockablePanelHeaderButtonMenu(DockablePanelHeaderButton):
     61     callback = None
     62     tooltipPanelMenu = None
     63 
     64     def __init__(self, dock_options, **kw):
     65         super(DockablePanelHeaderButtonMenu, self).__init__(**kw)
     66         self.dock_options = dock_options
     67 
     68     def ApplyAttributes(self, attributes):
     69         DockablePanelHeaderButton.ApplyAttributes(self, attributes)
     70         self.panelID = attributes.panelID
     71         self.callback = attributes.dockViewModeCallback
     72         self.UpdateButtonState()
     73 
     74     def Close(self, *args, **kwds):
     75         DockablePanelHeaderButton.Close(self)
     76         self.callback = None
     77         self.tooltipPanelMenu = None
     78 
     79     def UpdateButtonState(self):
     80         currentSetting = GetPanelSettings(self.panelID)
     81         align = currentSetting['align']
     82         if align == uiconst.TOALL:
     83             buttonID = BUTTON_FULLSCREEN
     84         elif align == uiconst.TOLEFT:
     85             buttonID = BUTTON_DOCKLEFT
     86         elif align == uiconst.TORIGHT:
     87             buttonID = BUTTON_DOCKRIGHT
     88         else:
     89             buttonID = BUTTON_FLOAT
     90         texturePath, labelPath = BUTTON_DATA_BY_ID[buttonID]
     91         self.SetTexturePath(texturePath)
     92 
     93     def GetTooltipPointer(self):
     94         return uiconst.POINT_TOP_1
     95 
     96     def GetTooltipDelay(self):
     97         return 5
     98 
     99     def GetTooltipPositionFallbacks(self, *args, **kwds):
    100         return [uiconst.POINT_TOP_2,
    101          uiconst.POINT_TOP_1,
    102          uiconst.POINT_TOP_3,
    103          uiconst.POINT_BOTTOM_2,
    104          uiconst.POINT_BOTTOM_1,
    105          uiconst.POINT_BOTTOM_3]
    106 
    107     def LoadTooltipPanel(self, tooltipPanel, *args):
    108         tooltipPanel.columns = 2
    109         tooltipPanel.margin = 4
    110         for buttonID in self.dock_options:
    111             texturePath, labelPath = BUTTON_DATA_BY_ID[buttonID]
    112             tooltipPanel.AddRow(rowClass=ButtonTooltipRow, buttonID=buttonID, texturePath=texturePath, label=localization.GetByLabel(labelPath), callback=self._LocalCallback)
    113 
    114         tooltipPanel.state = uiconst.UI_NORMAL
    115         self.tooltipPanelMenu = tooltipPanel
    116 
    117     def _LocalCallback(self, buttonID):
    118         if self.tooltipPanelMenu and not self.tooltipPanelMenu.destroyed:
    119             self.tooltipPanelMenu.state = uiconst.UI_HIDDEN
    120         if self.callback:
    121             self.callback(buttonID)
    122         self.UpdateButtonState()
    123 
    124 
    125 class ButtonTooltipRow(LayoutGridRow):
    126     default_state = uiconst.UI_NORMAL
    127     callback = None
    128     buttonID = None
    129 
    130     def ApplyAttributes(self, attributes):
    131         LayoutGridRow.ApplyAttributes(self, attributes)
    132         self.callback = attributes.callback
    133         self.buttonID = attributes.buttonID
    134         self.icon = ButtonIcon(pos=(0, 0, 16, 16), texturePath=attributes.texturePath, state=uiconst.UI_DISABLED)
    135         self.AddCell(cellObject=self.icon, cellPadding=(3, 2, 3, 2))
    136         label = EveLabelMedium(text=attributes.label, align=uiconst.CENTERLEFT)
    137         self.AddCell(cellObject=label, cellPadding=(0, 1, 6, 1))
    138         self.highLight = Fill(bgParent=self, color=(1, 1, 1, 0.1), state=uiconst.UI_HIDDEN)
    139 
    140     def Close(self, *args):
    141         LayoutGridRow.Close(self, *args)
    142         self.callback = None
    143 
    144     def OnMouseEnter(self, *args):
    145         self.icon.OnMouseEnter()
    146         self.highLight.display = True
    147 
    148     def OnMouseExit(self, *args):
    149         self.icon.OnMouseExit()
    150         self.highLight.display = False
    151 
    152     def OnClick(self, *args):
    153         if self.callback:
    154             self.callback(self.buttonID)
    155 
    156 
    157 class DockablePanel(Window):
    158     default_captionLabelPath = None
    159     default_isStackable = False
    160     default_performFitsCheck = True
    161     default_isLightBackgroundConfigurable = False
    162     dock_options = (BUTTON_FULLSCREEN,
    163      BUTTON_DOCKLEFT,
    164      BUTTON_DOCKRIGHT,
    165      BUTTON_FLOAT)
    166     panelSize = (500, 500)
    167     panelID = None
    168     toolbarContainer = None
    169     snapToDockMode = None
    170     snapIndicator = None
    171     viewState = None
    172     hasImmersiveAudioOverlay = False
    173     __notifyevents__ = ['OnViewStateChanged']
    174 
    175     def ApplyAttributes(self, attributes):
    176         self._immersiveAudioOverlay = None
    177         self._caption_label = None
    178         attributes.windowID = attributes.panelID or self.panelID
    179         attributes.parent = attributes.parent or uicore.layer.main
    180         self.performFitsCheck = attributes.get('performFitsCheck', self.default_performFitsCheck)
    181         super(DockablePanel, self).ApplyAttributes(attributes)
    182         GetDockPanelManager()
    183         self.content.state = uiconst.UI_NORMAL
    184         self.CreateToolbar()
    185         self.panelID = attributes.panelID or self.panelID
    186         uicore.dockablePanelManager.RegisterPanel(self)
    187         self.ConstructSidePanel()
    188         if self.hasImmersiveAudioOverlay:
    189             self._CreateImmersiveAudioOverlay()
    190 
    191     @property
    192     def content_padding(self):
    193         return (2, 2, 2, 2)
    194 
    195     def GetWindowBorderPadding(self):
    196         if self.align == Align.TOALL:
    197             return (0, 0, 0, 0)
    198         else:
    199             return super(DockablePanel, self).GetWindowBorderPadding()
    200 
    201     def ConstructSidePanel(self):
    202         self.sidePanel = Container(parent=self.GetMainArea(), name='sidePanel', align=uiconst.TOLEFT_NOPUSH, width=infoPanelUIConst.PANELWIDTH, padding=(0, 30, 0, 0), idx=0)
    203         self.infoPanelContainer = InfoPanelContainer(parent=self.sidePanel, align=uiconst.TOTOP, settingsID='%s_dockablePanel' % self.windowID, performFitsCheck=self.performFitsCheck)
    204 
    205     def OnViewStateChanged(self, from_view, to_view):
    206         if not self.IsFullscreen():
    207             return
    208         fullScreenView = self.GetFullscreenViewStateName()
    209         viewStateSvc = sm.GetService('viewState')
    210         if fullScreenView and from_view == fullScreenView and viewStateSvc.GetViewInfo(to_view).IsSecondary():
    211             self.Minimize()
    212 
    213     def GetFullscreenViewStateName(self):
    214         return self.viewState
    215 
    216     def SetOrder(self, idx):
    217         if not self.IsFloating():
    218             return
    219         return Window.SetOrder(self, idx)
    220 
    221     def InitializeStatesAndPosition(self, *args, **kw):
    222         self.InitDockPanelPosition()
    223         self._SetOpen(True)
    224         if self.IsMinimized():
    225             self._SetMaximized()
    226 
    227     def GetRegisteredPushedByIndex(self):
    228         currentSettings = self.GetPanelSettings()
    229         pushedBy = currentSettings['pushedBy']
    230         if pushedBy:
    231             for objectName in reversed(pushedBy):
    232                 for obj in uicore.layer.sidePanels.children:
    233                     if obj.name == objectName:
    234                         index = uicore.layer.sidePanels.children.index(obj) + 1
    235                         return index
    236 
    237         return 0
    238 
    239     def GetFullScreenViewsOrdered(self):
    240         return [ panel for panel in uicore.layer.sidePanels.children if isinstance(panel, DockablePanel) and panel.IsFullscreen() ]
    241 
    242     def InitDockPanelPosition(self):
    243         currentSettings = self.GetPanelSettings()
    244         if currentSettings['align'] == uiconst.TOPLEFT:
    245             self.UnDock()
    246         elif currentSettings['align'] == uiconst.TOLEFT:
    247             self.DockOnLeft()
    248         elif currentSettings['align'] == uiconst.TORIGHT:
    249             self.DockOnRight()
    250         elif currentSettings['align'] == uiconst.TOALL:
    251             self.DockFullscreen()
    252         self.state = uiconst.UI_NORMAL
    253 
    254     def GetAbsolutePosition(self):
    255         stack = self.GetStack()
    256         if stack:
    257             return stack.contentCont.GetAbsolutePosition()
    258         return Base.GetAbsolutePosition(self)
    259 
    260     def InitializeSize(self, *args, **kwds):
    261         pass
    262 
    263     def RegisterPositionAndSize(self, *args, **kwds):
    264         pass
    265 
    266     def ToggleCollapse(self, *args, **kwds):
    267         pass
    268 
    269     def ToggleMinimize(self):
    270         if not self.IsFullscreen():
    271             return Window.ToggleMinimize(self)
    272         if self.IsMinimizable():
    273             if self.IsMinimized():
    274                 self.Maximize()
    275             else:
    276                 self.Minimize()
    277 
    278     def Minimize(self, animate = True):
    279         if self.GetFullscreenViewStateName() and self.IsFullscreen():
    280             uthread.new(sm.GetService('viewState').CloseSecondaryView, self.GetFullscreenViewStateName())
    281         if self._immersiveAudioOverlay:
    282             self._immersiveAudioOverlay.set_minimized()
    283         super(DockablePanel, self).Minimize(animate)
    284 
    285     def Collapse(self, *args, **kwds):
    286         pass
    287 
    288     def Prepare_Header_(self):
    289         if self.sr.headerParent:
    290             self.sr.headerParent.Close()
    291         self.sr.headerParent = Container(parent=self.sr.maincontainer, name='__headerParent', state=uiconst.UI_PICKCHILDREN, align=uiconst.TOTOP_NOPUSH, clipChildren=True, pos=(0,
    292          0,
    293          0,
    294          TOOLBARHEIGHT + 2), padding=2, idx=0)
    295         self.headerParent = self.sr.headerParent
    296 
    297     def _is_side_resizable(self, side):
    298         if self.IsFullscreen():
    299             return False
    300         elif self.IsDockedLeft() and side != Side.RIGHT:
    301             return False
    302         elif self.IsDockedRight() and side != Side.LEFT:
    303             return False
    304         else:
    305             return super(DockablePanel, self)._is_side_resizable(side)
    306 
    307     @classmethod
    308     def GetIfOpen(cls, *args, **kwds):
    309         dockablePanelManager = GetDockPanelManager()
    310         return dockablePanelManager.GetIfOpen(cls.panelID)
    311 
    312     @classmethod
    313     def CloseIfOpen(cls, *args, **kwds):
    314         dockablePanelManager = GetDockPanelManager()
    315         panel = dockablePanelManager.GetIfOpen(cls.panelID)
    316         if panel:
    317             panel.CloseByUser()
    318             return True
    319         return False
    320 
    321     @classmethod
    322     def Open(cls, *args, **kwds):
    323         dockablePanelManager = GetDockPanelManager()
    324         panel = dockablePanelManager.GetIfOpen(cls.panelID)
    325         if panel:
    326             return panel
    327         return cls(*args, **kwds)
    328 
    329     def CreateToolbar(self):
    330         self.toolbarContainer = Container(parent=self.headerParent, name='toolbarContainer', align=uiconst.CENTERTOP, width=TOOLBARWIDTH_FULLSCREEN, height=TOOLBARHEIGHT, state=uiconst.UI_NORMAL)
    331         self.toolbarContainer.DelegateEvents(self)
    332         self._caption_label = EveLabelMediumBold(parent=self.toolbarContainer, align=uiconst.CENTER, text=self.caption)
    333         FillThemeColored(bgParent=self.toolbarContainer, opacity=0.9, padding=(1, 1, 1, 0))
    334         grid = LayoutGrid(parent=self.toolbarContainer, columns=5, cellPadding=2, align=uiconst.CENTERRIGHT, left=6)
    335         self.menuButton = MenuButtonIcon(name='DockPanel_MenuButton', parent=grid, texturePath=eveicon.more_vertical, get_menu_func=self.GetMenuMoreOptions, height=16, width=16)
    336         self.menuButton.display = False
    337         if len(self.dock_options) > 1:
    338             self.dockViewModeButton = DockablePanelHeaderButtonMenu(name='DockPanel_HeaderButton_ChangeViewMode', parent=grid, dockViewModeCallback=self.ChangeViewMode, panelID=self.panelID, dock_options=self.dock_options)
    339         else:
    340             self.dockViewModeButton = None
    341         if self.minimizable:
    342             DockablePanelHeaderButton(name='DockPanel_HeaderButton_Minimize', parent=grid, hint=localization.GetByLabel('/Carbon/UI/Controls/Window/Minimize'), texturePath='res:/UI/Texture/classes/DockPanel/minimizeButton.png', func=self.Minimize)
    343         DockablePanelHeaderButton(name='DockPanel_HeaderButton_Close', parent=grid, hint=localization.GetByLabel('UI/Generic/Close'), texturePath='res:/UI/Texture/classes/DockPanel/closeButton.png', func=self.CloseByUser)
    344         StretchSpriteHorizontalThemeColored(texturePath='res:/UI/Texture/classes/MapView/toolbarLine.png', colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=uiconst.OPACITY_FRAME, leftEdgeSize=64, rightEdgeSize=64, parent=self.toolbarContainer, align=uiconst.TOBOTTOM_NOPUSH, padding=(-48, 0, -48, -15), height=64)
    345         StretchSpriteHorizontalThemeColored(texturePath='res:/UI/Texture/classes/MapView/toolbarFill.png', colorType=uiconst.COLORTYPE_UIBASE, leftEdgeSize=64, rightEdgeSize=64, parent=self.toolbarContainer, align=uiconst.TOBOTTOM_NOPUSH, padding=(-48, 0, -48, -15), height=64)
    346 
    347     def Close(self, setClosed = False, *args, **kwds):
    348         if self.destroyed:
    349             return
    350         Window.Close(self, setClosed=setClosed, *args)
    351         if self.snapIndicator and not self.snapIndicator.destroyed:
    352             self.snapIndicator.Close()
    353             self.snapIndicator = None
    354         uicore.dockablePanelManager.UnregisterPanel(self)
    355         if self.GetFullscreenViewStateName() and self.IsFullscreen():
    356             sm.GetService('viewState').CloseSecondaryView(self.GetFullscreenViewStateName())
    357         if self._immersiveAudioOverlay:
    358             self._immersiveAudioOverlay.close()
    359             self._immersiveAudioOverlay = None
    360 
    361     def ChangeViewMode(self, buttonID, *args, **kwds):
    362         if buttonID == BUTTON_FULLSCREEN:
    363             if not self.IsFullscreen():
    364                 self.DockFullscreen()
    365         elif buttonID == BUTTON_DOCKLEFT:
    366             self.DockOnLeft()
    367         elif buttonID == BUTTON_DOCKRIGHT:
    368             self.DockOnRight()
    369         elif self.is_undockable():
    370             self.UnDock()
    371         self.sr.underlay.padding = self.GetWindowBorderPadding()
    372 
    373     def is_undockable(self):
    374         return BUTTON_FLOAT in self.dock_options
    375 
    376     def _OnCaptionChanged(self):
    377         if self._caption_label:
    378             self._caption_label.text = self.caption
    379 
    380     def IsFullscreen(self):
    381         return self.align == uiconst.TOALL
    382 
    383     def IsDockedLeft(self):
    384         return self.align == uiconst.TOLEFT
    385 
    386     def IsDockedRight(self):
    387         return self.align == uiconst.TORIGHT
    388 
    389     def IsFloating(self):
    390         return self.align == uiconst.TOPLEFT
    391 
    392     @apply
    393     def displayRect():
    394         fget = Container.displayRect.fget
    395 
    396         def fset(self, value):
    397             Container.displayRect.fset(self, value)
    398             uicore.dockablePanelManager.UpdateCameraCenter()
    399 
    400         return property(**locals())
    401 
    402     def GetPanelSettings(self):
    403         return GetPanelSettings(self.panelID)
    404 
    405     def RegisterPanelSettings(self):
    406         if not self.panelID:
    407             return
    408         current = self.GetPanelSettings()
    409         current['align'] = self.align
    410         if not self.IsFullscreen():
    411             current['dblToggleFullScreenAlign'] = self.align
    412         if self.IsFloating():
    413             current['positionX'] = (self.left + self.width / 2) / float(uicore.desktop.width)
    414             current['positionY'] = (self.top + self.height / 2) / float(uicore.desktop.height)
    415             current['heightProportion'] = self.height / float(uicore.desktop.height)
    416             current['widthProportion'] = self.width / float(uicore.desktop.width)
    417         elif self.align in (uiconst.TOLEFT, uiconst.TORIGHT):
    418             current['widthProportion_docked'] = min(0.5, self.width / float(uicore.desktop.width))
    419             current['heightProportion_docked'] = 1.0
    420         RegisterPanelSettings(self.panelID, current)
    421         if self.align in (uiconst.TOLEFT, uiconst.TORIGHT):
    422             uicore.dockablePanelManager.UpdatePanelsPushedBySettings()
    423         if self.dockViewModeButton:
    424             self.dockViewModeButton.UpdateButtonState()
    425 
    426     def CloseByUser(self, *args):
    427         if not self.destroyed and self.IsClosable():
    428             self._LogWindowClosed()
    429             PlaySound(uiconst.SOUND_CLOSE)
    430             uthread.new(self.Close, True)
    431 
    432     def _StartScale(self, sides):
    433         if self.align == uiconst.TOALL:
    434             return
    435         self.scaleData = (self.left,
    436          self.top,
    437          self.width,
    438          self.height)
    439         if 'left' in sides and self.align != uiconst.TOLEFT:
    440             modX = -1
    441         elif 'right' in sides and self.align != uiconst.TORIGHT:
    442             modX = 1
    443         else:
    444             modX = 0
    445         if 'top' in sides:
    446             modY = -1
    447         elif 'bottom' in sides:
    448             modY = 1
    449         else:
    450             modY = 0
    451         if not (modX or modY):
    452             return
    453         self.scaleModifiers = (modX, modY)
    454         self._scaling = True
    455         uthread.new(self.OnScale, uicore.uilib.x, uicore.uilib.y)
    456 
    457     def OnDblClick(self, *args, **kwds):
    458         if self.IsFullscreen():
    459             currentSettings = self.GetPanelSettings()
    460             currentSettings['align'] = currentSettings['dblToggleFullScreenAlign']
    461             RegisterPanelSettings(self.panelID, currentSettings)
    462             self.InitDockPanelPosition()
    463         else:
    464             self.DockFullscreen()
    465 
    466     def OnScale(self, initMouseX, initMouseY):
    467         while self._scaling and uicore.uilib.leftbtn and not self.destroyed:
    468             mouseX, mouseY = uicore.uilib.x, uicore.uilib.y
    469             dX = mouseX - initMouseX
    470             dY = mouseY - initMouseY
    471             l, t, w, h = self.scaleData
    472             widthMod, heightMod = self.scaleModifiers
    473             if widthMod:
    474                 newWidth = max(self.GetMinWidth(), w - dX * -widthMod)
    475                 if self.align in (uiconst.TOLEFT, uiconst.TORIGHT):
    476                     newWidth = min(uicore.desktop.width / 2, newWidth)
    477                 self.width = newWidth
    478                 if widthMod < 0 and self.IsFloating():
    479                     self.left = l + w - newWidth
    480             if heightMod and self.IsFloating():
    481                 newHeight = max(self.GetMinHeight(), h - dY * -heightMod)
    482                 self.height = newHeight
    483                 if heightMod < 0:
    484                     self.top = t + h - newHeight
    485             self.RegisterPanelSettings()
    486             blue.pyos.synchro.SleepWallclock(1)
    487 
    488         self.ValidateWindows()
    489         self.RegisterPanelSettings()
    490 
    491     def OnScaleMove(self, dX, dY, scaleModifiers):
    492         if self.align == uiconst.TOALL:
    493             return
    494         l, t, w, h = self.scaleData
    495         widthMod, heightMod = self.scaleModifiers
    496         if widthMod:
    497             newWidth = max(self.GetMinWidth(), w - dX * -widthMod)
    498             self.width = newWidth
    499             if widthMod < 0 and self.IsFloating():
    500                 self.left = l + w - newWidth
    501         if heightMod and self.IsFloating():
    502             newHeight = max(self.GetMinHeight(), h - dY * -heightMod)
    503             self.height = newHeight
    504             if heightMod < 0:
    505                 self.top = t + h - newHeight
    506         self.RegisterPanelSettings()
    507 
    508     def DockFullscreen(self, *args):
    509         if self.destroyed:
    510             return
    511         fullScreenViews = self.GetFullScreenViewsOrdered()
    512         self.SetParent(uicore.layer.sidePanels)
    513         self.toolbarContainer.align = uiconst.CENTERTOP
    514         self.align = uiconst.TOALL
    515         self.width = 0
    516         self.height = 0
    517         self.left = 0
    518         self.top = 0
    519         self.FinalizeModeChange()
    520         for each in fullScreenViews:
    521             each.SetParent(uicore.layer.sidePanels, -1)
    522 
    523         self._CheckActivateFullScreenViewState()
    524 
    525     def _CheckActivateFullScreenViewState(self):
    526         currViewName = sm.GetService('viewState').GetActiveViewName()
    527         fullScreenViewName = self.GetFullscreenViewStateName()
    528         if fullScreenViewName and currViewName != fullScreenViewName:
    529             self.ActivateViewStateView()
    530 
    531     def DockOnRight(self, index = None, *args):
    532         if index is None:
    533             index = self.GetRegisteredPushedByIndex()
    534         self._DockOnSide(align=uiconst.TORIGHT, index=index)
    535 
    536     def DockOnLeft(self, index = None, *args):
    537         if index is None:
    538             index = self.GetRegisteredPushedByIndex()
    539         self._DockOnSide(align=uiconst.TOLEFT, index=index)
    540 
    541     def _DockOnSide(self, align, index = 0):
    542         current = self.GetPanelSettings()
    543         self.SetParent(uicore.layer.sidePanels, idx=index)
    544         self.toolbarContainer.align = uiconst.TOTOP_NOPUSH
    545         self.align = align
    546         minWidth = min(self.GetMinWidth(), uicore.desktop.width / 2.0)
    547         width = int(current['widthProportion_docked'] * uicore.desktop.width)
    548         self.width = max(minWidth, min(width, uicore.desktop.width / 2.0))
    549         self.height = 0
    550         self.left = 0
    551         self.top = 0
    552         self.FinalizeModeChange()
    553         if self.GetFullscreenViewStateName():
    554             self.CloseViewStateView()
    555 
    556     def UnDock(self, setPosition = True, registerSettings = True, *args):
    557         if not self.is_undockable():
    558             return
    559         if self.destroyed:
    560             return
    561         current = self.GetPanelSettings()
    562         if self.parent != uicore.layer.main:
    563             self.SetParent(uicore.layer.main, idx=0)
    564         self.toolbarContainer.align = uiconst.TOTOP_NOPUSH
    565         self.toolbarContainer.SetParent(self.headerParent, idx=0)
    566         self.align = uiconst.TOPLEFT
    567         self.width = max(self.GetMinWidth(), int(current['widthProportion'] * uicore.desktop.width))
    568         self.height = max(self.GetMinHeight(), int(current['heightProportion'] * uicore.desktop.height))
    569         if setPosition:
    570             left = int(uicore.desktop.width * current['positionX']) - self.width / 2
    571             self.left = clamp(left, 0, uicore.desktop.width - self.WINDOW_NEVER_OFFSCREEN_BUFFER)
    572             top = int(uicore.desktop.height * current['positionY']) - self.height / 2
    573             self.top = clamp(top, 0, uicore.desktop.height - self.WINDOW_NEVER_OFFSCREEN_BUFFER)
    574         self.FinalizeModeChange(registerSettings)
    575         if self.GetFullscreenViewStateName():
    576             self.CloseViewStateView()
    577 
    578     def ActivateViewStateView(self):
    579         self.infoPanelContainer.Hide()
    580         uthread.new(sm.GetService('viewState').ActivateView, self.GetFullscreenViewStateName())
    581 
    582     def CloseViewStateView(self):
    583         self.infoPanelContainer.Show()
    584         sm.GetService('viewState').CloseSecondaryView(self.GetFullscreenViewStateName())
    585 
    586     def FinalizeModeChange(self, registerSettings = True):
    587         if registerSettings:
    588             self.RegisterPanelSettings()
    589         uthread.new(uicore.dockablePanelManager.CheckViewState)
    590         if self._immersiveAudioOverlay:
    591             if self.IsFullscreen():
    592                 self._immersiveAudioOverlay.set_full_screen()
    593             elif self.IsMinimized():
    594                 self._immersiveAudioOverlay.set_windowed()
    595             else:
    596                 self._immersiveAudioOverlay.set_windowed()
    597         self.Prepare_ScaleAreas_()
    598         self.OnDockModeChanged(self)
    599 
    600     def OnDockModeChanged(self, *args, **kwds):
    601         pass
    602 
    603     def DeactivateUnderlay(self):
    604         if self.IsFullscreen():
    605             return
    606         super(DockablePanel, self).DeactivateUnderlay()
    607 
    608     def OnDragTick(self, *args, **kwds):
    609 
    610         def FindSnapPosition(mousePosition, snapPositions):
    611             for snapPosition, index in snapPositions:
    612                 if snapPosition - 2 < mousePosition < snapPosition + 2:
    613                     return (snapPosition, index)
    614 
    615         snapToDockMode = self.snapToDockMode
    616         xLeftPositions, xRightPositions = self.snapPositions
    617         snapLeft = FindSnapPosition(uicore.uilib.x, xLeftPositions)
    618         snapRight = FindSnapPosition(uicore.uilib.x, xRightPositions)
    619         if snapLeft and BUTTON_DOCKLEFT in self.dock_options:
    620             self.snapToDockMode = (BUTTON_DOCKLEFT, snapLeft)
    621         elif snapRight and BUTTON_DOCKRIGHT in self.dock_options:
    622             self.snapToDockMode = (BUTTON_DOCKRIGHT, snapRight)
    623         elif uicore.uilib.y == 0:
    624             self.snapToDockMode = (BUTTON_FULLSCREEN, (0, -1))
    625         elif self.snapToDockMode:
    626             dockMode, (snapPosition, index) = self.snapToDockMode
    627             if dockMode == BUTTON_FULLSCREEN and uicore.uilib.y > SNAP_RELEASE_THRESHOLD:
    628                 self.snapToDockMode = None
    629             elif dockMode in (BUTTON_DOCKLEFT, BUTTON_DOCKRIGHT):
    630                 if not snapPosition - SNAP_RELEASE_THRESHOLD < uicore.uilib.x < snapPosition + SNAP_RELEASE_THRESHOLD:
    631                     self.snapToDockMode = None
    632         if snapToDockMode != self.snapToDockMode:
    633             self.UpdateSnapIndicator(xLeftPositions, xRightPositions)
    634 
    635     def UpdateSnapIndicator(self, xLeftPositions, xRightPositions):
    636         if self.snapToDockMode:
    637             dockMode, (position, index) = self.snapToDockMode
    638             if not self.snapIndicator:
    639                 self.snapIndicator = DockPanelSnapIndicator(parent=uicore.desktop, idx=0)
    640             if dockMode == BUTTON_FULLSCREEN:
    641                 self.snapIndicator.align = uiconst.TOPLEFT
    642                 self.snapIndicator.left = max([ snapPosition for snapPosition, index in xLeftPositions ])
    643                 self.snapIndicator.top = 0
    644                 self.snapIndicator.width = min([ snapPosition for snapPosition, index in xRightPositions ]) - self.snapIndicator.left
    645                 self.snapIndicator.height = SNAP_INDICATOR_SIZE
    646                 self.snapIndicator.Pulse()
    647                 return
    648             if dockMode == BUTTON_DOCKLEFT:
    649                 onBoundary = xLeftPositions.index((position, index)) == 0
    650                 self.snapIndicator.align = uiconst.TOLEFT_NOPUSH
    651                 if onBoundary:
    652                     self.snapIndicator.left = position
    653                 else:
    654                     self.snapIndicator.left = position - SNAP_INDICATOR_SIZE
    655                 self.snapIndicator.top = 0
    656                 self.snapIndicator.width = SNAP_INDICATOR_SIZE
    657                 self.snapIndicator.height = 0
    658                 self.snapIndicator.Pulse()
    659                 return
    660             if dockMode == BUTTON_DOCKRIGHT:
    661                 onBoundary = xRightPositions.index((position, index)) == 0
    662                 self.snapIndicator.align = uiconst.TOLEFT_NOPUSH
    663                 if onBoundary:
    664                     self.snapIndicator.left = position - SNAP_INDICATOR_SIZE
    665                 else:
    666                     self.snapIndicator.left = position - SNAP_INDICATOR_SIZE
    667                 self.snapIndicator.top = 0
    668                 self.snapIndicator.width = SNAP_INDICATOR_SIZE
    669                 self.snapIndicator.height = 0
    670                 self.snapIndicator.Pulse()
    671                 return
    672         if self.snapIndicator:
    673             self.snapIndicator.Rest(close=True)
    674             self.snapIndicator = None
    675 
    676     def _BeginDrag(self, *args, **kwds):
    677         if not self.is_undockable():
    678             return
    679         if not self.IsFloating():
    680             initMouseX, initMouseY = self.dragMousePosition
    681             left, top, width, height = self.toolbarContainer.GetAbsolute()
    682             if self.align == uiconst.TOALL:
    683                 propLeft = initMouseX / float(uicore.desktop.width)
    684             else:
    685                 propLeft = (initMouseX - left) / float(width)
    686             self.UnDock(setPosition=False, registerSettings=False)
    687             self.left = uicore.uilib.x - int(self.width * propLeft)
    688             self.top = 0
    689         self.snapPositions = self.GetSnapPositions()
    690         Window._BeginDrag(self)
    691         self.state = uiconst.UI_NORMAL
    692 
    693     def AdjustWindowPositionToFitScreen(self):
    694         if not self.IsFloating():
    695             return
    696         super(DockablePanel, self).AdjustWindowPositionToFitScreen()
    697 
    698     def OnMouseUp(self, *args):
    699         Window.OnMouseUp(self, *args)
    700         if self.snapToDockMode:
    701             dockMode, (position, index) = self.snapToDockMode
    702             if dockMode == BUTTON_FULLSCREEN:
    703                 self.DockFullscreen()
    704             elif dockMode == BUTTON_DOCKLEFT:
    705                 self.DockOnLeft(index=index)
    706             elif dockMode == BUTTON_DOCKRIGHT:
    707                 self.DockOnRight(index=index)
    708         self.snapToDockMode = None
    709         if self.snapIndicator:
    710             self.snapIndicator.Close()
    711             self.snapIndicator = None
    712         self.RegisterPanelSettings()
    713 
    714     def GetSnapPositions(self):
    715         xPositionLeft = []
    716         xPositionRight = []
    717         l, t, w, h = uicore.layer.sidePanels.GetAbsolute()
    718         xPositionLeft.append((l, 0))
    719         xPositionRight.append((l + w - 1, 0))
    720         for index, each in enumerate(uicore.layer.sidePanels.children):
    721             if each.align == uiconst.TOLEFT:
    722                 l, t, w, h = each.GetAbsolute()
    723                 xPositionLeft.append((l, index))
    724                 xPositionLeft.append((l + w, index + 1))
    725             elif each.align == uiconst.TORIGHT:
    726                 l, t, w, h = each.GetAbsolute()
    727                 xPositionRight.append((l, index + 1))
    728                 xPositionRight.append((l + w, index))
    729 
    730         return (xPositionLeft, xPositionRight)
    731 
    732     def IsClosable(self):
    733         return True
    734 
    735     def _CreateImmersiveAudioOverlay(self):
    736         if not self.hasImmersiveAudioOverlay:
    737             return
    738         self._immersiveAudioOverlay = sm.GetService('audio').CreateImmersiveOverlay(self.windowID)
    739 
    740     @EatsExceptions('protoClientLogs')
    741     def _LogWindowEventOpened(self):
    742         if self.analyticID:
    743             message_bus = WindowMessenger(sm.GetService('publicGatewaySvc'))
    744             currentSettings = self.GetPanelSettings()
    745             message_bus.window_opened(self.analyticID, DOCKING_MODES_CONVERSION[currentSettings.get('align', uiconst.TOPLEFT)])
    746 
    747     def UpdateUIScaling(self, *args):
    748         super(DockablePanel, self).UpdateUIScaling(*args)
    749         if not self.minimized and not self.IsFullscreen():
    750             self.InitDockPanelPosition()
    751 
    752 
    753 class DockPanelSnapIndicator(Container):
    754 
    755     def ApplyAttributes(self, attributes):
    756         Container.ApplyAttributes(self, attributes)
    757         self.fill = FillThemeColored(parent=self, colorType=uiconst.COLORTYPE_UIHILIGHTGLOW)
    758 
    759     def Pulse(self):
    760         uicore.animations.MorphScalar(self.fill, 'opacity', startVal=0.0, endVal=1.0, curveType=uiconst.ANIM_OVERSHOT, duration=0.1, callback=self.Rest)
    761 
    762     def Rest(self, close = False):
    763         if close:
    764             uicore.animations.MorphScalar(self.fill, 'opacity', startVal=self.fill.opacity, endVal=0.0, duration=0.3, callback=self.Close)
    765         else:
    766             uicore.animations.MorphScalar(self.fill, 'opacity', startVal=self.fill.opacity, endVal=0.3, duration=0.5)