summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/guilib
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xbmc/guilib/CMakeLists.txt233
-rw-r--r--xbmc/guilib/D3DResource.cpp1244
-rw-r--r--xbmc/guilib/D3DResource.h293
-rw-r--r--xbmc/guilib/DDSImage.cpp148
-rw-r--r--xbmc/guilib/DDSImage.h102
-rw-r--r--xbmc/guilib/DirectXGraphics.cpp374
-rw-r--r--xbmc/guilib/DirectXGraphics.h159
-rw-r--r--xbmc/guilib/DirtyRegion.h27
-rw-r--r--xbmc/guilib/DirtyRegionSolvers.cpp73
-rw-r--r--xbmc/guilib/DirtyRegionSolvers.h39
-rw-r--r--xbmc/guilib/DirtyRegionTracker.cpp88
-rw-r--r--xbmc/guilib/DirtyRegionTracker.h35
-rw-r--r--xbmc/guilib/DispResource.h32
-rw-r--r--xbmc/guilib/FFmpegImage.cpp754
-rw-r--r--xbmc/guilib/FFmpegImage.h95
-rw-r--r--xbmc/guilib/GUIAction.cpp153
-rw-r--r--xbmc/guilib/GUIAction.h119
-rw-r--r--xbmc/guilib/GUIAudioManager.cpp399
-rw-r--r--xbmc/guilib/GUIAudioManager.h89
-rw-r--r--xbmc/guilib/GUIBaseContainer.cpp1510
-rw-r--r--xbmc/guilib/GUIBaseContainer.h238
-rw-r--r--xbmc/guilib/GUIBorderedImage.cpp86
-rw-r--r--xbmc/guilib/GUIBorderedImage.h37
-rw-r--r--xbmc/guilib/GUIButtonControl.cpp409
-rw-r--r--xbmc/guilib/GUIButtonControl.dox85
-rw-r--r--xbmc/guilib/GUIButtonControl.h112
-rw-r--r--xbmc/guilib/GUIColorButtonControl.cpp217
-rw-r--r--xbmc/guilib/GUIColorButtonControl.dox90
-rw-r--r--xbmc/guilib/GUIColorButtonControl.h71
-rw-r--r--xbmc/guilib/GUIColorManager.cpp144
-rw-r--r--xbmc/guilib/GUIColorManager.h56
-rw-r--r--xbmc/guilib/GUIComponent.cpp103
-rw-r--r--xbmc/guilib/GUIComponent.h49
-rw-r--r--xbmc/guilib/GUIControl.cpp979
-rw-r--r--xbmc/guilib/GUIControl.h383
-rw-r--r--xbmc/guilib/GUIControlFactory.cpp1588
-rw-r--r--xbmc/guilib/GUIControlFactory.h160
-rw-r--r--xbmc/guilib/GUIControlGroup.cpp537
-rw-r--r--xbmc/guilib/GUIControlGroup.dox108
-rw-r--r--xbmc/guilib/GUIControlGroup.h124
-rw-r--r--xbmc/guilib/GUIControlGroupList.cpp601
-rw-r--r--xbmc/guilib/GUIControlGroupList.h84
-rw-r--r--xbmc/guilib/GUIControlLookup.cpp113
-rw-r--r--xbmc/guilib/GUIControlLookup.h47
-rw-r--r--xbmc/guilib/GUIControlProfiler.cpp337
-rw-r--r--xbmc/guilib/GUIControlProfiler.h88
-rw-r--r--xbmc/guilib/GUIDialog.cpp239
-rw-r--r--xbmc/guilib/GUIDialog.h83
-rw-r--r--xbmc/guilib/GUIEditControl.cpp743
-rw-r--r--xbmc/guilib/GUIEditControl.dox60
-rw-r--r--xbmc/guilib/GUIEditControl.h131
-rw-r--r--xbmc/guilib/GUIFadeLabelControl.cpp267
-rw-r--r--xbmc/guilib/GUIFadeLabelControl.dox72
-rw-r--r--xbmc/guilib/GUIFadeLabelControl.h78
-rw-r--r--xbmc/guilib/GUIFixedListContainer.cpp308
-rw-r--r--xbmc/guilib/GUIFixedListContainer.dox142
-rw-r--r--xbmc/guilib/GUIFixedListContainer.h60
-rw-r--r--xbmc/guilib/GUIFont.cpp353
-rw-r--r--xbmc/guilib/GUIFont.h183
-rw-r--r--xbmc/guilib/GUIFontCache.cpp260
-rw-r--r--xbmc/guilib/GUIFontCache.h286
-rw-r--r--xbmc/guilib/GUIFontManager.cpp671
-rw-r--r--xbmc/guilib/GUIFontManager.h140
-rw-r--r--xbmc/guilib/GUIFontTTF.cpp1166
-rw-r--r--xbmc/guilib/GUIFontTTF.h279
-rw-r--r--xbmc/guilib/GUIFontTTFDX.cpp372
-rw-r--r--xbmc/guilib/GUIFontTTFDX.h67
-rw-r--r--xbmc/guilib/GUIFontTTFGL.cpp494
-rw-r--r--xbmc/guilib/GUIFontTTFGL.h58
-rw-r--r--xbmc/guilib/GUIImage.cpp417
-rw-r--r--xbmc/guilib/GUIImage.dox98
-rw-r--r--xbmc/guilib/GUIImage.h119
-rw-r--r--xbmc/guilib/GUIIncludes.cpp703
-rw-r--r--xbmc/guilib/GUIIncludes.h133
-rw-r--r--xbmc/guilib/GUIKeyboard.h84
-rw-r--r--xbmc/guilib/GUIKeyboardFactory.cpp239
-rw-r--r--xbmc/guilib/GUIKeyboardFactory.h50
-rw-r--r--xbmc/guilib/GUILabel.cpp250
-rw-r--r--xbmc/guilib/GUILabel.h240
-rw-r--r--xbmc/guilib/GUILabelControl.cpp293
-rw-r--r--xbmc/guilib/GUILabelControl.dox112
-rw-r--r--xbmc/guilib/GUILabelControl.h80
-rw-r--r--xbmc/guilib/GUIListContainer.cpp299
-rw-r--r--xbmc/guilib/GUIListContainer.dox138
-rw-r--r--xbmc/guilib/GUIListContainer.h55
-rw-r--r--xbmc/guilib/GUIListGroup.cpp238
-rw-r--r--xbmc/guilib/GUIListGroup.dox67
-rw-r--r--xbmc/guilib/GUIListGroup.h50
-rw-r--r--xbmc/guilib/GUIListItem.cpp441
-rw-r--r--xbmc/guilib/GUIListItem.h201
-rw-r--r--xbmc/guilib/GUIListItemLayout.cpp245
-rw-r--r--xbmc/guilib/GUIListItemLayout.h68
-rw-r--r--xbmc/guilib/GUIListLabel.cpp106
-rw-r--r--xbmc/guilib/GUIListLabel.h57
-rw-r--r--xbmc/guilib/GUIMessage.cpp146
-rw-r--r--xbmc/guilib/GUIMessage.h407
-rw-r--r--xbmc/guilib/GUIMoverControl.cpp254
-rw-r--r--xbmc/guilib/GUIMoverControl.dox92
-rw-r--r--xbmc/guilib/GUIMoverControl.h78
-rw-r--r--xbmc/guilib/GUIMultiImage.cpp323
-rw-r--r--xbmc/guilib/GUIMultiImage.dox78
-rw-r--r--xbmc/guilib/GUIMultiImage.h89
-rw-r--r--xbmc/guilib/GUIPanelContainer.cpp567
-rw-r--r--xbmc/guilib/GUIPanelContainer.dox126
-rw-r--r--xbmc/guilib/GUIPanelContainer.h62
-rw-r--r--xbmc/guilib/GUIProgressControl.cpp348
-rw-r--r--xbmc/guilib/GUIProgressControl.dox62
-rw-r--r--xbmc/guilib/GUIProgressControl.h71
-rw-r--r--xbmc/guilib/GUIRSSControl.cpp196
-rw-r--r--xbmc/guilib/GUIRSSControl.dox106
-rw-r--r--xbmc/guilib/GUIRSSControl.h72
-rw-r--r--xbmc/guilib/GUIRadioButtonControl.cpp260
-rw-r--r--xbmc/guilib/GUIRadioButtonControl.dox102
-rw-r--r--xbmc/guilib/GUIRadioButtonControl.h68
-rw-r--r--xbmc/guilib/GUIRangesControl.cpp414
-rw-r--r--xbmc/guilib/GUIRangesControl.dox56
-rw-r--r--xbmc/guilib/GUIRangesControl.h91
-rw-r--r--xbmc/guilib/GUIRenderingControl.cpp117
-rw-r--r--xbmc/guilib/GUIRenderingControl.dox34
-rw-r--r--xbmc/guilib/GUIRenderingControl.h33
-rw-r--r--xbmc/guilib/GUIResizeControl.cpp227
-rw-r--r--xbmc/guilib/GUIResizeControl.dox93
-rw-r--r--xbmc/guilib/GUIResizeControl.h70
-rw-r--r--xbmc/guilib/GUIScrollBarControl.cpp397
-rw-r--r--xbmc/guilib/GUIScrollBarControl.dox79
-rw-r--r--xbmc/guilib/GUIScrollBarControl.h72
-rw-r--r--xbmc/guilib/GUISettingsSliderControl.cpp169
-rw-r--r--xbmc/guilib/GUISettingsSliderControl.dox80
-rw-r--r--xbmc/guilib/GUISettingsSliderControl.h66
-rw-r--r--xbmc/guilib/GUIShaderDX.cpp423
-rw-r--r--xbmc/guilib/GUIShaderDX.h142
-rw-r--r--xbmc/guilib/GUISliderControl.cpp773
-rw-r--r--xbmc/guilib/GUISliderControl.dox71
-rw-r--r--xbmc/guilib/GUISliderControl.h129
-rw-r--r--xbmc/guilib/GUISpinControl.cpp1078
-rw-r--r--xbmc/guilib/GUISpinControl.dox83
-rw-r--r--xbmc/guilib/GUISpinControl.h133
-rw-r--r--xbmc/guilib/GUISpinControlEx.cpp161
-rw-r--r--xbmc/guilib/GUISpinControlEx.dox96
-rw-r--r--xbmc/guilib/GUISpinControlEx.h57
-rw-r--r--xbmc/guilib/GUIStaticItem.cpp121
-rw-r--r--xbmc/guilib/GUIStaticItem.h91
-rw-r--r--xbmc/guilib/GUITextBox.cpp451
-rw-r--r--xbmc/guilib/GUITextBox.dox63
-rw-r--r--xbmc/guilib/GUITextBox.h97
-rw-r--r--xbmc/guilib/GUITextLayout.cpp736
-rw-r--r--xbmc/guilib/GUITextLayout.h195
-rw-r--r--xbmc/guilib/GUITexture.cpp714
-rw-r--r--xbmc/guilib/GUITexture.h207
-rw-r--r--xbmc/guilib/GUITextureD3D.cpp156
-rw-r--r--xbmc/guilib/GUITextureD3D.h41
-rw-r--r--xbmc/guilib/GUITextureGL.cpp361
-rw-r--r--xbmc/guilib/GUITextureGL.h58
-rw-r--r--xbmc/guilib/GUITextureGLES.cpp304
-rw-r--r--xbmc/guilib/GUITextureGLES.h60
-rw-r--r--xbmc/guilib/GUIToggleButtonControl.cpp175
-rw-r--r--xbmc/guilib/GUIToggleButtonControl.dox91
-rw-r--r--xbmc/guilib/GUIToggleButtonControl.h56
-rw-r--r--xbmc/guilib/GUIVideoControl.cpp108
-rw-r--r--xbmc/guilib/GUIVideoControl.dox42
-rw-r--r--xbmc/guilib/GUIVideoControl.h37
-rw-r--r--xbmc/guilib/GUIVisualisationControl.cpp452
-rw-r--r--xbmc/guilib/GUIVisualisationControl.dox42
-rw-r--r--xbmc/guilib/GUIVisualisationControl.h103
-rw-r--r--xbmc/guilib/GUIWindow.cpp1089
-rw-r--r--xbmc/guilib/GUIWindow.h293
-rw-r--r--xbmc/guilib/GUIWindowManager.cpp1808
-rw-r--r--xbmc/guilib/GUIWindowManager.h285
-rw-r--r--xbmc/guilib/GUIWrappingListContainer.cpp240
-rw-r--r--xbmc/guilib/GUIWrappingListContainer.dox139
-rw-r--r--xbmc/guilib/GUIWrappingListContainer.h50
-rw-r--r--xbmc/guilib/IAudioDeviceChangedCallback.h18
-rw-r--r--xbmc/guilib/IDirtyRegionSolver.h25
-rw-r--r--xbmc/guilib/IGUIContainer.h43
-rw-r--r--xbmc/guilib/IMsgTargetCallback.h27
-rw-r--r--xbmc/guilib/IRenderingCallback.h19
-rw-r--r--xbmc/guilib/ISliderCallback.h36
-rw-r--r--xbmc/guilib/IWindowManagerCallback.cpp14
-rw-r--r--xbmc/guilib/IWindowManagerCallback.h30
-rw-r--r--xbmc/guilib/LocalizeStrings.cpp250
-rw-r--r--xbmc/guilib/LocalizeStrings.h72
-rw-r--r--xbmc/guilib/Shader.cpp384
-rw-r--r--xbmc/guilib/Shader.h174
-rw-r--r--xbmc/guilib/StereoscopicsManager.cpp636
-rw-r--r--xbmc/guilib/StereoscopicsManager.h99
-rw-r--r--xbmc/guilib/Texture.cpp495
-rw-r--r--xbmc/guilib/Texture.h144
-rw-r--r--xbmc/guilib/TextureBundle.cpp83
-rw-r--r--xbmc/guilib/TextureBundle.h76
-rw-r--r--xbmc/guilib/TextureBundleXBT.cpp301
-rw-r--r--xbmc/guilib/TextureBundleXBT.h69
-rw-r--r--xbmc/guilib/TextureDX.cpp193
-rw-r--r--xbmc/guilib/TextureDX.h41
-rw-r--r--xbmc/guilib/TextureFormats.h22
-rw-r--r--xbmc/guilib/TextureGL.cpp226
-rw-r--r--xbmc/guilib/TextureGL.h33
-rw-r--r--xbmc/guilib/TextureManager.cpp658
-rw-r--r--xbmc/guilib/TextureManager.h131
-rw-r--r--xbmc/guilib/Tween.h401
-rw-r--r--xbmc/guilib/VisibleEffect.cpp835
-rw-r--r--xbmc/guilib/VisibleEffect.h269
-rw-r--r--xbmc/guilib/WindowIDs.dox145
-rw-r--r--xbmc/guilib/WindowIDs.h193
-rw-r--r--xbmc/guilib/XBTF.cpp235
-rw-r--r--xbmc/guilib/XBTF.h107
-rw-r--r--xbmc/guilib/XBTFReader.cpp216
-rw-r--r--xbmc/guilib/XBTFReader.h37
-rw-r--r--xbmc/guilib/_Controls.dox40
-rw-r--r--xbmc/guilib/gui3d.h44
-rw-r--r--xbmc/guilib/guiinfo/AddonsGUIInfo.cpp279
-rw-r--r--xbmc/guilib/guiinfo/AddonsGUIInfo.h37
-rw-r--r--xbmc/guilib/guiinfo/CMakeLists.txt42
-rw-r--r--xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp814
-rw-r--r--xbmc/guilib/guiinfo/GUIControlsGUIInfo.h56
-rw-r--r--xbmc/guilib/guiinfo/GUIInfo.cpp33
-rw-r--r--xbmc/guilib/guiinfo/GUIInfo.h110
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoBool.cpp41
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoBool.h46
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoColor.cpp69
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoColor.h53
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoHelper.cpp205
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoHelper.h43
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabel.cpp337
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabel.h146
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabels.h985
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoProvider.h49
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoProviders.cpp122
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoProviders.h157
-rw-r--r--xbmc/guilib/guiinfo/GamesGUIInfo.cpp87
-rw-r--r--xbmc/guilib/guiinfo/GamesGUIInfo.h37
-rw-r--r--xbmc/guilib/guiinfo/IGUIInfoProvider.h97
-rw-r--r--xbmc/guilib/guiinfo/LibraryGUIInfo.cpp290
-rw-r--r--xbmc/guilib/guiinfo/LibraryGUIInfo.h59
-rw-r--r--xbmc/guilib/guiinfo/MusicGUIInfo.cpp713
-rw-r--r--xbmc/guilib/guiinfo/MusicGUIInfo.h46
-rw-r--r--xbmc/guilib/guiinfo/PicturesGUIInfo.cpp259
-rw-r--r--xbmc/guilib/guiinfo/PicturesGUIInfo.h45
-rw-r--r--xbmc/guilib/guiinfo/PlayerGUIInfo.cpp726
-rw-r--r--xbmc/guilib/guiinfo/PlayerGUIInfo.h93
-rw-r--r--xbmc/guilib/guiinfo/SkinGUIInfo.cpp140
-rw-r--r--xbmc/guilib/guiinfo/SkinGUIInfo.h37
-rw-r--r--xbmc/guilib/guiinfo/SystemGUIInfo.cpp727
-rw-r--r--xbmc/guilib/guiinfo/SystemGUIInfo.h55
-rw-r--r--xbmc/guilib/guiinfo/VideoGUIInfo.cpp818
-rw-r--r--xbmc/guilib/guiinfo/VideoGUIInfo.h53
-rw-r--r--xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp115
-rw-r--r--xbmc/guilib/guiinfo/VisualisationGUIInfo.h37
-rw-r--r--xbmc/guilib/guiinfo/WeatherGUIInfo.cpp81
-rw-r--r--xbmc/guilib/guiinfo/WeatherGUIInfo.h37
-rw-r--r--xbmc/guilib/iimage.h74
-rw-r--r--xbmc/guilib/imagefactory.cpp59
-rw-r--r--xbmc/guilib/imagefactory.h27
252 files changed, 56580 insertions, 0 deletions
diff --git a/xbmc/guilib/CMakeLists.txt b/xbmc/guilib/CMakeLists.txt
new file mode 100644
index 0000000..d96ce09
--- /dev/null
+++ b/xbmc/guilib/CMakeLists.txt
@@ -0,0 +1,233 @@
+set(SOURCES DDSImage.cpp
+ DirtyRegionSolvers.cpp
+ DirtyRegionTracker.cpp
+ FFmpegImage.cpp
+ GUIAction.cpp
+ GUIAudioManager.cpp
+ GUIBaseContainer.cpp
+ GUIBorderedImage.cpp
+ GUIButtonControl.cpp
+ GUIColorButtonControl.cpp
+ GUIColorManager.cpp
+ GUIComponent.cpp
+ GUIControl.cpp
+ GUIControlFactory.cpp
+ GUIControlGroup.cpp
+ GUIControlGroupList.cpp
+ GUIControlLookup.cpp
+ GUIControlProfiler.cpp
+ GUIDialog.cpp
+ GUIEditControl.cpp
+ GUIFadeLabelControl.cpp
+ GUIFixedListContainer.cpp
+ GUIFont.cpp
+ GUIFontCache.cpp
+ GUIFontManager.cpp
+ GUIFontTTF.cpp
+ GUIImage.cpp
+ GUIIncludes.cpp
+ GUIKeyboardFactory.cpp
+ GUILabelControl.cpp
+ GUILabel.cpp
+ GUIListContainer.cpp
+ GUIListGroup.cpp
+ GUIListItem.cpp
+ GUIListItemLayout.cpp
+ GUIListLabel.cpp
+ GUIMessage.cpp
+ GUIMoverControl.cpp
+ GUIMultiImage.cpp
+ GUIPanelContainer.cpp
+ GUIProgressControl.cpp
+ GUIRadioButtonControl.cpp
+ GUIRangesControl.cpp
+ GUIRenderingControl.cpp
+ GUIResizeControl.cpp
+ GUIRSSControl.cpp
+ GUIScrollBarControl.cpp
+ GUISettingsSliderControl.cpp
+ GUISliderControl.cpp
+ GUISpinControl.cpp
+ GUISpinControlEx.cpp
+ GUIStaticItem.cpp
+ GUITextBox.cpp
+ GUITextLayout.cpp
+ GUITexture.cpp
+ GUIToggleButtonControl.cpp
+ GUIVideoControl.cpp
+ GUIVisualisationControl.cpp
+ GUIWindow.cpp
+ GUIWindowManager.cpp
+ GUIWrappingListContainer.cpp
+ imagefactory.cpp
+ IWindowManagerCallback.cpp
+ LocalizeStrings.cpp
+ StereoscopicsManager.cpp
+ TextureBundle.cpp
+ TextureBundleXBT.cpp
+ Texture.cpp
+ TextureManager.cpp
+ VisibleEffect.cpp
+ XBTF.cpp
+ XBTFReader.cpp)
+
+set(HEADERS DDSImage.h
+ DirtyRegion.h
+ DirtyRegionSolvers.h
+ DirtyRegionTracker.h
+ DispResource.h
+ FFmpegImage.h
+ gui3d.h
+ GUIAction.h
+ GUIAudioManager.h
+ GUIBaseContainer.h
+ GUIBorderedImage.h
+ GUIButtonControl.h
+ GUIColorButtonControl.h
+ GUIColorManager.h
+ GUIComponent.h
+ GUIControl.h
+ GUIControlFactory.h
+ GUIControlGroup.h
+ GUIControlGroupList.h
+ GUIControlProfiler.h
+ GUIControlLookup.h
+ GUIDialog.h
+ GUIEditControl.h
+ GUIFadeLabelControl.h
+ GUIFixedListContainer.h
+ GUIFont.h
+ GUIFontCache.h
+ GUIFontManager.h
+ GUIFontTTF.h
+ GUIImage.h
+ GUIIncludes.h
+ GUIKeyboard.h
+ GUIKeyboardFactory.h
+ GUILabel.h
+ GUILabelControl.h
+ GUIListContainer.h
+ GUIListGroup.h
+ GUIListItem.h
+ GUIListItemLayout.h
+ GUIListLabel.h
+ GUIMessage.h
+ GUIMoverControl.h
+ GUIMultiImage.h
+ GUIPanelContainer.h
+ GUIProgressControl.h
+ GUIRadioButtonControl.h
+ GUIRangesControl.h
+ GUIRenderingControl.h
+ GUIResizeControl.h
+ GUIRSSControl.h
+ GUIScrollBarControl.h
+ GUISettingsSliderControl.h
+ GUISliderControl.h
+ GUISpinControl.h
+ GUISpinControlEx.h
+ GUIStaticItem.h
+ GUITextBox.h
+ GUITextLayout.h
+ GUITexture.h
+ GUIToggleButtonControl.h
+ GUIVideoControl.h
+ GUIVisualisationControl.h
+ GUIWindow.h
+ GUIWindowManager.h
+ GUIWrappingListContainer.h
+ IAudioDeviceChangedCallback.h
+ IDirtyRegionSolver.h
+ IGUIContainer.h
+ iimage.h
+ imagefactory.h
+ IMsgTargetCallback.h
+ IRenderingCallback.h
+ ISliderCallback.h
+ IWindowManagerCallback.h
+ LocalizeStrings.h
+ StereoscopicsManager.h
+ Texture.h
+ TextureBundle.h
+ TextureBundleXBT.h
+ TextureManager.h
+ Tween.h
+ VisibleEffect.h
+ WindowIDs.h
+ XBTF.h
+ XBTFReader.h)
+
+if(OPENGL_FOUND OR OPENGLES_FOUND)
+ list(APPEND SOURCES GUIFontTTFGL.cpp
+ Shader.cpp
+ TextureGL.cpp)
+ list(APPEND HEADERS GUIFontTTFGL.h
+ Shader.h
+ TextureGL.h)
+
+ if(OPENGL_FOUND)
+ list(APPEND SOURCES GUITextureGL.cpp)
+ list(APPEND HEADERS GUITextureGL.h)
+ endif()
+
+ if(OPENGLES_FOUND)
+ list(APPEND SOURCES GUITextureGLES.cpp)
+ list(APPEND HEADERS GUITextureGLES.h)
+ endif()
+
+endif()
+
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ list(APPEND SOURCES D3DResource.cpp
+ DirectXGraphics.cpp
+ GUIFontTTFDX.cpp
+ GUIShaderDX.cpp
+ GUITextureD3D.cpp
+ TextureDX.cpp)
+ list(APPEND HEADERS D3DResource.h
+ DirectXGraphics.h
+ GUIFontTTFDX.h
+ GUIShaderDX.h
+ GUITextureD3D.h
+ TextureDX.h)
+endif()
+
+core_add_library(guilib)
+
+if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore)
+ set(SHADERS_VERTEX guishader_vert.hlsl)
+ set(SHADERS_PIXEL guishader_checkerboard_right.hlsl
+ guishader_checkerboard_left.hlsl
+ guishader_default.hlsl
+ guishader_fonts.hlsl
+ guishader_interlaced_right.hlsl
+ guishader_interlaced_left.hlsl
+ guishader_multi_texture_blend.hlsl
+ guishader_texture.hlsl
+ guishader_texture_noblend.hlsl)
+ foreach(shader ${SHADERS_VERTEX})
+ get_filename_component(file ${shader} NAME_WE)
+ add_custom_command(OUTPUT ${file}.h
+ COMMAND ${FXC} /Fh ${file}.h /E VS /T vs_4_0_level_9_1 /Vn ${file} /Qstrip_reflect
+ ${CMAKE_SOURCE_DIR}/system/shaders/${shader}
+ DEPENDS ${CMAKE_SOURCE_DIR}/system/shaders/${shader}
+ COMMENT "FX compile vertex shader ${shader}"
+ VERBATIM)
+ list(APPEND SHADERS ${file}.h)
+ endforeach()
+ foreach(shader ${SHADERS_PIXEL})
+ get_filename_component(file ${shader} NAME_WE)
+ add_custom_command(OUTPUT ${file}.h
+ COMMAND ${FXC} /Fh ${file}.h /E PS /T ps_4_0_level_9_1 /Vn ${file} /Qstrip_reflect
+ ${CMAKE_SOURCE_DIR}/system/shaders/${shader}
+ DEPENDS ${CMAKE_SOURCE_DIR}/system/shaders/${shader}
+ COMMENT "FX compile pixel shader ${shader}"
+ VERBATIM)
+ list(APPEND SHADERS ${file}.h)
+ endforeach()
+
+ add_custom_target(generate_shaders ALL DEPENDS ${SHADERS})
+ set_target_properties(generate_shaders PROPERTIES FOLDER "Build Utilities")
+ target_include_directories(${CORE_LIBRARY} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+ add_dependencies(${CORE_LIBRARY} generate_shaders)
+endif()
diff --git a/xbmc/guilib/D3DResource.cpp b/xbmc/guilib/D3DResource.cpp
new file mode 100644
index 0000000..0f1dc89
--- /dev/null
+++ b/xbmc/guilib/D3DResource.cpp
@@ -0,0 +1,1244 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "D3DResource.h"
+
+#include "GUIShaderDX.h"
+#include "filesystem/File.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+#include <d3dcompiler.h>
+
+using namespace DirectX;
+using namespace Microsoft::WRL;
+
+#ifdef TARGET_WINDOWS_DESKTOP
+#pragma comment(lib, "d3dcompiler.lib")
+#endif
+
+size_t CD3DHelper::BitsPerPixel(DXGI_FORMAT fmt)
+{
+ switch (fmt)
+ {
+ case DXGI_FORMAT_R32G32B32A32_TYPELESS:
+ case DXGI_FORMAT_R32G32B32A32_FLOAT:
+ case DXGI_FORMAT_R32G32B32A32_UINT:
+ case DXGI_FORMAT_R32G32B32A32_SINT:
+ return 128;
+
+ case DXGI_FORMAT_R32G32B32_TYPELESS:
+ case DXGI_FORMAT_R32G32B32_FLOAT:
+ case DXGI_FORMAT_R32G32B32_UINT:
+ case DXGI_FORMAT_R32G32B32_SINT:
+ return 96;
+
+ case DXGI_FORMAT_R16G16B16A16_TYPELESS:
+ case DXGI_FORMAT_R16G16B16A16_FLOAT:
+ case DXGI_FORMAT_R16G16B16A16_UNORM:
+ case DXGI_FORMAT_R16G16B16A16_UINT:
+ case DXGI_FORMAT_R16G16B16A16_SNORM:
+ case DXGI_FORMAT_R16G16B16A16_SINT:
+ case DXGI_FORMAT_R32G32_TYPELESS:
+ case DXGI_FORMAT_R32G32_FLOAT:
+ case DXGI_FORMAT_R32G32_UINT:
+ case DXGI_FORMAT_R32G32_SINT:
+ case DXGI_FORMAT_R32G8X24_TYPELESS:
+ case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
+ case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
+ case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
+ return 64;
+
+ case DXGI_FORMAT_R10G10B10A2_TYPELESS:
+ case DXGI_FORMAT_R10G10B10A2_UNORM:
+ case DXGI_FORMAT_R10G10B10A2_UINT:
+ case DXGI_FORMAT_R11G11B10_FLOAT:
+ case DXGI_FORMAT_R8G8B8A8_TYPELESS:
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ case DXGI_FORMAT_R8G8B8A8_UINT:
+ case DXGI_FORMAT_R8G8B8A8_SNORM:
+ case DXGI_FORMAT_R8G8B8A8_SINT:
+ case DXGI_FORMAT_R16G16_TYPELESS:
+ case DXGI_FORMAT_R16G16_FLOAT:
+ case DXGI_FORMAT_R16G16_UNORM:
+ case DXGI_FORMAT_R16G16_UINT:
+ case DXGI_FORMAT_R16G16_SNORM:
+ case DXGI_FORMAT_R16G16_SINT:
+ case DXGI_FORMAT_R32_TYPELESS:
+ case DXGI_FORMAT_D32_FLOAT:
+ case DXGI_FORMAT_R32_FLOAT:
+ case DXGI_FORMAT_R32_UINT:
+ case DXGI_FORMAT_R32_SINT:
+ case DXGI_FORMAT_R24G8_TYPELESS:
+ case DXGI_FORMAT_D24_UNORM_S8_UINT:
+ case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
+ case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
+ case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
+ case DXGI_FORMAT_R8G8_B8G8_UNORM:
+ case DXGI_FORMAT_G8R8_G8B8_UNORM:
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ case DXGI_FORMAT_B8G8R8X8_UNORM:
+ case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
+ case DXGI_FORMAT_B8G8R8A8_TYPELESS:
+ case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ case DXGI_FORMAT_B8G8R8X8_TYPELESS:
+ case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
+ return 32;
+
+ case DXGI_FORMAT_R8G8_TYPELESS:
+ case DXGI_FORMAT_R8G8_UNORM:
+ case DXGI_FORMAT_R8G8_UINT:
+ case DXGI_FORMAT_R8G8_SNORM:
+ case DXGI_FORMAT_R8G8_SINT:
+ case DXGI_FORMAT_R16_TYPELESS:
+ case DXGI_FORMAT_R16_FLOAT:
+ case DXGI_FORMAT_D16_UNORM:
+ case DXGI_FORMAT_R16_UNORM:
+ case DXGI_FORMAT_R16_UINT:
+ case DXGI_FORMAT_R16_SNORM:
+ case DXGI_FORMAT_R16_SINT:
+ case DXGI_FORMAT_B5G6R5_UNORM:
+ case DXGI_FORMAT_B5G5R5A1_UNORM:
+ case DXGI_FORMAT_B4G4R4A4_UNORM:
+ return 16;
+
+ case DXGI_FORMAT_R8_TYPELESS:
+ case DXGI_FORMAT_R8_UNORM:
+ case DXGI_FORMAT_R8_UINT:
+ case DXGI_FORMAT_R8_SNORM:
+ case DXGI_FORMAT_R8_SINT:
+ case DXGI_FORMAT_A8_UNORM:
+ return 8;
+
+ case DXGI_FORMAT_R1_UNORM:
+ return 1;
+
+ case DXGI_FORMAT_BC1_TYPELESS:
+ case DXGI_FORMAT_BC1_UNORM:
+ case DXGI_FORMAT_BC1_UNORM_SRGB:
+ case DXGI_FORMAT_BC4_TYPELESS:
+ case DXGI_FORMAT_BC4_UNORM:
+ case DXGI_FORMAT_BC4_SNORM:
+ return 4;
+
+ case DXGI_FORMAT_BC2_TYPELESS:
+ case DXGI_FORMAT_BC2_UNORM:
+ case DXGI_FORMAT_BC2_UNORM_SRGB:
+ case DXGI_FORMAT_BC3_TYPELESS:
+ case DXGI_FORMAT_BC3_UNORM:
+ case DXGI_FORMAT_BC3_UNORM_SRGB:
+ case DXGI_FORMAT_BC5_TYPELESS:
+ case DXGI_FORMAT_BC5_UNORM:
+ case DXGI_FORMAT_BC5_SNORM:
+ case DXGI_FORMAT_BC6H_TYPELESS:
+ case DXGI_FORMAT_BC6H_UF16:
+ case DXGI_FORMAT_BC6H_SF16:
+ case DXGI_FORMAT_BC7_TYPELESS:
+ case DXGI_FORMAT_BC7_UNORM:
+ case DXGI_FORMAT_BC7_UNORM_SRGB:
+ return 8;
+
+ default:
+ return 0;
+ }
+}
+
+void ID3DResource::Register()
+{
+ if (!m_bRegistered)
+ DX::Windowing()->Register(this);
+ m_bRegistered = true;
+}
+
+void ID3DResource::Unregister()
+{
+ if (m_bRegistered)
+ DX::Windowing()->Unregister(this);
+ m_bRegistered = false;
+}
+
+CD3DTexture::CD3DTexture()
+{
+ m_width = 0;
+ m_height = 0;
+ m_mipLevels = 0;
+ m_usage = D3D11_USAGE_DEFAULT;
+ m_format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ m_texture = nullptr;
+ m_renderTargets[0] = nullptr;
+ m_renderTargets[1] = nullptr;
+ m_data = nullptr;
+ m_pitch = 0;
+ m_bindFlags = 0;
+ m_cpuFlags = 0;
+ m_viewIdx = 0;
+ m_views.clear();
+}
+
+CD3DTexture::~CD3DTexture()
+{
+ Release();
+ delete[] m_data;
+}
+
+bool CD3DTexture::Create(UINT width, UINT height, UINT mipLevels, D3D11_USAGE usage, DXGI_FORMAT format, const void* pixels /* nullptr */, unsigned int srcPitch /* 0 */)
+{
+ m_width = width;
+ m_height = height;
+ m_mipLevels = mipLevels;
+ // create the texture
+ Release();
+
+ if (format == DXGI_FORMAT_UNKNOWN)
+ format = DXGI_FORMAT_B8G8R8A8_UNORM; // DXGI_FORMAT_UNKNOWN
+
+ if (!DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_TEXTURE2D))
+ {
+ CLog::LogF(LOGERROR, "unsupported texture format {}", format);
+ return false;
+ }
+
+ m_cpuFlags = 0;
+ if (usage == D3D11_USAGE_DYNAMIC || usage == D3D11_USAGE_STAGING)
+ {
+ m_cpuFlags |= D3D11_CPU_ACCESS_WRITE;
+ if (usage == D3D11_USAGE_STAGING)
+ m_cpuFlags |= D3D11_CPU_ACCESS_READ;
+ }
+
+ m_format = format;
+ m_usage = usage;
+
+ m_bindFlags = 0; // D3D11_BIND_SHADER_RESOURCE;
+ if (D3D11_USAGE_DEFAULT == usage && DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_RENDER_TARGET))
+ m_bindFlags |= D3D11_BIND_RENDER_TARGET;
+ if ( D3D11_USAGE_STAGING != m_usage )
+ {
+ if (DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_SHADER_LOAD)
+ || DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE))
+ {
+ m_bindFlags |= D3D11_BIND_SHADER_RESOURCE;
+ }
+ if (DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_DECODER_OUTPUT))
+ {
+ m_bindFlags |= D3D11_BIND_DECODER;
+ }
+ }
+
+ if (!CreateInternal(pixels, srcPitch))
+ {
+ CLog::LogF(LOGERROR, "failed to create texture.");
+ return false;
+ }
+
+ Register();
+
+ return true;
+}
+
+bool CD3DTexture::CreateInternal(const void* pixels /* nullptr */, unsigned int srcPitch /* 0 */)
+{
+ ComPtr<ID3D11Device> pD3DDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ ComPtr<ID3D11DeviceContext> pD3D11Context = DX::DeviceResources::Get()->GetD3DContext();
+
+ UINT miscFlags = 0;
+ bool autogenmm = false;
+ if (m_mipLevels == 0 && DX::Windowing()->IsFormatSupport(m_format, D3D11_FORMAT_SUPPORT_MIP_AUTOGEN))
+ {
+ autogenmm = pixels != nullptr;
+ miscFlags |= D3D11_RESOURCE_MISC_GENERATE_MIPS;
+ }
+ else
+ m_mipLevels = 1;
+
+ CD3D11_TEXTURE2D_DESC textureDesc(m_format, m_width, m_height, 1, m_mipLevels, m_bindFlags, m_usage, m_cpuFlags, 1, 0, miscFlags);
+ D3D11_SUBRESOURCE_DATA initData = {};
+ initData.pSysMem = pixels;
+ initData.SysMemPitch = srcPitch ? srcPitch : CD3DHelper::BitsPerPixel(m_format) * m_width / 8;
+ initData.SysMemSlicePitch = 0;
+
+ HRESULT hr = pD3DDevice->CreateTexture2D(&textureDesc, (!autogenmm && pixels) ? &initData : nullptr, m_texture.ReleaseAndGetAddressOf());
+ if (SUCCEEDED(hr) && autogenmm)
+ {
+ pD3D11Context->UpdateSubresource(m_texture.Get(), 0, nullptr, pixels,
+ (srcPitch ? srcPitch : CD3DHelper::BitsPerPixel(m_format) * m_width / 8), 0);
+ }
+
+ if (autogenmm)
+ GenerateMipmaps();
+
+ return SUCCEEDED(hr);
+}
+
+ID3D11ShaderResourceView* CD3DTexture::GetShaderResource(DXGI_FORMAT format /* = DXGI_FORMAT_UNKNOWN */)
+{
+ if (!m_texture)
+ return nullptr;
+
+ if (format == DXGI_FORMAT_UNKNOWN)
+ format = m_format;
+
+ if (!DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_SHADER_SAMPLE)
+ && !DX::Windowing()->IsFormatSupport(format, D3D11_FORMAT_SUPPORT_SHADER_LOAD))
+ return nullptr;
+
+ if (!m_views[format])
+ {
+ ComPtr<ID3D11ShaderResourceView> view;
+ CD3D11_SHADER_RESOURCE_VIEW_DESC cSRVDesc(D3D11_SRV_DIMENSION_TEXTURE2D, format);
+ HRESULT hr = DX::DeviceResources::Get()->GetD3DDevice()->CreateShaderResourceView(m_texture.Get(), &cSRVDesc, view.GetAddressOf());
+ if (SUCCEEDED(hr))
+ m_views.insert_or_assign(format, view);
+ else
+ CLog::LogF(LOGWARNING, "cannot create texture view.");
+ }
+
+ return m_views[format].Get();
+}
+
+ID3D11ShaderResourceView** CD3DTexture::GetAddressOfSRV(DXGI_FORMAT format)
+{
+ if (format == DXGI_FORMAT_UNKNOWN)
+ format = m_format;
+
+ if (!m_views[format])
+ GetShaderResource(format);
+
+ return m_views[format].GetAddressOf();
+}
+
+ID3D11RenderTargetView* CD3DTexture::GetRenderTarget()
+{
+ return GetRenderTargetInternal(m_viewIdx);
+}
+
+ID3D11RenderTargetView** CD3DTexture::GetAddressOfRTV()
+{
+ if (!m_renderTargets[m_viewIdx])
+ GetRenderTargetInternal(m_viewIdx);
+ return m_renderTargets[m_viewIdx].GetAddressOf();
+}
+
+void CD3DTexture::Release()
+{
+ Unregister();
+
+ m_views.clear();
+ m_renderTargets[0] = nullptr;
+ m_renderTargets[1] = nullptr;
+ m_texture = nullptr;
+}
+
+bool CD3DTexture::GetDesc(D3D11_TEXTURE2D_DESC *desc) const
+{
+ if (m_texture)
+ {
+ m_texture->GetDesc(desc);
+ return true;
+ }
+ return false;
+}
+
+bool CD3DTexture::LockRect(UINT subresource, D3D11_MAPPED_SUBRESOURCE *res, D3D11_MAP mapType) const
+{
+ if (m_texture)
+ {
+ if (m_usage == D3D11_USAGE_DEFAULT)
+ return false;
+ if ((mapType == D3D11_MAP_READ || mapType == D3D11_MAP_READ_WRITE) && m_usage == D3D11_USAGE_DYNAMIC)
+ return false;
+
+ return (S_OK == DX::DeviceResources::Get()->GetImmediateContext()->Map(m_texture.Get(), subresource, mapType, 0, res));
+ }
+ return false;
+}
+
+bool CD3DTexture::UnlockRect(UINT subresource) const
+{
+ if (m_texture)
+ {
+ DX::DeviceResources::Get()->GetImmediateContext()->Unmap(m_texture.Get(), subresource);
+ return true;
+ }
+ return false;
+}
+
+void CD3DTexture::SaveTexture()
+{
+ if (m_texture)
+ {
+ delete[] m_data;
+ m_data = nullptr;
+
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetImmediateContext();
+
+ D3D11_TEXTURE2D_DESC textureDesc;
+ m_texture->GetDesc(&textureDesc);
+
+ ComPtr<ID3D11Texture2D> texture = nullptr;
+ if (textureDesc.Usage != D3D11_USAGE_STAGING || 0 == (textureDesc.CPUAccessFlags & D3D11_CPU_ACCESS_READ))
+ {
+ // create texture which can be readed by CPU - D3D11_USAGE_STAGING
+ CD3D11_TEXTURE2D_DESC stagingDesc(textureDesc);
+ stagingDesc.Usage = D3D11_USAGE_STAGING;
+ stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ stagingDesc.BindFlags = 0;
+
+ if (FAILED(DX::DeviceResources::Get()->GetD3DDevice()->CreateTexture2D(&stagingDesc, NULL, &texture)))
+ return;
+
+ // copy contents to new texture
+ pContext->CopyResource(texture.Get(), m_texture.Get());
+ }
+ else
+ texture = m_texture;
+
+ // read data from texture
+ D3D11_MAPPED_SUBRESOURCE res;
+ if (SUCCEEDED(pContext->Map(texture.Get(), 0, D3D11_MAP_READ, 0, &res)))
+ {
+ m_pitch = res.RowPitch;
+ unsigned int memUsage = GetMemoryUsage(res.RowPitch);
+ m_data = new unsigned char[memUsage];
+ memcpy(m_data, res.pData, memUsage);
+ pContext->Unmap(texture.Get(), 0);
+ }
+ else
+ CLog::LogF(LOGERROR, "Failed to store resource.");
+ }
+}
+
+void CD3DTexture::OnDestroyDevice(bool fatal)
+{
+ if (!fatal)
+ SaveTexture();
+ m_views.clear();
+ m_renderTargets[0] = nullptr;
+ m_renderTargets[1] = nullptr;
+ m_texture = nullptr;
+}
+
+void CD3DTexture::RestoreTexture()
+{
+ // yay, we're back - make a new copy of the texture
+ if (!m_texture && m_data)
+ {
+ if (!CreateInternal(m_data, m_pitch))
+ {
+ CLog::LogF(LOGERROR, "failed restore texture");
+ }
+
+ delete[] m_data;
+ m_data = NULL;
+ m_pitch = 0;
+ }
+}
+
+void CD3DTexture::OnCreateDevice()
+{
+ RestoreTexture();
+}
+
+ID3D11RenderTargetView* CD3DTexture::GetRenderTargetInternal(unsigned idx)
+{
+ if (idx > 1)
+ return nullptr;
+
+ if (!m_texture)
+ return nullptr;
+
+ if (!DX::Windowing()->IsFormatSupport(m_format, D3D11_FORMAT_SUPPORT_RENDER_TARGET))
+ return nullptr;
+
+ if (!m_renderTargets[idx])
+ {
+ CD3D11_RENDER_TARGET_VIEW_DESC cRTVDesc(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, DXGI_FORMAT_UNKNOWN, 0, idx, 1);
+ if (FAILED(DX::DeviceResources::Get()->GetD3DDevice()->CreateRenderTargetView(m_texture.Get(), &cRTVDesc, m_renderTargets[idx].ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGWARNING, "cannot create texture view.");
+ }
+ }
+
+ return m_renderTargets[idx].Get();
+}
+
+unsigned int CD3DTexture::GetMemoryUsage(unsigned int pitch) const
+{
+ switch (m_format)
+ {
+ case DXGI_FORMAT_BC1_UNORM:
+ case DXGI_FORMAT_BC2_UNORM:
+ case DXGI_FORMAT_BC3_UNORM:
+ return pitch * m_height / 4;
+ default:
+ return pitch * m_height;
+ }
+}
+
+void CD3DTexture::GenerateMipmaps()
+{
+ if (m_mipLevels == 0)
+ {
+ ID3D11ShaderResourceView* pSRView = GetShaderResource();
+ if (pSRView != nullptr)
+ DX::DeviceResources::Get()->GetD3DContext()->GenerateMips(pSRView);
+ }
+}
+
+// static methods
+void CD3DTexture::DrawQuad(const CPoint points[4],
+ UTILS::COLOR::Color color,
+ CD3DTexture* texture,
+ const CRect* texCoords,
+ SHADER_METHOD options)
+{
+ unsigned numViews = 0;
+ ID3D11ShaderResourceView* views = nullptr;
+
+ if (texture)
+ {
+ numViews = 1;
+ views = texture->GetShaderResource();
+ }
+
+ DrawQuad(points, color, numViews, &views, texCoords, options);
+}
+
+void CD3DTexture::DrawQuad(const CRect& rect,
+ UTILS::COLOR::Color color,
+ CD3DTexture* texture,
+ const CRect* texCoords,
+ SHADER_METHOD options)
+{
+ CPoint points[] =
+ {
+ { rect.x1, rect.y1 },
+ { rect.x2, rect.y1 },
+ { rect.x2, rect.y2 },
+ { rect.x1, rect.y2 },
+ };
+ DrawQuad(points, color, texture, texCoords, options);
+}
+
+void CD3DTexture::DrawQuad(const CPoint points[4],
+ UTILS::COLOR::Color color,
+ unsigned numViews,
+ ID3D11ShaderResourceView** view,
+ const CRect* texCoords,
+ SHADER_METHOD options)
+{
+ XMFLOAT4 xcolor;
+ CD3DHelper::XMStoreColor(&xcolor, color);
+ CRect coords = texCoords ? *texCoords : CRect(0.0f, 0.0f, 1.0f, 1.0f);
+
+ Vertex verts[4] = {
+ { XMFLOAT3(points[0].x, points[0].y, 0), xcolor, XMFLOAT2(coords.x1, coords.y1), XMFLOAT2(0.0f, 0.0f) },
+ { XMFLOAT3(points[1].x, points[1].y, 0), xcolor, XMFLOAT2(coords.x2, coords.y1), XMFLOAT2(0.0f, 0.0f) },
+ { XMFLOAT3(points[2].x, points[2].y, 0), xcolor, XMFLOAT2(coords.x2, coords.y2), XMFLOAT2(0.0f, 0.0f) },
+ { XMFLOAT3(points[3].x, points[3].y, 0), xcolor, XMFLOAT2(coords.x1, coords.y2), XMFLOAT2(0.0f, 0.0f) },
+ };
+
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+
+ pGUIShader->Begin(view && numViews > 0 ? options : SHADER_METHOD_RENDER_DEFAULT);
+ if (view && numViews > 0)
+ pGUIShader->SetShaderViews(numViews, view);
+ pGUIShader->DrawQuad(verts[0], verts[1], verts[2], verts[3]);
+}
+
+void CD3DTexture::DrawQuad(const CRect& rect,
+ UTILS::COLOR::Color color,
+ unsigned numViews,
+ ID3D11ShaderResourceView** view,
+ const CRect* texCoords,
+ SHADER_METHOD options)
+{
+ CPoint points[] =
+ {
+ { rect.x1, rect.y1 },
+ { rect.x2, rect.y1 },
+ { rect.x2, rect.y2 },
+ { rect.x1, rect.y2 },
+ };
+ DrawQuad(points, color, numViews, view, texCoords, options);
+}
+
+CD3DEffect::CD3DEffect()
+{
+ m_effect = nullptr;
+ m_techniquie = nullptr;
+ m_currentPass = nullptr;
+}
+
+CD3DEffect::~CD3DEffect()
+{
+ Release();
+}
+
+bool CD3DEffect::Create(const std::string &effectString, DefinesMap* defines)
+{
+ Release();
+ m_effectString = effectString;
+ m_defines.clear();
+ if (defines != nullptr)
+ m_defines = *defines; //FIXME: is this a copy of all members?
+ if (CreateEffect())
+ {
+ Register();
+ return true;
+ }
+ return false;
+}
+
+void CD3DEffect::Release()
+{
+ Unregister();
+ OnDestroyDevice(false);
+}
+
+void CD3DEffect::OnDestroyDevice(bool fatal)
+{
+ m_effect = nullptr;
+ m_techniquie = nullptr;
+ m_currentPass = nullptr;
+}
+
+void CD3DEffect::OnCreateDevice()
+{
+ CreateEffect();
+}
+
+HRESULT CD3DEffect::Open(D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID* ppData, UINT* pBytes)
+{
+ XFILE::CFile includeFile;
+
+ std::string fileName("special://xbmc/system/shaders/");
+ fileName.append(pFileName);
+
+ if (!includeFile.Open(fileName))
+ {
+ CLog::LogF(LOGERROR, "Could not open 3DLUT file: {}", fileName);
+ return E_FAIL;
+ }
+
+ int64_t length = includeFile.GetLength();
+ void *pData = malloc(length);
+ if (includeFile.Read(pData, length) != length)
+ {
+ free(pData);
+ return E_FAIL;
+ }
+ *ppData = pData;
+ *pBytes = length;
+
+ return S_OK;
+}
+
+HRESULT CD3DEffect::Close(LPCVOID pData)
+{
+ free((void*)pData);
+ return S_OK;
+}
+
+bool CD3DEffect::SetFloatArray(LPCSTR handle, const float* val, unsigned int count)
+{
+ if (m_effect)
+ {
+ return S_OK == m_effect->GetVariableByName(handle)->SetRawValue(val, 0, sizeof(float) * count);
+ }
+ return false;
+}
+
+bool CD3DEffect::SetMatrix(LPCSTR handle, const float* mat)
+{
+ if (m_effect)
+ {
+ return S_OK == m_effect->GetVariableByName(handle)->AsMatrix()->SetMatrix(mat);
+ }
+ return false;
+}
+
+bool CD3DEffect::SetTechnique(LPCSTR handle)
+{
+ if (m_effect)
+ {
+ m_techniquie = m_effect->GetTechniqueByName(handle);
+ if (!m_techniquie->IsValid())
+ m_techniquie = nullptr;
+
+ return nullptr != m_techniquie;
+ }
+ return false;
+}
+
+bool CD3DEffect::SetTexture(LPCSTR handle, CD3DTexture &texture)
+{
+ if (m_effect)
+ {
+ ID3DX11EffectShaderResourceVariable* var = m_effect->GetVariableByName(handle)->AsShaderResource();
+ if (var->IsValid())
+ return SUCCEEDED(var->SetResource(texture.GetShaderResource()));
+ }
+ return false;
+}
+
+bool CD3DEffect::SetResources(LPCSTR handle, ID3D11ShaderResourceView** ppSRViews, size_t count)
+{
+ if (m_effect)
+ {
+ ID3DX11EffectShaderResourceVariable* var = m_effect->GetVariableByName(handle)->AsShaderResource();
+ if (var->IsValid())
+ return SUCCEEDED(var->SetResourceArray(ppSRViews, 0, count));
+ }
+ return false;
+}
+
+bool CD3DEffect::SetConstantBuffer(LPCSTR handle, ID3D11Buffer *buffer)
+{
+ if (m_effect)
+ {
+ ID3DX11EffectConstantBuffer* effectbuffer = m_effect->GetConstantBufferByName(handle);
+ if (effectbuffer->IsValid())
+ return (S_OK == effectbuffer->SetConstantBuffer(buffer));
+ }
+ return false;
+}
+
+bool CD3DEffect::SetScalar(LPCSTR handle, float value)
+{
+ if (m_effect)
+ {
+ ID3DX11EffectScalarVariable* scalar = m_effect->GetVariableByName(handle)->AsScalar();
+ if (scalar->IsValid())
+ return (S_OK == scalar->SetFloat(value));
+ }
+
+ return false;
+}
+
+bool CD3DEffect::Begin(UINT *passes, DWORD flags)
+{
+ if (m_effect && m_techniquie)
+ {
+ D3DX11_TECHNIQUE_DESC desc = {};
+ HRESULT hr = m_techniquie->GetDesc(&desc);
+ *passes = desc.Passes;
+ return S_OK == hr;
+ }
+ return false;
+}
+
+bool CD3DEffect::BeginPass(UINT pass)
+{
+ if (m_effect && m_techniquie)
+ {
+ m_currentPass = m_techniquie->GetPassByIndex(pass);
+ if (!m_currentPass || !m_currentPass->IsValid())
+ {
+ m_currentPass = nullptr;
+ return false;
+ }
+ return (S_OK == m_currentPass->Apply(0, DX::DeviceResources::Get()->GetD3DContext()));
+ }
+ return false;
+}
+
+bool CD3DEffect::EndPass()
+{
+ if (m_effect && m_currentPass)
+ {
+ m_currentPass = nullptr;
+ return true;
+ }
+ return false;
+}
+
+bool CD3DEffect::End()
+{
+ if (m_effect && m_techniquie)
+ {
+ m_techniquie = nullptr;
+ return true;
+ }
+ return false;
+}
+
+bool CD3DEffect::CreateEffect()
+{
+ HRESULT hr;
+ ID3DBlob* pError = nullptr;
+
+ std::vector<D3D_SHADER_MACRO> definemacros;
+
+ for (const auto& it : m_defines)
+ {
+ D3D_SHADER_MACRO m;
+ m.Name = it.first.c_str();
+ if (it.second.empty())
+ m.Definition = nullptr;
+ else
+ m.Definition = it.second.c_str();
+ definemacros.push_back( m );
+ }
+
+ definemacros.push_back(D3D_SHADER_MACRO());
+ definemacros.back().Name = nullptr;
+ definemacros.back().Definition = nullptr;
+
+ UINT dwShaderFlags = 0;
+
+#ifdef _DEBUG
+ //dwShaderFlags |= D3DCOMPILE_DEBUG;
+ // Disable optimizations to further improve shader debugging
+ //dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
+#endif
+
+ hr = D3DX11CompileEffectFromMemory(m_effectString.c_str(), m_effectString.length(), "", &definemacros[0], this,
+ dwShaderFlags, 0, DX::DeviceResources::Get()->GetD3DDevice(), m_effect.ReleaseAndGetAddressOf(), &pError);
+
+ if(hr == S_OK)
+ return true;
+ else if(pError)
+ {
+ std::string error;
+ error.assign((const char*)pError->GetBufferPointer(), pError->GetBufferSize());
+ CLog::Log(LOGERROR, "CD3DEffect::CreateEffect(): {}", error);
+ }
+ else
+ CLog::Log(LOGERROR, "CD3DEffect::CreateEffect(): call to D3DXCreateEffect() failed with {}",
+ hr);
+ return false;
+}
+
+CD3DBuffer::CD3DBuffer()
+{
+ m_length = 0;
+ m_stride = 0;
+ m_usage = D3D11_USAGE_DEFAULT;
+ m_format = DXGI_FORMAT_UNKNOWN;
+ m_buffer = nullptr;
+ m_data = nullptr;
+}
+
+CD3DBuffer::~CD3DBuffer()
+{
+ Release();
+ delete[] m_data;
+}
+
+bool CD3DBuffer::Create(D3D11_BIND_FLAG type, UINT count, UINT stride, DXGI_FORMAT format, D3D11_USAGE usage, const void* data)
+{
+ m_type = type;
+ m_stride = stride ? stride : CD3DHelper::BitsPerPixel(format);
+ m_format = format;
+ m_length = count * m_stride;
+ m_usage = usage;
+ m_cpuFlags = 0;
+
+ if (m_usage == D3D11_USAGE_DYNAMIC || m_usage == D3D11_USAGE_STAGING)
+ {
+ m_cpuFlags |= D3D11_CPU_ACCESS_WRITE;
+ if (m_usage == D3D11_USAGE_STAGING)
+ m_cpuFlags |= D3D11_CPU_ACCESS_READ;
+ }
+
+ Release();
+
+ // create the vertex buffer
+ if (CreateBuffer(data))
+ {
+ Register();
+ return true;
+ }
+ return false;
+}
+
+void CD3DBuffer::Release()
+{
+ Unregister();
+ m_buffer = nullptr;
+}
+
+bool CD3DBuffer::Map(void **data)
+{
+ if (m_buffer)
+ {
+ D3D11_MAPPED_SUBRESOURCE resource;
+ if (SUCCEEDED(DX::DeviceResources::Get()->GetD3DContext()->Map(m_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &resource)))
+ {
+ *data = resource.pData;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CD3DBuffer::Unmap()
+{
+ if (m_buffer)
+ {
+ DX::DeviceResources::Get()->GetD3DContext()->Unmap(m_buffer.Get(), 0);
+ return true;
+ }
+ return false;
+}
+
+void CD3DBuffer::OnDestroyDevice(bool fatal)
+{
+ if (fatal)
+ {
+ m_buffer = nullptr;
+ return;
+ }
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+
+ if (!pDevice || !pContext || !m_buffer)
+ return;
+
+ D3D11_BUFFER_DESC srcDesc;
+ m_buffer->GetDesc(&srcDesc);
+
+ ComPtr<ID3D11Buffer> buffer;
+ if (srcDesc.Usage != D3D11_USAGE_STAGING || 0 == (srcDesc.CPUAccessFlags & D3D11_CPU_ACCESS_READ))
+ {
+ CD3D11_BUFFER_DESC trgDesc(srcDesc);
+ trgDesc.Usage = D3D11_USAGE_STAGING;
+ trgDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ trgDesc.BindFlags = 0;
+
+ if (SUCCEEDED(pDevice->CreateBuffer(&trgDesc, NULL, &buffer)))
+ pContext->CopyResource(buffer.Get(), m_buffer.Get());
+ }
+ else
+ buffer = m_buffer;
+
+ if (buffer != nullptr)
+ {
+ D3D11_MAPPED_SUBRESOURCE res;
+ if (SUCCEEDED(pContext->Map(buffer.Get(), 0, D3D11_MAP_READ, 0, &res)))
+ {
+ m_data = new unsigned char[srcDesc.ByteWidth];
+ memcpy(m_data, res.pData, srcDesc.ByteWidth);
+ pContext->Unmap(buffer.Get(), 0);
+ }
+ }
+ m_buffer = nullptr;
+}
+
+void CD3DBuffer::OnCreateDevice()
+{
+ // yay, we're back - make a new copy of the vertices
+ if (!m_buffer && m_data)
+ {
+ CreateBuffer(m_data);
+ delete[] m_data;
+ m_data = nullptr;
+ }
+}
+
+bool CD3DBuffer::CreateBuffer(const void* pData)
+{
+ CD3D11_BUFFER_DESC bDesc(m_length, m_type, m_usage, m_cpuFlags);
+ D3D11_SUBRESOURCE_DATA initData;
+ initData.pSysMem = pData;
+ return (S_OK == DX::DeviceResources::Get()->GetD3DDevice()->CreateBuffer(&bDesc, (pData ? &initData : nullptr), m_buffer.ReleaseAndGetAddressOf()));
+}
+
+/****************************************************/
+/* D3D Vertex Shader Class */
+/****************************************************/
+CD3DVertexShader::CD3DVertexShader()
+{
+ m_VS = nullptr;
+ m_vertexLayout = nullptr;
+ m_VSBuffer = nullptr;
+ m_inputLayout = nullptr;
+ m_vertexLayoutSize = 0;
+ m_inited = false;
+}
+
+CD3DVertexShader::~CD3DVertexShader()
+{
+ Release();
+}
+
+void CD3DVertexShader::Release()
+{
+ Unregister();
+ ReleaseShader();
+ m_VSBuffer = nullptr;
+ if (m_vertexLayout)
+ {
+ delete[] m_vertexLayout;
+ m_vertexLayout = nullptr;
+ }
+}
+
+void CD3DVertexShader::ReleaseShader()
+{
+ UnbindShader();
+
+ m_VS = nullptr;
+ m_inputLayout = nullptr;
+ m_inited = false;
+}
+
+bool CD3DVertexShader::Create(const std::wstring& vertexFile, D3D11_INPUT_ELEMENT_DESC* vertexLayout, unsigned int vertexLayoutSize)
+{
+ ReleaseShader();
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (!pDevice)
+ return false;
+
+ if (FAILED(D3DReadFileToBlob(vertexFile.c_str(), m_VSBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "failed to load the vertex shader.");
+ return false;
+ }
+
+ if (vertexLayout && vertexLayoutSize)
+ {
+ m_vertexLayoutSize = vertexLayoutSize;
+ m_vertexLayout = new D3D11_INPUT_ELEMENT_DESC[vertexLayoutSize];
+ for (unsigned int i = 0; i < vertexLayoutSize; ++i)
+ m_vertexLayout[i] = vertexLayout[i];
+ }
+ else
+ return false;
+
+ m_inited = CreateInternal();
+
+ if (m_inited)
+ Register();
+
+ return m_inited;
+}
+
+bool CD3DVertexShader::Create(const void* code, size_t codeLength, D3D11_INPUT_ELEMENT_DESC* vertexLayout, unsigned int vertexLayoutSize)
+{
+ ReleaseShader();
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (!pDevice)
+ return false;
+
+ // trick to load bytecode into ID3DBlob
+ if (FAILED(D3DStripShader(code, codeLength, D3DCOMPILER_STRIP_REFLECTION_DATA, m_VSBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "failed to load the vertex shader.");
+ return false;
+ }
+
+ if (vertexLayout && vertexLayoutSize)
+ {
+ m_vertexLayoutSize = vertexLayoutSize;
+ m_vertexLayout = new D3D11_INPUT_ELEMENT_DESC[vertexLayoutSize];
+ for (unsigned int i = 0; i < vertexLayoutSize; ++i)
+ m_vertexLayout[i] = vertexLayout[i];
+ }
+ else
+ return false;
+
+ m_inited = CreateInternal();
+
+ if (m_inited)
+ Register();
+
+ return m_inited;
+}
+
+bool CD3DVertexShader::CreateInternal()
+{
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ CLog::LogF(LOGDEBUG, "creating vertex shader.");
+
+ // Create the vertex shader
+ if (FAILED(pDevice->CreateVertexShader(m_VSBuffer->GetBufferPointer(), m_VSBuffer->GetBufferSize(), nullptr, m_VS.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "failed to Create the vertex shader.");
+ m_VSBuffer = nullptr;
+ return false;
+ }
+
+ CLog::LogF(LOGDEBUG, "creating input layout.");
+
+ if (FAILED(pDevice->CreateInputLayout(m_vertexLayout, m_vertexLayoutSize, m_VSBuffer->GetBufferPointer(), m_VSBuffer->GetBufferSize(), m_inputLayout.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "failed to create the input layout.");
+ return false;
+ }
+
+ return true;
+}
+
+void CD3DVertexShader::BindShader()
+{
+ if (!m_inited)
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ if (!pContext)
+ return;
+
+ pContext->IASetInputLayout(m_inputLayout.Get());
+ pContext->VSSetShader(m_VS.Get(), nullptr, 0);
+}
+
+void CD3DVertexShader::UnbindShader()
+{
+ if (!m_inited)
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ pContext->IASetInputLayout(nullptr);
+ pContext->VSSetShader(nullptr, nullptr, 0);
+}
+
+void CD3DVertexShader::OnCreateDevice()
+{
+ if (m_VSBuffer && !m_VS)
+ m_inited = CreateInternal();
+}
+
+void CD3DVertexShader::OnDestroyDevice(bool fatal)
+{
+ ReleaseShader();
+}
+
+/****************************************************/
+/* D3D Pixel Shader Class */
+/****************************************************/
+CD3DPixelShader::CD3DPixelShader()
+{
+ m_PS = nullptr;
+ m_PSBuffer = nullptr;
+ m_inited = false;
+}
+
+CD3DPixelShader::~CD3DPixelShader()
+{
+ Release();
+}
+
+void CD3DPixelShader::Release()
+{
+ Unregister();
+ ReleaseShader();
+ m_PSBuffer = nullptr;
+}
+
+void CD3DPixelShader::ReleaseShader()
+{
+ m_inited = false;
+ m_PS = nullptr;
+}
+
+bool CD3DPixelShader::Create(const std::wstring& wstrFile)
+{
+ ReleaseShader();
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (!pDevice)
+ return false;
+
+ if (FAILED(D3DReadFileToBlob(wstrFile.c_str(), m_PSBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to load the vertex shader.");
+ return false;
+ }
+
+ m_inited = CreateInternal();
+
+ if (m_inited)
+ Register();
+
+ return m_inited;
+}
+
+bool CD3DPixelShader::Create(const void* code, size_t codeLength)
+{
+ ReleaseShader();
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ if (!pDevice)
+ return false;
+
+ // trick to load bytecode into ID3DBlob
+ if (FAILED(D3DStripShader(code, codeLength, D3DCOMPILER_STRIP_REFLECTION_DATA, m_PSBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to load the vertex shader.");
+ return false;
+ }
+
+ m_inited = CreateInternal();
+
+ if (m_inited)
+ Register();
+
+ return m_inited;
+}
+
+bool CD3DPixelShader::CreateInternal()
+{
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ CLog::LogF(LOGDEBUG, "Create the pixel shader.");
+
+ // Create the vertex shader
+ if (FAILED(pDevice->CreatePixelShader(m_PSBuffer->GetBufferPointer(), m_PSBuffer->GetBufferSize(), nullptr, m_PS.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to Create the pixel shader.");
+ m_PSBuffer = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void CD3DPixelShader::BindShader()
+{
+ if (!m_inited)
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ if (!pContext)
+ return;
+
+ pContext->PSSetShader(m_PS.Get(), nullptr, 0);
+}
+
+void CD3DPixelShader::UnbindShader()
+{
+ if (!m_inited)
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ pContext->IASetInputLayout(nullptr);
+ pContext->VSSetShader(nullptr, nullptr, 0);
+}
+
+void CD3DPixelShader::OnCreateDevice()
+{
+ if (m_PSBuffer && !m_PS)
+ m_inited = CreateInternal();
+}
+
+void CD3DPixelShader::OnDestroyDevice(bool fatal)
+{
+ ReleaseShader();
+}
diff --git a/xbmc/guilib/D3DResource.h b/xbmc/guilib/D3DResource.h
new file mode 100644
index 0000000..e7f6517
--- /dev/null
+++ b/xbmc/guilib/D3DResource.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIColorManager.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h"
+
+#include <map>
+
+#include <DirectXMath.h>
+#include <d3dx11effect.h>
+#include <wrl/client.h>
+
+#define KODI_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT 4
+
+typedef enum SHADER_METHOD {
+ SHADER_METHOD_RENDER_DEFAULT,
+ SHADER_METHOD_RENDER_TEXTURE_NOBLEND,
+ SHADER_METHOD_RENDER_FONT,
+ SHADER_METHOD_RENDER_TEXTURE_BLEND,
+ SHADER_METHOD_RENDER_MULTI_TEXTURE_BLEND,
+ SHADER_METHOD_RENDER_STEREO_INTERLACED_LEFT,
+ SHADER_METHOD_RENDER_STEREO_INTERLACED_RIGHT,
+ SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_LEFT,
+ SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_RIGHT,
+ SHADER_METHOD_RENDER_COUNT
+} _SHADER_METHOD;
+
+class ID3DResource
+{
+public:
+ virtual ~ID3DResource() {}
+
+ virtual void OnDestroyDevice(bool fatal)=0;
+ virtual void OnCreateDevice()=0;
+
+protected:
+ void Register();
+ void Unregister();
+
+ bool m_bRegistered = false;
+};
+
+class CD3DHelper
+{
+public:
+ static inline void XMStoreColor(float* floats, DWORD dword)
+ {
+ floats[0] = float((dword >> 16) & 0xFF) * (1.0f / 255.0f); // r
+ floats[1] = float((dword >> 8) & 0xFF) * (1.0f / 255.0f); // g
+ floats[2] = float((dword >> 0) & 0xFF) * (1.0f / 255.0f); // b
+ floats[3] = float((dword >> 24) & 0xFF) * (1.0f / 255.0f); // a
+ }
+
+ static inline void XMStoreColor(DirectX::XMFLOAT4* floats, DWORD dword)
+ {
+ XMStoreColor(reinterpret_cast<float*>(floats), dword);
+ }
+
+ static inline void XMStoreColor(float* floats, unsigned char a, unsigned char r, unsigned char g, unsigned char b)
+ {
+ floats[0] = r * (1.0f / 255.0f);
+ floats[1] = g * (1.0f / 255.0f);
+ floats[2] = b * (1.0f / 255.0f);
+ floats[3] = a * (1.0f / 255.0f);
+ }
+
+ static inline void XMStoreColor(DirectX::XMFLOAT4* floats, unsigned char a, unsigned char r, unsigned char g, unsigned char b)
+ {
+ XMStoreColor(reinterpret_cast<float*>(floats), a, r, g, b);
+ }
+
+ // helper function to properly "clear" shader resources
+ static inline void PSClearShaderResources(ID3D11DeviceContext* pContext)
+ {
+ ID3D11ShaderResourceView* shader_resource_views[KODI_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
+ pContext->PSSetShaderResources(0, ARRAYSIZE(shader_resource_views), shader_resource_views);
+ }
+
+ static size_t BitsPerPixel(DXGI_FORMAT fmt);
+};
+
+class CD3DTexture : public ID3DResource
+{
+public:
+ CD3DTexture();
+ virtual ~CD3DTexture();
+
+ bool Create(UINT width, UINT height, UINT mipLevels, D3D11_USAGE usage, DXGI_FORMAT format, const void* pInitData = nullptr, unsigned int srcPitch = 0);
+
+ void Release();
+ bool GetDesc(D3D11_TEXTURE2D_DESC *desc) const;
+ bool LockRect(UINT subresource, D3D11_MAPPED_SUBRESOURCE *res, D3D11_MAP mapType) const;
+ bool UnlockRect(UINT subresource) const;
+
+ // Accessors
+ ID3D11Texture2D* Get() const { return m_texture.Get(); }
+ ID3D11ShaderResourceView* GetShaderResource(DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN);
+ ID3D11ShaderResourceView** GetAddressOfSRV(DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN);
+ ID3D11RenderTargetView* GetRenderTarget();
+ ID3D11RenderTargetView** GetAddressOfRTV();
+ UINT GetWidth() const { return m_width; }
+ UINT GetHeight() const { return m_height; }
+ DXGI_FORMAT GetFormat() const { return m_format; }
+ void GenerateMipmaps();
+
+ // static methods
+ static void DrawQuad(const CPoint points[4],
+ UTILS::COLOR::Color color,
+ CD3DTexture* texture,
+ const CRect* texCoords,
+ SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ static void DrawQuad(const CPoint points[4],
+ UTILS::COLOR::Color color,
+ unsigned numViews,
+ ID3D11ShaderResourceView** view,
+ const CRect* texCoords,
+ SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CD3DTexture* texture,
+ const CRect* texCoords,
+ SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ unsigned numViews,
+ ID3D11ShaderResourceView** view,
+ const CRect* texCoords,
+ SHADER_METHOD options = SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+protected:
+ ID3D11RenderTargetView* GetRenderTargetInternal(unsigned idx = 0);
+ unsigned int GetMemoryUsage(unsigned int pitch) const;
+ bool CreateInternal(const void* pInitData = nullptr, unsigned int srcPitch = 0);
+
+ void SaveTexture();
+ void RestoreTexture();
+
+ // saved data
+ BYTE* m_data;
+ // creation parameters
+ UINT m_width;
+ UINT m_height;
+ UINT m_mipLevels;
+ UINT m_pitch;
+ UINT m_bindFlags;
+ UINT m_cpuFlags;
+ UINT m_viewIdx;
+ D3D11_USAGE m_usage;
+ DXGI_FORMAT m_format;
+
+ // created texture
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> m_texture;
+ Microsoft::WRL::ComPtr<ID3D11RenderTargetView> m_renderTargets[2];
+ // store views in different formats
+ std::map<DXGI_FORMAT, Microsoft::WRL::ComPtr<ID3D11ShaderResourceView>> m_views;
+};
+
+typedef std::map<std::string, std::string> DefinesMap;
+
+class CD3DEffect : public ID3DResource, public ID3DInclude
+{
+public:
+ CD3DEffect();
+ virtual ~CD3DEffect();
+ bool Create(const std::string &effectString, DefinesMap* defines);
+ void Release();
+ bool SetFloatArray(LPCSTR handle, const float* val, unsigned int count);
+ bool SetMatrix(LPCSTR handle, const float* mat);
+ bool SetTechnique(LPCSTR handle);
+ bool SetTexture(LPCSTR handle, CD3DTexture &texture);
+ bool SetResources(LPCSTR handle, ID3D11ShaderResourceView** ppSRViews, size_t count);
+ bool SetConstantBuffer(LPCSTR handle, ID3D11Buffer *buffer);
+ bool SetScalar(LPCSTR handle, float value);
+ bool Begin(UINT *passes, DWORD flags);
+ bool BeginPass(UINT pass);
+ bool EndPass();
+ bool End();
+
+ ID3DX11Effect* Get() const { return m_effect.Get(); }
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+ // ID3DInclude interface
+ __declspec(nothrow) HRESULT __stdcall Open(D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes) override;
+ __declspec(nothrow) HRESULT __stdcall Close(LPCVOID pData) override;
+
+private:
+ bool CreateEffect();
+
+ std::string m_effectString;
+ DefinesMap m_defines;
+ Microsoft::WRL::ComPtr<ID3DX11Effect> m_effect;
+ Microsoft::WRL::ComPtr<ID3DX11EffectTechnique> m_techniquie;
+ Microsoft::WRL::ComPtr<ID3DX11EffectPass> m_currentPass;
+};
+
+class CD3DBuffer : public ID3DResource
+{
+public:
+ CD3DBuffer();
+ virtual ~CD3DBuffer();
+ bool Create(D3D11_BIND_FLAG type, UINT count, UINT stride, DXGI_FORMAT format, D3D11_USAGE usage, const void* initData = nullptr);
+ bool Map(void** resource);
+ bool Unmap();
+ void Release();
+ unsigned int GetStride() { return m_stride; }
+ DXGI_FORMAT GetFormat() { return m_format; }
+ ID3D11Buffer* Get() const { return m_buffer.Get(); }
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+private:
+ bool CreateBuffer(const void *pData);
+
+ // saved data
+ BYTE* m_data;
+ UINT m_length;
+ UINT m_stride;
+ UINT m_cpuFlags;
+ DXGI_FORMAT m_format;
+ D3D11_USAGE m_usage;
+ D3D11_BIND_FLAG m_type;
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_buffer;
+};
+
+class CD3DVertexShader : public ID3DResource
+{
+public:
+ CD3DVertexShader();
+ ~CD3DVertexShader();
+
+ bool Create(const std::wstring& vertexFile, D3D11_INPUT_ELEMENT_DESC* vertexLayout, unsigned int vertexLayoutSize);
+ bool Create(const void* code, size_t codeLength, D3D11_INPUT_ELEMENT_DESC* vertexLayout, unsigned int vertexLayoutSize);
+ void ReleaseShader();
+ void BindShader();
+ void UnbindShader();
+ void Release();
+ bool IsInited() { return m_inited; }
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+private:
+ bool CreateInternal();
+
+ bool m_inited;
+ unsigned int m_vertexLayoutSize;
+ D3D11_INPUT_ELEMENT_DESC* m_vertexLayout;
+ Microsoft::WRL::ComPtr<ID3DBlob> m_VSBuffer;
+ Microsoft::WRL::ComPtr<ID3D11VertexShader> m_VS;
+ Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout;
+};
+
+class CD3DPixelShader : public ID3DResource
+{
+public:
+ CD3DPixelShader();
+ ~CD3DPixelShader();
+
+ bool Create(const std::wstring& wstrFile);
+ bool Create(const void* code, size_t codeLength);
+ void ReleaseShader();
+ void BindShader();
+ void UnbindShader();
+ void Release();
+ bool IsInited() { return m_inited; }
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+private:
+ bool CreateInternal();
+
+ bool m_inited;
+ Microsoft::WRL::ComPtr<ID3DBlob> m_PSBuffer;
+ Microsoft::WRL::ComPtr<ID3D11PixelShader> m_PS;
+};
diff --git a/xbmc/guilib/DDSImage.cpp b/xbmc/guilib/DDSImage.cpp
new file mode 100644
index 0000000..124352d
--- /dev/null
+++ b/xbmc/guilib/DDSImage.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DDSImage.h"
+
+#include "XBTF.h"
+#include "filesystem/File.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <string.h>
+using namespace XFILE;
+
+CDDSImage::CDDSImage()
+{
+ m_data = NULL;
+ memset(&m_desc, 0, sizeof(m_desc));
+}
+
+CDDSImage::CDDSImage(unsigned int width, unsigned int height, unsigned int format)
+{
+ m_data = NULL;
+ Allocate(width, height, format);
+}
+
+CDDSImage::~CDDSImage()
+{
+ delete[] m_data;
+}
+
+unsigned int CDDSImage::GetWidth() const
+{
+ return m_desc.width;
+}
+
+unsigned int CDDSImage::GetHeight() const
+{
+ return m_desc.height;
+}
+
+unsigned int CDDSImage::GetFormat() const
+{
+ if (m_desc.pixelFormat.flags & DDPF_RGB)
+ return 0; // Not supported
+ if (m_desc.pixelFormat.flags & DDPF_FOURCC)
+ {
+ if (strncmp((const char *)&m_desc.pixelFormat.fourcc, "DXT1", 4) == 0)
+ return XB_FMT_DXT1;
+ if (strncmp((const char *)&m_desc.pixelFormat.fourcc, "DXT3", 4) == 0)
+ return XB_FMT_DXT3;
+ if (strncmp((const char *)&m_desc.pixelFormat.fourcc, "DXT5", 4) == 0)
+ return XB_FMT_DXT5;
+ if (strncmp((const char *)&m_desc.pixelFormat.fourcc, "ARGB", 4) == 0)
+ return XB_FMT_A8R8G8B8;
+ }
+ return 0;
+}
+
+unsigned int CDDSImage::GetSize() const
+{
+ return m_desc.linearSize;
+}
+
+unsigned char *CDDSImage::GetData() const
+{
+ return m_data;
+}
+
+bool CDDSImage::ReadFile(const std::string &inputFile)
+{
+ // open the file
+ CFile file;
+ if (!file.Open(inputFile))
+ return false;
+
+ // read the header
+ uint32_t magic;
+ if (file.Read(&magic, 4) != 4)
+ return false;
+ if (file.Read(&m_desc, sizeof(m_desc)) != sizeof(m_desc))
+ return false;
+ if (!GetFormat())
+ return false; // not supported
+
+ // allocate our data
+ m_data = new unsigned char[m_desc.linearSize];
+ if (!m_data)
+ return false;
+
+ // and read it in
+ if (file.Read(m_data, m_desc.linearSize) != static_cast<ssize_t>(m_desc.linearSize))
+ return false;
+
+ file.Close();
+ return true;
+}
+
+unsigned int CDDSImage::GetStorageRequirements(unsigned int width, unsigned int height, unsigned int format)
+{
+ switch (format)
+ {
+ case XB_FMT_DXT1:
+ return ((width + 3) / 4) * ((height + 3) / 4) * 8;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ return ((width + 3) / 4) * ((height + 3) / 4) * 16;
+ case XB_FMT_A8R8G8B8:
+ default:
+ return width * height * 4;
+ }
+}
+
+void CDDSImage::Allocate(unsigned int width, unsigned int height, unsigned int format)
+{
+ memset(&m_desc, 0, sizeof(m_desc));
+ m_desc.size = sizeof(m_desc);
+ m_desc.flags = ddsd_caps | ddsd_pixelformat | ddsd_width | ddsd_height | ddsd_linearsize;
+ m_desc.height = height;
+ m_desc.width = width;
+ m_desc.linearSize = GetStorageRequirements(width, height, format);
+ m_desc.pixelFormat.size = sizeof(m_desc.pixelFormat);
+ m_desc.pixelFormat.flags = ddpf_fourcc;
+ memcpy(&m_desc.pixelFormat.fourcc, GetFourCC(format), 4);
+ m_desc.caps.flags1 = ddscaps_texture;
+ delete[] m_data;
+ m_data = new unsigned char[m_desc.linearSize];
+}
+
+const char *CDDSImage::GetFourCC(unsigned int format)
+{
+ switch (format)
+ {
+ case XB_FMT_DXT1:
+ return "DXT1";
+ case XB_FMT_DXT3:
+ return "DXT3";
+ case XB_FMT_DXT5:
+ return "DXT5";
+ case XB_FMT_A8R8G8B8:
+ default:
+ return "ARGB";
+ }
+}
diff --git a/xbmc/guilib/DDSImage.h b/xbmc/guilib/DDSImage.h
new file mode 100644
index 0000000..8c8bca2
--- /dev/null
+++ b/xbmc/guilib/DDSImage.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+class CDDSImage
+{
+public:
+ CDDSImage();
+ CDDSImage(unsigned int width, unsigned int height, unsigned int format);
+ ~CDDSImage();
+
+ unsigned int GetWidth() const;
+ unsigned int GetHeight() const;
+ unsigned int GetFormat() const;
+ unsigned int GetSize() const;
+ unsigned char *GetData() const;
+
+ bool ReadFile(const std::string &file);
+
+private:
+ void Allocate(unsigned int width, unsigned int height, unsigned int format);
+ static const char *GetFourCC(unsigned int format);
+
+ static unsigned int GetStorageRequirements(unsigned int width, unsigned int height, unsigned int format);
+ enum {
+ ddsd_caps = 0x00000001,
+ ddsd_height = 0x00000002,
+ ddsd_width = 0x00000004,
+ ddsd_pitch = 0x00000008,
+ ddsd_pixelformat = 0x00001000,
+ ddsd_mipmapcount = 0x00020000,
+ ddsd_linearsize = 0x00080000,
+ ddsd_depth = 0x00800000
+ };
+
+ enum {
+ ddpf_alphapixels = 0x00000001,
+ ddpf_fourcc = 0x00000004,
+ ddpf_rgb = 0x00000040
+ };
+
+ enum {
+ ddscaps_complex = 0x00000008,
+ ddscaps_texture = 0x00001000,
+ ddscaps_mipmap = 0x00400000
+ };
+
+ #pragma pack(push, 2)
+ typedef struct
+ {
+ uint32_t size;
+ uint32_t flags;
+ uint32_t fourcc;
+ uint32_t rgbBitCount;
+ uint32_t rBitMask;
+ uint32_t gBitMask;
+ uint32_t bBitMask;
+ uint32_t aBitMask;
+ } ddpixelformat;
+
+#define DDPF_ALPHAPIXELS 0x00000001
+#define DDPF_ALPHA 0x00000002
+#define DDPF_FOURCC 0x00000004
+#define DDPF_RGB 0x00000040
+#define DDPF_YUV 0x00000200
+#define DDPF_LUMINANCE 0x00020000
+
+ typedef struct
+ {
+ uint32_t flags1;
+ uint32_t flags2;
+ uint32_t reserved[2];
+ } ddcaps2;
+
+ typedef struct
+ {
+ uint32_t size;
+ uint32_t flags;
+ uint32_t height;
+ uint32_t width;
+ uint32_t linearSize;
+ uint32_t depth;
+ uint32_t mipmapcount;
+ uint32_t reserved[11];
+ ddpixelformat pixelFormat;
+ ddcaps2 caps;
+ uint32_t reserved2;
+ } ddsurfacedesc2;
+ #pragma pack(pop)
+
+ ddsurfacedesc2 m_desc;
+ unsigned char *m_data;
+};
diff --git a/xbmc/guilib/DirectXGraphics.cpp b/xbmc/guilib/DirectXGraphics.cpp
new file mode 100644
index 0000000..5bab895
--- /dev/null
+++ b/xbmc/guilib/DirectXGraphics.cpp
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirectXGraphics.h"
+
+#include "Texture.h"
+#include "XBTF.h"
+
+LPVOID XPhysicalAlloc(SIZE_T s, DWORD ulPhysicalAddress, DWORD ulAlignment, DWORD flProtect)
+{
+ return malloc(s);
+}
+
+void XPhysicalFree(LPVOID lpAddress)
+{
+ free(lpAddress);
+}
+
+DWORD GetD3DFormat(XB_D3DFORMAT format)
+{
+#ifndef MAKEFOURCC
+#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
+ ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
+ ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
+#endif
+ switch (format)
+ {
+ case XB_D3DFMT_A8R8G8B8:
+ case XB_D3DFMT_LIN_A8R8G8B8:
+ case XB_D3DFMT_P8:
+ return 21;
+ case XB_D3DFMT_DXT1:
+ return MAKEFOURCC('D', 'X', 'T', '1');
+ case XB_D3DFMT_DXT2:
+ return MAKEFOURCC('D', 'X', 'T', '2');
+ case XB_D3DFMT_DXT4:
+ return MAKEFOURCC('D', 'X', 'T', '4');
+ default:
+ return 0;
+ }
+}
+
+DWORD BytesPerPixelFromFormat(XB_D3DFORMAT format)
+{
+ switch (format)
+ {
+ case XB_D3DFMT_A8R8G8B8:
+ case XB_D3DFMT_LIN_A8R8G8B8:
+ case XB_D3DFMT_DXT4:
+ return 4;
+ case XB_D3DFMT_P8:
+ case XB_D3DFMT_DXT1:
+ case XB_D3DFMT_DXT2:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+bool IsPalettedFormat(XB_D3DFORMAT format)
+{
+ if (format == XB_D3DFMT_P8)
+ return true;
+ return false;
+}
+
+void ParseTextureHeader(D3DTexture *tex, XB_D3DFORMAT &fmt, DWORD &width, DWORD &height, DWORD &pitch, DWORD &offset)
+{
+ fmt = (XB_D3DFORMAT)((tex->Format & 0xff00) >> 8);
+ offset = tex->Data;
+ if (tex->Size)
+ {
+ width = (tex->Size & 0x00000fff) + 1;
+ height = ((tex->Size & 0x00fff000) >> 12) + 1;
+ pitch = (((tex->Size & 0xff000000) >> 24) + 1) << 6;
+ }
+ else
+ {
+ width = 1 << ((tex->Format & 0x00f00000) >> 20);
+ height = 1 << ((tex->Format & 0x0f000000) >> 24);
+ pitch = width * BytesPerPixelFromFormat(fmt);
+ }
+}
+
+bool IsSwizzledFormat(XB_D3DFORMAT format)
+{
+ switch (format)
+ {
+ case XB_D3DFMT_A8R8G8B8:
+ case XB_D3DFMT_P8:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Unswizzle.
+// Format is:
+
+// 00 01 04 05
+// 02 03 06 07
+// 08 09 12 13
+// 10 11 14 15 ...
+
+// Currently only works for 32bit and 8bit textures, with power of 2 width and height
+void Unswizzle(const void *src, unsigned int depth, unsigned int width, unsigned int height, void *dest)
+{
+ if (height == 0 || width == 0)
+ return;
+
+ for (UINT y = 0; y < height; y++)
+ {
+ UINT sy = 0;
+ if (y < width)
+ {
+ for (int bit = 0; bit < 16; bit++)
+ sy |= ((y >> bit) & 1) << (2*bit);
+ sy <<= 1; // y counts twice
+ }
+ else
+ {
+ UINT y_mask = y % width;
+ for (int bit = 0; bit < 16; bit++)
+ sy |= ((y_mask >> bit) & 1) << (2*bit);
+ sy <<= 1; // y counts twice
+ sy += (y / width) * width * width;
+ }
+ BYTE *d = (BYTE *)dest + y * width * depth;
+ for (UINT x = 0; x < width; x++)
+ {
+ UINT sx = 0;
+ if (x < height * 2)
+ {
+ for (int bit = 0; bit < 16; bit++)
+ sx |= ((x >> bit) & 1) << (2*bit);
+ }
+ else
+ {
+ int x_mask = x % (2*height);
+ for (int bit = 0; bit < 16; bit++)
+ sx |= ((x_mask >> bit) & 1) << (2*bit);
+ sx += (x / (2 * height)) * 2 * height * height;
+ }
+ BYTE *s = (BYTE *)src + (sx + sy)*depth;
+ for (unsigned int i = 0; i < depth; ++i)
+ *d++ = *s++;
+ }
+ }
+}
+
+void DXT1toARGB(const void *src, void *dest, unsigned int destWidth)
+{
+ const BYTE *b = (const BYTE *)src;
+ // colour is in R5G6B5 format, convert to R8G8B8
+ DWORD colour[4];
+ BYTE red[4];
+ BYTE green[4];
+ BYTE blue[4];
+ for (int i = 0; i < 2; i++)
+ {
+ red[i] = b[2*i+1] & 0xf8;
+ green[i] = ((b[2*i+1] & 0x7) << 5) | ((b[2*i] & 0xe0) >> 3);
+ blue[i] = (b[2*i] & 0x1f) << 3;
+ colour[i] = (red[i] << 16) | (green[i] << 8) | blue[i];
+ }
+ if (colour[0] > colour[1])
+ {
+ red[2] = (2 * red[0] + red[1] + 1) / 3;
+ green[2] = (2 * green[0] + green[1] + 1) / 3;
+ blue[2] = (2 * blue[0] + blue[1] + 1) / 3;
+ red[3] = (red[0] + 2 * red[1] + 1) / 3;
+ green[3] = (green[0] + 2 * green[1] + 1) / 3;
+ blue[3] = (blue[0] + 2 * blue[1] + 1) / 3;
+ for (int i = 0; i < 4; i++)
+ colour[i] = (red[i] << 16) | (green[i] << 8) | blue[i] | 0xFF000000;
+ }
+ else
+ {
+ red[2] = (red[0] + red[1]) / 2;
+ green[2] = (green[0] + green[1]) / 2;
+ blue[2] = (blue[0] + blue[1]) / 2;
+ for (int i = 0; i < 3; i++)
+ colour[i] = (red[i] << 16) | (green[i] << 8) | blue[i] | 0xFF000000;
+ colour[3] = 0; // transparent
+ }
+ // ok, now grab the bits
+ for (int y = 0; y < 4; y++)
+ {
+ DWORD *d = (DWORD *)dest + destWidth * y;
+ *d++ = colour[(b[4 + y] & 0x03)];
+ *d++ = colour[(b[4 + y] & 0x0c) >> 2];
+ *d++ = colour[(b[4 + y] & 0x30) >> 4];
+ *d++ = colour[(b[4 + y] & 0xc0) >> 6];
+ }
+}
+
+void DXT4toARGB(const void *src, void *dest, unsigned int destWidth)
+{
+ const BYTE *b = (const BYTE *)src;
+ BYTE alpha[8];
+ alpha[0] = b[0];
+ alpha[1] = b[1];
+ if (alpha[0] > alpha[1])
+ {
+ alpha[2] = (6 * alpha[0] + 1 * alpha[1]+ 3) / 7;
+ alpha[3] = (5 * alpha[0] + 2 * alpha[1] + 3) / 7; // bit code 011
+ alpha[4] = (4 * alpha[0] + 3 * alpha[1] + 3) / 7; // bit code 100
+ alpha[5] = (3 * alpha[0] + 4 * alpha[1] + 3) / 7; // bit code 101
+ alpha[6] = (2 * alpha[0] + 5 * alpha[1] + 3) / 7; // bit code 110
+ alpha[7] = (1 * alpha[0] + 6 * alpha[1] + 3) / 7; // bit code 111
+ }
+ else
+ {
+ alpha[2] = (4 * alpha[0] + 1 * alpha[1] + 2) / 5; // Bit code 010
+ alpha[3] = (3 * alpha[0] + 2 * alpha[1] + 2) / 5; // Bit code 011
+ alpha[4] = (2 * alpha[0] + 3 * alpha[1] + 2) / 5; // Bit code 100
+ alpha[5] = (1 * alpha[0] + 4 * alpha[1] + 2) / 5; // Bit code 101
+ alpha[6] = 0; // Bit code 110
+ alpha[7] = 255; // Bit code 111
+ }
+ // ok, now grab the bits
+ BYTE a[4][4];
+ a[0][0] = alpha[(b[2] & 0xe0) >> 5];
+ a[0][1] = alpha[(b[2] & 0x1c) >> 2];
+ a[0][2] = alpha[((b[2] & 0x03) << 1) | ((b[3] & 0x80) >> 7)];
+ a[0][3] = alpha[(b[3] & 0x70) >> 4];
+ a[1][0] = alpha[(b[3] & 0x0e) >> 1];
+ a[1][1] = alpha[((b[3] & 0x01) << 2) | ((b[4] & 0xc0) >> 6)];
+ a[1][2] = alpha[(b[4] & 0x38) >> 3];
+ a[1][3] = alpha[(b[4] & 0x07)];
+ a[2][0] = alpha[(b[5] & 0xe0) >> 5];
+ a[2][1] = alpha[(b[5] & 0x1c) >> 2];
+ a[2][2] = alpha[((b[5] & 0x03) << 1) | ((b[6] & 0x80) >> 7)];
+ a[2][3] = alpha[(b[6] & 0x70) >> 4];
+ a[3][0] = alpha[(b[6] & 0x0e) >> 1];
+ a[3][1] = alpha[((b[6] & 0x01) << 2) | ((b[7] & 0xc0) >> 6)];
+ a[3][2] = alpha[(b[7] & 0x38) >> 3];
+ a[3][3] = alpha[(b[7] & 0x07)];
+
+ b = (BYTE *)src + 8;
+ // colour is in R5G6B5 format, convert to R8G8B8
+ DWORD colour[4];
+ BYTE red[4];
+ BYTE green[4];
+ BYTE blue[4];
+ for (int i = 0; i < 2; i++)
+ {
+ red[i] = b[2*i+1] & 0xf8;
+ green[i] = ((b[2*i+1] & 0x7) << 5) | ((b[2*i] & 0xe0) >> 3);
+ blue[i] = (b[2*i] & 0x1f) << 3;
+ }
+ red[2] = (2 * red[0] + red[1] + 1) / 3;
+ green[2] = (2 * green[0] + green[1] + 1) / 3;
+ blue[2] = (2 * blue[0] + blue[1] + 1) / 3;
+ red[3] = (red[0] + 2 * red[1] + 1) / 3;
+ green[3] = (green[0] + 2 * green[1] + 1) / 3;
+ blue[3] = (blue[0] + 2 * blue[1] + 1) / 3;
+ for (int i = 0; i < 4; i++)
+ colour[i] = (red[i] << 16) | (green[i] << 8) | blue[i];
+ // and assign them to our texture
+ for (int y = 0; y < 4; y++)
+ {
+ DWORD *d = (DWORD *)dest + destWidth * y;
+ *d++ = colour[(b[4 + y] & 0x03)] | (a[y][0] << 24);
+ *d++ = colour[(b[4 + y] & 0x0e) >> 2] | (a[y][1] << 24);
+ *d++ = colour[(b[4 + y] & 0x30) >> 4] | (a[y][2] << 24);
+ *d++ = colour[(b[4 + y] & 0xe0) >> 6] | (a[y][3] << 24);
+ }
+
+}
+
+void ConvertDXT1(const void *src, unsigned int width, unsigned int height, void *dest)
+{
+ for (unsigned int y = 0; y < height; y += 4)
+ {
+ for (unsigned int x = 0; x < width; x += 4)
+ {
+ const BYTE *s = (const BYTE *)src + y * width / 2 + x * 2;
+ DWORD *d = (DWORD *)dest + y * width + x;
+ DXT1toARGB(s, d, width);
+ }
+ }
+}
+
+void ConvertDXT4(const void *src, unsigned int width, unsigned int height, void *dest)
+{
+ // [4 4 4 4][4 4 4 4]
+ //
+ //
+ //
+ for (unsigned int y = 0; y < height; y += 4)
+ {
+ for (unsigned int x = 0; x < width; x += 4)
+ {
+ const BYTE *s = (const BYTE *)src + y * width + x * 4;
+ DWORD *d = (DWORD *)dest + y * width + x;
+ DXT4toARGB(s, d, width);
+ }
+ }
+}
+
+void GetTextureFromData(D3DTexture* pTex, void* texData, std::unique_ptr<CTexture>* ppTexture)
+{
+ XB_D3DFORMAT fmt;
+ DWORD width, height, pitch, offset;
+ ParseTextureHeader(pTex, fmt, width, height, pitch, offset);
+
+ *ppTexture = CTexture::CreateTexture(width, height, XB_FMT_A8R8G8B8);
+
+ if (*ppTexture)
+ {
+ BYTE *texDataStart = (BYTE *)texData;
+ COLOR *color = (COLOR *)texData;
+ texDataStart += offset;
+/* DXMERGE - We should really support DXT1,DXT2 and DXT4 in both renderers
+ Perhaps we should extend CTexture::Update() to support a bunch of different texture types
+ Rather than assuming linear 32bits
+ We could just override, as at least then all the loading code from various texture formats
+ will be in one place
+
+ BYTE *dstPixels = (BYTE *)lr.pBits;
+ DWORD destPitch = lr.Pitch;
+ if (fmt == XB_D3DFMT_DXT1) // Not sure if these are 100% correct, but they seem to work :P
+ {
+ pitch /= 2;
+ destPitch /= 4;
+ }
+ else if (fmt == XB_D3DFMT_DXT2)
+ {
+ destPitch /= 4;
+ }
+ else if (fmt == XB_D3DFMT_DXT4)
+ {
+ pitch /= 4;
+ destPitch /= 4;
+ }
+*/
+ if (fmt == XB_D3DFMT_DXT1)
+ {
+ pitch = width * 4;
+ BYTE *decoded = new BYTE[pitch * height];
+ ConvertDXT1(texDataStart, width, height, decoded);
+ texDataStart = decoded;
+ }
+ else if (fmt == XB_D3DFMT_DXT2 || fmt == XB_D3DFMT_DXT4)
+ {
+ pitch = width * 4;
+ BYTE *decoded = new BYTE[pitch * height];
+ ConvertDXT4(texDataStart, width, height, decoded);
+ texDataStart = decoded;
+ }
+ if (IsSwizzledFormat(fmt))
+ { // first we unswizzle
+ BYTE *unswizzled = new BYTE[pitch * height];
+ Unswizzle(texDataStart, BytesPerPixelFromFormat(fmt), width, height, unswizzled);
+ texDataStart = unswizzled;
+ }
+
+ if (IsPalettedFormat(fmt))
+ (*ppTexture)->LoadPaletted(width, height, pitch, XB_FMT_A8R8G8B8, texDataStart, color);
+ else
+ (*ppTexture)->LoadFromMemory(width, height, pitch, XB_FMT_A8R8G8B8, true, texDataStart);
+
+ if (IsSwizzledFormat(fmt) || fmt == XB_D3DFMT_DXT1 || fmt == XB_D3DFMT_DXT2 || fmt == XB_D3DFMT_DXT4)
+ {
+ delete[] texDataStart;
+ }
+ }
+}
diff --git a/xbmc/guilib/DirectXGraphics.h b/xbmc/guilib/DirectXGraphics.h
new file mode 100644
index 0000000..01159da
--- /dev/null
+++ b/xbmc/guilib/DirectXGraphics.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+class CTexture;
+
+#include "gui3d.h"
+
+#include <memory>
+
+LPVOID XPhysicalAlloc(SIZE_T s, DWORD ulPhysicalAddress, DWORD ulAlignment, DWORD flProtect);
+void XPhysicalFree(LPVOID lpAddress);
+
+// XPR header
+struct XPR_HEADER
+{
+ DWORD dwMagic;
+ DWORD dwTotalSize;
+ DWORD dwHeaderSize;
+};
+#define XPR_MAGIC_VALUE (('0' << 24) | ('R' << 16) | ('P' << 8) | 'X')
+
+typedef enum _XB_D3DFORMAT
+{
+ XB_D3DFMT_UNKNOWN = 0xFFFFFFFF,
+
+ /* Swizzled formats */
+
+ XB_D3DFMT_A8R8G8B8 = 0x00000006,
+ XB_D3DFMT_X8R8G8B8 = 0x00000007,
+ XB_D3DFMT_R5G6B5 = 0x00000005,
+ XB_D3DFMT_R6G5B5 = 0x00000027,
+ XB_D3DFMT_X1R5G5B5 = 0x00000003,
+ XB_D3DFMT_A1R5G5B5 = 0x00000002,
+ XB_D3DFMT_A4R4G4B4 = 0x00000004,
+ XB_D3DFMT_A8 = 0x00000019,
+ XB_D3DFMT_A8B8G8R8 = 0x0000003A,
+ XB_D3DFMT_B8G8R8A8 = 0x0000003B,
+ XB_D3DFMT_R4G4B4A4 = 0x00000039,
+ XB_D3DFMT_R5G5B5A1 = 0x00000038,
+ XB_D3DFMT_R8G8B8A8 = 0x0000003C,
+ XB_D3DFMT_R8B8 = 0x00000029,
+ XB_D3DFMT_G8B8 = 0x00000028,
+
+ XB_D3DFMT_P8 = 0x0000000B,
+
+ XB_D3DFMT_L8 = 0x00000000,
+ XB_D3DFMT_A8L8 = 0x0000001A,
+ XB_D3DFMT_AL8 = 0x00000001,
+ XB_D3DFMT_L16 = 0x00000032,
+
+ XB_D3DFMT_V8U8 = 0x00000028,
+ XB_D3DFMT_L6V5U5 = 0x00000027,
+ XB_D3DFMT_X8L8V8U8 = 0x00000007,
+ XB_D3DFMT_Q8W8V8U8 = 0x0000003A,
+ XB_D3DFMT_V16U16 = 0x00000033,
+
+ XB_D3DFMT_D16_LOCKABLE = 0x0000002C,
+ XB_D3DFMT_D16 = 0x0000002C,
+ XB_D3DFMT_D24S8 = 0x0000002A,
+ XB_D3DFMT_F16 = 0x0000002D,
+ XB_D3DFMT_F24S8 = 0x0000002B,
+
+ /* YUV formats */
+
+ XB_D3DFMT_YUY2 = 0x00000024,
+ XB_D3DFMT_UYVY = 0x00000025,
+
+ /* Compressed formats */
+
+ XB_D3DFMT_DXT1 = 0x0000000C,
+ XB_D3DFMT_DXT2 = 0x0000000E,
+ XB_D3DFMT_DXT3 = 0x0000000E,
+ XB_D3DFMT_DXT4 = 0x0000000F,
+ XB_D3DFMT_DXT5 = 0x0000000F,
+
+ /* Linear formats */
+
+ XB_D3DFMT_LIN_A1R5G5B5 = 0x00000010,
+ XB_D3DFMT_LIN_A4R4G4B4 = 0x0000001D,
+ XB_D3DFMT_LIN_A8 = 0x0000001F,
+ XB_D3DFMT_LIN_A8B8G8R8 = 0x0000003F,
+ XB_D3DFMT_LIN_A8R8G8B8 = 0x00000012,
+ XB_D3DFMT_LIN_B8G8R8A8 = 0x00000040,
+ XB_D3DFMT_LIN_G8B8 = 0x00000017,
+ XB_D3DFMT_LIN_R4G4B4A4 = 0x0000003E,
+ XB_D3DFMT_LIN_R5G5B5A1 = 0x0000003D,
+ XB_D3DFMT_LIN_R5G6B5 = 0x00000011,
+ XB_D3DFMT_LIN_R6G5B5 = 0x00000037,
+ XB_D3DFMT_LIN_R8B8 = 0x00000016,
+ XB_D3DFMT_LIN_R8G8B8A8 = 0x00000041,
+ XB_D3DFMT_LIN_X1R5G5B5 = 0x0000001C,
+ XB_D3DFMT_LIN_X8R8G8B8 = 0x0000001E,
+
+ XB_D3DFMT_LIN_A8L8 = 0x00000020,
+ XB_D3DFMT_LIN_AL8 = 0x0000001B,
+ XB_D3DFMT_LIN_L16 = 0x00000035,
+ XB_D3DFMT_LIN_L8 = 0x00000013,
+
+ XB_D3DFMT_LIN_V16U16 = 0x00000036,
+ XB_D3DFMT_LIN_V8U8 = 0x00000017,
+ XB_D3DFMT_LIN_L6V5U5 = 0x00000037,
+ XB_D3DFMT_LIN_X8L8V8U8 = 0x0000001E,
+ XB_D3DFMT_LIN_Q8W8V8U8 = 0x00000012,
+
+ XB_D3DFMT_LIN_D24S8 = 0x0000002E,
+ XB_D3DFMT_LIN_F24S8 = 0x0000002F,
+ XB_D3DFMT_LIN_D16 = 0x00000030,
+ XB_D3DFMT_LIN_F16 = 0x00000031,
+
+ XB_D3DFMT_VERTEXDATA = 100,
+ XB_D3DFMT_INDEX16 = 101,
+
+ XB_D3DFMT_FORCE_DWORD =0x7fffffff
+} XB_D3DFORMAT;
+
+DWORD GetD3DFormat(XB_D3DFORMAT format);
+DWORD BytesPerPixelFromFormat(XB_D3DFORMAT format);
+bool IsPalettedFormat(XB_D3DFORMAT format);
+void ParseTextureHeader(D3DTexture *tex, XB_D3DFORMAT &fmt, DWORD &width, DWORD &height, DWORD &pitch, DWORD &offset);
+bool IsSwizzledFormat(XB_D3DFORMAT format);
+
+#ifndef TARGET_POSIX
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+#endif
+
+#pragma pack(push, 2)
+typedef struct {
+ uint8_t id[2]; // offset
+ uint32_t filesize; // 2
+ uint32_t reserved; // 6
+ uint32_t headersize; // 10
+ uint32_t infoSize; // 14
+ uint32_t width; // 18
+ uint32_t height; // 22
+ uint16_t biPlanes; // 26
+ uint16_t bits; // 28
+ uint32_t biCompression; // 30
+ uint32_t biSizeImage; // 34
+ uint32_t biXPelsPerMeter; // 38
+ uint32_t biYPelsPerMeter; // 42
+ uint32_t biClrUsed; // 46
+ uint32_t biClrImportant; // 50
+} BMPHEAD;
+#pragma pack(pop)
+
+void Unswizzle(const void *src, unsigned int depth, unsigned int width, unsigned int height, void *dest);
+void DXT1toARGB(const void *src, void *dest, unsigned int destWidth);
+void DXT4toARGB(const void *src, void *dest, unsigned int destWidth);
+void ConvertDXT1(const void *src, unsigned int width, unsigned int height, void *dest);
+void ConvertDXT4(const void *src, unsigned int width, unsigned int height, void *dest);
+void GetTextureFromData(D3DTexture* pTex, void* texData, std::unique_ptr<CTexture>* ppTexture);
diff --git a/xbmc/guilib/DirtyRegion.h b/xbmc/guilib/DirtyRegion.h
new file mode 100644
index 0000000..51af101
--- /dev/null
+++ b/xbmc/guilib/DirtyRegion.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/Geometry.h"
+
+#include <vector>
+
+class CDirtyRegion : public CRect
+{
+public:
+ explicit CDirtyRegion(const CRect &rect) : CRect(rect) { m_age = 0; }
+ CDirtyRegion(float left, float top, float right, float bottom) : CRect(left, top, right, bottom) { m_age = 0; }
+ CDirtyRegion() : CRect() { m_age = 0; }
+
+ int UpdateAge() { return ++m_age; }
+private:
+ int m_age;
+};
+
+typedef std::vector<CDirtyRegion> CDirtyRegionList;
diff --git a/xbmc/guilib/DirtyRegionSolvers.cpp b/xbmc/guilib/DirtyRegionSolvers.cpp
new file mode 100644
index 0000000..58c32a1
--- /dev/null
+++ b/xbmc/guilib/DirtyRegionSolvers.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirtyRegionSolvers.h"
+
+#include "windowing/GraphicContext.h"
+
+#include <stdio.h>
+
+void CUnionDirtyRegionSolver::Solve(const CDirtyRegionList &input, CDirtyRegionList &output)
+{
+ CDirtyRegion unifiedRegion;
+ for (unsigned int i = 0; i < input.size(); i++)
+ unifiedRegion.Union(input[i]);
+
+ if (!unifiedRegion.IsEmpty())
+ output.push_back(unifiedRegion);
+}
+
+void CFillViewportAlwaysRegionSolver::Solve(const CDirtyRegionList &input, CDirtyRegionList &output)
+{
+ CDirtyRegion unifiedRegion(CServiceBroker::GetWinSystem()->GetGfxContext().GetViewWindow());
+ output.push_back(unifiedRegion);
+}
+
+void CFillViewportOnChangeRegionSolver::Solve(const CDirtyRegionList &input, CDirtyRegionList &output)
+{
+ if (!input.empty())
+ output.assign(1,CDirtyRegion(CServiceBroker::GetWinSystem()->GetGfxContext().GetViewWindow()));
+}
+
+CGreedyDirtyRegionSolver::CGreedyDirtyRegionSolver()
+{
+ m_costNewRegion = 10.0f;
+ m_costPerArea = 0.01f;
+}
+
+void CGreedyDirtyRegionSolver::Solve(const CDirtyRegionList &input, CDirtyRegionList &output)
+{
+ for (unsigned int i = 0; i < input.size(); i++)
+ {
+ CDirtyRegion possibleUnionRegion;
+ int possibleUnionNbr = -1;
+ float possibleUnionCost = 100000.0f;
+
+ CDirtyRegion currentRegion = input[i];
+ for (unsigned int j = 0; j < output.size(); j++)
+ {
+ CDirtyRegion temporaryUnion = output[j];
+ temporaryUnion.Union(currentRegion);
+ float temporaryCost = m_costPerArea * (temporaryUnion.Area() - output[j].Area());
+ if (temporaryCost < possibleUnionCost)
+ {
+ //! @todo if the temporaryCost is 0 then we could skip checking the other regions since there exist no better solution
+ possibleUnionRegion = temporaryUnion;
+ possibleUnionNbr = j;
+ possibleUnionCost = temporaryCost;
+ }
+ }
+
+ float newRegionTotalCost = m_costPerArea * currentRegion.Area() + m_costNewRegion;
+
+ if (possibleUnionNbr >= 0 && possibleUnionCost < newRegionTotalCost)
+ output[possibleUnionNbr] = possibleUnionRegion;
+ else
+ output.push_back(currentRegion);
+ }
+}
diff --git a/xbmc/guilib/DirtyRegionSolvers.h b/xbmc/guilib/DirtyRegionSolvers.h
new file mode 100644
index 0000000..ace6264
--- /dev/null
+++ b/xbmc/guilib/DirtyRegionSolvers.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirtyRegionSolver.h"
+
+class CUnionDirtyRegionSolver : public IDirtyRegionSolver
+{
+public:
+ void Solve(const CDirtyRegionList &input, CDirtyRegionList &output) override;
+};
+
+class CFillViewportAlwaysRegionSolver : public IDirtyRegionSolver
+{
+public:
+ void Solve(const CDirtyRegionList &input, CDirtyRegionList &output) override;
+};
+
+class CFillViewportOnChangeRegionSolver : public IDirtyRegionSolver
+{
+public:
+ void Solve(const CDirtyRegionList &input, CDirtyRegionList &output) override;
+};
+
+class CGreedyDirtyRegionSolver : public IDirtyRegionSolver
+{
+public:
+ CGreedyDirtyRegionSolver();
+ void Solve(const CDirtyRegionList &input, CDirtyRegionList &output) override;
+private:
+ float m_costNewRegion;
+ float m_costPerArea;
+};
diff --git a/xbmc/guilib/DirtyRegionTracker.cpp b/xbmc/guilib/DirtyRegionTracker.cpp
new file mode 100644
index 0000000..398d455
--- /dev/null
+++ b/xbmc/guilib/DirtyRegionTracker.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "DirtyRegionTracker.h"
+
+#include "DirtyRegionSolvers.h"
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include <stdio.h>
+
+CDirtyRegionTracker::CDirtyRegionTracker(int buffering)
+{
+ m_buffering = buffering;
+ m_solver = NULL;
+}
+
+CDirtyRegionTracker::~CDirtyRegionTracker()
+{
+ delete m_solver;
+}
+
+void CDirtyRegionTracker::SelectAlgorithm()
+{
+ delete m_solver;
+
+ switch (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions)
+ {
+ case DIRTYREGION_SOLVER_FILL_VIEWPORT_ON_CHANGE:
+ CLog::Log(LOGDEBUG, "guilib: Fill viewport on change for solving rendering passes");
+ m_solver = new CFillViewportOnChangeRegionSolver();
+ break;
+ case DIRTYREGION_SOLVER_COST_REDUCTION:
+ CLog::Log(LOGDEBUG, "guilib: Cost reduction as algorithm for solving rendering passes");
+ m_solver = new CGreedyDirtyRegionSolver();
+ break;
+ case DIRTYREGION_SOLVER_UNION:
+ m_solver = new CUnionDirtyRegionSolver();
+ CLog::Log(LOGDEBUG, "guilib: Union as algorithm for solving rendering passes");
+ break;
+ case DIRTYREGION_SOLVER_FILL_VIEWPORT_ALWAYS:
+ default:
+ CLog::Log(LOGDEBUG, "guilib: Fill viewport always for solving rendering passes");
+ m_solver = new CFillViewportAlwaysRegionSolver();
+ break;
+ }
+}
+
+void CDirtyRegionTracker::MarkDirtyRegion(const CDirtyRegion &region)
+{
+ if (!region.IsEmpty())
+ m_markedRegions.push_back(region);
+}
+
+const CDirtyRegionList &CDirtyRegionTracker::GetMarkedRegions() const
+{
+ return m_markedRegions;
+}
+
+CDirtyRegionList CDirtyRegionTracker::GetDirtyRegions()
+{
+ CDirtyRegionList output;
+
+ if (m_solver)
+ m_solver->Solve(m_markedRegions, output);
+
+ return output;
+}
+
+void CDirtyRegionTracker::CleanMarkedRegions()
+{
+ int buffering = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiVisualizeDirtyRegions ? 20 : m_buffering;
+ int i = m_markedRegions.size() - 1;
+ while (i >= 0)
+ {
+ if (m_markedRegions[i].UpdateAge() >= buffering)
+ m_markedRegions.erase(m_markedRegions.begin() + i);
+
+ i--;
+ }
+}
diff --git a/xbmc/guilib/DirtyRegionTracker.h b/xbmc/guilib/DirtyRegionTracker.h
new file mode 100644
index 0000000..697cb64
--- /dev/null
+++ b/xbmc/guilib/DirtyRegionTracker.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "IDirtyRegionSolver.h"
+
+#if defined(TARGET_DARWIN_EMBEDDED)
+#define DEFAULT_BUFFERING 4
+#else
+#define DEFAULT_BUFFERING 3
+#endif
+
+class CDirtyRegionTracker
+{
+public:
+ explicit CDirtyRegionTracker(int buffering = DEFAULT_BUFFERING);
+ ~CDirtyRegionTracker();
+ void SelectAlgorithm();
+ void MarkDirtyRegion(const CDirtyRegion &region);
+
+ const CDirtyRegionList &GetMarkedRegions() const;
+ CDirtyRegionList GetDirtyRegions();
+ void CleanMarkedRegions();
+
+private:
+ CDirtyRegionList m_markedRegions;
+ int m_buffering;
+ IDirtyRegionSolver *m_solver;
+};
diff --git a/xbmc/guilib/DispResource.h b/xbmc/guilib/DispResource.h
new file mode 100644
index 0000000..e22bb21
--- /dev/null
+++ b/xbmc/guilib/DispResource.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// interface for registering into windowing
+// to get notified about display events
+// interface only, does not control lifetime of the object
+class IDispResource
+{
+public:
+ virtual ~IDispResource() = default;
+
+ virtual void OnLostDisplay() {}
+ virtual void OnResetDisplay() {}
+ virtual void OnAppFocusChange(bool focus) {}
+};
+
+// interface used by clients to register into render loop
+// interface only, does not control lifetime of the object
+class IRenderLoop
+{
+public:
+ virtual ~IRenderLoop() = default;
+
+ virtual void FrameMove() = 0;
+};
diff --git a/xbmc/guilib/FFmpegImage.cpp b/xbmc/guilib/FFmpegImage.cpp
new file mode 100644
index 0000000..fa25527
--- /dev/null
+++ b/xbmc/guilib/FFmpegImage.cpp
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+#include "FFmpegImage.h"
+
+#include "cores/FFmpeg.h"
+#include "guilib/Texture.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include <algorithm>
+
+extern "C"
+{
+#include <libavformat/avformat.h>
+#include <libavutil/imgutils.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libswscale/swscale.h>
+#include <libavutil/pixdesc.h>
+}
+
+Frame::Frame(const Frame& src) :
+ m_delay(src.m_delay),
+ m_imageSize(src.m_imageSize),
+ m_height(src.m_height),
+ m_width(src.m_width)
+{
+ if (src.m_pImage)
+ {
+ m_pImage = (unsigned char*) av_malloc(m_imageSize);
+ memcpy(m_pImage, src.m_pImage, m_imageSize);
+ }
+}
+
+Frame::~Frame()
+{
+ av_free(m_pImage);
+ m_pImage = nullptr;
+}
+
+struct ThumbDataManagement
+{
+ uint8_t* intermediateBuffer = nullptr; // gets av_alloced
+ AVFrame* frame_input = nullptr;
+ AVFrame* frame_temporary = nullptr;
+ SwsContext* sws = nullptr;
+ AVCodecContext* avOutctx = nullptr;
+ AVCodec* codec = nullptr;
+ ~ThumbDataManagement()
+ {
+ av_free(intermediateBuffer);
+ intermediateBuffer = nullptr;
+ av_frame_free(&frame_input);
+ frame_input = nullptr;
+ av_frame_free(&frame_temporary);
+ frame_temporary = nullptr;
+ avcodec_free_context(&avOutctx);
+ avOutctx = nullptr;
+ sws_freeContext(sws);
+ sws = nullptr;
+ }
+};
+
+// valid positions are including 0 (start of buffer)
+// and bufferSize -1 last data point
+static inline size_t Clamp(int64_t newPosition, size_t bufferSize)
+{
+ return std::min(std::max((int64_t) 0, newPosition), (int64_t) (bufferSize -1));
+}
+
+static int mem_file_read(void *h, uint8_t* buf, int size)
+{
+ if (size < 0)
+ return -1;
+
+ MemBuffer* mbuf = static_cast<MemBuffer*>(h);
+ int64_t unread = mbuf->size - mbuf->pos;
+ if (unread <= 0)
+ return AVERROR_EOF;
+
+ size_t tocopy = std::min((size_t)size, (size_t)unread);
+ memcpy(buf, mbuf->data + mbuf->pos, tocopy);
+ mbuf->pos += tocopy;
+ return static_cast<int>(tocopy);
+}
+
+static int64_t mem_file_seek(void *h, int64_t pos, int whence)
+{
+ MemBuffer* mbuf = static_cast<MemBuffer*>(h);
+ if (whence == AVSEEK_SIZE)
+ return mbuf->size;
+
+ // we want to ignore the AVSEEK_FORCE flag and therefore mask it away
+ whence &= ~AVSEEK_FORCE;
+
+ if (whence == SEEK_SET)
+ {
+ mbuf->pos = Clamp(pos, mbuf->size);
+ }
+ else if (whence == SEEK_CUR)
+ {
+ mbuf->pos = Clamp(((int64_t)mbuf->pos) + pos, mbuf->size);
+ }
+ else
+ CLog::LogF(LOGERROR, "Unknown seek mode: {}", whence);
+
+ return mbuf->pos;
+}
+
+CFFmpegImage::CFFmpegImage(const std::string& strMimeType) : m_strMimeType(strMimeType)
+{
+ m_hasAlpha = false;
+ m_pFrame = nullptr;
+ m_outputBuffer = nullptr;
+}
+
+CFFmpegImage::~CFFmpegImage()
+{
+ av_frame_free(&m_pFrame);
+ // someone could have forgotten to call us
+ CleanupLocalOutputBuffer();
+ if (m_fctx)
+ {
+ avcodec_free_context(&m_codec_ctx);
+ avformat_close_input(&m_fctx);
+ }
+ if (m_ioctx)
+ FreeIOCtx(&m_ioctx);
+
+ m_buf.data = nullptr;
+ m_buf.pos = 0;
+ m_buf.size = 0;
+}
+
+bool CFFmpegImage::LoadImageFromMemory(unsigned char* buffer, unsigned int bufSize,
+ unsigned int width, unsigned int height)
+{
+
+ if (!Initialize(buffer, bufSize))
+ {
+ //log
+ return false;
+ }
+
+ av_frame_free(&m_pFrame);
+ m_pFrame = ExtractFrame();
+
+ return !(m_pFrame == nullptr);
+}
+
+bool CFFmpegImage::Initialize(unsigned char* buffer, size_t bufSize)
+{
+ int bufferSize = 4096;
+ uint8_t* fbuffer = (uint8_t*)av_malloc(bufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
+ if (!fbuffer)
+ {
+ CLog::LogF(LOGERROR, "Could not allocate buffer");
+ return false;
+ }
+ m_buf.data = buffer;
+ m_buf.size = bufSize;
+ m_buf.pos = 0;
+
+ m_ioctx = avio_alloc_context(fbuffer, bufferSize, 0, &m_buf,
+ mem_file_read, NULL, mem_file_seek);
+
+ if (!m_ioctx)
+ {
+ av_free(fbuffer);
+ CLog::LogF(LOGERROR, "Could not allocate AVIOContext");
+ return false;
+ }
+
+ // signal to ffmepg this is not streaming protocol
+ m_ioctx->max_packet_size = bufferSize;
+
+ m_fctx = avformat_alloc_context();
+ if (!m_fctx)
+ {
+ FreeIOCtx(&m_ioctx);
+ CLog::LogF(LOGERROR, "Could not allocate AVFormatContext");
+ return false;
+ }
+
+ m_fctx->pb = m_ioctx;
+
+ // Some clients have pngs saved as jpeg or ask us for png but are jpeg
+ // mythv throws all mimetypes away and asks us with application/octet-stream
+ // this is poor man's fallback to at least identify png / jpeg
+ bool is_jpeg = (bufSize > 2 && buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF);
+ bool is_png = (bufSize > 3 && buffer[1] == 'P' && buffer[2] == 'N' && buffer[3] == 'G');
+ bool is_tiff = (bufSize > 2 && buffer[0] == 'I' && buffer[1] == 'I' && buffer[2] == '*');
+
+ AVInputFormat* inp = nullptr;
+ if (is_jpeg)
+ inp = av_find_input_format("image2");
+ else if (m_strMimeType == "image/apng")
+ inp = av_find_input_format("apng");
+ else if (is_png)
+ inp = av_find_input_format("png_pipe");
+ else if (is_tiff)
+ inp = av_find_input_format("tiff_pipe");
+ else if (m_strMimeType == "image/jp2")
+ inp = av_find_input_format("j2k_pipe");
+ else if (m_strMimeType == "image/webp")
+ inp = av_find_input_format("webp_pipe");
+ // brute force parse if above check already failed
+ else if (m_strMimeType == "image/jpeg" || m_strMimeType == "image/jpg")
+ inp = av_find_input_format("image2");
+ else if (m_strMimeType == "image/png")
+ inp = av_find_input_format("png_pipe");
+ else if (m_strMimeType == "image/tiff")
+ inp = av_find_input_format("tiff_pipe");
+ else if (m_strMimeType == "image/gif")
+ inp = av_find_input_format("gif");
+
+ if (avformat_open_input(&m_fctx, NULL, inp, NULL) < 0)
+ {
+ CLog::Log(LOGERROR, "Could not find suitable input format: {}", m_strMimeType);
+ avformat_close_input(&m_fctx);
+ FreeIOCtx(&m_ioctx);
+ return false;
+ }
+
+ if (m_fctx->nb_streams <= 0)
+ {
+ avformat_close_input(&m_fctx);
+ FreeIOCtx(&m_ioctx);
+ return false;
+ }
+ AVCodecParameters* codec_params = m_fctx->streams[0]->codecpar;
+ AVCodec* codec = avcodec_find_decoder(codec_params->codec_id);
+ m_codec_ctx = avcodec_alloc_context3(codec);
+ if (!m_codec_ctx)
+ {
+ avformat_close_input(&m_fctx);
+ FreeIOCtx(&m_ioctx);
+ return false;
+ }
+
+ if (avcodec_parameters_to_context(m_codec_ctx, codec_params) < 0)
+ {
+ avformat_close_input(&m_fctx);
+ avcodec_free_context(&m_codec_ctx);
+ FreeIOCtx(&m_ioctx);
+ return false;
+ }
+
+ if (avcodec_open2(m_codec_ctx, codec, NULL) < 0)
+ {
+ avformat_close_input(&m_fctx);
+ avcodec_free_context(&m_codec_ctx);
+ FreeIOCtx(&m_ioctx);
+ return false;
+ }
+
+ return true;
+}
+
+AVFrame* CFFmpegImage::ExtractFrame()
+{
+ if (!m_fctx || !m_fctx->streams[0])
+ {
+ CLog::LogF(LOGERROR, "No valid format context or stream");
+ return nullptr;
+ }
+
+ AVPacket pkt;
+ AVFrame* frame = av_frame_alloc();
+ int frame_decoded = 0;
+ int ret = 0;
+ ret = av_read_frame(m_fctx, &pkt);
+ if (ret < 0)
+ {
+ CLog::Log(LOGDEBUG, "Error [{}] while reading frame: {}", ret, strerror(AVERROR(ret)));
+ av_frame_free(&frame);
+ av_packet_unref(&pkt);
+ return nullptr;
+ }
+
+ ret = DecodeFFmpegFrame(m_codec_ctx, frame, &frame_decoded, &pkt);
+ if (ret < 0 || frame_decoded == 0 || !frame)
+ {
+ CLog::Log(LOGDEBUG, "Error [{}] while decoding frame: {}", ret, strerror(AVERROR(ret)));
+ av_frame_free(&frame);
+ av_packet_unref(&pkt);
+ return nullptr;
+ }
+ //we need milliseconds
+ frame->pkt_duration = av_rescale_q(frame->pkt_duration, m_fctx->streams[0]->time_base, AVRational{ 1, 1000 });
+ m_height = frame->height;
+ m_width = frame->width;
+ m_originalWidth = m_width;
+ m_originalHeight = m_height;
+
+ const AVPixFmtDescriptor* pixDescriptor = av_pix_fmt_desc_get(static_cast<AVPixelFormat>(frame->format));
+ if (pixDescriptor && ((pixDescriptor->flags & (AV_PIX_FMT_FLAG_ALPHA | AV_PIX_FMT_FLAG_PAL)) != 0))
+ m_hasAlpha = true;
+
+ AVDictionary* dic = frame->metadata;
+ AVDictionaryEntry* entry = NULL;
+ if (dic)
+ {
+ entry = av_dict_get(dic, "Orientation", NULL, AV_DICT_MATCH_CASE);
+ if (entry && entry->value)
+ {
+ int orientation = atoi(entry->value);
+ // only values between including 0 and including 8
+ // http://sylvana.net/jpegcrop/exif_orientation.html
+ if (orientation >= 0 && orientation <= 8)
+ m_orientation = (unsigned int)orientation;
+ }
+ }
+ av_packet_unref(&pkt);
+
+ return frame;
+}
+
+AVPixelFormat CFFmpegImage::ConvertFormats(AVFrame* frame)
+{
+ switch (frame->format) {
+ case AV_PIX_FMT_YUVJ420P:
+ return AV_PIX_FMT_YUV420P;
+ break;
+ case AV_PIX_FMT_YUVJ422P:
+ return AV_PIX_FMT_YUV422P;
+ break;
+ case AV_PIX_FMT_YUVJ444P:
+ return AV_PIX_FMT_YUV444P;
+ break;
+ case AV_PIX_FMT_YUVJ440P:
+ return AV_PIX_FMT_YUV440P;
+ default:
+ return static_cast<AVPixelFormat>(frame->format);
+ break;
+ }
+}
+
+void CFFmpegImage::FreeIOCtx(AVIOContext** ioctx)
+{
+ av_freep(&((*ioctx)->buffer));
+ av_freep(ioctx);
+}
+
+bool CFFmpegImage::Decode(unsigned char * const pixels, unsigned int width, unsigned int height,
+ unsigned int pitch, unsigned int format)
+{
+ if (m_width == 0 || m_height == 0 || format != XB_FMT_A8R8G8B8)
+ return false;
+
+ if (pixels == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - No valid buffer pointer (nullptr) passed", __FUNCTION__);
+ return false;
+ }
+
+ if (!m_pFrame || !m_pFrame->data[0])
+ {
+ CLog::LogF(LOGERROR, "AVFrame member not allocated");
+ return false;
+ }
+
+ return DecodeFrame(m_pFrame, width, height, pitch, pixels);
+}
+
+int CFFmpegImage::EncodeFFmpegFrame(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
+{
+ int ret;
+
+ *got_packet = 0;
+
+ ret = avcodec_send_frame(avctx, frame);
+ if (ret < 0)
+ return ret;
+
+ ret = avcodec_receive_packet(avctx, pkt);
+ if (!ret)
+ *got_packet = 1;
+
+ if (ret == AVERROR(EAGAIN))
+ return 0;
+
+ return ret;
+}
+
+int CFFmpegImage::DecodeFFmpegFrame(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
+{
+ int ret;
+
+ *got_frame = 0;
+
+ if (pkt)
+ {
+ ret = avcodec_send_packet(avctx, pkt);
+ // In particular, we don't expect AVERROR(EAGAIN), because we read all
+ // decoded frames with avcodec_receive_frame() until done.
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = avcodec_receive_frame(avctx, frame);
+ if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
+ return ret;
+ if (ret >= 0) // static code analysers would complain
+ *got_frame = 1;
+
+ return 0;
+}
+
+bool CFFmpegImage::DecodeFrame(AVFrame* frame, unsigned int width, unsigned int height, unsigned int pitch, unsigned char * const pixels)
+{
+ if (pixels == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - No valid buffer pointer (nullptr) passed", __FUNCTION__);
+ return false;
+ }
+
+ AVFrame* pictureRGB = av_frame_alloc();
+ if (!pictureRGB)
+ {
+ CLog::LogF(LOGERROR, "AVFrame could not be allocated");
+ return false;
+ }
+
+ // we align on 16 as the input provided by the Texture also aligns the buffer size to 16
+ int size = av_image_fill_arrays(pictureRGB->data, pictureRGB->linesize, NULL, AV_PIX_FMT_RGB32, width, height, 16);
+ if (size < 0)
+ {
+ CLog::LogF(LOGERROR, "Could not allocate AVFrame member with {} x {} pixels", width, height);
+ av_frame_free(&pictureRGB);
+ return false;
+ }
+
+ bool needsCopy = false;
+ int pixelsSize = pitch * height;
+ bool aligned = (((uintptr_t)(const void *)(pixels)) % (32) == 0);
+ if (!aligned)
+ CLog::Log(LOGDEBUG, "Alignment of external buffer is not suitable for ffmpeg intrinsics - please fix your malloc");
+
+ if (aligned && size == pixelsSize && (int)pitch == pictureRGB->linesize[0])
+ {
+ // We can use the pixels buffer directly
+ pictureRGB->data[0] = pixels;
+ }
+ else
+ {
+ // We need an extra buffer and copy it manually afterwards
+ pictureRGB->format = AV_PIX_FMT_RGB32;
+ pictureRGB->width = width;
+ pictureRGB->height = height;
+ // we copy the data manually later so give a chance to intrinsics (e.g. mmx, neon)
+ if (av_frame_get_buffer(pictureRGB, 32) < 0)
+ {
+ CLog::LogF(LOGERROR, "Could not allocate temp buffer of size {} bytes", size);
+ av_frame_free(&pictureRGB);
+ return false;
+ }
+ needsCopy = true;
+ }
+
+ // Especially jpeg formats are full range this we need to take care here
+ // Input Formats like RGBA are handled correctly automatically
+ AVColorRange range = frame->color_range;
+ AVPixelFormat pixFormat = ConvertFormats(frame);
+
+ // assumption quadratic maximums e.g. 2048x2048
+ float ratio = m_width / (float)m_height;
+ unsigned int nHeight = m_originalHeight;
+ unsigned int nWidth = m_originalWidth;
+ if (nHeight > height)
+ {
+ nHeight = height;
+ nWidth = (unsigned int)(nHeight * ratio + 0.5f);
+ }
+ if (nWidth > width)
+ {
+ nWidth = width;
+ nHeight = (unsigned int)(nWidth / ratio + 0.5f);
+ }
+
+ struct SwsContext* context = sws_getContext(m_originalWidth, m_originalHeight, pixFormat,
+ nWidth, nHeight, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
+
+ if (range == AVCOL_RANGE_JPEG)
+ {
+ int* inv_table = nullptr;
+ int* table = nullptr;
+ int srcRange, dstRange, brightness, contrast, saturation;
+ sws_getColorspaceDetails(context, &inv_table, &srcRange, &table, &dstRange, &brightness, &contrast, &saturation);
+ srcRange = 1;
+ sws_setColorspaceDetails(context, inv_table, srcRange, table, dstRange, brightness, contrast, saturation);
+ }
+
+ sws_scale(context, frame->data, frame->linesize, 0, m_originalHeight,
+ pictureRGB->data, pictureRGB->linesize);
+ sws_freeContext(context);
+
+ if (needsCopy)
+ {
+ int minPitch = std::min((int)pitch, pictureRGB->linesize[0]);
+ if (minPitch < 0)
+ {
+ CLog::LogF(LOGERROR, "negative pitch or height");
+ av_frame_free(&pictureRGB);
+ return false;
+ }
+ const unsigned char *src = pictureRGB->data[0];
+ unsigned char* dst = pixels;
+
+ for (unsigned int y = 0; y < nHeight; y++)
+ {
+ memcpy(dst, src, minPitch);
+ src += pictureRGB->linesize[0];
+ dst += pitch;
+ }
+ av_frame_free(&pictureRGB);
+ }
+ else
+ {
+ // we only lended the data so don't get it deleted
+ pictureRGB->data[0] = nullptr;
+ av_frame_free(&pictureRGB);
+ }
+
+ // update width and height original dimensions are kept
+ m_height = nHeight;
+ m_width = nWidth;
+
+ return true;
+}
+
+bool CFFmpegImage::CreateThumbnailFromSurface(unsigned char* bufferin, unsigned int width,
+ unsigned int height, unsigned int format,
+ unsigned int pitch,
+ const std::string& destFile,
+ unsigned char* &bufferout,
+ unsigned int &bufferoutSize)
+{
+ // It seems XB_FMT_A8R8G8B8 mean RGBA and not ARGB
+ if (format != XB_FMT_A8R8G8B8)
+ {
+ CLog::Log(LOGERROR, "Supplied format: {} is not supported.", format);
+ return false;
+ }
+
+ bool jpg_output = false;
+ if (m_strMimeType == "image/jpeg" || m_strMimeType == "image/jpg")
+ jpg_output = true;
+ else if (m_strMimeType == "image/png")
+ jpg_output = false;
+ else
+ {
+ CLog::Log(LOGERROR, "Output Format is not supported: {} is not supported.", destFile);
+ return false;
+ }
+
+ ThumbDataManagement tdm;
+
+ tdm.codec = avcodec_find_encoder(jpg_output ? AV_CODEC_ID_MJPEG : AV_CODEC_ID_PNG);
+ if (!tdm.codec)
+ {
+ CLog::Log(LOGERROR, "You are missing a working encoder for format: {}",
+ jpg_output ? "JPEG" : "PNG");
+ return false;
+ }
+
+ tdm.avOutctx = avcodec_alloc_context3(tdm.codec);
+ if (!tdm.avOutctx)
+ {
+ CLog::Log(LOGERROR, "Could not allocate context for thumbnail: {}", destFile);
+ return false;
+ }
+
+ tdm.avOutctx->height = height;
+ tdm.avOutctx->width = width;
+ tdm.avOutctx->time_base.num = 1;
+ tdm.avOutctx->time_base.den = 1;
+ tdm.avOutctx->pix_fmt = jpg_output ? AV_PIX_FMT_YUVJ420P : AV_PIX_FMT_RGBA;
+ tdm.avOutctx->flags = AV_CODEC_FLAG_QSCALE;
+ tdm.avOutctx->mb_lmin = tdm.avOutctx->qmin * FF_QP2LAMBDA;
+ tdm.avOutctx->mb_lmax = tdm.avOutctx->qmax * FF_QP2LAMBDA;
+ unsigned int quality =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageQualityJpeg;
+ tdm.avOutctx->global_quality =
+ jpg_output ? quality * FF_QP2LAMBDA : tdm.avOutctx->qmin * FF_QP2LAMBDA;
+
+ unsigned int internalBufOutSize = 0;
+
+ int size = av_image_get_buffer_size(tdm.avOutctx->pix_fmt, tdm.avOutctx->width, tdm.avOutctx->height, 16);
+ if (size < 0)
+ {
+ CLog::Log(LOGERROR, "Could not compute picture size for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+ internalBufOutSize = (unsigned int) size;
+
+ tdm.intermediateBuffer = (uint8_t*) av_malloc(internalBufOutSize);
+ if (!tdm.intermediateBuffer)
+ {
+ CLog::Log(LOGERROR, "Could not allocate memory for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ if (avcodec_open2(tdm.avOutctx, tdm.codec, NULL) < 0)
+ {
+ CLog::Log(LOGERROR, "Could not open avcodec context thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ tdm.frame_input = av_frame_alloc();
+ if (!tdm.frame_input)
+ {
+ CLog::Log(LOGERROR, "Could not allocate frame for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ // convert the RGB32 frame to AV_PIX_FMT_YUV420P - we use this later on as AV_PIX_FMT_YUVJ420P
+ tdm.frame_temporary = av_frame_alloc();
+ if (!tdm.frame_temporary)
+ {
+ CLog::Log(LOGERROR, "Could not allocate frame for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ if (av_image_fill_arrays(tdm.frame_temporary->data, tdm.frame_temporary->linesize, tdm.intermediateBuffer, jpg_output ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_RGBA, width, height, 16) < 0)
+ {
+ CLog::Log(LOGERROR, "Could not fill picture for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ uint8_t* src[] = { bufferin, NULL, NULL, NULL };
+ int srcStride[] = { (int) pitch, 0, 0, 0};
+
+ //input size == output size which means only pix_fmt conversion
+ tdm.sws = sws_getContext(width, height, AV_PIX_FMT_RGB32, width, height, jpg_output ? AV_PIX_FMT_YUV420P : AV_PIX_FMT_RGBA, 0, 0, 0, 0);
+ if (!tdm.sws)
+ {
+ CLog::Log(LOGERROR, "Could not setup scaling context for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+
+ // Setup jpeg range for sws
+ if (jpg_output)
+ {
+ int* inv_table = nullptr;
+ int* table = nullptr;
+ int srcRange, dstRange, brightness, contrast, saturation;
+
+ if (sws_getColorspaceDetails(tdm.sws, &inv_table, &srcRange, &table, &dstRange, &brightness, &contrast, &saturation) < 0)
+ {
+ CLog::Log(LOGERROR, "SWS_SCALE failed to get ColorSpaceDetails for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+ dstRange = 1; // jpeg full range yuv420p output
+ srcRange = 0; // full range RGB32 input
+ if (sws_setColorspaceDetails(tdm.sws, inv_table, srcRange, table, dstRange, brightness, contrast, saturation) < 0)
+ {
+ CLog::Log(LOGERROR, "SWS_SCALE failed to set ColorSpace Details for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+ }
+
+ if (sws_scale(tdm.sws, src, srcStride, 0, height, tdm.frame_temporary->data, tdm.frame_temporary->linesize) < 0)
+ {
+ CLog::Log(LOGERROR, "SWS_SCALE failed for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ return false;
+ }
+ tdm.frame_input->pts = 1;
+ tdm.frame_input->quality = tdm.avOutctx->global_quality;
+ tdm.frame_input->data[0] = tdm.frame_temporary->data[0];
+ tdm.frame_input->data[1] = tdm.frame_temporary->data[1];
+ tdm.frame_input->data[2] = tdm.frame_temporary->data[2];
+ tdm.frame_input->height = height;
+ tdm.frame_input->width = width;
+ tdm.frame_input->linesize[0] = tdm.frame_temporary->linesize[0];
+ tdm.frame_input->linesize[1] = tdm.frame_temporary->linesize[1];
+ tdm.frame_input->linesize[2] = tdm.frame_temporary->linesize[2];
+ // this is deprecated but mjpeg is not yet transitioned
+ tdm.frame_input->format = jpg_output ? AV_PIX_FMT_YUVJ420P : AV_PIX_FMT_RGBA;
+
+ int got_package = 0;
+ AVPacket* avpkt;
+ avpkt = av_packet_alloc();
+
+ int ret = EncodeFFmpegFrame(tdm.avOutctx, avpkt, &got_package, tdm.frame_input);
+
+ if ((ret < 0) || (got_package == 0))
+ {
+ CLog::Log(LOGERROR, "Could not encode thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ av_packet_free(&avpkt);
+ return false;
+ }
+
+ bufferoutSize = avpkt->size;
+ m_outputBuffer = (uint8_t*) av_malloc(bufferoutSize);
+ if (!m_outputBuffer)
+ {
+ CLog::Log(LOGERROR, "Could not generate allocate memory for thumbnail: {}", destFile);
+ CleanupLocalOutputBuffer();
+ av_packet_free(&avpkt);
+ return false;
+ }
+ // update buffer ptr for caller
+ bufferout = m_outputBuffer;
+
+ // copy avpkt data into outputbuffer
+ memcpy(m_outputBuffer, avpkt->data, avpkt->size);
+ av_packet_free(&avpkt);
+
+ return true;
+}
+
+void CFFmpegImage::ReleaseThumbnailBuffer()
+{
+ CleanupLocalOutputBuffer();
+}
+
+void CFFmpegImage::CleanupLocalOutputBuffer()
+{
+ av_free(m_outputBuffer);
+ m_outputBuffer = nullptr;
+}
+
+std::shared_ptr<Frame> CFFmpegImage::ReadFrame()
+{
+ AVFrame* avframe = ExtractFrame();
+ if (avframe == nullptr)
+ return nullptr;
+ std::shared_ptr<Frame> frame(new Frame());
+ frame->m_delay = (unsigned int)avframe->pkt_duration;
+ frame->m_pitch = avframe->width * 4;
+ frame->m_pImage = (unsigned char*) av_malloc(avframe->height * frame->m_pitch);
+ DecodeFrame(avframe, avframe->width, avframe->height, frame->m_pitch, frame->m_pImage);
+ av_frame_free(&avframe);
+ return frame;
+}
diff --git a/xbmc/guilib/FFmpegImage.h b/xbmc/guilib/FFmpegImage.h
new file mode 100644
index 0000000..0f7cee3
--- /dev/null
+++ b/xbmc/guilib/FFmpegImage.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "iimage.h"
+#include <memory>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+class Frame
+{
+public:
+ friend class CFFmpegImage;
+
+ Frame() = default;
+ virtual ~Frame();
+
+ int GetPitch() const { return m_pitch; }
+
+ unsigned char* m_pImage = nullptr;
+ unsigned int m_delay = 0;
+
+private:
+ Frame(const Frame& src);
+
+ int m_pitch = 0;
+ unsigned int m_imageSize = 0;
+ unsigned int m_height = 0;
+ unsigned int m_width = 0;
+};
+
+
+struct MemBuffer
+{
+ uint8_t* data = nullptr;
+ size_t size = 0;
+ size_t pos = 0;
+};
+
+struct AVFrame;
+struct AVIOContext;
+struct AVFormatContext;
+struct AVCodecContext;
+struct AVPacket;
+
+class CFFmpegImage : public IImage
+{
+public:
+ explicit CFFmpegImage(const std::string& strMimeType);
+ ~CFFmpegImage() override;
+
+ bool LoadImageFromMemory(unsigned char* buffer, unsigned int bufSize,
+ unsigned int width, unsigned int height) override;
+ bool Decode(unsigned char * const pixels, unsigned int width, unsigned int height,
+ unsigned int pitch, unsigned int format) override;
+ bool CreateThumbnailFromSurface(unsigned char* bufferin, unsigned int width,
+ unsigned int height, unsigned int format,
+ unsigned int pitch, const std::string& destFile,
+ unsigned char* &bufferout,
+ unsigned int &bufferoutSize) override;
+ void ReleaseThumbnailBuffer() override;
+
+ bool Initialize(unsigned char* buffer, size_t bufSize);
+
+ std::shared_ptr<Frame> ReadFrame();
+
+private:
+ static void FreeIOCtx(AVIOContext** ioctx);
+ AVFrame* ExtractFrame();
+ bool DecodeFrame(AVFrame* m_pFrame, unsigned int width, unsigned int height, unsigned int pitch, unsigned char * const pixels);
+ static int EncodeFFmpegFrame(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame);
+ static int DecodeFFmpegFrame(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt);
+ static AVPixelFormat ConvertFormats(AVFrame* frame);
+ std::string m_strMimeType;
+ void CleanupLocalOutputBuffer();
+
+
+ MemBuffer m_buf;
+
+ AVIOContext* m_ioctx = nullptr;
+ AVFormatContext* m_fctx = nullptr;
+ AVCodecContext* m_codec_ctx = nullptr;
+
+ AVFrame* m_pFrame;
+ uint8_t* m_outputBuffer;
+};
diff --git a/xbmc/guilib/GUIAction.cpp b/xbmc/guilib/GUIAction.cpp
new file mode 100644
index 0000000..030fdc3
--- /dev/null
+++ b/xbmc/guilib/GUIAction.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIAction.h"
+
+#include "GUIComponent.h"
+#include "GUIInfoManager.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "utils/StringUtils.h"
+
+namespace
+{
+constexpr int DEFAULT_CONTROL_ID = 0;
+}
+
+CGUIAction::CExecutableAction::CExecutableAction(const std::string& action) : m_action{action}
+{
+}
+
+CGUIAction::CExecutableAction::CExecutableAction(const std::string& condition,
+ const std::string& action)
+ : m_condition{condition}, m_action{action}
+{
+}
+
+std::string CGUIAction::CExecutableAction::GetCondition() const
+{
+ return m_condition;
+}
+
+std::string CGUIAction::CExecutableAction::GetAction() const
+{
+ return m_action;
+}
+
+bool CGUIAction::CExecutableAction::HasCondition() const
+{
+ return !m_condition.empty();
+}
+
+void CGUIAction::CExecutableAction::SetAction(const std::string& action)
+{
+ m_action = action;
+}
+
+CGUIAction::CGUIAction(int controlID)
+{
+ SetNavigation(controlID);
+}
+
+bool CGUIAction::ExecuteActions() const
+{
+ return ExecuteActions(DEFAULT_CONTROL_ID, DEFAULT_CONTROL_ID);
+}
+
+bool CGUIAction::ExecuteActions(int controlID, int parentID, const CGUIListItemPtr &item /* = NULL */) const
+{
+ if (m_actions.empty())
+ return false;
+
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ // take a copy of actions that satisfy our conditions
+ std::vector<std::string> actions;
+ for (const auto &i : m_actions)
+ {
+ if (!i.HasCondition() || infoMgr.EvaluateBool(i.GetCondition(), 0, item))
+ {
+ if (!StringUtils::IsInteger(i.GetAction()))
+ actions.emplace_back(i.GetAction());
+ }
+ }
+ // execute them
+ bool retval = false;
+ for (const auto &i : actions)
+ {
+ CGUIMessage msg(GUI_MSG_EXECUTE, controlID, parentID, 0, 0, item);
+ msg.SetStringParam(i);
+ if (m_sendThreadMessages)
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ else
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ retval = true;
+ }
+ return retval;
+}
+
+int CGUIAction::GetNavigation() const
+{
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ for (const auto &i : m_actions)
+ {
+ if (StringUtils::IsInteger(i.GetAction()))
+ {
+ if (!i.HasCondition() || infoMgr.EvaluateBool(i.GetCondition(), INFO::DEFAULT_CONTEXT))
+ return std::stoi(i.GetAction());
+ }
+ }
+ return 0;
+}
+
+void CGUIAction::SetNavigation(int id)
+{
+ if (id == 0)
+ return;
+
+ std::string strId = std::to_string(id);
+ for (auto &i : m_actions)
+ {
+ if (StringUtils::IsInteger(i.GetAction()) && !i.HasCondition())
+ {
+ i.SetAction(strId);
+ return;
+ }
+ }
+ m_actions.emplace_back(std::move(strId));
+}
+
+bool CGUIAction::HasActionsMeetingCondition() const
+{
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ for (const auto &i : m_actions)
+ {
+ if (!i.HasCondition() || infoMgr.EvaluateBool(i.GetCondition(), INFO::DEFAULT_CONTEXT))
+ return true;
+ }
+ return false;
+}
+
+bool CGUIAction::HasAnyActions() const
+{
+ return m_actions.size() > 0;
+}
+
+void CGUIAction::Append(const CExecutableAction& action)
+{
+ m_actions.emplace_back(action);
+}
+
+void CGUIAction::EnableSendThreadMessageMode()
+{
+ m_sendThreadMessages = true;
+}
+
+void CGUIAction::Reset()
+{
+ m_actions.clear();
+}
diff --git a/xbmc/guilib/GUIAction.h b/xbmc/guilib/GUIAction.h
new file mode 100644
index 0000000..1c881c5
--- /dev/null
+++ b/xbmc/guilib/GUIAction.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CGUIControl;
+class CGUIListItem; typedef std::shared_ptr<CGUIListItem> CGUIListItemPtr;
+
+/**
+ * Class containing vector of condition->(action/navigation route) and handling its execution.
+ */
+class CGUIAction
+{
+public:
+ /**
+ * Class which defines an executable action
+ */
+ class CExecutableAction
+ {
+ public:
+ /**
+ * Executable action constructor (without conditional execution)
+ * @param action - The action to be executed
+ */
+ explicit CExecutableAction(const std::string& action);
+ /**
+ * Executable action constructor (providing the condition and the action)
+ * @param condition - The condition that dictates the action execution
+ * @param action - The actual action
+ */
+ CExecutableAction(const std::string& condition, const std::string& action);
+
+ /**
+ * Get the condition of this executable action (may be empty)
+ * @return condition - The condition that dictates the action execution (or an empty string)
+ */
+ std::string GetCondition() const;
+
+ /**
+ * Checks if the executable action has any condition
+ * @return true if the executable action has any condition, else false
+ */
+ bool HasCondition() const;
+
+ /**
+ * Get the action string of this executable action
+ * @return action - The action string
+ */
+ std::string GetAction() const;
+
+ /**
+ * Sets/Replaces the action string of this executable action
+ * @param action - The action string
+ */
+ void SetAction(const std::string& action);
+
+ private:
+ /**
+ * Executable action default constructor
+ */
+ CExecutableAction() = delete;
+ /* The condition that dictates the action execution */
+ std::string m_condition;
+ /* The actual action */
+ std::string m_action;
+ };
+
+ CGUIAction() = default;
+ explicit CGUIAction(int controlID);
+ /**
+ * Execute actions without specifying any target control or parent control. Action will use the default focused control.
+ */
+ bool ExecuteActions() const;
+ /**
+ * Execute actions (no navigation paths); if action is paired with condition - evaluate condition first
+ */
+ bool ExecuteActions(int controlID, int parentID, const CGUIListItemPtr& item = nullptr) const;
+ /**
+ * Check if there is any action that meet its condition
+ */
+ bool HasActionsMeetingCondition() const;
+ /**
+ * Check if there is any action
+ */
+ bool HasAnyActions() const;
+ /**
+ * Get navigation route that meet its conditions first
+ */
+ int GetNavigation() const;
+ /**
+ * Set navigation route
+ */
+ void SetNavigation(int id);
+ /**
+ * Configure CGUIAction to send threaded messages
+ */
+ void EnableSendThreadMessageMode();
+ /**
+ * Add an executable action to the CGUIAction container
+ */
+ void Append(const CExecutableAction& action);
+ /**
+ * Prune any executable actions stored in the CGUIAction
+ */
+ void Reset();
+
+private:
+ std::vector<CExecutableAction> m_actions;
+ bool m_sendThreadMessages = false;
+};
diff --git a/xbmc/guilib/GUIAudioManager.cpp b/xbmc/guilib/GUIAudioManager.cpp
new file mode 100644
index 0000000..7f461c4
--- /dev/null
+++ b/xbmc/guilib/GUIAudioManager.cpp
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIAudioManager.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/Skin.h"
+#include "addons/addoninfo/AddonType.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "filesystem/Directory.h"
+#include "input/Key.h"
+#include "input/WindowTranslator.h"
+#include "input/actions/ActionIDs.h"
+#include "input/actions/ActionTranslator.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+CGUIAudioManager::CGUIAudioManager()
+ : m_settings(CServiceBroker::GetSettingsComponent()->GetSettings())
+{
+ m_bEnabled = false;
+
+ m_settings->RegisterCallback(this, {CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN,
+ CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME});
+}
+
+CGUIAudioManager::~CGUIAudioManager()
+{
+ m_settings->UnregisterCallback(this);
+}
+
+void CGUIAudioManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == NULL)
+ return;
+
+ const std::string &settingId = setting->GetId();
+ if (settingId == CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN)
+ {
+ Enable(true);
+ Load();
+ }
+}
+
+bool CGUIAudioManager::OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
+ const char* oldSettingId,
+ const TiXmlNode* oldSettingNode)
+{
+ if (setting == NULL)
+ return false;
+
+ if (setting->GetId() == CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN)
+ {
+ //Migrate the old settings
+ if (std::static_pointer_cast<CSettingString>(setting)->GetValue() == "SKINDEFAULT")
+ std::static_pointer_cast<CSettingString>(setting)->Reset();
+ else if (std::static_pointer_cast<CSettingString>(setting)->GetValue() == "OFF")
+ std::static_pointer_cast<CSettingString>(setting)->SetValue("");
+ }
+ if (setting->GetId() == CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME)
+ {
+ int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
+ SetVolume(0.01f * vol);
+ }
+ return true;
+}
+
+
+void CGUIAudioManager::Initialize()
+{
+}
+
+void CGUIAudioManager::DeInitialize()
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ UnLoad();
+}
+
+void CGUIAudioManager::Stop()
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ for (const auto& windowSound : m_windowSoundMap)
+ {
+ if (windowSound.second.initSound)
+ windowSound.second.initSound->Stop();
+ if (windowSound.second.deInitSound)
+ windowSound.second.deInitSound->Stop();
+ }
+
+ for (const auto& pythonSound : m_pythonSounds)
+ {
+ pythonSound.second->Stop();
+ }
+}
+
+// \brief Play a sound associated with a CAction
+void CGUIAudioManager::PlayActionSound(const CAction& action)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // it's not possible to play gui sounds when passthrough is active
+ if (!m_bEnabled)
+ return;
+
+ const auto it = m_actionSoundMap.find(action.GetID());
+ if (it == m_actionSoundMap.end())
+ return;
+
+ if (it->second)
+ {
+ int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
+ it->second->SetVolume(0.01f * vol);
+ it->second->Play();
+ }
+}
+
+// \brief Play a sound associated with a window and its event
+// Events: SOUND_INIT, SOUND_DEINIT
+void CGUIAudioManager::PlayWindowSound(int id, WINDOW_SOUND event)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // it's not possible to play gui sounds when passthrough is active
+ if (!m_bEnabled)
+ return;
+
+ const auto it = m_windowSoundMap.find(id);
+ if (it==m_windowSoundMap.end())
+ return;
+
+ std::shared_ptr<IAESound> sound;
+ switch (event)
+ {
+ case SOUND_INIT:
+ sound = it->second.initSound;
+ break;
+ case SOUND_DEINIT:
+ sound = it->second.deInitSound;
+ break;
+ }
+
+ if (!sound)
+ return;
+
+ int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
+ sound->SetVolume(0.01f * vol);
+ sound->Play();
+}
+
+// \brief Play a sound given by filename
+void CGUIAudioManager::PlayPythonSound(const std::string& strFileName, bool useCached /*= true*/)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ // it's not possible to play gui sounds when passthrough is active
+ if (!m_bEnabled)
+ return;
+
+ // If we already loaded the sound, just play it
+ const auto itsb = m_pythonSounds.find(strFileName);
+ if (itsb != m_pythonSounds.end())
+ {
+ const auto& sound = itsb->second;
+ if (useCached)
+ {
+ sound->Play();
+ return;
+ }
+ else
+ {
+ m_pythonSounds.erase(itsb);
+ }
+ }
+
+ auto sound = LoadSound(strFileName);
+ if (!sound)
+ return;
+
+ int vol = m_settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME);
+ sound->SetVolume(0.01f * vol);
+ sound->Play();
+ m_pythonSounds.emplace(strFileName, std::move(sound));
+}
+
+void CGUIAudioManager::UnLoad()
+{
+ m_windowSoundMap.clear();
+ m_pythonSounds.clear();
+ m_actionSoundMap.clear();
+ m_soundCache.clear();
+}
+
+
+std::string GetSoundSkinPath()
+{
+ auto setting = std::static_pointer_cast<CSettingString>(CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN));
+ auto value = setting->GetValue();
+ if (value.empty())
+ return "";
+
+ ADDON::AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(value, addon, ADDON::AddonType::RESOURCE_UISOUNDS,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ CLog::Log(LOGINFO, "Unknown sounds addon '{}'. Setting default sounds.", value);
+ setting->Reset();
+ }
+ return URIUtils::AddFileToFolder("resource://", setting->GetValue());
+}
+
+
+// \brief Load the config file (sounds.xml) for nav sounds
+bool CGUIAudioManager::Load()
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ UnLoad();
+
+ m_strMediaDir = GetSoundSkinPath();
+ if (m_strMediaDir.empty())
+ return true;
+
+ Enable(true);
+ std::string strSoundsXml = URIUtils::AddFileToFolder(m_strMediaDir, "sounds.xml");
+
+ // Load our xml file
+ CXBMCTinyXML xmlDoc;
+
+ CLog::Log(LOGINFO, "Loading {}", strSoundsXml);
+
+ // Load the config file
+ if (!xmlDoc.LoadFile(strSoundsXml))
+ {
+ CLog::Log(LOGINFO, "{}, Line {}\n{}", strSoundsXml, xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
+ return false;
+ }
+
+ TiXmlElement* pRoot = xmlDoc.RootElement();
+ std::string strValue = pRoot->Value();
+ if ( strValue != "sounds")
+ {
+ CLog::Log(LOGINFO, "{} Doesn't contain <sounds>", strSoundsXml);
+ return false;
+ }
+
+ // Load sounds for actions
+ TiXmlElement* pActions = pRoot->FirstChildElement("actions");
+ if (pActions)
+ {
+ TiXmlNode* pAction = pActions->FirstChild("action");
+
+ while (pAction)
+ {
+ TiXmlNode* pIdNode = pAction->FirstChild("name");
+ unsigned int id = ACTION_NONE; // action identity
+ if (pIdNode && pIdNode->FirstChild())
+ {
+ CActionTranslator::TranslateString(pIdNode->FirstChild()->Value(), id);
+ }
+
+ TiXmlNode* pFileNode = pAction->FirstChild("file");
+ std::string strFile;
+ if (pFileNode && pFileNode->FirstChild())
+ strFile += pFileNode->FirstChild()->Value();
+
+ if (id != ACTION_NONE && !strFile.empty())
+ {
+ std::string filename = URIUtils::AddFileToFolder(m_strMediaDir, strFile);
+ auto sound = LoadSound(filename);
+ if (sound)
+ m_actionSoundMap.emplace(id, std::move(sound));
+ }
+
+ pAction = pAction->NextSibling();
+ }
+ }
+
+ // Load window specific sounds
+ TiXmlElement* pWindows = pRoot->FirstChildElement("windows");
+ if (pWindows)
+ {
+ TiXmlNode* pWindow = pWindows->FirstChild("window");
+
+ while (pWindow)
+ {
+ int id = 0;
+
+ TiXmlNode* pIdNode = pWindow->FirstChild("name");
+ if (pIdNode)
+ {
+ if (pIdNode->FirstChild())
+ id = CWindowTranslator::TranslateWindow(pIdNode->FirstChild()->Value());
+ }
+
+ CWindowSounds sounds;
+ sounds.initSound = LoadWindowSound(pWindow, "activate" );
+ sounds.deInitSound = LoadWindowSound(pWindow, "deactivate");
+
+ if (id > 0)
+ m_windowSoundMap.insert(std::pair<int, CWindowSounds>(id, sounds));
+
+ pWindow = pWindow->NextSibling();
+ }
+ }
+
+ return true;
+}
+
+std::shared_ptr<IAESound> CGUIAudioManager::LoadSound(const std::string& filename)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ const auto it = m_soundCache.find(filename);
+ if (it != m_soundCache.end())
+ {
+ auto sound = it->second.lock();
+ if (sound)
+ return sound;
+ else
+ m_soundCache.erase(it); // cleanup orphaned cache entry
+ }
+
+ IAE *ae = CServiceBroker::GetActiveAE();
+ if (!ae)
+ return nullptr;
+
+ std::shared_ptr<IAESound> sound(ae->MakeSound(filename));
+ if (!sound)
+ return nullptr;
+
+ m_soundCache[filename] = sound;
+
+ return sound;
+}
+
+// \brief Load a window node of the config file (sounds.xml)
+std::shared_ptr<IAESound> CGUIAudioManager::LoadWindowSound(TiXmlNode* pWindowNode,
+ const std::string& strIdentifier)
+{
+ if (!pWindowNode)
+ return NULL;
+
+ TiXmlNode* pFileNode = pWindowNode->FirstChild(strIdentifier);
+ if (pFileNode && pFileNode->FirstChild())
+ return LoadSound(URIUtils::AddFileToFolder(m_strMediaDir, pFileNode->FirstChild()->Value()));
+
+ return NULL;
+}
+
+// \brief Enable/Disable nav sounds
+void CGUIAudioManager::Enable(bool bEnable)
+{
+ // always deinit audio when we don't want gui sounds
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN).empty())
+ bEnable = false;
+
+ std::unique_lock<CCriticalSection> lock(m_cs);
+ m_bEnabled = bEnable;
+}
+
+// \brief Sets the volume of all playing sounds
+void CGUIAudioManager::SetVolume(float level)
+{
+ std::unique_lock<CCriticalSection> lock(m_cs);
+
+ {
+ for (const auto& actionSound : m_actionSoundMap)
+ {
+ if (actionSound.second)
+ actionSound.second->SetVolume(level);
+ }
+ }
+
+ for (const auto& windowSound : m_windowSoundMap)
+ {
+ if (windowSound.second.initSound)
+ windowSound.second.initSound->SetVolume(level);
+ if (windowSound.second.deInitSound)
+ windowSound.second.deInitSound->SetVolume(level);
+ }
+
+ {
+ for (const auto& pythonSound : m_pythonSounds)
+ {
+ if (pythonSound.second)
+ pythonSound.second->SetVolume(level);
+ }
+ }
+}
diff --git a/xbmc/guilib/GUIAudioManager.h b/xbmc/guilib/GUIAudioManager.h
new file mode 100644
index 0000000..893cbdc
--- /dev/null
+++ b/xbmc/guilib/GUIAudioManager.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIComponent.h"
+#include "cores/AudioEngine/Interfaces/AESound.h"
+#include "settings/lib/ISettingCallback.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+// forward definitions
+class CAction;
+class CSettings;
+class TiXmlNode;
+class IAESound;
+
+enum WINDOW_SOUND { SOUND_INIT = 0, SOUND_DEINIT };
+
+class CGUIAudioManager : public ISettingCallback
+{
+ class CWindowSounds
+ {
+ public:
+ std::shared_ptr<IAESound> initSound;
+ std::shared_ptr<IAESound> deInitSound;
+ };
+
+ struct IAESoundDeleter
+ {
+ void operator()(IAESound* s);
+ };
+
+public:
+ CGUIAudioManager();
+ ~CGUIAudioManager() override;
+
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting,
+ const char* oldSettingId,
+ const TiXmlNode* oldSettingNode) override;
+
+ void Initialize();
+ void DeInitialize();
+
+ bool Load();
+ void UnLoad();
+
+
+ void PlayActionSound(const CAction& action);
+ void PlayWindowSound(int id, WINDOW_SOUND event);
+ void PlayPythonSound(const std::string& strFileName, bool useCached = true);
+
+ void Enable(bool bEnable);
+ void SetVolume(float level);
+ void Stop();
+
+private:
+ // Construction parameters
+ std::shared_ptr<CSettings> m_settings;
+
+ typedef std::map<const std::string, std::weak_ptr<IAESound>> soundCache;
+ typedef std::map<int, std::shared_ptr<IAESound>> actionSoundMap;
+ typedef std::map<int, CWindowSounds> windowSoundMap;
+ typedef std::map<const std::string, std::shared_ptr<IAESound>> pythonSoundsMap;
+
+ soundCache m_soundCache;
+ actionSoundMap m_actionSoundMap;
+ windowSoundMap m_windowSoundMap;
+ pythonSoundsMap m_pythonSounds;
+
+ std::string m_strMediaDir;
+ bool m_bEnabled;
+
+ CCriticalSection m_cs;
+
+ std::shared_ptr<IAESound> LoadSound(const std::string& filename);
+ std::shared_ptr<IAESound> LoadWindowSound(TiXmlNode* pWindowNode,
+ const std::string& strIdentifier);
+};
+
diff --git a/xbmc/guilib/GUIBaseContainer.cpp b/xbmc/guilib/GUIBaseContainer.cpp
new file mode 100644
index 0000000..f8089da
--- /dev/null
+++ b/xbmc/guilib/GUIBaseContainer.cpp
@@ -0,0 +1,1510 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIBaseContainer.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "ServiceBroker.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "listproviders/IListProvider.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CharsetConverter.h"
+#include "utils/MathUtils.h"
+#include "utils/SortUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#define HOLD_TIME_START 100
+#define HOLD_TIME_END 3000
+#define SCROLLING_GAP 200U
+#define SCROLLING_THRESHOLD 300U
+
+CGUIBaseContainer::CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
+ : IGUIContainer(parentID, controlID, posX, posY, width, height)
+ , m_scroller(scroller)
+{
+ m_cursor = 0;
+ m_offset = 0;
+ m_lastHoldTime = 0;
+ m_itemsPerPage = 10;
+ m_pageControl = 0;
+ m_orientation = orientation;
+ m_analogScrollCount = 0;
+ m_wasReset = false;
+ m_layout = NULL;
+ m_focusedLayout = NULL;
+ m_cacheItems = preloadItems;
+ m_scrollItemsPerFrame = 0.0f;
+ m_type = VIEW_TYPE_NONE;
+ m_autoScrollMoveTime = 0;
+ m_autoScrollDelayTime = 0;
+ m_autoScrollIsReversed = false;
+ m_lastRenderTime = 0;
+}
+
+CGUIBaseContainer::CGUIBaseContainer(const CGUIBaseContainer& other)
+ : IGUIContainer(other),
+ m_renderOffset(other.m_renderOffset),
+ m_analogScrollCount(other.m_analogScrollCount),
+ m_lastHoldTime(other.m_lastHoldTime),
+ m_orientation(other.m_orientation),
+ m_itemsPerPage(other.m_itemsPerPage),
+ m_pageControl(other.m_pageControl),
+ m_layoutCondition(other.m_layoutCondition),
+ m_focusedLayoutCondition(other.m_focusedLayoutCondition),
+ m_scroller(other.m_scroller),
+ m_listProvider(other.m_listProvider ? other.m_listProvider->Clone() : nullptr),
+ m_wasReset(other.m_wasReset),
+ m_letterOffsets(other.m_letterOffsets),
+ m_autoScrollCondition(other.m_autoScrollCondition),
+ m_autoScrollMoveTime(other.m_autoScrollMoveTime),
+ m_autoScrollDelayTime(other.m_autoScrollDelayTime),
+ m_autoScrollIsReversed(other.m_autoScrollIsReversed),
+ m_lastRenderTime(other.m_lastRenderTime),
+ m_cursor(other.m_cursor),
+ m_offset(other.m_offset),
+ m_cacheItems(other.m_cacheItems),
+ m_scrollTimer(other.m_scrollTimer),
+ m_lastScrollStartTimer(other.m_lastScrollStartTimer),
+ m_pageChangeTimer(other.m_pageChangeTimer),
+ m_clickActions(other.m_clickActions),
+ m_focusActions(other.m_focusActions),
+ m_unfocusActions(other.m_unfocusActions),
+ m_matchTimer(other.m_matchTimer),
+ m_match(other.m_match),
+ m_scrollItemsPerFrame(other.m_scrollItemsPerFrame),
+ m_gestureActive(other.m_gestureActive),
+ m_waitForScrollEnd(other.m_waitForScrollEnd),
+ m_lastScrollValue(other.m_lastScrollValue)
+{
+ // Initialize CGUIControl
+ m_bInvalidated = true;
+
+ for (const auto& item : other.m_items)
+ m_items.emplace_back(std::make_shared<CGUIListItem>(*item));
+
+ for (const auto& layout : other.m_layouts)
+ m_layouts.emplace_back(layout, this);
+
+ for (const auto& focusedLayout : other.m_focusedLayouts)
+ m_focusedLayouts.emplace_back(focusedLayout, this);
+}
+
+CGUIBaseContainer::~CGUIBaseContainer(void)
+{
+ // release the container from items
+ for (const auto& item : m_items)
+ item->FreeMemory();
+}
+
+void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CGUIControl::DoProcess(currentTime, dirtyregions);
+
+ if (m_pageChangeTimer.IsRunning() && m_pageChangeTimer.GetElapsedMilliseconds() > 200)
+ m_pageChangeTimer.Stop();
+ m_wasReset = false;
+
+ // if not visible, we reset the autoscroll timer
+ if (!IsVisible() && m_autoScrollMoveTime)
+ {
+ ResetAutoScrolling();
+ }
+}
+
+void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // update our auto-scrolling as necessary
+ UpdateAutoScrolling(currentTime);
+
+ if (!m_waitForScrollEnd && !m_gestureActive)
+ ValidateOffset();
+
+ if (m_bInvalidated)
+ UpdateLayout();
+
+ if (!m_layout || !m_focusedLayout) return;
+
+ UpdateScrollOffset(currentTime);
+
+ if (m_scroller.IsScrolling())
+ MarkDirtyRegion();
+
+ int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ // Free memory not used on screen
+ if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
+ FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
+
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ if (GetOffset() + GetCursor() < offset)
+ drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
+ pos += drawOffset;
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ int current = offset - cacheBefore;
+ while (pos < end && m_items.size())
+ {
+ int itemNo = CorrectOffset(current, 0);
+ if (itemNo >= (int)m_items.size())
+ break;
+ bool focused = (current == GetOffset() + GetCursor());
+ if (itemNo >= 0)
+ {
+ CGUIListItemPtr item = m_items[itemNo];
+ item->SetCurrentItem(itemNo + 1);
+
+ // render our item
+ if (m_orientation == VERTICAL)
+ ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
+ else
+ ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
+ }
+ // increment our position
+ pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
+ current++;
+ }
+
+ // when we are scrolling up, offset will become lower (integer division, see offset calc)
+ // to have same behaviour when scrolling down, we need to set page control to offset+1
+ UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
+
+ m_lastRenderTime = currentTime;
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIBaseContainer::ProcessItem(float posX, float posY, CGUIListItemPtr& item, bool focused, unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (!m_focusedLayout || !m_layout) return;
+
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (m_bInvalidated)
+ item->SetInvalid();
+ if (focused)
+ {
+ if (!item->GetFocusedLayout())
+ {
+ item->SetFocusedLayout(std::make_unique<CGUIListItemLayout>(*m_focusedLayout, this));
+ }
+ if (item->GetFocusedLayout())
+ {
+ if (item != m_lastItem || !HasFocus())
+ {
+ item->GetFocusedLayout()->SetFocusedItem(0);
+ }
+ if (item != m_lastItem && HasFocus())
+ {
+ item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
+ unsigned int subItem = 1;
+ if (m_lastItem && m_lastItem->GetFocusedLayout())
+ subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem();
+ item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
+ }
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ }
+ m_lastItem = item;
+ }
+ else
+ {
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
+ if (!item->GetLayout())
+ {
+ CGUIListItemLayoutPtr layout = std::make_unique<CGUIListItemLayout>(*m_layout, this);
+ item->SetLayout(std::move(layout));
+ }
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ if (item->GetLayout())
+ item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+void CGUIBaseContainer::Render()
+{
+ if (!m_layout || !m_focusedLayout) return;
+
+ int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ {
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ if (GetOffset() + GetCursor() < offset)
+ drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
+ pos += drawOffset;
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ float focusedPos = 0;
+ CGUIListItemPtr focusedItem;
+ int current = offset - cacheBefore;
+ while (pos < end && m_items.size())
+ {
+ int itemNo = CorrectOffset(current, 0);
+ if (itemNo >= (int)m_items.size())
+ break;
+ bool focused = (current == GetOffset() + GetCursor());
+ if (itemNo >= 0)
+ {
+ CGUIListItemPtr item = m_items[itemNo];
+ // render our item
+ if (focused)
+ {
+ focusedPos = pos;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x, pos, item.get(), false);
+ else
+ RenderItem(pos, origin.y, item.get(), false);
+ }
+ }
+ // increment our position
+ pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
+ current++;
+ }
+ // render focused item last so it can overlap other items
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x, focusedPos, focusedItem.get(), true);
+ else
+ RenderItem(focusedPos, origin.y, focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+
+ CGUIControl::Render();
+}
+
+
+void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
+{
+ if (!m_focusedLayout || !m_layout) return;
+
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (focused)
+ {
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ }
+ else
+ {
+ if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ else if (item->GetLayout())
+ item->GetLayout()->Render(item, m_parentID);
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+bool CGUIBaseContainer::OnAction(const CAction &action)
+{
+ if (action.GetID() == KEY_UNICODE)
+ {
+ std::string letter;
+ g_charsetConverter.wToUTF8({action.GetUnicode()}, letter);
+ OnJumpLetter(letter);
+ return true;
+ }
+ // stop the timer on any other action
+ m_matchTimer.Stop();
+
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_LEFT:
+ case ACTION_MOVE_RIGHT:
+ case ACTION_MOVE_DOWN:
+ case ACTION_MOVE_UP:
+ case ACTION_NAV_BACK:
+ case ACTION_PREVIOUS_MENU:
+ {
+ if (!HasFocus()) return false;
+
+ if (action.GetHoldTime() > HOLD_TIME_START &&
+ ((m_orientation == VERTICAL && (action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN)) ||
+ (m_orientation == HORIZONTAL && (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT))))
+ { // action is held down - repeat a number of times
+ float speed = std::min(1.0f, (float)(action.GetHoldTime() - HOLD_TIME_START) / (HOLD_TIME_END - HOLD_TIME_START));
+ unsigned int frameDuration = std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime, 50u); // max 20fps
+
+ // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
+ // i.e. timed to take 7 seconds to traverse the list at full speed.
+ // minimal scroll rate is at least 10 items per second
+ float maxSpeed = std::max(frameDuration * 0.001f * 30, frameDuration * 0.001f * GetRows() / 7);
+ float minSpeed = frameDuration * 0.001f * 10;
+ m_scrollItemsPerFrame += std::max(minSpeed, speed*maxSpeed); // accelerate to max speed
+ m_lastHoldTime = CTimeUtils::GetFrameTime();
+
+ if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
+ return true;
+
+ while (m_scrollItemsPerFrame >= 1)
+ {
+ if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
+ MoveUp(false);
+ else
+ MoveDown(false);
+ m_scrollItemsPerFrame--;
+ }
+ return true;
+ }
+ else
+ {
+ //if HOLD_TIME_START is reached we need
+ //a sane initial value for calculating m_scrollItemsPerPage
+ m_lastHoldTime = CTimeUtils::GetFrameTime();
+ m_scrollItemsPerFrame = 0.0f;
+ return CGUIControl::OnAction(action);
+ }
+ }
+ case ACTION_CONTEXT_MENU:
+ if (OnContextMenu())
+ return true;
+ break;
+ case ACTION_SHOW_INFO:
+ if (m_listProvider)
+ {
+ const int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_listProvider->OnInfo(m_items[selected]))
+ return true;
+ }
+ }
+ if (OnInfo())
+ return true;
+ else if (action.GetID())
+ return OnClick(action.GetID());
+
+ return false;
+
+ case ACTION_PLAYER_PLAY:
+ if (m_listProvider)
+ {
+ const int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_listProvider->OnPlay(m_items[selected]))
+ return true;
+ }
+ }
+ break;
+
+ case ACTION_FIRST_PAGE:
+ SelectItem(0);
+ return true;
+
+ case ACTION_LAST_PAGE:
+ if (m_items.size())
+ SelectItem(m_items.size() - 1);
+ return true;
+
+ case ACTION_NEXT_LETTER:
+ OnNextLetter();
+ return true;
+ case ACTION_PREV_LETTER:
+ OnPrevLetter();
+ return true;
+ case ACTION_JUMP_SMS2:
+ case ACTION_JUMP_SMS3:
+ case ACTION_JUMP_SMS4:
+ case ACTION_JUMP_SMS5:
+ case ACTION_JUMP_SMS6:
+ case ACTION_JUMP_SMS7:
+ case ACTION_JUMP_SMS8:
+ case ACTION_JUMP_SMS9:
+ OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
+ return true;
+
+ default:
+ break;
+ }
+ return action.GetID() && OnClick(action.GetID());
+}
+
+bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (!m_listProvider)
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
+ { // bind our items
+ Reset();
+ CFileItemList *items = static_cast<CFileItemList*>(message.GetPointer());
+ for (int i = 0; i < items->Size(); i++)
+ m_items.push_back(items->Get(i));
+ UpdateLayout(true); // true to refresh all items
+ UpdateScrollByLetter();
+ SelectItem(message.GetParam1());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ Reset();
+ SetPageControlRange();
+ return true;
+ }
+ }
+ if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
+ {
+ SelectItem(message.GetParam1());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_SETFOCUS)
+ {
+ if (message.GetParam1()) // subfocus item is specified, so set the offset appropriately
+ {
+ int offset = GetOffset();
+ if (message.GetParam2() && message.GetParam2() == 1)
+ offset = 0;
+ int item = std::min(offset + message.GetParam1() - 1, (int)m_items.size() - 1);
+ SelectItem(item);
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
+ {
+ message.SetParam1(GetSelectedItem());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
+ {
+ if (message.GetSenderId() == m_pageControl && IsVisible())
+ { // update our page if we're visible - not much point otherwise
+ if (message.GetParam1() != GetOffset())
+ m_pageChangeTimer.StartZero();
+ ScrollToOffset(message.GetParam1());
+ return true;
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
+ { // update our list contents
+ for (unsigned int i = 0; i < m_items.size(); ++i)
+ m_items[i]->SetInvalid();
+ }
+ else if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
+ {
+ if (m_listProvider)
+ m_listProvider->FreeResources(true);
+ }
+ else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
+ {
+ int count = message.GetParam1();
+ while (count < 0)
+ {
+ MoveUp(true);
+ count++;
+ }
+ while (count > 0)
+ {
+ MoveDown(true);
+ count--;
+ }
+ return true;
+ }
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIBaseContainer::OnUp()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_UP);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveUp(wrapAround))
+ return;
+ // with horizontal lists it doesn't make much sense to have multiselect labels
+ CGUIControl::OnUp();
+}
+
+void CGUIBaseContainer::OnDown()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_DOWN);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveDown(wrapAround))
+ return;
+ // with horizontal lists it doesn't make much sense to have multiselect labels
+ CGUIControl::OnDown();
+}
+
+void CGUIBaseContainer::OnLeft()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_LEFT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
+ return;
+ else if (m_orientation == VERTICAL)
+ {
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout && focusedLayout->MoveLeft())
+ return;
+ }
+ CGUIControl::OnLeft();
+}
+
+void CGUIBaseContainer::OnRight()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
+ return;
+ else if (m_orientation == VERTICAL)
+ {
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout && focusedLayout->MoveRight())
+ return;
+ }
+ CGUIControl::OnRight();
+}
+
+void CGUIBaseContainer::OnNextLetter()
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
+ {
+ if (m_letterOffsets[i].first > offset)
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+}
+
+void CGUIBaseContainer::OnPrevLetter()
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ if (!m_letterOffsets.size())
+ return;
+ for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
+ {
+ if (m_letterOffsets[i].first < offset)
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+}
+
+void CGUIBaseContainer::OnJumpLetter(const std::string& letter, bool skip /*=false*/)
+{
+ if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
+ m_match += letter;
+ else
+ m_match = letter;
+
+ m_matchTimer.StartZero();
+
+ // we can't jump through letters if we have none
+ if (0 == m_letterOffsets.size())
+ return;
+
+ // find the current letter we're focused on
+ unsigned int offset = CorrectOffset(GetOffset(), GetCursor());
+ unsigned int i = (offset + ((skip) ? 1 : 0)) % m_items.size();
+ do
+ {
+ CGUIListItemPtr item = m_items[i];
+ std::string label = item->GetLabel();
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
+ label = SortUtils::RemoveArticles(label);
+ if (0 == StringUtils::CompareNoCase(label, m_match, m_match.size()))
+ {
+ SelectItem(i);
+ return;
+ }
+ i = (i+1) % m_items.size();
+ } while (i != offset);
+
+ // no match found - repeat with a single letter
+ std::wstring wmatch;
+ g_charsetConverter.utf8ToW(m_match, wmatch);
+ if (wmatch.length() > 1)
+ {
+ m_match.clear();
+ OnJumpLetter(letter, true);
+ }
+}
+
+void CGUIBaseContainer::OnJumpSMS(int letter)
+{
+ static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
+
+ // only 2..9 supported
+ if (letter < 2 || letter > 9 || !m_letterOffsets.size())
+ return;
+
+ const std::string letters = letterMap[letter - 2];
+ // find where we currently are
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ unsigned int currentLetter = 0;
+ while (currentLetter + 1 < m_letterOffsets.size() && m_letterOffsets[currentLetter + 1].first <= offset)
+ currentLetter++;
+
+ // now switch to the next letter
+ std::string current = m_letterOffsets[currentLetter].second;
+ size_t startPos = (letters.find(current) + 1) % letters.size();
+ // now jump to letters[startPos], or another one in the same range if possible
+ size_t pos = startPos;
+ while (true)
+ {
+ // check if we can jump to this letter
+ for (size_t i = 0; i < m_letterOffsets.size(); i++)
+ {
+ if (m_letterOffsets[i].second == letters.substr(pos, 1))
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+ pos = (pos + 1) % letters.size();
+ if (pos == startPos)
+ return;
+ }
+}
+
+bool CGUIBaseContainer::MoveUp(bool wrapAround)
+{
+ return true;
+}
+
+bool CGUIBaseContainer::MoveDown(bool wrapAround)
+{
+ return true;
+}
+
+// scrolls the said amount
+void CGUIBaseContainer::Scroll(int amount)
+{
+ ResetAutoScrolling();
+ ScrollToOffset(GetOffset() + amount);
+}
+
+int CGUIBaseContainer::GetSelectedItem() const
+{
+ return CorrectOffset(GetOffset(), GetCursor());
+}
+
+CGUIListItemPtr CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
+{
+ if (!m_items.size() || !m_layout)
+ return CGUIListItemPtr();
+ int item = GetSelectedItem() + offset;
+ if (flag & INFOFLAG_LISTITEM_POSITION) // use offset from the first item displayed, taking into account scrolling
+ item = CorrectOffset((int)(m_scroller.GetValue() / m_layout->Size(m_orientation)), offset);
+
+ if (flag & INFOFLAG_LISTITEM_ABSOLUTE) // use offset from the first item
+ item = CorrectOffset(0, offset);
+
+ if (flag & INFOFLAG_LISTITEM_WRAP)
+ {
+ item %= ((int)m_items.size());
+ if (item < 0) item += m_items.size();
+ return m_items[item];
+ }
+ else
+ {
+ if (item >= 0 && item < (int)m_items.size())
+ return m_items[item];
+ }
+ return CGUIListItemPtr();
+}
+
+CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
+{
+ CGUIListItemPtr item = GetListItem(0);
+ if (item.get()) return item->GetFocusedLayout();
+ return NULL;
+}
+
+bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
+{
+ // select the item under the pointer
+ if (!m_waitForScrollEnd)
+ SelectItemFromPoint(point - CPoint(m_posX, m_posY));
+ return CGUIControl::OnMouseOver(point);
+}
+
+EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_LEFT_CLICK ||
+ event.m_id == ACTION_MOUSE_DOUBLE_CLICK ||
+ event.m_id == ACTION_MOUSE_RIGHT_CLICK)
+ {
+ // Cancel touch
+ m_waitForScrollEnd = false;
+ int select = GetSelectedItem();
+ if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
+ {
+ if (event.m_id != ACTION_MOUSE_RIGHT_CLICK || select == GetSelectedItem())
+ OnClick(event.m_id);
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
+ {
+ Scroll(-1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ Scroll(1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_NOTIFY)
+ {
+ m_waitForScrollEnd = true;
+ m_lastScrollValue = m_scroller.GetValue();
+ return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ { // grab exclusive access
+ m_gestureActive = true;
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ { // do the drag and validate our offset (corrects for end of scroll)
+ m_scroller.SetValue(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY));
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ int offset = MathUtils::round_int(static_cast<double>(m_scroller.GetValue() / size));
+ m_lastScrollStartTimer.Stop();
+ m_scrollTimer.Start();
+ const int absCursor = CorrectOffset(GetOffset(), GetCursor());
+ SetOffset(offset);
+ ValidateOffset();
+ // Notify Application if Inertial scrolling reaches lists end
+ if (m_waitForScrollEnd)
+ {
+ if (fabs(m_scroller.GetValue() - m_lastScrollValue) < 0.001f)
+ {
+ m_waitForScrollEnd = false;
+ return EVENT_RESULT_UNHANDLED;
+ }
+ else
+ m_lastScrollValue = m_scroller.GetValue();
+ }
+ else
+ {
+ CGUIBaseContainer::SetCursor(absCursor - CorrectOffset(GetOffset(), 0));
+ }
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ m_scrollTimer.Stop();
+ // and compute the nearest offset from this and scroll there
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ float offset = m_scroller.GetValue() / size;
+ int toOffset = MathUtils::round_int(static_cast<double>(offset));
+ if (toOffset < offset)
+ SetOffset(toOffset+1);
+ else
+ SetOffset(toOffset-1);
+ ScrollToOffset(toOffset);
+ ValidateOffset();
+ SetCursor(GetCursor());
+ SetFocus(true);
+ m_waitForScrollEnd = false;
+ m_gestureActive = false;
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+bool CGUIBaseContainer::OnClick(int actionID)
+{
+ int subItem = 0;
+ if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
+ {
+ if (m_listProvider)
+ { // "select" action
+ int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_clickActions.HasActionsMeetingCondition())
+ m_clickActions.ExecuteActions(0, GetParentID(), m_items[selected]);
+ else
+ m_listProvider->OnClick(m_items[selected]);
+ }
+ return true;
+ }
+ // grab the currently focused subitem (if applicable)
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout)
+ subItem = focusedLayout->GetFocusedItem();
+ }
+ else if (actionID == ACTION_MOUSE_RIGHT_CLICK)
+ {
+ if (OnContextMenu())
+ return true;
+ }
+ // Don't know what to do, so send to our parent window.
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
+ return SendWindowMessage(msg);
+}
+
+bool CGUIBaseContainer::OnContextMenu()
+{
+ if (m_listProvider)
+ {
+ int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ m_listProvider->OnContextMenu(m_items[selected]);
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string CGUIBaseContainer::GetDescription() const
+{
+ std::string strLabel;
+ int item = GetSelectedItem();
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ CGUIListItemPtr pItem = m_items[item];
+ if (pItem->m_bIsFolder)
+ strLabel = StringUtils::Format("[{}]", pItem->GetLabel());
+ else
+ strLabel = pItem->GetLabel();
+ }
+ return strLabel;
+}
+
+void CGUIBaseContainer::SetFocus(bool bOnOff)
+{
+ if (bOnOff != HasFocus())
+ {
+ SetInvalid();
+ m_lastItem.reset();
+ }
+ CGUIControl::SetFocus(bOnOff);
+}
+
+void CGUIBaseContainer::SaveStates(std::vector<CControlState> &states)
+{
+ if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
+ states.emplace_back(GetID(), GetSelectedItem());
+}
+
+void CGUIBaseContainer::SetPageControl(int id)
+{
+ m_pageControl = id;
+}
+
+bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
+{
+ minOffset = 0;
+ maxOffset = GetRows() - m_itemsPerPage;
+ return true;
+}
+
+void CGUIBaseContainer::ValidateOffset()
+{
+}
+
+void CGUIBaseContainer::AllocResources()
+{
+ CGUIControl::AllocResources();
+ CalculateLayout();
+ if (m_listProvider)
+ {
+ UpdateListProvider(true);
+ }
+}
+
+void CGUIBaseContainer::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ if (m_listProvider)
+ {
+ if (immediately)
+ {
+ Reset();
+ m_listProvider->Reset();
+ }
+ }
+ m_scroller.Stop();
+}
+
+void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
+{
+ if (updateAllItems)
+ { // free memory of items
+ for (iItems it = m_items.begin(); it != m_items.end(); ++it)
+ (*it)->FreeMemory();
+ }
+ // and recalculate the layout
+ CalculateLayout();
+ SetPageControlRange();
+ MarkDirtyRegion();
+}
+
+void CGUIBaseContainer::SetPageControlRange()
+{
+ if (m_pageControl)
+ {
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
+ SendWindowMessage(msg);
+ }
+}
+
+void CGUIBaseContainer::UpdatePageControl(int offset)
+{
+ if (m_pageControl)
+ { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, offset);
+ SendWindowMessage(msg);
+ }
+}
+
+void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
+{
+ CGUIControl::UpdateVisibility(item);
+
+ if (!IsVisible() && !CGUIControl::CanFocus())
+ return; // no need to update the content if we're not visible and we can't focus
+
+ // update layouts in case of condition changed
+ if ((m_layout && m_layout->CheckCondition() != m_layoutCondition) ||
+ (m_focusedLayout && m_focusedLayout->CheckCondition() != m_focusedLayoutCondition))
+ {
+ if (m_layout)
+ m_layoutCondition = m_layout->CheckCondition();
+ if (m_focusedLayout)
+ m_focusedLayoutCondition = m_focusedLayout->CheckCondition();
+
+ int itemIndex = GetSelectedItem();
+ UpdateLayout(true); // true to refresh all items
+ SelectItem(itemIndex);
+ }
+
+ UpdateListProvider();
+}
+
+void CGUIBaseContainer::UpdateListProvider(bool forceRefresh /* = false */)
+{
+ if (m_listProvider)
+ {
+ if (m_listProvider->Update(forceRefresh))
+ {
+ // save the current item
+ int currentItem = GetSelectedItem();
+ CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
+ const std::string prevSelectedPath((current && current->IsFileItem()) ? static_cast<CFileItem *>(current)->GetPath() : "");
+
+ Reset();
+ m_listProvider->Fetch(m_items);
+ SetPageControlRange();
+ // update the newly selected item
+ bool found = false;
+
+ // first, try to re-identify selected item by comparing item pointers, though it is not guaranteed that item instances got not recreated on update.
+ for (int i = 0; i < (int)m_items.size(); i++)
+ {
+ if (m_items[i].get() == current)
+ {
+ found = true;
+ if (i != currentItem)
+ {
+ SelectItem(i);
+ break;
+ }
+ }
+ }
+ if (!found && !prevSelectedPath.empty())
+ {
+ // as fallback, try to re-identify selected item by comparing item paths.
+ for (int i = 0; i < static_cast<int>(m_items.size()); i++)
+ {
+ const CGUIListItemPtr c(m_items[i]);
+ if (c->IsFileItem())
+ {
+ const std::string &selectedPath = static_cast<CFileItem *>(c.get())->GetPath();
+ if (selectedPath == prevSelectedPath)
+ {
+ found = true;
+ if (i != currentItem)
+ {
+ SelectItem(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!found && currentItem >= (int)m_items.size())
+ SelectItem(m_items.size()-1);
+ SetInvalid();
+ }
+ // always update the scroll by letter, as the list provider may have altered labels
+ // while not actually changing the list items.
+ UpdateScrollByLetter();
+ }
+}
+
+void CGUIBaseContainer::CalculateLayout()
+{
+ CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
+ CGUIListItemLayout *oldLayout = m_layout;
+ GetCurrentLayouts();
+
+ // calculate the number of items to display
+ if (!m_focusedLayout || !m_layout)
+ return;
+
+ if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
+ return; // nothing has changed, so don't update stuff
+
+ m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
+
+ // ensure that the scroll offset is a multiple of our size
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+}
+
+void CGUIBaseContainer::UpdateScrollByLetter()
+{
+ m_letterOffsets.clear();
+
+ // for scrolling by letter we have an offset table into our vector.
+ std::string currentMatch;
+ for (unsigned int i = 0; i < m_items.size(); i++)
+ {
+ CGUIListItemPtr item = m_items[i];
+ // The letter offset jumping is only for ASCII characters at present, and
+ // our checks are all done in uppercase
+ std::string nextLetter;
+ std::wstring character = item->GetSortLabel().substr(0, 1);
+ StringUtils::ToUpper(character);
+ g_charsetConverter.wToUTF8(character, nextLetter);
+ if (currentMatch != nextLetter)
+ {
+ currentMatch = nextLetter;
+ m_letterOffsets.emplace_back(static_cast<int>(i), currentMatch);
+ }
+ }
+}
+
+unsigned int CGUIBaseContainer::GetRows() const
+{
+ return m_items.size();
+}
+
+inline float CGUIBaseContainer::Size() const
+{
+ return (m_orientation == HORIZONTAL) ? m_width : m_height;
+}
+
+int CGUIBaseContainer::ScrollCorrectionRange() const
+{
+ int range = m_itemsPerPage / 4;
+ if (range <= 0) range = 1;
+ return range;
+}
+
+void CGUIBaseContainer::ScrollToOffset(int offset)
+{
+ int minOffset, maxOffset;
+ if(GetOffsetRange(minOffset, maxOffset))
+ offset = std::max(minOffset, std::min(offset, maxOffset));
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ int range = ScrollCorrectionRange();
+ if (offset * size < m_scroller.GetValue() && m_scroller.GetValue() - offset * size > size * range)
+ { // scrolling up, and we're jumping more than 0.5 of a screen
+ m_scroller.SetValue((offset + range) * size);
+ }
+ if (offset * size > m_scroller.GetValue() && offset * size - m_scroller.GetValue() > size * range)
+ { // scrolling down, and we're jumping more than 0.5 of a screen
+ m_scroller.SetValue((offset - range) * size);
+ }
+ m_scroller.ScrollTo(offset * size);
+ m_lastScrollStartTimer.StartZero();
+ if (!m_wasReset)
+ {
+ SetContainerMoving(offset - GetOffset());
+ if (m_scroller.IsScrolling())
+ m_scrollTimer.Start();
+ else
+ m_scrollTimer.Stop();
+ }
+ else
+ {
+ m_scrollTimer.Stop();
+ m_scroller.Update(~0U);
+ }
+ SetOffset(offset);
+}
+
+void CGUIBaseContainer::SetAutoScrolling(const TiXmlNode *node)
+{
+ if (!node) return;
+ const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
+ if (scroll)
+ {
+ scroll->Attribute("time", &m_autoScrollMoveTime);
+ if (scroll->Attribute("reverse"))
+ m_autoScrollIsReversed = true;
+ if (scroll->FirstChild())
+ m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
+ }
+}
+
+void CGUIBaseContainer::ResetAutoScrolling()
+{
+ m_autoScrollDelayTime = 0;
+}
+
+void CGUIBaseContainer::UpdateAutoScrolling(unsigned int currentTime)
+{
+ if (m_autoScrollCondition && m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
+ {
+ if (m_lastRenderTime)
+ m_autoScrollDelayTime += currentTime - m_lastRenderTime;
+ if (m_autoScrollDelayTime > (unsigned int)m_autoScrollMoveTime && !m_scroller.IsScrolling())
+ { // delay is finished - start moving
+ m_autoScrollDelayTime = 0;
+ // Move up or down whether reversed moving is true or false
+ m_autoScrollIsReversed ? MoveUp(true) : MoveDown(true);
+ }
+ }
+ else
+ ResetAutoScrolling();
+}
+
+void CGUIBaseContainer::SetContainerMoving(int direction)
+{
+ if (direction)
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
+}
+
+void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
+{
+ if (m_scroller.Update(currentTime))
+ MarkDirtyRegion();
+ else if (m_lastScrollStartTimer.IsRunning() && m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
+ {
+ m_scrollTimer.Stop();
+ m_lastScrollStartTimer.Stop();
+ SetCursor(GetCursor());
+ }
+}
+
+int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
+{
+ return offset + cursor;
+}
+
+void CGUIBaseContainer::Reset()
+{
+ m_wasReset = true;
+ m_items.clear();
+ m_lastItem.reset();
+ ResetAutoScrolling();
+}
+
+void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
+{
+ TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
+ while (itemElement)
+ { // we have a new item layout
+ m_layouts.emplace_back();
+ m_layouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("itemlayout");
+ m_layouts.back().SetParentControl(this);
+ }
+ itemElement = layout->FirstChildElement("focusedlayout");
+ while (itemElement)
+ { // we have a new item layout
+ m_focusedLayouts.emplace_back();
+ m_focusedLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("focusedlayout");
+ m_focusedLayouts.back().SetParentControl(this);
+ }
+}
+
+void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
+{
+ m_listProvider = IListProvider::Create(content, GetParentID());
+ if (m_listProvider)
+ m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
+}
+
+void CGUIBaseContainer::SetListProvider(std::unique_ptr<IListProvider> provider)
+{
+ m_listProvider = std::move(provider);
+ UpdateListProvider(true);
+}
+
+void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
+{
+ m_renderOffset = offset;
+}
+
+void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
+{
+ if (keepStart < keepEnd)
+ { // remove before keepStart and after keepEnd
+ for (int i = 0; i < keepStart && i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ for (int i = std::max(keepEnd + 1, 0); i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ }
+ else
+ { // wrapping
+ for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ }
+}
+
+bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
+{
+ if (!layout) return false;
+ if ((m_orientation == VERTICAL && (layout->Size(HORIZONTAL) > 1) && point.x > layout->Size(HORIZONTAL)) ||
+ (m_orientation == HORIZONTAL && (layout->Size(VERTICAL) > 1)&& point.y > layout->Size(VERTICAL)))
+ return false;
+ return true;
+}
+
+#ifdef _DEBUG
+void CGUIBaseContainer::DumpTextureUse()
+{
+ CLog::Log(LOGDEBUG, "{} for container {}", __FUNCTION__, GetID());
+ for (unsigned int i = 0; i < m_items.size(); ++i)
+ {
+ CGUIListItemPtr item = m_items[i];
+ if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
+ if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
+ }
+}
+#endif
+
+bool CGUIBaseContainer::GetCondition(int condition, int data) const
+{
+ switch (condition)
+ {
+ case CONTAINER_ROW:
+ return (m_orientation == VERTICAL) ? (GetCursor() == data) : true;
+ case CONTAINER_COLUMN:
+ return (m_orientation == HORIZONTAL) ? (GetCursor() == data) : true;
+ case CONTAINER_POSITION:
+ return (GetCursor() == data);
+ case CONTAINER_HAS_NEXT:
+ return (HasNextPage());
+ case CONTAINER_HAS_PREVIOUS:
+ return (HasPreviousPage());
+ case CONTAINER_HAS_PARENT_ITEM:
+ return (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder());
+ case CONTAINER_SUBITEM:
+ {
+ CGUIListItemLayout *layout = GetFocusedLayout();
+ return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
+ }
+ case CONTAINER_SCROLLING:
+ return ((m_scrollTimer.IsRunning() && m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD)) || m_pageChangeTimer.IsRunning());
+ case CONTAINER_ISUPDATING:
+ return (m_listProvider) ? m_listProvider->IsUpdating() : false;
+ default:
+ return false;
+ }
+}
+
+void CGUIBaseContainer::GetCurrentLayouts()
+{
+ m_layout = NULL;
+ for (auto &layout : m_layouts)
+ {
+ if (layout.CheckCondition())
+ {
+ m_layout = &layout;
+ break;
+ }
+ }
+ if (!m_layout && !m_layouts.empty())
+ m_layout = &m_layouts.front(); // failsafe
+
+ m_focusedLayout = NULL;
+ for (auto &layout : m_focusedLayouts)
+ {
+ if (layout.CheckCondition())
+ {
+ m_focusedLayout = &layout;
+ break;
+ }
+ }
+ if (!m_focusedLayout && !m_focusedLayouts.empty())
+ m_focusedLayout = &m_focusedLayouts.front(); // failsafe
+}
+
+bool CGUIBaseContainer::HasNextPage() const
+{
+ return false;
+}
+
+bool CGUIBaseContainer::HasPreviousPage() const
+{
+ return false;
+}
+
+std::string CGUIBaseContainer::GetLabel(int info) const
+{
+ std::string label;
+ switch (info)
+ {
+ case CONTAINER_NUM_PAGES:
+ label = std::to_string((GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
+ break;
+ case CONTAINER_CURRENT_PAGE:
+ label = std::to_string(GetCurrentPage());
+ break;
+ case CONTAINER_POSITION:
+ label = std::to_string(GetCursor());
+ break;
+ case CONTAINER_CURRENT_ITEM:
+ {
+ if (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
+ label = std::to_string(GetSelectedItem());
+ else
+ label = std::to_string(GetSelectedItem() + 1);
+ }
+ break;
+ case CONTAINER_NUM_ALL_ITEMS:
+ case CONTAINER_NUM_ITEMS:
+ {
+ unsigned int numItems = GetNumItems();
+ if (info == CONTAINER_NUM_ITEMS && numItems && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
+ label = std::to_string(numItems - 1);
+ else
+ label = std::to_string(numItems);
+ }
+ break;
+ case CONTAINER_NUM_NONFOLDER_ITEMS:
+ {
+ int numItems = 0;
+ for (const auto& item : m_items)
+ {
+ if (!item->m_bIsFolder)
+ numItems++;
+ }
+ label = std::to_string(numItems);
+ }
+ break;
+ default:
+ break;
+ }
+ return label;
+}
+
+int CGUIBaseContainer::GetCurrentPage() const
+{
+ if (GetOffset() + m_itemsPerPage >= (int)GetRows()) // last page
+ return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
+ return GetOffset() / m_itemsPerPage + 1;
+}
+
+void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
+{
+ if (m_scroller.IsScrollingDown())
+ {
+ cacheBefore = 0;
+ cacheAfter = m_cacheItems;
+ }
+ else if (m_scroller.IsScrollingUp())
+ {
+ cacheBefore = m_cacheItems;
+ cacheAfter = 0;
+ }
+ else
+ {
+ cacheBefore = m_cacheItems / 2;
+ cacheAfter = m_cacheItems / 2;
+ }
+}
+
+void CGUIBaseContainer::SetCursor(int cursor)
+{
+ if (m_cursor != cursor)
+ MarkDirtyRegion();
+ m_cursor = cursor;
+}
+
+void CGUIBaseContainer::SetOffset(int offset)
+{
+ if (m_offset != offset)
+ MarkDirtyRegion();
+ m_offset = offset;
+}
+
+bool CGUIBaseContainer::CanFocus() const
+{
+ if (CGUIControl::CanFocus())
+ {
+ /*
+ We allow focus if we have items available or if we have a list provider
+ that's in the process of updating.
+ */
+ return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
+ }
+ return false;
+}
+
+void CGUIBaseContainer::OnFocus()
+{
+ if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
+ SelectItem(m_listProvider->GetDefaultItem());
+
+ if (m_focusActions.HasAnyActions())
+ m_focusActions.ExecuteActions(GetID(), GetParentID());
+
+ CGUIControl::OnFocus();
+}
+
+void CGUIBaseContainer::OnUnFocus()
+{
+ if (m_unfocusActions.HasAnyActions())
+ m_unfocusActions.ExecuteActions(GetID(), GetParentID());
+
+ CGUIControl::OnUnFocus();
+}
diff --git a/xbmc/guilib/GUIBaseContainer.h b/xbmc/guilib/GUIBaseContainer.h
new file mode 100644
index 0000000..2ca35f0
--- /dev/null
+++ b/xbmc/guilib/GUIBaseContainer.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListContainer.h
+\brief
+*/
+
+#include "GUIAction.h"
+#include "IGUIContainer.h"
+#include "utils/Stopwatch.h"
+
+#include <list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief
+ */
+
+class IListProvider;
+class TiXmlNode;
+class CGUIListItemLayout;
+
+class CGUIBaseContainer : public IGUIContainer
+{
+public:
+ CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems);
+ explicit CGUIBaseContainer(const CGUIBaseContainer& other);
+ ~CGUIBaseContainer(void) override;
+
+ bool OnAction(const CAction &action) override;
+ void OnDown() override;
+ void OnUp() override;
+ void OnLeft() override;
+ void OnRight() override;
+ bool OnMouseOver(const CPoint &point) override;
+ bool CanFocus() const override;
+ bool OnMessage(CGUIMessage& message) override;
+ void SetFocus(bool bOnOff) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+
+ virtual unsigned int GetRows() const;
+
+ virtual bool HasNextPage() const;
+ virtual bool HasPreviousPage() const;
+
+ void SetPageControl(int id);
+
+ std::string GetDescription() const override;
+ void SaveStates(std::vector<CControlState> &states) override;
+ virtual int GetSelectedItem() const;
+
+ void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+
+ void LoadLayout(TiXmlElement *layout);
+ void LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways);
+
+ CGUIListItemPtr GetListItem(int offset, unsigned int flag = 0) const override;
+
+ bool GetCondition(int condition, int data) const override;
+ std::string GetLabel(int info) const override;
+
+ /*! \brief Set the list provider for this container (for python).
+ \param provider the list provider to use for this container.
+ */
+ void SetListProvider(std::unique_ptr<IListProvider> provider);
+
+ /*! \brief Set the offset of the first item in the container from the container's position
+ Useful for lists/panels where the focused item may be larger than the non-focused items and thus
+ normally cut off from the clipping window defined by the container's position + size.
+ \param offset CPoint holding the offset in skin coordinates.
+ */
+ void SetRenderOffset(const CPoint &offset);
+
+ void SetClickActions(const CGUIAction& clickActions) { m_clickActions = clickActions; }
+ void SetFocusActions(const CGUIAction& focusActions) { m_focusActions = focusActions; }
+ void SetUnFocusActions(const CGUIAction& unfocusActions) { m_unfocusActions = unfocusActions; }
+
+ void SetAutoScrolling(const TiXmlNode *node);
+ void ResetAutoScrolling();
+ void UpdateAutoScrolling(unsigned int currentTime);
+
+#ifdef _DEBUG
+ void DumpTextureUse() override;
+#endif
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool OnClick(int actionID);
+
+ virtual void ProcessItem(float posX, float posY, CGUIListItemPtr& item, bool focused, unsigned int currentTime, CDirtyRegionList &dirtyregions);
+
+ void Render() override;
+ virtual void RenderItem(float posX, float posY, CGUIListItem *item, bool focused);
+ virtual void Scroll(int amount);
+ virtual bool MoveDown(bool wrapAround);
+ virtual bool MoveUp(bool wrapAround);
+ virtual bool GetOffsetRange(int &minOffset, int &maxOffset) const;
+ virtual void ValidateOffset();
+ virtual int CorrectOffset(int offset, int cursor) const;
+ virtual void UpdateLayout(bool refreshAllItems = false);
+ virtual void SetPageControlRange();
+ virtual void UpdatePageControl(int offset);
+ virtual void CalculateLayout();
+ virtual void SelectItem(int item) {}
+ virtual bool SelectItemFromPoint(const CPoint& point) { return false; }
+ virtual int GetCursorFromPoint(const CPoint& point, CPoint* itemPoint = NULL) const { return -1; }
+ virtual void Reset();
+ virtual size_t GetNumItems() const { return m_items.size(); }
+ virtual int GetCurrentPage() const;
+ bool InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const;
+ void OnFocus() override;
+ void OnUnFocus() override;
+ void UpdateListProvider(bool forceRefresh = false);
+
+ int ScrollCorrectionRange() const;
+ inline float Size() const;
+ void FreeMemory(int keepStart, int keepEnd);
+ void GetCurrentLayouts();
+ CGUIListItemLayout *GetFocusedLayout() const;
+
+ CPoint m_renderOffset; ///< \brief render offset of the first item in the list \sa SetRenderOffset
+
+ float m_analogScrollCount;
+ unsigned int m_lastHoldTime;
+
+ ORIENTATION m_orientation;
+ int m_itemsPerPage;
+
+ std::vector< CGUIListItemPtr > m_items;
+ typedef std::vector<CGUIListItemPtr> ::iterator iItems;
+ CGUIListItemPtr m_lastItem;
+
+ int m_pageControl;
+
+ std::list<CGUIListItemLayout> m_layouts;
+ std::list<CGUIListItemLayout> m_focusedLayouts;
+
+ CGUIListItemLayout* m_layout{nullptr};
+ CGUIListItemLayout* m_focusedLayout{nullptr};
+ bool m_layoutCondition = false;
+ bool m_focusedLayoutCondition = false;
+
+ void ScrollToOffset(int offset);
+ void SetContainerMoving(int direction);
+ void UpdateScrollOffset(unsigned int currentTime);
+
+ CScroller m_scroller;
+
+ std::unique_ptr<IListProvider> m_listProvider;
+
+ bool m_wasReset; // true if we've received a Reset message until we've rendered once. Allows
+ // us to make sure we don't tell the infomanager that we've been moving when
+ // the "movement" was simply due to the list being repopulated (thus cursor position
+ // changing around)
+
+ void UpdateScrollByLetter();
+ void GetCacheOffsets(int &cacheBefore, int &cacheAfter) const;
+ int GetCacheCount() const { return m_cacheItems; }
+ bool ScrollingDown() const { return m_scroller.IsScrollingDown(); }
+ bool ScrollingUp() const { return m_scroller.IsScrollingUp(); }
+ void OnNextLetter();
+ void OnPrevLetter();
+ void OnJumpLetter(const std::string& letter, bool skip = false);
+ void OnJumpSMS(int letter);
+ std::vector< std::pair<int, std::string> > m_letterOffsets;
+
+ /*! \brief Set the cursor position
+ Should be used by all base classes rather than directly setting it, as
+ this also marks the control as dirty (if needed)
+ */
+ virtual void SetCursor(int cursor);
+ inline int GetCursor() const { return m_cursor; }
+
+ /*! \brief Set the container offset
+ Should be used by all base classes rather than directly setting it, as
+ this also marks the control as dirty (if needed)
+ */
+ void SetOffset(int offset);
+ /*! \brief Returns the index of the first visible row
+ returns the first row. This may be outside of the range of available items. Use GetItemOffset() to retrieve the first visible item in the list.
+ \sa GetItemOffset
+ */
+ inline int GetOffset() const { return m_offset; }
+ /*! \brief Returns the index of the first visible item
+ returns the first visible item. This will always be in the range of available items. Use GetOffset() to retrieve the first visible row in the list.
+ \sa GetOffset
+ */
+ inline int GetItemOffset() const { return CorrectOffset(GetOffset(), 0); }
+
+ // autoscrolling
+ INFO::InfoPtr m_autoScrollCondition;
+ int m_autoScrollMoveTime; // time between to moves
+ unsigned int m_autoScrollDelayTime; // current offset into the delay
+ bool m_autoScrollIsReversed; // scroll backwards
+
+ unsigned int m_lastRenderTime;
+
+private:
+ bool OnContextMenu();
+
+ int m_cursor;
+ int m_offset;
+ int m_cacheItems;
+ CStopWatch m_scrollTimer;
+ CStopWatch m_lastScrollStartTimer;
+ CStopWatch m_pageChangeTimer;
+
+ CGUIAction m_clickActions;
+ CGUIAction m_focusActions;
+ CGUIAction m_unfocusActions;
+
+ // letter match searching
+ CStopWatch m_matchTimer;
+ std::string m_match;
+ float m_scrollItemsPerFrame;
+ static const int letter_match_timeout = 1000;
+
+ bool m_gestureActive = false;
+
+ // early inertial scroll cancellation
+ bool m_waitForScrollEnd = false;
+ float m_lastScrollValue = 0.0f;
+};
+
+
diff --git a/xbmc/guilib/GUIBorderedImage.cpp b/xbmc/guilib/GUIBorderedImage.cpp
new file mode 100644
index 0000000..7c48ddb
--- /dev/null
+++ b/xbmc/guilib/GUIBorderedImage.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIBorderedImage.h"
+
+CGUIBorderedImage::CGUIBorderedImage(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& texture,
+ const CTextureInfo& borderTexture,
+ const CRect& borderSize)
+ : CGUIImage(parentID,
+ controlID,
+ posX + borderSize.x1,
+ posY + borderSize.y1,
+ width - borderSize.x1 - borderSize.x2,
+ height - borderSize.y1 - borderSize.y2,
+ texture),
+ m_borderImage(CGUITexture::CreateTexture(posX, posY, width, height, borderTexture)),
+ m_borderSize(borderSize)
+{
+ ControlType = GUICONTROL_BORDEREDIMAGE;
+}
+
+CGUIBorderedImage::CGUIBorderedImage(const CGUIBorderedImage& right)
+ : CGUIImage(right), m_borderImage(right.m_borderImage->Clone()), m_borderSize(right.m_borderSize)
+{
+ ControlType = GUICONTROL_BORDEREDIMAGE;
+}
+
+void CGUIBorderedImage::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CGUIImage::Process(currentTime, dirtyregions);
+ if (!m_borderImage->GetFileName().empty() && m_texture->ReadyToRender())
+ {
+ CRect rect = CRect(m_texture->GetXPosition(), m_texture->GetYPosition(),
+ m_texture->GetXPosition() + m_texture->GetWidth(),
+ m_texture->GetYPosition() + m_texture->GetHeight());
+ rect.Intersect(m_texture->GetRenderRect());
+ m_borderImage->SetPosition(rect.x1 - m_borderSize.x1, rect.y1 - m_borderSize.y1);
+ m_borderImage->SetWidth(rect.Width() + m_borderSize.x1 + m_borderSize.x2);
+ m_borderImage->SetHeight(rect.Height() + m_borderSize.y1 + m_borderSize.y2);
+ m_borderImage->SetDiffuseColor(m_diffuseColor);
+ if (m_borderImage->Process(currentTime))
+ MarkDirtyRegion();
+ }
+}
+
+void CGUIBorderedImage::Render()
+{
+ if (!m_borderImage->GetFileName().empty() && m_texture->ReadyToRender())
+ m_borderImage->Render();
+ CGUIImage::Render();
+}
+
+CRect CGUIBorderedImage::CalcRenderRegion() const
+{
+ // have to union the image as well as fading images may still exist that are bigger than our current border image
+ return CGUIImage::CalcRenderRegion().Union(m_borderImage->GetRenderRect());
+}
+
+void CGUIBorderedImage::AllocResources()
+{
+ m_borderImage->AllocResources();
+ CGUIImage::AllocResources();
+}
+
+void CGUIBorderedImage::FreeResources(bool immediately)
+{
+ m_borderImage->FreeResources(immediately);
+ CGUIImage::FreeResources(immediately);
+}
+
+void CGUIBorderedImage::DynamicResourceAlloc(bool bOnOff)
+{
+ m_borderImage->DynamicResourceAlloc(bOnOff);
+ CGUIImage::DynamicResourceAlloc(bOnOff);
+}
diff --git a/xbmc/guilib/GUIBorderedImage.h b/xbmc/guilib/GUIBorderedImage.h
new file mode 100644
index 0000000..cbe2955
--- /dev/null
+++ b/xbmc/guilib/GUIBorderedImage.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+#include "GUIImage.h"
+#include "TextureManager.h"
+
+class CGUIBorderedImage : public CGUIImage
+{
+public:
+ CGUIBorderedImage(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& texture, const CTextureInfo& borderTexture, const CRect &borderSize);
+ ~CGUIBorderedImage(void) override = default;
+ CGUIBorderedImage* Clone() const override { return new CGUIBorderedImage(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+
+ CRect CalcRenderRegion() const override;
+
+protected:
+ std::unique_ptr<CGUITexture> m_borderImage;
+ CRect m_borderSize;
+
+private:
+ CGUIBorderedImage(const CGUIBorderedImage& right);
+};
+
diff --git a/xbmc/guilib/GUIButtonControl.cpp b/xbmc/guilib/GUIButtonControl.cpp
new file mode 100644
index 0000000..0e7f757
--- /dev/null
+++ b/xbmc/guilib/GUIButtonControl.cpp
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIButtonControl.h"
+
+#include "GUIFontManager.h"
+#include "input/Key.h"
+
+CGUIButtonControl::CGUIButtonControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ const CLabelInfo& labelInfo,
+ bool wrapMultiline)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_imgFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureFocus)),
+ m_imgNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureNoFocus)),
+ m_label(posX,
+ posY,
+ width,
+ height,
+ labelInfo,
+ wrapMultiline ? CGUILabel::OVER_FLOW_WRAP : CGUILabel::OVER_FLOW_TRUNCATE),
+ m_label2(posX, posY, width, height, labelInfo)
+{
+ m_bSelected = false;
+ m_alpha = 255;
+ m_focusCounter = 0;
+ m_minWidth = 0;
+ m_maxWidth = width;
+ ControlType = GUICONTROL_BUTTON;
+}
+
+CGUIButtonControl::CGUIButtonControl(const CGUIButtonControl& control)
+ : CGUIControl(control),
+ m_imgFocus(control.m_imgFocus->Clone()),
+ m_imgNoFocus(control.m_imgNoFocus->Clone()),
+ m_focusCounter(control.m_focusCounter),
+ m_alpha(control.m_alpha),
+ m_minWidth(control.m_minWidth),
+ m_maxWidth(control.m_maxWidth),
+ m_info(control.m_info),
+ m_info2(control.m_info2),
+ m_label(control.m_label),
+ m_label2(control.m_label2),
+ m_clickActions(control.m_clickActions),
+ m_focusActions(control.m_focusActions),
+ m_unfocusActions(control.m_unfocusActions),
+ m_bSelected(control.m_bSelected)
+{
+}
+
+void CGUIButtonControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ ProcessText(currentTime);
+ if (m_bInvalidated)
+ {
+ m_imgFocus->SetWidth(GetWidth());
+ m_imgFocus->SetHeight(m_height);
+
+ m_imgNoFocus->SetWidth(GetWidth());
+ m_imgNoFocus->SetHeight(m_height);
+ }
+
+ if (HasFocus())
+ {
+ unsigned int alphaChannel = m_alpha;
+ if (m_pulseOnSelect)
+ {
+ unsigned int alphaCounter = m_focusCounter + 2;
+ if ((alphaCounter % 128) >= 64)
+ alphaChannel = alphaCounter % 64;
+ else
+ alphaChannel = 63 - (alphaCounter % 64);
+
+ alphaChannel += 192;
+ alphaChannel = (unsigned int)((float)m_alpha * (float)alphaChannel / 255.0f);
+ }
+ if (m_imgFocus->SetAlpha((unsigned char)alphaChannel))
+ MarkDirtyRegion();
+
+ m_imgFocus->SetVisible(true);
+ m_imgNoFocus->SetVisible(false);
+ m_focusCounter++;
+ }
+ else
+ {
+ m_imgFocus->SetVisible(false);
+ m_imgNoFocus->SetVisible(true);
+ }
+
+ m_imgFocus->Process(currentTime);
+ m_imgNoFocus->Process(currentTime);
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIButtonControl::Render()
+{
+ m_imgFocus->Render();
+ m_imgNoFocus->Render();
+
+ RenderText();
+ CGUIControl::Render();
+}
+
+void CGUIButtonControl::RenderText()
+{
+ m_label.Render();
+ m_label2.Render();
+}
+
+CGUILabel::COLOR CGUIButtonControl::GetTextColor() const
+{
+ if (IsDisabled())
+ return CGUILabel::COLOR_DISABLED;
+ if (HasFocus())
+ return CGUILabel::COLOR_FOCUSED;
+ return CGUILabel::COLOR_TEXT;
+}
+
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+float CGUIButtonControl::GetWidth() const
+{
+ if (m_minWidth && m_minWidth != m_width)
+ {
+ float txtWidth = m_label.GetTextWidth() + 2 * m_label.GetLabelInfo().offsetX;
+ if (m_label2.GetTextWidth())
+ {
+ static const float min_space = 10;
+ txtWidth += m_label2.GetTextWidth() + 2 * m_label2.GetLabelInfo().offsetX + min_space;
+ }
+ float maxWidth = m_maxWidth ? m_maxWidth : txtWidth;
+ return CLAMP(txtWidth, m_minWidth, maxWidth);
+ }
+ return m_width;
+}
+
+void CGUIButtonControl::SetMinWidth(float minWidth)
+{
+ if (m_minWidth != minWidth)
+ MarkDirtyRegion();
+
+ m_minWidth = minWidth;
+}
+
+void CGUIButtonControl::ProcessText(unsigned int currentTime)
+{
+ CRect labelRenderRect = m_label.GetRenderRect();
+ CRect label2RenderRect = m_label2.GetRenderRect();
+
+ float renderWidth = GetWidth();
+ float renderTextWidth = renderWidth;
+ if (m_labelMaxWidth > 0 && m_labelMaxWidth < renderWidth)
+ renderTextWidth = m_labelMaxWidth;
+
+ bool changed = m_label.SetMaxRect(m_posX, m_posY, renderTextWidth, m_height);
+ changed |= m_label.SetText(m_info.GetLabel(m_parentID));
+ changed |= m_label.SetScrolling(HasFocus());
+ changed |= m_label2.SetMaxRect(m_posX, m_posY, renderTextWidth, m_height);
+ changed |= m_label2.SetText(m_info2.GetLabel(m_parentID));
+
+ // text changed - images need resizing
+ if (m_minWidth && (m_label.GetRenderRect() != labelRenderRect))
+ SetInvalid();
+
+ // auto-width - adjust hitrect
+ if (m_minWidth && m_width != renderWidth)
+ {
+ CRect rect{m_posX, m_posY, m_posX + renderWidth, m_posY + m_height};
+ SetHitRect(rect, m_hitColor);
+ }
+
+ // render the second label if it exists
+ if (!m_info2.GetLabel(m_parentID).empty())
+ {
+ changed |= m_label2.SetAlign(XBFONT_RIGHT | (m_label.GetLabelInfo().align & XBFONT_CENTER_Y) | XBFONT_TRUNCATED);
+ changed |= m_label2.SetScrolling(HasFocus());
+
+ // If overlapping was corrected - compare render rects to determine
+ // if they changed since last frame.
+ if (CGUILabel::CheckAndCorrectOverlap(m_label, m_label2))
+ changed |= (m_label.GetRenderRect() != labelRenderRect ||
+ m_label2.GetRenderRect() != label2RenderRect);
+
+ changed |= m_label2.SetColor(GetTextColor());
+ changed |= m_label2.Process(currentTime);
+ }
+ changed |= m_label.SetColor(GetTextColor());
+ changed |= m_label.Process(currentTime);
+ if (changed)
+ MarkDirtyRegion();
+}
+
+bool CGUIButtonControl::OnAction(const CAction &action)
+{
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ OnClick();
+ return true;
+ }
+ return CGUIControl::OnAction(action);
+}
+
+bool CGUIButtonControl::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID())
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_SET)
+ {
+ SetLabel(message.GetLabel());
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_LABEL2_SET)
+ {
+ SetLabel2(message.GetLabel());
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_IS_SELECTED)
+ {
+ message.SetParam1(m_bSelected ? 1 : 0);
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_SET_SELECTED)
+ {
+ if (!m_bSelected)
+ SetInvalid();
+ m_bSelected = true;
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_SET_DESELECTED)
+ {
+ if (m_bSelected)
+ SetInvalid();
+ m_bSelected = false;
+ return true;
+ }
+ }
+
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIButtonControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_focusCounter = 0;
+ m_imgFocus->AllocResources();
+ m_imgNoFocus->AllocResources();
+ if (!m_width)
+ m_width = m_imgFocus->GetWidth();
+ if (!m_height)
+ m_height = m_imgFocus->GetHeight();
+}
+
+void CGUIButtonControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_imgFocus->FreeResources(immediately);
+ m_imgNoFocus->FreeResources(immediately);
+}
+
+void CGUIButtonControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgFocus->DynamicResourceAlloc(bOnOff);
+ m_imgNoFocus->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIButtonControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_label.SetInvalid();
+ m_label2.SetInvalid();
+ m_imgFocus->SetInvalid();
+ m_imgNoFocus->SetInvalid();
+}
+
+void CGUIButtonControl::SetLabel(const std::string &label)
+{ // NOTE: No fallback for buttons at this point
+ if (m_info.GetLabel(GetParentID(), false) != label)
+ {
+ m_info.SetLabel(label, "", GetParentID());
+ SetInvalid();
+ }
+}
+
+void CGUIButtonControl::SetLabel2(const std::string &label2)
+{ // NOTE: No fallback for buttons at this point
+ if (m_info2.GetLabel(GetParentID(), false) != label2)
+ {
+ m_info2.SetLabel(label2, "", GetParentID());
+ SetInvalid();
+ }
+}
+
+void CGUIButtonControl::SetPosition(float posX, float posY)
+{
+ CGUIControl::SetPosition(posX, posY);
+ m_imgFocus->SetPosition(posX, posY);
+ m_imgNoFocus->SetPosition(posX, posY);
+}
+
+void CGUIButtonControl::SetAlpha(unsigned char alpha)
+{
+ if (m_alpha != alpha)
+ MarkDirtyRegion();
+ m_alpha = alpha;
+}
+
+bool CGUIButtonControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+ changed |= m_label2.UpdateColors();
+ changed |= m_imgFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgNoFocus->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+CRect CGUIButtonControl::CalcRenderRegion() const
+{
+ CRect buttonRect = CGUIControl::CalcRenderRegion();
+ CRect textRect = m_label.GetRenderRect();
+ buttonRect.Union(textRect);
+ return buttonRect;
+}
+
+EVENT_RESULT CGUIButtonControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_LEFT_CLICK)
+ {
+ OnAction(CAction(ACTION_SELECT_ITEM));
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+std::string CGUIButtonControl::GetDescription() const
+{
+ std::string strLabel(m_info.GetLabel(m_parentID));
+ return strLabel;
+}
+
+std::string CGUIButtonControl::GetLabel2() const
+{
+ std::string strLabel(m_info2.GetLabel(m_parentID));
+ return strLabel;
+}
+
+void CGUIButtonControl::PythonSetLabel(const std::string& strFont,
+ const std::string& strText,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ UTILS::COLOR::Color focusedColor)
+{
+ m_label.GetLabelInfo().font = g_fontManager.GetFont(strFont);
+ m_label.GetLabelInfo().textColor = textColor;
+ m_label.GetLabelInfo().focusedColor = focusedColor;
+ m_label.GetLabelInfo().shadowColor = shadowColor;
+ SetLabel(strText);
+}
+
+void CGUIButtonControl::PythonSetDisabledColor(UTILS::COLOR::Color disabledColor)
+{
+ m_label.GetLabelInfo().disabledColor = disabledColor;
+}
+
+void CGUIButtonControl::OnClick()
+{
+ // Save values, as the click message may deactivate the window
+ int controlID = GetID();
+ int parentID = GetParentID();
+ CGUIAction clickActions = m_clickActions;
+
+ // button selected, send a message
+ CGUIMessage msg(GUI_MSG_CLICKED, controlID, parentID, 0);
+ SendWindowMessage(msg);
+
+ clickActions.ExecuteActions(controlID, parentID);
+}
+
+void CGUIButtonControl::OnFocus()
+{
+ m_focusActions.ExecuteActions(GetID(), GetParentID());
+}
+
+void CGUIButtonControl::OnUnFocus()
+{
+ m_unfocusActions.ExecuteActions(GetID(), GetParentID());
+}
+
+void CGUIButtonControl::SetSelected(bool bSelected)
+{
+ if (m_bSelected != bSelected)
+ {
+ m_bSelected = bSelected;
+ SetInvalid();
+ }
+}
diff --git a/xbmc/guilib/GUIButtonControl.dox b/xbmc/guilib/GUIButtonControl.dox
new file mode 100644
index 0000000..bbcf631
--- /dev/null
+++ b/xbmc/guilib/GUIButtonControl.dox
@@ -0,0 +1,85 @@
+/*!
+
+\page skin_Button_control Button control
+\brief **A standard push button control.**
+
+\tableofcontents
+
+The button control is used for creating push buttons in Kodi. You can
+choose the position, size, and look of the button, as well as choosing what
+action(s) should be performed when pushed.
+
+--------------------------------------------------------------------------------
+\section skin_Button_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="button" id="1">
+ <description>My first button control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <texturefocus colordiffuse="FFFFAAFF">myfocustexture.png</texturefocus>
+ <texturenofocus colordiffuse="FFFFAAFF">mynormaltexture.png</texturenofocus>
+ <label>29</label>
+ <wrapmultiline>true</wrapmultiline>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <focusedcolor>FFFFFFFF</focusedcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <invalidcolor>FFFFFFFF</invalidcolor>
+ <align></align>
+ <aligny></aligny>
+ <textoffsetx></textoffsetx>
+ <textoffsety></textoffsety>
+ <pulseonselect></pulseonselect>
+ <onclick>ActivateWindow(MyVideos)</onclick>
+ <onfocus>-</onfocus>
+ <onunfocus>-</onunfocus>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section skin_Button_control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the button has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus| Specifies the image file which should be displayed when the button does not have focus.
+| label | The label used on the button. It can be a link into <b>`strings.po`</b>, or an actual text label.
+| font | Font used for the button label. From fonts.xml.
+| textcolor | Color used for displaying the button label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| focusedcolor | Color used for the button label when the button has in focus. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the button label if the button is disabled. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| invalidcolor | Color used for the button if the user entered some invalid value. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| angle | The angle the text should be rendered at, in degrees. A value of 0 is horizontal.
+| align | Label horizontal alignment on the button. Defaults to left, can also be center or right.
+| aligny | Label vertical alignment on the button. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| textwidth | Will truncate any text that's too long.
+| onclick | Specifies the action to perform when the button is pressed. Should be a built in function. [See here for more information](http://kodi.wiki/view/Built-in_functions_available_to_FTP,_Webserver,_skins,_keymap_and_to_python). You may have more than one <b>`<onclick>`</b> tag, and they'll be executed in sequence.
+| onfocus | Specifies the action to perform when the button is focused. Should be a built in function. The action is performed after any focus animations have completed. [See here for more information](http://kodi.wiki/view/Built-in_functions_available_to_FTP,_Webserver,_skins,_keymap_and_to_python).
+| onunfocus | Specifies the action to perform when the button loses focus. Should be a built in function.
+| wrapmultiline | Will wrap the label across multiple lines if the label exceeds the control width.
+
+
+--------------------------------------------------------------------------------
+\section skin_Button_control_sect3 See also
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIButtonControl.h b/xbmc/guilib/GUIButtonControl.h
new file mode 100644
index 0000000..2de7695
--- /dev/null
+++ b/xbmc/guilib/GUIButtonControl.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIButtonControl.h
+\brief
+*/
+
+#include "GUIAction.h"
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "GUITexture.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+#include "utils/ColorUtils.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIButtonControl : public CGUIControl
+{
+public:
+ CGUIButtonControl(int parentID, int controlID,
+ float posX, float posY, float width, float height,
+ const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus,
+ const CLabelInfo &label, bool wrapMultiline = false);
+
+ CGUIButtonControl(const CGUIButtonControl& control);
+
+ ~CGUIButtonControl() override = default;
+ CGUIButtonControl* Clone() const override { return new CGUIButtonControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override ;
+ bool OnMessage(CGUIMessage& message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ virtual void SetLabel(const std::string & aLabel);
+ virtual void SetLabel2(const std::string & aLabel2);
+ void SetClickActions(const CGUIAction& clickActions) { m_clickActions = clickActions; }
+ const CGUIAction& GetClickActions() const { return m_clickActions; }
+ void SetFocusActions(const CGUIAction& focusActions) { m_focusActions = focusActions; }
+ void SetUnFocusActions(const CGUIAction& unfocusActions) { m_unfocusActions = unfocusActions; }
+ const CLabelInfo& GetLabelInfo() const { return m_label.GetLabelInfo(); }
+ virtual std::string GetLabel() const { return GetDescription(); }
+ virtual std::string GetLabel2() const;
+ void SetSelected(bool bSelected);
+ std::string GetDescription() const override;
+ float GetWidth() const override;
+ virtual void SetMinWidth(float minWidth);
+ void SetAlpha(unsigned char alpha);
+
+ void PythonSetLabel(const std::string& strFont,
+ const std::string& strText,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ UTILS::COLOR::Color focusedColor);
+ void PythonSetDisabledColor(UTILS::COLOR::Color disabledColor);
+
+ virtual void OnClick();
+ bool HasClickActions() const { return m_clickActions.HasActionsMeetingCondition(); }
+
+ bool UpdateColors(const CGUIListItem* item) override;
+
+ CRect CalcRenderRegion() const override;
+
+protected:
+ friend class CGUISpinControlEx;
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ void OnFocus() override;
+ void OnUnFocus() override;
+ virtual void ProcessText(unsigned int currentTime);
+ virtual void RenderText();
+ virtual CGUILabel::COLOR GetTextColor() const;
+
+ /*!
+ * \brief Set the maximum width for the left label
+ */
+ void SetMaxWidth(float labelMaxWidth) { m_labelMaxWidth = labelMaxWidth; }
+
+ std::unique_ptr<CGUITexture> m_imgFocus;
+ std::unique_ptr<CGUITexture> m_imgNoFocus;
+ unsigned int m_focusCounter;
+ unsigned char m_alpha;
+
+ float m_minWidth;
+ float m_maxWidth;
+ float m_labelMaxWidth{0};
+
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info2;
+ CGUILabel m_label;
+ CGUILabel m_label2;
+
+ CGUIAction m_clickActions;
+ CGUIAction m_focusActions;
+ CGUIAction m_unfocusActions;
+
+ bool m_bSelected;
+};
+
diff --git a/xbmc/guilib/GUIColorButtonControl.cpp b/xbmc/guilib/GUIColorButtonControl.cpp
new file mode 100644
index 0000000..67597d7
--- /dev/null
+++ b/xbmc/guilib/GUIColorButtonControl.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIColorButtonControl.h"
+
+#include "GUIInfoManager.h"
+#include "LocalizeStrings.h"
+#include "input/Key.h"
+#include "utils/ColorUtils.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI;
+using namespace GUILIB;
+
+CGUIColorButtonControl::CGUIColorButtonControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ const CLabelInfo& labelInfo,
+ const CTextureInfo& colorMask,
+ const CTextureInfo& colorDisabledMask)
+ : CGUIButtonControl(
+ parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo),
+ m_imgColorMask(CGUITexture::CreateTexture(posX, posY, 16, 16, colorMask)),
+ m_imgColorDisabledMask(CGUITexture::CreateTexture(posX, posY, 16, 16, colorDisabledMask)),
+ m_labelInfo(posX, posY, width, height, labelInfo, CGUILabel::OVER_FLOW_SCROLL)
+{
+ m_colorPosX = 0;
+ m_colorPosY = 0;
+ m_imgColorMask->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgColorDisabledMask->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::NONE);
+ ControlType = GUICONTROL_COLORBUTTON;
+ // offsetX is like a left/right padding, "hex" label does not require high values
+ m_labelInfo.GetLabelInfo().offsetX = 2;
+}
+
+CGUIColorButtonControl::CGUIColorButtonControl(const CGUIColorButtonControl& control)
+ : CGUIButtonControl(control),
+ m_imgColorMask(control.m_imgColorMask->Clone()),
+ m_imgColorDisabledMask(control.m_imgColorDisabledMask->Clone()),
+ m_colorPosX(control.m_colorPosX),
+ m_colorPosY(control.m_colorPosY),
+ m_labelInfo(control.m_labelInfo)
+{
+}
+
+void CGUIColorButtonControl::Render()
+{
+ CGUIButtonControl::Render();
+ RenderInfoText();
+ if (IsDisabled())
+ m_imgColorDisabledMask->Render();
+ else
+ m_imgColorMask->Render();
+}
+
+void CGUIColorButtonControl::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ ProcessInfoText(currentTime);
+ m_imgColorMask->Process(currentTime);
+ m_imgColorDisabledMask->Process(currentTime);
+ CGUIButtonControl::Process(currentTime, dirtyregions);
+}
+
+bool CGUIColorButtonControl::OnAction(const CAction& action)
+{
+ return CGUIButtonControl::OnAction(action);
+}
+
+bool CGUIColorButtonControl::OnMessage(CGUIMessage& message)
+{
+ return CGUIButtonControl::OnMessage(message);
+}
+
+void CGUIColorButtonControl::AllocResources()
+{
+ CGUIButtonControl::AllocResources();
+ m_imgColorMask->AllocResources();
+ m_imgColorDisabledMask->AllocResources();
+ SetPosition(m_posX, m_posY);
+}
+
+void CGUIColorButtonControl::FreeResources(bool immediately)
+{
+ CGUIButtonControl::FreeResources(immediately);
+ m_imgColorMask->FreeResources(immediately);
+ m_imgColorDisabledMask->FreeResources(immediately);
+}
+
+void CGUIColorButtonControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgColorMask->DynamicResourceAlloc(bOnOff);
+ m_imgColorDisabledMask->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIColorButtonControl::SetInvalid()
+{
+ CGUIButtonControl::SetInvalid();
+ m_imgColorMask->SetInvalid();
+ m_imgColorDisabledMask->SetInvalid();
+ m_labelInfo.SetInvalid();
+}
+
+void CGUIColorButtonControl::SetPosition(float posX, float posY)
+{
+ CGUIButtonControl::SetPosition(posX, posY);
+ float colorPosX =
+ m_colorPosX ? m_posX + m_colorPosX : (m_posX + m_width) - m_imgColorMask->GetWidth();
+ float colorPosY =
+ m_colorPosY ? m_posY + m_colorPosY : m_posY + (m_height - m_imgColorMask->GetHeight()) / 2;
+ m_imgColorMask->SetPosition(colorPosX, colorPosY);
+ m_imgColorDisabledMask->SetPosition(colorPosX, colorPosY);
+}
+
+void CGUIColorButtonControl::SetColorDimensions(float posX, float posY, float width, float height)
+{
+ m_colorPosX = posX;
+ m_colorPosY = posY;
+ if (width)
+ {
+ m_imgColorMask->SetWidth(width);
+ m_imgColorDisabledMask->SetWidth(width);
+ }
+ if (height)
+ {
+ m_imgColorMask->SetHeight(height);
+ m_imgColorDisabledMask->SetHeight(height);
+ }
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+void CGUIColorButtonControl::SetWidth(float width)
+{
+ CGUIButtonControl::SetWidth(width);
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+void CGUIColorButtonControl::SetHeight(float height)
+{
+ CGUIButtonControl::SetHeight(height);
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+std::string CGUIColorButtonControl::GetDescription() const
+{
+ return CGUIButtonControl::GetDescription();
+}
+
+void CGUIColorButtonControl::SetImageBoxColor(GUIINFO::CGUIInfoColor color)
+{
+ m_imgBoxColor = color;
+}
+
+void CGUIColorButtonControl::SetImageBoxColor(const std::string& hexColor)
+{
+ if (hexColor.empty())
+ m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::NONE);
+ else
+ m_imgBoxColor = GUIINFO::CGUIInfoColor(UTILS::COLOR::ConvertHexToColor(hexColor));
+}
+
+bool CGUIColorButtonControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIButtonControl::UpdateColors(nullptr);
+ changed |= m_imgBoxColor.Update();
+ changed |= m_imgColorMask->SetDiffuseColor(m_imgBoxColor, item);
+ changed |= m_imgColorDisabledMask->SetDiffuseColor(m_diffuseColor);
+ changed |= m_labelInfo.UpdateColors();
+ return changed;
+}
+
+void CGUIColorButtonControl::RenderInfoText()
+{
+ m_labelInfo.Render();
+}
+
+void CGUIColorButtonControl::ProcessInfoText(unsigned int currentTime)
+{
+ CRect labelRenderRect = m_labelInfo.GetRenderRect();
+ bool changed = m_labelInfo.SetText(
+ StringUtils::Format("#{:08X}", static_cast<UTILS::COLOR::Color>(m_imgBoxColor)));
+ // Set Label X position based on image mask control position
+ float textWidth = m_labelInfo.GetTextWidth() + 2 * m_labelInfo.GetLabelInfo().offsetX;
+ float textPosX = m_imgColorMask->GetXPosition() - textWidth;
+ changed = m_labelInfo.SetMaxRect(textPosX, m_posY, textWidth, m_height);
+ // Limit the width for the left label to avoid text overlapping
+ SetMaxWidth(textPosX);
+
+ // text changed - images need resizing
+ if (m_minWidth && (m_labelInfo.GetRenderRect() != labelRenderRect))
+ SetInvalid();
+
+ changed |= m_labelInfo.SetColor(GetTextColor());
+ changed |= m_labelInfo.Process(currentTime);
+ if (changed)
+ MarkDirtyRegion();
+}
+
+CGUILabel::COLOR CGUIColorButtonControl::GetTextColor() const
+{
+ if (IsDisabled())
+ return CGUILabel::COLOR_DISABLED;
+ if (HasFocus())
+ return CGUILabel::COLOR_FOCUSED;
+ return CGUILabel::COLOR_TEXT;
+}
diff --git a/xbmc/guilib/GUIColorButtonControl.dox b/xbmc/guilib/GUIColorButtonControl.dox
new file mode 100644
index 0000000..7705a02
--- /dev/null
+++ b/xbmc/guilib/GUIColorButtonControl.dox
@@ -0,0 +1,90 @@
+/*!
+
+\page Color_button_control Color button control
+\brief **A color button control (as used for color settings).**
+
+\tableofcontents
+
+The color button control is used for creating push buttons in Kodi with a
+box for color preview. You can choose the position, size, and look of
+the button, as well as choosing what action(s) should be performed when pushed.
+
+--------------------------------------------------------------------------------
+\section Color_button_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="colorbutton" id="2">
+ <description>My first colorbutton control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <texturecolormask>mycolormask.png</texturecolormask>
+ <texturecolordisabledmask>mycolormask.png</texturecolordisabledmask>
+ <colorbox>FF0533ff</colorbox>
+ <onclick>ActivateWindow(MyVideos)</onclick>
+ <label>29</label>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <focusedcolor>FFFFFFFF</focusedcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <align>left</align>
+ <aligny>center</aligny>
+ <pulseonselect>false</pulseonselect>
+ <onfocus>-</onfocus>
+ <onunfocus>-</onunfocus>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Color_button_control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the button has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus| Specifies the image file which should be displayed when the button does not have focus.
+| label | The label used on the button. It can be a link into <b>`strings.po`</b>, or an actual text label.
+| font | Font used for the button label. From fonts.xml.
+| textcolor | Color used for displaying the button label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| focusedcolor | Color used for the button label when the button has in focus. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the button label if the button is disabled. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| invalidcolor | Color used for the button if the user entered some invalid value. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| angle | The angle the text should be rendered at, in degrees. A value of 0 is horizontal.
+| align | Label horizontal alignment on the button. Defaults to left, can also be center or right.
+| aligny | Label vertical alignment on the button. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| textwidth | Will truncate any text that's too long.
+| onclick | Specifies the action to perform when the button is pressed. Should be a built in function. [See here for more information](http://kodi.wiki/view/Built-in_functions_available_to_FTP,_Webserver,_skins,_keymap_and_to_python). You may have more than one <b>`<onclick>`</b> tag, and they'll be executed in sequence.
+| onfocus | Specifies the action to perform when the button is focused. Should be a built in function. The action is performed after any focus animations have completed. [See here for more information](http://kodi.wiki/view/Built-in_functions_available_to_FTP,_Webserver,_skins,_keymap_and_to_python).
+| onunfocus | Specifies the action to perform when the button loses focus. Should be a built in function.
+| wrapmultiline | Will wrap the label across multiple lines if the label exceeds the control width.
+| colorbox | Specifies the color of the preview color box, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| colorposx | X offset of the preview color box
+| colorposy | Y offset of the preview color box
+| colorwidth | Width in Pixels of the preview color box
+| colorheight | Height in Pixels of the preview color box
+| texturecolormask | Specifies the image mask which should be displayed to see the color preview. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturecolordisabledmask | Specifies the image mask which should be displayed when the control is disabled. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+
+
+--------------------------------------------------------------------------------
+\section Color_button_control_sect3 See also
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIColorButtonControl.h b/xbmc/guilib/GUIColorButtonControl.h
new file mode 100644
index 0000000..828898c
--- /dev/null
+++ b/xbmc/guilib/GUIColorButtonControl.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005-2021 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file CGUIColorButtonControl.h
+\brief
+*/
+#include "GUIButtonControl.h"
+#include "guilib/GUILabel.h"
+#include "guilib/guiinfo/GUIInfoColor.h"
+#include "utils/ColorUtils.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIColorButtonControl : public CGUIButtonControl
+{
+public:
+ CGUIColorButtonControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ const CLabelInfo& labelInfo,
+ const CTextureInfo& colorMask,
+ const CTextureInfo& colorDisabledMask);
+
+ ~CGUIColorButtonControl() override = default;
+ CGUIColorButtonControl* Clone() const override { return new CGUIColorButtonControl(*this); }
+ CGUIColorButtonControl(const CGUIColorButtonControl& control);
+
+ void Process(unsigned int currentTime, CDirtyRegionList& dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction& action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ void SetWidth(float width) override;
+ void SetHeight(float height) override;
+ std::string GetDescription() const override;
+ void SetColorDimensions(float posX, float posY, float width, float height);
+ bool IsSelected() const { return m_bSelected; }
+ void SetImageBoxColor(const std::string& hexColor);
+ void SetImageBoxColor(KODI::GUILIB::GUIINFO::CGUIInfoColor color);
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ void ProcessInfoText(unsigned int currentTime);
+ void RenderInfoText();
+ CGUILabel::COLOR GetTextColor() const override;
+ std::unique_ptr<CGUITexture> m_imgColorMask;
+ std::unique_ptr<CGUITexture> m_imgColorDisabledMask;
+ float m_colorPosX;
+ float m_colorPosY;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor m_imgBoxColor;
+ CGUILabel m_labelInfo;
+};
diff --git a/xbmc/guilib/GUIColorManager.cpp b/xbmc/guilib/GUIColorManager.cpp
new file mode 100644
index 0000000..b3ff2db
--- /dev/null
+++ b/xbmc/guilib/GUIColorManager.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIColorManager.h"
+
+#include "addons/Skin.h"
+#include "filesystem/SpecialProtocol.h"
+#include "utils/ColorUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+CGUIColorManager::CGUIColorManager(void) = default;
+
+CGUIColorManager::~CGUIColorManager(void)
+{
+ Clear();
+}
+
+void CGUIColorManager::Clear()
+{
+ m_colors.clear();
+}
+
+// load the color file in
+void CGUIColorManager::Load(const std::string &colorFile)
+{
+ Clear();
+
+ // load the global color map if it exists
+ CXBMCTinyXML xmlDoc;
+ if (xmlDoc.LoadFile(CSpecialProtocol::TranslatePathConvertCase("special://xbmc/system/colors.xml")))
+ LoadXML(xmlDoc);
+
+ // first load the default color map if it exists
+ std::string path = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors", "defaults.xml");
+
+ if (xmlDoc.LoadFile(CSpecialProtocol::TranslatePathConvertCase(path)))
+ LoadXML(xmlDoc);
+
+ // now the color map requested
+ if (StringUtils::EqualsNoCase(colorFile, "SKINDEFAULT"))
+ return; // nothing to do
+
+ path = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors", colorFile);
+ if (!URIUtils::HasExtension(path))
+ path += ".xml";
+ CLog::Log(LOGINFO, "Loading colors from {}", path);
+
+ if (xmlDoc.LoadFile(path))
+ LoadXML(xmlDoc);
+}
+
+bool CGUIColorManager::LoadXML(CXBMCTinyXML &xmlDoc)
+{
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+
+ std::string strValue = pRootElement->Value();
+ if (strValue != std::string("colors"))
+ {
+ CLog::Log(LOGERROR, "color file doesn't start with <colors>");
+ return false;
+ }
+
+ const TiXmlElement *color = pRootElement->FirstChildElement("color");
+
+ while (color)
+ {
+ if (color->FirstChild() && color->Attribute("name"))
+ {
+ UTILS::COLOR::Color value = 0xffffffff;
+ sscanf(color->FirstChild()->Value(), "%x", (unsigned int*) &value);
+ std::string name = color->Attribute("name");
+ const auto it = m_colors.find(name);
+ if (it != m_colors.end())
+ (*it).second = value;
+ else
+ m_colors.insert(make_pair(name, value));
+ }
+ color = color->NextSiblingElement("color");
+ }
+ return true;
+}
+
+// lookup a color and return it's hex value
+UTILS::COLOR::Color CGUIColorManager::GetColor(const std::string& color) const
+{
+ // look in our color map
+ std::string trimmed(color);
+ StringUtils::TrimLeft(trimmed, "= ");
+ const auto it = m_colors.find(trimmed);
+ if (it != m_colors.end())
+ return (*it).second;
+
+ // try converting hex directly
+ UTILS::COLOR::Color value = 0;
+ sscanf(trimmed.c_str(), "%x", &value);
+ return value;
+}
+
+bool CGUIColorManager::LoadColorsListFromXML(
+ const std::string& filePath,
+ std::vector<std::pair<std::string, UTILS::COLOR::ColorInfo>>& colors,
+ bool sortColors)
+{
+ CLog::Log(LOGDEBUG, "Loading colors from file {}", filePath);
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(filePath))
+ {
+ CLog::Log(LOGERROR, "{} - Failed to load colors from file {}", __FUNCTION__, filePath);
+ return false;
+ }
+
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ std::string strValue = pRootElement->Value();
+ if (strValue != std::string("colors"))
+ {
+ CLog::Log(LOGERROR, "{} - Color file doesn't start with <colors>", __FUNCTION__);
+ return false;
+ }
+
+ const TiXmlElement* xmlColor = pRootElement->FirstChildElement("color");
+ while (xmlColor)
+ {
+ if (xmlColor->FirstChild() && xmlColor->Attribute("name"))
+ {
+ colors.emplace_back(
+ std::make_pair(xmlColor->Attribute("name"),
+ UTILS::COLOR::MakeColorInfo(xmlColor->FirstChild()->Value())));
+ }
+ xmlColor = xmlColor->NextSiblingElement("color");
+ }
+
+ if (sortColors)
+ std::sort(colors.begin(), colors.end(), UTILS::COLOR::comparePairColorInfo);
+
+ return true;
+}
diff --git a/xbmc/guilib/GUIColorManager.h b/xbmc/guilib/GUIColorManager.h
new file mode 100644
index 0000000..ac33c6d
--- /dev/null
+++ b/xbmc/guilib/GUIColorManager.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIColorManager.h
+\brief
+*/
+
+/*!
+ \ingroup textures
+ \brief
+ */
+
+#include "utils/ColorUtils.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+class CXBMCTinyXML;
+
+class CGUIColorManager
+{
+public:
+ CGUIColorManager(void);
+ virtual ~CGUIColorManager(void);
+
+ void Load(const std::string &colorFile);
+
+ UTILS::COLOR::Color GetColor(const std::string& color) const;
+
+ void Clear();
+
+ /*! \brief Load a colors list from a XML file
+ \param filePath The path to the XML file
+ \param colors The vector to populate
+ \param sortColors if true the colors will be sorted in a hue scale
+ \return true if success, otherwise false
+ */
+ bool LoadColorsListFromXML(const std::string& filePath,
+ std::vector<std::pair<std::string, UTILS::COLOR::ColorInfo>>& colors,
+ bool sortColors);
+
+protected:
+ bool LoadXML(CXBMCTinyXML& xmlDoc);
+
+ std::map<std::string, UTILS::COLOR::Color> m_colors;
+};
diff --git a/xbmc/guilib/GUIComponent.cpp b/xbmc/guilib/GUIComponent.cpp
new file mode 100644
index 0000000..e143e75
--- /dev/null
+++ b/xbmc/guilib/GUIComponent.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIComponent.h"
+
+#include "GUIAudioManager.h"
+#include "GUIColorManager.h"
+#include "GUIInfoManager.h"
+#include "GUILargeTextureManager.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "StereoscopicsManager.h"
+#include "TextureManager.h"
+#include "URL.h"
+#include "dialogs/GUIDialogYesNo.h"
+
+CGUIComponent::CGUIComponent()
+{
+ m_pWindowManager.reset(new CGUIWindowManager());
+ m_pTextureManager.reset(new CGUITextureManager());
+ m_pLargeTextureManager.reset(new CGUILargeTextureManager());
+ m_stereoscopicsManager.reset(new CStereoscopicsManager());
+ m_guiInfoManager.reset(new CGUIInfoManager());
+ m_guiColorManager.reset(new CGUIColorManager());
+ m_guiAudioManager.reset(new CGUIAudioManager());
+}
+
+CGUIComponent::~CGUIComponent()
+{
+ Deinit();
+}
+
+void CGUIComponent::Init()
+{
+ m_pWindowManager->Initialize();
+ m_stereoscopicsManager->Initialize();
+ m_guiInfoManager->Initialize();
+
+ CServiceBroker::RegisterGUI(this);
+}
+
+void CGUIComponent::Deinit()
+{
+ CServiceBroker::UnregisterGUI();
+
+ m_pWindowManager->DeInitialize();
+}
+
+CGUIWindowManager& CGUIComponent::GetWindowManager()
+{
+ return *m_pWindowManager;
+}
+
+CGUITextureManager& CGUIComponent::GetTextureManager()
+{
+ return *m_pTextureManager;
+}
+
+CGUILargeTextureManager& CGUIComponent::GetLargeTextureManager()
+{
+ return *m_pLargeTextureManager;
+}
+
+CStereoscopicsManager &CGUIComponent::GetStereoscopicsManager()
+{
+ return *m_stereoscopicsManager;
+}
+
+CGUIInfoManager &CGUIComponent::GetInfoManager()
+{
+ return *m_guiInfoManager;
+}
+
+CGUIColorManager &CGUIComponent::GetColorManager()
+{
+ return *m_guiColorManager;
+}
+
+CGUIAudioManager &CGUIComponent::GetAudioManager()
+{
+ return *m_guiAudioManager;
+}
+
+bool CGUIComponent::ConfirmDelete(const std::string& path)
+{
+ CGUIDialogYesNo* pDialog = GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
+ if (pDialog)
+ {
+ pDialog->SetHeading(CVariant{122});
+ pDialog->SetLine(0, CVariant{125});
+ pDialog->SetLine(1, CVariant{CURL(path).GetWithoutUserDetails()});
+ pDialog->SetLine(2, CVariant{""});
+ pDialog->Open();
+ if (pDialog->IsConfirmed())
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/guilib/GUIComponent.h b/xbmc/guilib/GUIComponent.h
new file mode 100644
index 0000000..97ada48
--- /dev/null
+++ b/xbmc/guilib/GUIComponent.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+class CGUIWindowManager;
+class CGUITextureManager;
+class CGUILargeTextureManager;
+class CStereoscopicsManager;
+class CGUIInfoManager;
+class CGUIColorManager;
+class CGUIAudioManager;
+
+class CGUIComponent
+{
+public:
+ CGUIComponent();
+ virtual ~CGUIComponent();
+ void Init();
+ void Deinit();
+
+ CGUIWindowManager& GetWindowManager();
+ CGUITextureManager& GetTextureManager();
+ CGUILargeTextureManager& GetLargeTextureManager();
+ CStereoscopicsManager &GetStereoscopicsManager();
+ CGUIInfoManager &GetInfoManager();
+ CGUIColorManager &GetColorManager();
+ CGUIAudioManager &GetAudioManager();
+
+ bool ConfirmDelete(const std::string& path);
+
+protected:
+ // members are pointers in order to avoid includes
+ std::unique_ptr<CGUIWindowManager> m_pWindowManager;
+ std::unique_ptr<CGUITextureManager> m_pTextureManager;
+ std::unique_ptr<CGUILargeTextureManager> m_pLargeTextureManager;
+ std::unique_ptr<CStereoscopicsManager> m_stereoscopicsManager;
+ std::unique_ptr<CGUIInfoManager> m_guiInfoManager;
+ std::unique_ptr<CGUIColorManager> m_guiColorManager;
+ std::unique_ptr<CGUIAudioManager> m_guiAudioManager;
+};
diff --git a/xbmc/guilib/GUIControl.cpp b/xbmc/guilib/GUIControl.cpp
new file mode 100644
index 0000000..bd40475
--- /dev/null
+++ b/xbmc/guilib/GUIControl.cpp
@@ -0,0 +1,979 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControl.h"
+
+#include "GUIAction.h"
+#include "GUIComponent.h"
+#include "GUIControlProfiler.h"
+#include "GUIInfoManager.h"
+#include "GUIMessage.h"
+#include "GUITexture.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "input/InputManager.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB;
+
+CGUIControl::CGUIControl()
+{
+ m_hasProcessed = false;
+ m_bHasFocus = false;
+ m_controlID = 0;
+ m_parentID = 0;
+ m_visible = VISIBLE;
+ m_visibleFromSkinCondition = true;
+ m_forceHidden = false;
+ m_enabled = true;
+ m_posX = 0;
+ m_posY = 0;
+ m_width = 0;
+ m_height = 0;
+ ControlType = GUICONTROL_UNKNOWN;
+ m_bInvalidated = true;
+ m_bAllocated=false;
+ m_parentControl = NULL;
+ m_hasCamera = false;
+ m_pushedUpdates = false;
+ m_pulseOnSelect = false;
+ m_controlDirtyState = DIRTY_STATE_CONTROL;
+ m_stereo = 0.0f;
+ m_controlStats = nullptr;
+}
+
+CGUIControl::CGUIControl(int parentID, int controlID, float posX, float posY, float width, float height)
+: m_hitRect(posX, posY, posX + width, posY + height),
+ m_diffuseColor(0xffffffff)
+{
+ m_posX = posX;
+ m_posY = posY;
+ m_width = width;
+ m_height = height;
+ m_bHasFocus = false;
+ m_controlID = controlID;
+ m_parentID = parentID;
+ m_visible = VISIBLE;
+ m_visibleFromSkinCondition = true;
+ m_forceHidden = false;
+ m_enabled = true;
+ ControlType = GUICONTROL_UNKNOWN;
+ m_bInvalidated = true;
+ m_bAllocated=false;
+ m_hasProcessed = false;
+ m_parentControl = NULL;
+ m_hasCamera = false;
+ m_pushedUpdates = false;
+ m_pulseOnSelect = false;
+ m_controlDirtyState = DIRTY_STATE_CONTROL;
+ m_stereo = 0.0f;
+ m_controlStats = nullptr;
+}
+
+CGUIControl::CGUIControl(const CGUIControl &) = default;
+
+CGUIControl::~CGUIControl(void) = default;
+
+void CGUIControl::AllocResources()
+{
+ m_hasProcessed = false;
+ m_bInvalidated = true;
+ m_bAllocated=true;
+}
+
+void CGUIControl::FreeResources(bool immediately)
+{
+ if (m_bAllocated)
+ {
+ // Reset our animation states - not conditional anims though.
+ // I'm not sure if this is needed for most cases anyway. I believe it's only here
+ // because some windows aren't loaded on demand
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() != ANIM_TYPE_CONDITIONAL)
+ anim.ResetAnimation();
+ }
+ m_bAllocated=false;
+ }
+ m_hasProcessed = false;
+}
+
+void CGUIControl::DynamicResourceAlloc(bool bOnOff)
+{
+
+}
+
+// the main processing routine.
+// 1. animate and set animation transform
+// 2. if visible, process
+// 3. reset the animation transform
+void CGUIControl::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CRect dirtyRegion = m_renderRegion;
+
+ bool changed = (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0 || (m_bInvalidated && IsVisible());
+ m_controlDirtyState = 0;
+
+ if (Animate(currentTime))
+ MarkDirtyRegion();
+
+ // if the control changed culling state from true to false, mark it
+ const bool culled = m_transform.alpha <= 0.01f;
+ if (m_isCulled != culled)
+ {
+ m_isCulled = false;
+ MarkDirtyRegion();
+ }
+ m_isCulled = culled;
+
+ if (IsVisible())
+ {
+ m_cachedTransform = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(m_transform);
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
+
+ Process(currentTime, dirtyregions);
+ m_bInvalidated = false;
+
+ if (dirtyRegion != m_renderRegion)
+ {
+ dirtyRegion.Union(m_renderRegion);
+ changed = true;
+ }
+
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+
+ UpdateControlStats();
+
+ changed |= (m_controlDirtyState & DIRTY_STATE_CONTROL) != 0;
+
+ if (changed)
+ {
+ dirtyregions.emplace_back(dirtyRegion);
+ }
+}
+
+void CGUIControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // update our render region
+ m_renderRegion = CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(CalcRenderRegion());
+ m_hasProcessed = true;
+}
+
+// the main render routine.
+// 1. set the animation transform
+// 2. if visible, paint
+// 3. reset the animation transform
+void CGUIControl::DoRender()
+{
+ if (IsVisible() && !m_isCulled)
+ {
+ bool hasStereo =
+ m_stereo != 0.0f &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() !=
+ RENDER_STEREO_MODE_MONO &&
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() != RENDER_STEREO_MODE_OFF;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTransform);
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetCameraPosition(m_camera);
+ if (hasStereo)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoFactor(m_stereo);
+
+ GUIPROFILER_RENDER_BEGIN(this);
+
+ if (m_hitColor != 0xffffffff)
+ {
+ UTILS::COLOR::Color color =
+ CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(m_hitColor);
+ CGUITexture::DrawQuad(CServiceBroker::GetWinSystem()->GetGfxContext().GenerateAABB(m_hitRect), color);
+ }
+
+ Render();
+
+ GUIPROFILER_RENDER_END(this);
+
+ if (hasStereo)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreStereoFactor();
+ if (m_hasCamera)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreCameraPosition();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+}
+
+bool CGUIControl::OnAction(const CAction &action)
+{
+ if (HasFocus())
+ {
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_DOWN:
+ OnDown();
+ return true;
+
+ case ACTION_MOVE_UP:
+ OnUp();
+ return true;
+
+ case ACTION_MOVE_LEFT:
+ OnLeft();
+ return true;
+
+ case ACTION_MOVE_RIGHT:
+ OnRight();
+ return true;
+
+ case ACTION_SHOW_INFO:
+ return OnInfo();
+
+ case ACTION_NAV_BACK:
+ return OnBack();
+
+ case ACTION_NEXT_CONTROL:
+ OnNextControl();
+ return true;
+
+ case ACTION_PREV_CONTROL:
+ OnPrevControl();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControl::Navigate(int direction) const
+{
+ if (HasFocus())
+ {
+ CGUIMessage msg(GUI_MSG_MOVE, GetParentID(), GetID(), direction);
+ return SendWindowMessage(msg);
+ }
+ return false;
+}
+
+// Movement controls (derived classes can override)
+void CGUIControl::OnUp()
+{
+ Navigate(ACTION_MOVE_UP);
+}
+
+void CGUIControl::OnDown()
+{
+ Navigate(ACTION_MOVE_DOWN);
+}
+
+void CGUIControl::OnLeft()
+{
+ Navigate(ACTION_MOVE_LEFT);
+}
+
+void CGUIControl::OnRight()
+{
+ Navigate(ACTION_MOVE_RIGHT);
+}
+
+bool CGUIControl::OnBack()
+{
+ return Navigate(ACTION_NAV_BACK);
+}
+
+bool CGUIControl::OnInfo()
+{
+ CGUIAction action = GetAction(ACTION_SHOW_INFO);
+ if (action.HasAnyActions())
+ return action.ExecuteActions(GetID(), GetParentID());
+ return false;
+}
+
+void CGUIControl::OnNextControl()
+{
+ Navigate(ACTION_NEXT_CONTROL);
+}
+
+void CGUIControl::OnPrevControl()
+{
+ Navigate(ACTION_PREV_CONTROL);
+}
+
+bool CGUIControl::SendWindowMessage(CGUIMessage &message) const
+{
+ CGUIWindow *pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(GetParentID());
+ if (pWindow)
+ return pWindow->OnMessage(message);
+ return CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+}
+
+int CGUIControl::GetID(void) const
+{
+ return m_controlID;
+}
+
+
+int CGUIControl::GetParentID(void) const
+{
+ return m_parentID;
+}
+
+bool CGUIControl::HasFocus(void) const
+{
+ return m_bHasFocus;
+}
+
+void CGUIControl::SetFocus(bool focus)
+{
+ if (m_bHasFocus && !focus)
+ QueueAnimation(ANIM_TYPE_UNFOCUS);
+ else if (!m_bHasFocus && focus)
+ QueueAnimation(ANIM_TYPE_FOCUS);
+ m_bHasFocus = focus;
+}
+
+bool CGUIControl::OnMessage(CGUIMessage& message)
+{
+ if ( message.GetControlId() == GetID() )
+ {
+ switch (message.GetMessage() )
+ {
+ case GUI_MSG_SETFOCUS:
+ // if control is disabled then move 2 the next control
+ if ( !CanFocus() )
+ {
+ CLog::Log(LOGERROR,
+ "Control {} in window {} has been asked to focus, "
+ "but it can't",
+ GetID(), GetParentID());
+ return false;
+ }
+ SetFocus(true);
+ {
+ // inform our parent window that this has happened
+ CGUIMessage message(GUI_MSG_FOCUSED, GetParentID(), GetID());
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ }
+ return true;
+ break;
+
+ case GUI_MSG_LOSTFOCUS:
+ {
+ SetFocus(false);
+ // and tell our parent so it can unfocus
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ return true;
+ }
+ break;
+
+ case GUI_MSG_VISIBLE:
+ SetVisible(true, true);
+ return true;
+ break;
+
+ case GUI_MSG_HIDDEN:
+ SetVisible(false);
+ return true;
+
+ // Note that the skin <enable> tag will override these messages
+ case GUI_MSG_ENABLED:
+ SetEnabled(true);
+ return true;
+
+ case GUI_MSG_DISABLED:
+ SetEnabled(false);
+ return true;
+
+ case GUI_MSG_WINDOW_RESIZE:
+ // invalidate controls to get them to recalculate sizing information
+ SetInvalid();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControl::CanFocus() const
+{
+ if (!IsVisible() && !m_allowHiddenFocus) return false;
+ if (IsDisabled()) return false;
+ return true;
+}
+
+bool CGUIControl::IsVisible() const
+{
+ if (m_forceHidden)
+ return false;
+ return m_visible == VISIBLE;
+}
+
+bool CGUIControl::IsDisabled() const
+{
+ return !m_enabled;
+}
+
+void CGUIControl::SetEnabled(bool bEnable)
+{
+ if (bEnable != m_enabled)
+ {
+ m_enabled = bEnable;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetEnableCondition(const std::string &expression)
+{
+ if (expression == "true")
+ m_enabled = true;
+ else if (expression == "false")
+ m_enabled = false;
+ else
+ m_enableCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
+}
+
+void CGUIControl::SetPosition(float posX, float posY)
+{
+ if ((m_posX != posX) || (m_posY != posY))
+ {
+ MarkDirtyRegion();
+
+ m_hitRect += CPoint(posX - m_posX, posY - m_posY);
+ m_posX = posX;
+ m_posY = posY;
+
+ SetInvalid();
+ }
+}
+
+bool CGUIControl::SetColorDiffuse(const GUIINFO::CGUIInfoColor &color)
+{
+ bool changed = m_diffuseColor != color;
+ m_diffuseColor = color;
+ return changed;
+}
+
+float CGUIControl::GetXPosition() const
+{
+ return m_posX;
+}
+
+float CGUIControl::GetYPosition() const
+{
+ return m_posY;
+}
+
+float CGUIControl::GetWidth() const
+{
+ return m_width;
+}
+
+float CGUIControl::GetHeight() const
+{
+ return m_height;
+}
+
+void CGUIControl::MarkDirtyRegion(const unsigned int dirtyState)
+{
+ // if the control is culled, bail
+ if (dirtyState == DIRTY_STATE_CONTROL && m_isCulled)
+ return;
+ if (!m_controlDirtyState && m_parentControl)
+ m_parentControl->MarkDirtyRegion(DIRTY_STATE_CHILD);
+
+ m_controlDirtyState |= dirtyState;
+}
+
+CRect CGUIControl::CalcRenderRegion() const
+{
+ CPoint tl(GetXPosition(), GetYPosition());
+ CPoint br(tl.x + GetWidth(), tl.y + GetHeight());
+
+ return CRect(tl.x, tl.y, br.x, br.y);
+}
+
+void CGUIControl::SetActions(const ActionMap &actions)
+{
+ m_actions = actions;
+}
+
+void CGUIControl::SetAction(int actionID, const CGUIAction &action, bool replace /*= true*/)
+{
+ ActionMap::iterator i = m_actions.find(actionID);
+ if (i == m_actions.end() || !i->second.HasAnyActions() || replace)
+ m_actions[actionID] = action;
+}
+
+void CGUIControl::SetWidth(float width)
+{
+ if (m_width != width)
+ {
+ MarkDirtyRegion();
+ m_width = width;
+ m_hitRect.x2 = m_hitRect.x1 + width;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetHeight(float height)
+{
+ if (m_height != height)
+ {
+ MarkDirtyRegion();
+ m_height = height;
+ m_hitRect.y2 = m_hitRect.y1 + height;
+ SetInvalid();
+ }
+}
+
+void CGUIControl::SetVisible(bool bVisible, bool setVisState)
+{
+ if (bVisible && setVisState)
+ { //! @todo currently we only update m_visible from GUI_MSG_VISIBLE (SET_CONTROL_VISIBLE)
+ //! otherwise we just set m_forceHidden
+ GUIVISIBLE visible;
+ if (m_visibleCondition)
+ visible = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT) ? VISIBLE : HIDDEN;
+ else
+ visible = VISIBLE;
+ if (visible != m_visible)
+ {
+ m_visible = visible;
+ SetInvalid();
+ }
+ }
+ if (m_forceHidden == bVisible)
+ {
+ m_forceHidden = !bVisible;
+ SetInvalid();
+ if (m_forceHidden)
+ MarkDirtyRegion();
+ }
+ if (m_forceHidden)
+ { // reset any visible animations that are in process
+ if (IsAnimating(ANIM_TYPE_VISIBLE))
+ {
+ // CLog::Log(LOGDEBUG, "Resetting visible animation on control {} (we are {})", m_controlID, m_visible ? "visible" : "hidden");
+ CAnimation *visibleAnim = GetAnimation(ANIM_TYPE_VISIBLE);
+ if (visibleAnim) visibleAnim->ResetAnimation();
+ }
+ }
+}
+
+bool CGUIControl::HitTest(const CPoint &point) const
+{
+ return m_hitRect.PtInRect(point);
+}
+
+EVENT_RESULT CGUIControl::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ CPoint childPoint(point);
+ m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
+ if (!CanFocusFromPoint(childPoint))
+ return EVENT_RESULT_UNHANDLED;
+
+ bool handled = event.m_id != ACTION_MOUSE_MOVE || OnMouseOver(childPoint);
+ EVENT_RESULT ret = OnMouseEvent(childPoint, event);
+ if (ret)
+ return ret;
+ return (handled && (event.m_id == ACTION_MOUSE_MOVE)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED;
+}
+
+// override this function to implement custom mouse behaviour
+bool CGUIControl::OnMouseOver(const CPoint &point)
+{
+ if (CServiceBroker::GetInputManager().GetMouseState() != MOUSE_STATE_DRAG)
+ CServiceBroker::GetInputManager().SetMouseState(MOUSE_STATE_FOCUS);
+ if (!CanFocus()) return false;
+ if (!HasFocus())
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), GetID());
+ OnMessage(msg);
+ }
+ return true;
+}
+
+void CGUIControl::UpdateVisibility(const CGUIListItem *item)
+{
+ if (m_visibleCondition)
+ {
+ bool bWasVisible = m_visibleFromSkinCondition;
+ m_visibleFromSkinCondition = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT, item);
+ if (!bWasVisible && m_visibleFromSkinCondition)
+ { // automatic change of visibility - queue the in effect
+ // CLog::Log(LOGDEBUG, "Visibility changed to visible for control id {}", m_controlID);
+ QueueAnimation(ANIM_TYPE_VISIBLE);
+ }
+ else if (bWasVisible && !m_visibleFromSkinCondition)
+ { // automatic change of visibility - do the out effect
+ // CLog::Log(LOGDEBUG, "Visibility changed to hidden for control id {}", m_controlID);
+ QueueAnimation(ANIM_TYPE_HIDDEN);
+ }
+ }
+ // check for conditional animations
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
+ anim.UpdateCondition(item);
+ }
+ // and check for conditional enabling - note this overrides SetEnabled() from the code currently
+ // this may need to be reviewed at a later date
+ bool enabled = m_enabled;
+ if (m_enableCondition)
+ m_enabled = m_enableCondition->Get(INFO::DEFAULT_CONTEXT, item);
+
+ if (m_enabled != enabled)
+ MarkDirtyRegion();
+
+ m_allowHiddenFocus.Update(INFO::DEFAULT_CONTEXT, item);
+ if (UpdateColors(item))
+ MarkDirtyRegion();
+ // and finally, update our control information (if not pushed)
+ if (!m_pushedUpdates)
+ UpdateInfo(item);
+}
+
+bool CGUIControl::UpdateColors(const CGUIListItem* item)
+{
+ return m_diffuseColor.Update(item);
+}
+
+void CGUIControl::SetInitialVisibility()
+{
+ if (m_visibleCondition)
+ {
+ m_visibleFromSkinCondition = m_visibleCondition->Get(INFO::DEFAULT_CONTEXT);
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ // CLog::Log(LOGDEBUG, "Set initial visibility for control {}: {}", m_controlID, m_visible == VISIBLE ? "visible" : "hidden");
+ }
+ else if (m_visible == DELAYED)
+ m_visible = VISIBLE;
+ // and handle animation conditions as well
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == ANIM_TYPE_CONDITIONAL)
+ anim.SetInitialCondition();
+ }
+ // and check for conditional enabling - note this overrides SetEnabled() from the code currently
+ // this may need to be reviewed at a later date
+ if (m_enableCondition)
+ m_enabled = m_enableCondition->Get(INFO::DEFAULT_CONTEXT);
+ m_allowHiddenFocus.Update(INFO::DEFAULT_CONTEXT);
+ UpdateColors(nullptr);
+
+ MarkDirtyRegion();
+}
+
+void CGUIControl::SetVisibleCondition(const std::string &expression, const std::string &allowHiddenFocus)
+{
+ if (expression == "true")
+ m_visible = VISIBLE;
+ else if (expression == "false")
+ m_visible = HIDDEN;
+ else // register with the infomanager for updates
+ m_visibleCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, GetParentID());
+ m_allowHiddenFocus.Parse(allowHiddenFocus, GetParentID());
+}
+
+void CGUIControl::SetAnimations(const std::vector<CAnimation> &animations)
+{
+ m_animations = animations;
+ MarkDirtyRegion();
+}
+
+void CGUIControl::ResetAnimation(ANIMATION_TYPE type)
+{
+ MarkDirtyRegion();
+
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ if (m_animations[i].GetType() == type)
+ m_animations[i].ResetAnimation();
+ }
+}
+
+void CGUIControl::ResetAnimations()
+{
+ MarkDirtyRegion();
+
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ m_animations[i].ResetAnimation();
+
+ MarkDirtyRegion();
+}
+
+bool CGUIControl::CheckAnimation(ANIMATION_TYPE animType)
+{
+ // rule out the animations we shouldn't perform
+ if (!IsVisible() || !HasProcessed())
+ { // hidden or never processed - don't allow exit or entry animations for this control
+ if (animType == ANIM_TYPE_WINDOW_CLOSE)
+ { // could be animating a (delayed) window open anim, so reset it
+ ResetAnimation(ANIM_TYPE_WINDOW_OPEN);
+ return false;
+ }
+ }
+ if (!IsVisible())
+ { // hidden - only allow hidden anims if we're animating a visible anim
+ if (animType == ANIM_TYPE_HIDDEN && !IsAnimating(ANIM_TYPE_VISIBLE))
+ {
+ // update states to force it hidden
+ UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
+ return false;
+ }
+ if (animType == ANIM_TYPE_WINDOW_OPEN)
+ return false;
+ }
+ return true;
+}
+
+void CGUIControl::QueueAnimation(ANIMATION_TYPE animType)
+{
+ if (!CheckAnimation(animType))
+ return;
+
+ MarkDirtyRegion();
+
+ CAnimation *reverseAnim = GetAnimation((ANIMATION_TYPE)-animType, false);
+ CAnimation *forwardAnim = GetAnimation(animType);
+ // we first check whether the reverse animation is in progress (and reverse it)
+ // then we check for the normal animation, and queue it
+ if (reverseAnim && reverseAnim->IsReversible() && (reverseAnim->GetState() == ANIM_STATE_IN_PROCESS || reverseAnim->GetState() == ANIM_STATE_DELAYED))
+ {
+ reverseAnim->QueueAnimation(ANIM_PROCESS_REVERSE);
+ if (forwardAnim) forwardAnim->ResetAnimation();
+ }
+ else if (forwardAnim)
+ {
+ forwardAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
+ if (reverseAnim) reverseAnim->ResetAnimation();
+ }
+ else
+ { // hidden and visible animations delay the change of state. If there is no animations
+ // to perform, then we should just change the state straightaway
+ if (reverseAnim) reverseAnim->ResetAnimation();
+ UpdateStates(animType, ANIM_PROCESS_NORMAL, ANIM_STATE_APPLIED);
+ }
+}
+
+CAnimation *CGUIControl::GetAnimation(ANIMATION_TYPE type, bool checkConditions /* = true */)
+{
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == type)
+ {
+ if (!checkConditions || anim.CheckCondition())
+ return &anim;
+ }
+ }
+ return NULL;
+}
+
+bool CGUIControl::HasAnimation(ANIMATION_TYPE type)
+{
+ return (NULL != GetAnimation(type, true));
+}
+
+void CGUIControl::UpdateStates(ANIMATION_TYPE type, ANIMATION_PROCESS currentProcess, ANIMATION_STATE currentState)
+{
+ // Make sure control is hidden or visible at the appropriate times
+ // while processing a visible or hidden animation it needs to be visible,
+ // but when finished a hidden operation it needs to be hidden
+ if (type == ANIM_TYPE_VISIBLE)
+ {
+ if (currentProcess == ANIM_PROCESS_REVERSE)
+ {
+ if (currentState == ANIM_STATE_APPLIED)
+ m_visible = HIDDEN;
+ }
+ else if (currentProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (currentState == ANIM_STATE_DELAYED)
+ m_visible = DELAYED;
+ else
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_HIDDEN)
+ {
+ if (currentProcess == ANIM_PROCESS_NORMAL) // a hide animation
+ {
+ if (currentState == ANIM_STATE_APPLIED)
+ m_visible = HIDDEN; // finished
+ else
+ m_visible = VISIBLE; // have to be visible until we are finished
+ }
+ else if (currentProcess == ANIM_PROCESS_REVERSE) // a visible animation
+ { // no delay involved here - just make sure it's visible
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_WINDOW_OPEN)
+ {
+ if (currentProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (currentState == ANIM_STATE_DELAYED)
+ m_visible = DELAYED; // delayed
+ else
+ m_visible = m_visibleFromSkinCondition ? VISIBLE : HIDDEN;
+ }
+ }
+ else if (type == ANIM_TYPE_FOCUS)
+ {
+ // call the focus function if we have finished a focus animation
+ // (buttons can "click" on focus)
+ if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
+ OnFocus();
+ }
+ else if (type == ANIM_TYPE_UNFOCUS)
+ {
+ // call the unfocus function if we have finished a focus animation
+ // (buttons can "click" on focus)
+ if (currentProcess == ANIM_PROCESS_NORMAL && currentState == ANIM_STATE_APPLIED)
+ OnUnFocus();
+ }
+}
+
+bool CGUIControl::Animate(unsigned int currentTime)
+{
+ // check visible state outside the loop, as it could change
+ GUIVISIBLE visible = m_visible;
+
+ m_transform.Reset();
+ bool changed = false;
+
+ CPoint center(GetXPosition() + GetWidth() * 0.5f, GetYPosition() + GetHeight() * 0.5f);
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ anim.Animate(currentTime, HasProcessed() || visible == DELAYED);
+ // Update the control states (such as visibility)
+ UpdateStates(anim.GetType(), anim.GetProcess(), anim.GetState());
+ // and render the animation effect
+ changed |= (anim.GetProcess() != ANIM_PROCESS_NONE);
+ anim.RenderAnimation(m_transform, center);
+
+ // debug stuff
+ //if (anim.GetProcess() != ANIM_PROCESS_NONE && IsVisible())
+ //{
+ // CLog::Log(LOGDEBUG, "Animating control {}", m_controlID);
+ //}
+ }
+
+ return changed;
+}
+
+bool CGUIControl::IsAnimating(ANIMATION_TYPE animType)
+{
+ for (unsigned int i = 0; i < m_animations.size(); i++)
+ {
+ CAnimation &anim = m_animations[i];
+ if (anim.GetType() == animType)
+ {
+ if (anim.GetQueuedProcess() == ANIM_PROCESS_NORMAL)
+ return true;
+ if (anim.GetProcess() == ANIM_PROCESS_NORMAL)
+ return true;
+ }
+ else if (anim.GetType() == -animType)
+ {
+ if (anim.GetQueuedProcess() == ANIM_PROCESS_REVERSE)
+ return true;
+ if (anim.GetProcess() == ANIM_PROCESS_REVERSE)
+ return true;
+ }
+ }
+ return false;
+}
+
+CGUIAction CGUIControl::GetAction(int actionID) const
+{
+ ActionMap::const_iterator i = m_actions.find(actionID);
+ if (i != m_actions.end())
+ return i->second;
+ return CGUIAction();
+}
+
+bool CGUIControl::CanFocusFromPoint(const CPoint &point) const
+{
+ return CanFocus() && HitTest(point);
+}
+
+void CGUIControl::UnfocusFromPoint(const CPoint &point)
+{
+ if (HasFocus())
+ {
+ CPoint controlPoint(point);
+ m_transform.InverseTransformPosition(controlPoint.x, controlPoint.y);
+ if (!HitTest(controlPoint))
+ {
+ SetFocus(false);
+
+ // and tell our parent so it can unfocus
+ if (m_parentControl)
+ {
+ CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), GetID());
+ m_parentControl->OnMessage(msgLostFocus);
+ }
+ }
+ }
+}
+
+void CGUIControl::SaveStates(std::vector<CControlState> &states)
+{
+ // empty for now - do nothing with the majority of controls
+}
+
+CGUIControl *CGUIControl::GetControl(int iControl, std::vector<CGUIControl*> *idCollector)
+{
+ return (iControl == m_controlID) ? this : nullptr;
+}
+
+
+void CGUIControl::UpdateControlStats()
+{
+ if (m_controlStats)
+ {
+ ++m_controlStats->nCountTotal;
+ if (IsVisible() && IsVisibleFromSkin())
+ ++m_controlStats->nCountVisible;
+ }
+}
+
+void CGUIControl::SetHitRect(const CRect& rect, const UTILS::COLOR::Color& color)
+{
+ m_hitRect = rect;
+ m_hitColor = color;
+}
+
+void CGUIControl::SetCamera(const CPoint &camera)
+{
+ m_camera = camera;
+ m_hasCamera = true;
+}
+
+CPoint CGUIControl::GetRenderPosition() const
+{
+ float z = 0;
+ CPoint point(GetPosition());
+ m_transform.TransformPosition(point.x, point.y, z);
+ if (m_parentControl)
+ point += m_parentControl->GetRenderPosition();
+ return point;
+}
+
+void CGUIControl::SetStereoFactor(const float &factor)
+{
+ m_stereo = factor;
+}
diff --git a/xbmc/guilib/GUIControl.h b/xbmc/guilib/GUIControl.h
new file mode 100644
index 0000000..49fadaa
--- /dev/null
+++ b/xbmc/guilib/GUIControl.h
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIControl.h
+\brief
+*/
+
+#include "DirtyRegion.h"
+#include "VisibleEffect.h" // needed for the CAnimation members
+#include "guiinfo/GUIInfoBool.h"
+#include "guiinfo/GUIInfoColor.h" // needed for CGUIInfoColor to handle infolabel'ed colors
+#include "utils/ColorUtils.h"
+#include "windowing/GraphicContext.h" // needed by any rendering operation (all controls)
+
+#include <vector>
+
+class CGUIListItem; // forward
+class CAction;
+class CMouseEvent;
+class CGUIMessage;
+class CGUIAction;
+
+enum ORIENTATION { HORIZONTAL = 0, VERTICAL };
+
+class CControlState
+{
+public:
+ CControlState(int id, int data)
+ {
+ m_id = id;
+ m_data = data;
+ }
+ int m_id;
+ int m_data;
+};
+
+struct GUICONTROLSTATS
+{
+ unsigned int nCountTotal;
+ unsigned int nCountVisible;
+
+ void Reset()
+ {
+ nCountTotal = nCountVisible = 0;
+ };
+};
+
+/*!
+ \brief Results of OnMouseEvent()
+ Any value not equal to EVENT_RESULT_UNHANDLED indicates that the event was handled.
+ */
+enum EVENT_RESULT { EVENT_RESULT_UNHANDLED = 0x00,
+ EVENT_RESULT_HANDLED = 0x01,
+ EVENT_RESULT_PAN_HORIZONTAL = 0x02,
+ EVENT_RESULT_PAN_VERTICAL = 0x04,
+ EVENT_RESULT_PAN_VERTICAL_WITHOUT_INERTIA = 0x08,
+ EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA = 0x10,
+ EVENT_RESULT_ROTATE = 0x20,
+ EVENT_RESULT_ZOOM = 0x40,
+ EVENT_RESULT_SWIPE = 0x80
+};
+
+/*!
+ \ingroup controls
+ \brief Base class for controls
+ */
+class CGUIControl
+{
+public:
+ CGUIControl();
+ CGUIControl(int parentID, int controlID, float posX, float posY, float width, float height);
+ CGUIControl(const CGUIControl &);
+ virtual ~CGUIControl(void);
+ virtual CGUIControl *Clone() const=0;
+
+ virtual void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions);
+ virtual void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions);
+ virtual void DoRender();
+ virtual void Render() {}
+ // Called after the actual rendering is completed to trigger additional
+ // non GUI rendering operations
+ virtual void RenderEx() {}
+
+ /*! \brief Returns whether or not we have processed */
+ bool HasProcessed() const { return m_hasProcessed; }
+
+ // OnAction() is called by our window when we are the focused control.
+ // We should process any control-specific actions in the derived classes,
+ // and return true if we have taken care of the action. Returning false
+ // indicates that the message may be handed down to the window or application
+ // levels. This base class implementation handles basic movement, and should
+ // be called from the derived classes when the action has not been handled.
+ // Return true to indicate that the action has been dealt with.
+ virtual bool OnAction(const CAction &action);
+
+ // Common actions to make the code easier to read (no ugly switch statements in derived controls)
+ virtual void OnUp();
+ virtual void OnDown();
+ virtual void OnLeft();
+ virtual void OnRight();
+ virtual bool OnBack();
+ virtual bool OnInfo();
+ virtual void OnNextControl();
+ virtual void OnPrevControl();
+ virtual void OnFocus() {}
+ virtual void OnUnFocus() {}
+
+ /*! \brief React to a mouse event
+
+ Mouse events are sent from the window to all controls, and each control can react based on the event
+ and location of the event.
+
+ \param point the location in transformed skin coordinates from the upper left corner of the parent control.
+ \param event the mouse event to perform
+ \return EVENT_RESULT corresponding to whether the control handles this event
+ \sa HitTest, CanFocusFromPoint, CMouseEvent, EVENT_RESULT
+ */
+ virtual EVENT_RESULT SendMouseEvent(const CPoint &point, const CMouseEvent &event);
+
+ /*! \brief Perform a mouse action
+
+ Mouse actions are sent from the window to all controls, and each control can react based on the event
+ and location of the actions.
+
+ \param point the location in transformed skin coordinates from the upper left corner of the parent control.
+ \param event the mouse event to perform
+ \return EVENT_RESULT corresponding to whether the control handles this event
+ \sa SendMouseEvent, HitTest, CanFocusFromPoint, CMouseEvent
+ */
+ virtual EVENT_RESULT OnMouseEvent(const CPoint& point, const CMouseEvent& event)
+ {
+ return EVENT_RESULT_UNHANDLED;
+ }
+
+ /*! \brief Unfocus the control if the given point on screen is not within it's boundary
+ \param point the location in transformed skin coordinates from the upper left corner of the parent control.
+ \sa CanFocusFromPoint
+ */
+ virtual void UnfocusFromPoint(const CPoint &point);
+
+ /*! \brief Used to test whether the point is inside a control.
+ \param point location to test
+ \return true if the point is inside the bounds of this control.
+ \sa SetHitRect
+ */
+ virtual bool HitTest(const CPoint &point) const;
+
+ virtual bool OnMessage(CGUIMessage& message);
+ virtual int GetID(void) const;
+ virtual void SetID(int id) { m_controlID = id; }
+ int GetParentID() const;
+ virtual bool HasFocus() const;
+ virtual void AllocResources();
+ virtual void FreeResources(bool immediately = false);
+ virtual void DynamicResourceAlloc(bool bOnOff);
+ virtual bool IsDynamicallyAllocated() { return false; }
+ virtual bool CanFocus() const;
+ virtual bool IsVisible() const;
+ bool IsVisibleFromSkin() const { return m_visibleFromSkinCondition; }
+ virtual bool IsDisabled() const;
+ virtual void SetPosition(float posX, float posY);
+ virtual void SetHitRect(const CRect& rect, const UTILS::COLOR::Color& color);
+ virtual void SetCamera(const CPoint &camera);
+ virtual void SetStereoFactor(const float &factor);
+ bool SetColorDiffuse(const KODI::GUILIB::GUIINFO::CGUIInfoColor &color);
+ CPoint GetRenderPosition() const;
+ virtual float GetXPosition() const;
+ virtual float GetYPosition() const;
+ virtual float GetWidth() const;
+ virtual float GetHeight() const;
+
+ void MarkDirtyRegion(const unsigned int dirtyState = DIRTY_STATE_CONTROL);
+ bool IsControlDirty() const { return m_controlDirtyState != 0; }
+
+ /*! \brief return the render region in screen coordinates of this control
+ */
+ const CRect& GetRenderRegion() const { return m_renderRegion; }
+ /*! \brief calculate the render region in parentcontrol coordinates of this control
+ Called during process to update m_renderRegion
+ */
+ virtual CRect CalcRenderRegion() const;
+
+ /*! \brief Set actions to perform on navigation
+ \param actions ActionMap of actions
+ \sa SetNavigationAction
+ */
+ typedef std::map<int, CGUIAction> ActionMap;
+ void SetActions(const ActionMap &actions);
+
+ /*! \brief Set actions to perform on navigation
+ Navigations are set if replace is true or if there is no previously set action
+ \param actionID id of the navigation action
+ \param action CGUIAction to set
+ \param replace Actions are set only if replace is true or there is no previously set action. Defaults to true
+ \sa SetNavigationActions
+ */
+ void SetAction(int actionID, const CGUIAction &action, bool replace = true);
+
+ /*! \brief Get an action the control can be perform.
+ \param actionID The actionID to retrieve.
+ */
+ CGUIAction GetAction(int actionID) const;
+
+ /*! \brief Start navigating in given direction.
+ */
+ bool Navigate(int direction) const;
+ virtual void SetFocus(bool focus);
+ virtual void SetWidth(float width);
+ virtual void SetHeight(float height);
+ virtual void SetVisible(bool bVisible, bool setVisState = false);
+ void SetVisibleCondition(const std::string &expression, const std::string &allowHiddenFocus = "");
+ bool HasVisibleCondition() const { return m_visibleCondition != NULL; }
+ void SetEnableCondition(const std::string &expression);
+ virtual void UpdateVisibility(const CGUIListItem *item);
+ virtual void SetInitialVisibility();
+ virtual void SetEnabled(bool bEnable);
+ virtual void SetInvalid() { m_bInvalidated = true; }
+ virtual void SetPulseOnSelect(bool pulse) { m_pulseOnSelect = pulse; }
+ virtual std::string GetDescription() const { return ""; }
+ virtual std::string GetDescriptionByIndex(int index) const { return ""; }
+
+ void SetAnimations(const std::vector<CAnimation> &animations);
+ const std::vector<CAnimation>& GetAnimations() const { return m_animations; }
+
+ virtual void QueueAnimation(ANIMATION_TYPE anim);
+ virtual bool IsAnimating(ANIMATION_TYPE anim);
+ virtual bool HasAnimation(ANIMATION_TYPE anim);
+ CAnimation *GetAnimation(ANIMATION_TYPE type, bool checkConditions = true);
+ virtual void ResetAnimation(ANIMATION_TYPE type);
+ virtual void ResetAnimations();
+
+ // push information updates
+ virtual void UpdateInfo(const CGUIListItem* item = NULL) {}
+ virtual void SetPushUpdates(bool pushUpdates) { m_pushedUpdates = pushUpdates; }
+
+ virtual bool IsGroup() const { return false; }
+ virtual bool IsContainer() const { return false; }
+ virtual bool GetCondition(int condition, int data) const { return false; }
+
+ void SetParentControl(CGUIControl* control) { m_parentControl = control; }
+ CGUIControl* GetParentControl(void) const { return m_parentControl; }
+ virtual void SaveStates(std::vector<CControlState> &states);
+ virtual CGUIControl *GetControl(int id, std::vector<CGUIControl*> *idCollector = nullptr);
+
+
+ void SetControlStats(GUICONTROLSTATS* controlStats) { m_controlStats = controlStats; }
+ virtual void UpdateControlStats();
+
+ enum GUICONTROLTYPES
+ {
+ GUICONTROL_UNKNOWN,
+
+ // Keep sorted
+ GUICONTAINER_EPGGRID,
+ GUICONTAINER_FIXEDLIST,
+ GUICONTAINER_LIST,
+ GUICONTAINER_PANEL,
+ GUICONTAINER_WRAPLIST,
+ GUICONTROL_BORDEREDIMAGE,
+ GUICONTROL_BUTTON,
+ GUICONTROL_COLORBUTTON,
+ GUICONTROL_EDIT,
+ GUICONTROL_FADELABEL,
+ GUICONTROL_GAME,
+ GUICONTROL_GAMECONTROLLER,
+ GUICONTROL_GROUP,
+ GUICONTROL_GROUPLIST,
+ GUICONTROL_IMAGE,
+ GUICONTROL_LABEL,
+ GUICONTROL_LISTGROUP,
+ GUICONTROL_LISTLABEL,
+ GUICONTROL_MOVER,
+ GUICONTROL_MULTI_IMAGE,
+ GUICONTROL_PROGRESS,
+ GUICONTROL_RADIO,
+ GUICONTROL_RANGES,
+ GUICONTROL_RENDERADDON,
+ GUICONTROL_RESIZE,
+ GUICONTROL_RSS,
+ GUICONTROL_SCROLLBAR,
+ GUICONTROL_SETTINGS_SLIDER,
+ GUICONTROL_SLIDER,
+ GUICONTROL_SPIN,
+ GUICONTROL_SPINEX,
+ GUICONTROL_TEXTBOX,
+ GUICONTROL_TOGGLEBUTTON,
+ GUICONTROL_VIDEO,
+ GUICONTROL_VISUALISATION,
+ };
+ GUICONTROLTYPES GetControlType() const { return ControlType; }
+
+ enum GUIVISIBLE { HIDDEN = 0, DELAYED, VISIBLE };
+
+ enum GUISCROLLVALUE { FOCUS = 0, NEVER, ALWAYS };
+
+#ifdef _DEBUG
+ virtual void DumpTextureUse() {}
+#endif
+protected:
+ /*!
+ \brief Return the coordinates of the top left of the control, in the control's parent coordinates
+ \return The top left coordinates of the control
+ */
+ virtual CPoint GetPosition() const { return CPoint(GetXPosition(), GetYPosition()); }
+
+ /*! \brief Called when the mouse is over the control.
+ Default implementation selects the control.
+ \param point location of the mouse in transformed skin coordinates
+ \return true if handled, false otherwise.
+ */
+ virtual bool OnMouseOver(const CPoint &point);
+
+ /*! \brief Test whether we can focus a control from a point on screen
+ \param point the location in vanilla skin coordinates from the upper left corner of the parent control.
+ \return true if the control can be focused from this location
+ \sa UnfocusFromPoint, HitRect
+ */
+ virtual bool CanFocusFromPoint(const CPoint &point) const;
+
+ virtual bool UpdateColors(const CGUIListItem* item);
+ virtual bool Animate(unsigned int currentTime);
+ virtual bool CheckAnimation(ANIMATION_TYPE animType);
+ void UpdateStates(ANIMATION_TYPE type, ANIMATION_PROCESS currentProcess, ANIMATION_STATE currentState);
+ bool SendWindowMessage(CGUIMessage &message) const;
+
+ // navigation and actions
+ ActionMap m_actions;
+
+ float m_posX;
+ float m_posY;
+ float m_height;
+ float m_width;
+ CRect m_hitRect;
+ UTILS::COLOR::Color m_hitColor = 0xffffffff;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor m_diffuseColor;
+ int m_controlID;
+ int m_parentID;
+ bool m_bHasFocus;
+ bool m_bInvalidated;
+ bool m_bAllocated;
+ bool m_pulseOnSelect;
+ GUICONTROLTYPES ControlType;
+ GUICONTROLSTATS *m_controlStats;
+
+ CGUIControl *m_parentControl; // our parent control if we're part of a group
+
+ // visibility condition/state
+ INFO::InfoPtr m_visibleCondition;
+ GUIVISIBLE m_visible;
+ bool m_visibleFromSkinCondition;
+ bool m_forceHidden; // set from the code when a hidden operation is given - overrides m_visible
+ KODI::GUILIB::GUIINFO::CGUIInfoBool m_allowHiddenFocus;
+ bool m_hasProcessed;
+ // enable/disable state
+ INFO::InfoPtr m_enableCondition;
+ bool m_enabled;
+
+ bool m_pushedUpdates;
+
+ // animation effects
+ std::vector<CAnimation> m_animations;
+ CPoint m_camera;
+ bool m_hasCamera;
+ float m_stereo;
+ TransformMatrix m_transform;
+ TransformMatrix m_cachedTransform; // Contains the absolute transform the control
+ bool m_isCulled{true};
+
+ static const unsigned int DIRTY_STATE_CONTROL = 1; //This control is dirty
+ static const unsigned int DIRTY_STATE_CHILD = 2; //One / more children are dirty
+
+ unsigned int m_controlDirtyState;
+ CRect m_renderRegion; // In screen coordinates
+};
+
diff --git a/xbmc/guilib/GUIControlFactory.cpp b/xbmc/guilib/GUIControlFactory.cpp
new file mode 100644
index 0000000..cf10978
--- /dev/null
+++ b/xbmc/guilib/GUIControlFactory.cpp
@@ -0,0 +1,1588 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControlFactory.h"
+
+#include "GUIAction.h"
+#include "GUIBorderedImage.h"
+#include "GUIButtonControl.h"
+#include "GUIColorButtonControl.h"
+#include "GUIColorManager.h"
+#include "GUIControlGroup.h"
+#include "GUIControlGroupList.h"
+#include "GUIEditControl.h"
+#include "GUIFadeLabelControl.h"
+#include "GUIFixedListContainer.h"
+#include "GUIFontManager.h"
+#include "GUIImage.h"
+#include "GUIInfoManager.h"
+#include "GUILabelControl.h"
+#include "GUIListContainer.h"
+#include "GUIListGroup.h"
+#include "GUIListLabel.h"
+#include "GUIMoverControl.h"
+#include "GUIMultiImage.h"
+#include "GUIPanelContainer.h"
+#include "GUIProgressControl.h"
+#include "GUIRSSControl.h"
+#include "GUIRadioButtonControl.h"
+#include "GUIRangesControl.h"
+#include "GUIRenderingControl.h"
+#include "GUIResizeControl.h"
+#include "GUIScrollBarControl.h"
+#include "GUISettingsSliderControl.h"
+#include "GUISliderControl.h"
+#include "GUISpinControl.h"
+#include "GUISpinControlEx.h"
+#include "GUITextBox.h"
+#include "GUIToggleButtonControl.h"
+#include "GUIVideoControl.h"
+#include "GUIVisualisationControl.h"
+#include "GUIWrappingListContainer.h"
+#include "LocalizeStrings.h"
+#include "addons/Skin.h"
+#include "cores/RetroPlayer/guicontrols/GUIGameControl.h"
+#include "games/controllers/guicontrols/GUIGameController.h"
+#include "input/Key.h"
+#include "pvr/guilib/GUIEPGGridContainer.h"
+#include "utils/CharsetConverter.h"
+#include "utils/RssManager.h"
+#include "utils/StringUtils.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+using namespace KODI;
+using namespace KODI::GUILIB;
+using namespace PVR;
+
+typedef struct
+{
+ const char* name;
+ CGUIControl::GUICONTROLTYPES type;
+} ControlMapping;
+
+static const ControlMapping controls[] = {
+ {"button", CGUIControl::GUICONTROL_BUTTON},
+ {"colorbutton", CGUIControl::GUICONTROL_COLORBUTTON},
+ {"edit", CGUIControl::GUICONTROL_EDIT},
+ {"epggrid", CGUIControl::GUICONTAINER_EPGGRID},
+ {"fadelabel", CGUIControl::GUICONTROL_FADELABEL},
+ {"fixedlist", CGUIControl::GUICONTAINER_FIXEDLIST},
+ {"gamecontroller", CGUIControl::GUICONTROL_GAMECONTROLLER},
+ {"gamewindow", CGUIControl::GUICONTROL_GAME},
+ {"group", CGUIControl::GUICONTROL_GROUP},
+ {"group", CGUIControl::GUICONTROL_LISTGROUP},
+ {"grouplist", CGUIControl::GUICONTROL_GROUPLIST},
+ {"image", CGUIControl::GUICONTROL_IMAGE},
+ {"image", CGUIControl::GUICONTROL_BORDEREDIMAGE},
+ {"label", CGUIControl::GUICONTROL_LABEL},
+ {"label", CGUIControl::GUICONTROL_LISTLABEL},
+ {"list", CGUIControl::GUICONTAINER_LIST},
+ {"mover", CGUIControl::GUICONTROL_MOVER},
+ {"multiimage", CGUIControl::GUICONTROL_MULTI_IMAGE},
+ {"panel", CGUIControl::GUICONTAINER_PANEL},
+ {"progress", CGUIControl::GUICONTROL_PROGRESS},
+ {"radiobutton", CGUIControl::GUICONTROL_RADIO},
+ {"ranges", CGUIControl::GUICONTROL_RANGES},
+ {"renderaddon", CGUIControl::GUICONTROL_RENDERADDON},
+ {"resize", CGUIControl::GUICONTROL_RESIZE},
+ {"rss", CGUIControl::GUICONTROL_RSS},
+ {"scrollbar", CGUIControl::GUICONTROL_SCROLLBAR},
+ {"slider", CGUIControl::GUICONTROL_SLIDER},
+ {"sliderex", CGUIControl::GUICONTROL_SETTINGS_SLIDER},
+ {"spincontrol", CGUIControl::GUICONTROL_SPIN},
+ {"spincontrolex", CGUIControl::GUICONTROL_SPINEX},
+ {"textbox", CGUIControl::GUICONTROL_TEXTBOX},
+ {"togglebutton", CGUIControl::GUICONTROL_TOGGLEBUTTON},
+ {"videowindow", CGUIControl::GUICONTROL_VIDEO},
+ {"visualisation", CGUIControl::GUICONTROL_VISUALISATION},
+ {"wraplist", CGUIControl::GUICONTAINER_WRAPLIST},
+};
+
+CGUIControl::GUICONTROLTYPES CGUIControlFactory::TranslateControlType(const std::string &type)
+{
+ for (const ControlMapping& control : controls)
+ if (StringUtils::EqualsNoCase(type, control.name))
+ return control.type;
+ return CGUIControl::GUICONTROL_UNKNOWN;
+}
+
+std::string CGUIControlFactory::TranslateControlType(CGUIControl::GUICONTROLTYPES type)
+{
+ for (const ControlMapping& control : controls)
+ if (type == control.type)
+ return control.name;
+ return "";
+}
+
+CGUIControlFactory::CGUIControlFactory(void) = default;
+
+CGUIControlFactory::~CGUIControlFactory(void) = default;
+
+bool CGUIControlFactory::GetIntRange(const TiXmlNode* pRootNode, const char* strTag, int& iMinValue, int& iMaxValue, int& iIntervalValue)
+{
+ const TiXmlNode* pNode = pRootNode->FirstChild(strTag);
+ if (!pNode || !pNode->FirstChild()) return false;
+ iMinValue = atoi(pNode->FirstChild()->Value());
+ const char* maxValue = strchr(pNode->FirstChild()->Value(), ',');
+ if (maxValue)
+ {
+ maxValue++;
+ iMaxValue = atoi(maxValue);
+
+ const char* intervalValue = strchr(maxValue, ',');
+ if (intervalValue)
+ {
+ intervalValue++;
+ iIntervalValue = atoi(intervalValue);
+ }
+ }
+
+ return true;
+}
+
+bool CGUIControlFactory::GetFloatRange(const TiXmlNode* pRootNode, const char* strTag, float& fMinValue, float& fMaxValue, float& fIntervalValue)
+{
+ const TiXmlNode* pNode = pRootNode->FirstChild(strTag);
+ if (!pNode || !pNode->FirstChild()) return false;
+ fMinValue = (float)atof(pNode->FirstChild()->Value());
+ const char* maxValue = strchr(pNode->FirstChild()->Value(), ',');
+ if (maxValue)
+ {
+ maxValue++;
+ fMaxValue = (float)atof(maxValue);
+
+ const char* intervalValue = strchr(maxValue, ',');
+ if (intervalValue)
+ {
+ intervalValue++;
+ fIntervalValue = (float)atof(intervalValue);
+ }
+ }
+
+ return true;
+}
+
+float CGUIControlFactory::ParsePosition(const char* pos, const float parentSize)
+{
+ char* end = NULL;
+ float value = pos ? (float)strtod(pos, &end) : 0;
+ if (end)
+ {
+ if (*end == 'r')
+ value = parentSize - value;
+ else if (*end == '%')
+ value = value * parentSize / 100.0f;
+ }
+ return value;
+}
+
+bool CGUIControlFactory::GetPosition(const TiXmlNode *node, const char* strTag, const float parentSize, float& value)
+{
+ const TiXmlElement* pNode = node->FirstChildElement(strTag);
+ if (!pNode || !pNode->FirstChild()) return false;
+
+ value = ParsePosition(pNode->FirstChild()->Value(), parentSize);
+ return true;
+}
+
+bool CGUIControlFactory::GetDimension(const TiXmlNode *pRootNode, const char* strTag, const float parentSize, float &value, float &min)
+{
+ const TiXmlElement* pNode = pRootNode->FirstChildElement(strTag);
+ if (!pNode || !pNode->FirstChild()) return false;
+ if (0 == StringUtils::CompareNoCase("auto", pNode->FirstChild()->Value(), 4))
+ { // auto-width - at least min must be set
+ value = ParsePosition(pNode->Attribute("max"), parentSize);
+ min = ParsePosition(pNode->Attribute("min"), parentSize);
+ if (!min) min = 1;
+ return true;
+ }
+ value = ParsePosition(pNode->FirstChild()->Value(), parentSize);
+ return true;
+}
+
+bool CGUIControlFactory::GetDimensions(const TiXmlNode *node, const char *leftTag, const char *rightTag, const char *centerLeftTag,
+ const char *centerRightTag, const char *widthTag, const float parentSize, float &left,
+ float &width, float &min_width)
+{
+ float center = 0, right = 0;
+
+ // read from the XML
+ bool hasLeft = GetPosition(node, leftTag, parentSize, left);
+ bool hasCenter = GetPosition(node, centerLeftTag, parentSize, center);
+ if (!hasCenter && GetPosition(node, centerRightTag, parentSize, center))
+ {
+ center = parentSize - center;
+ hasCenter = true;
+ }
+ bool hasRight = false;
+ if (GetPosition(node, rightTag, parentSize, right))
+ {
+ right = parentSize - right;
+ hasRight = true;
+ }
+ bool hasWidth = GetDimension(node, widthTag, parentSize, width, min_width);
+
+ if (!hasLeft)
+ { // figure out position
+ if (hasCenter) // no left specified
+ {
+ if (hasWidth)
+ {
+ left = center - width/2;
+ hasLeft = true;
+ }
+ else
+ {
+ if (hasRight)
+ {
+ width = (right - center) * 2;
+ left = right - width;
+ hasLeft = true;
+ }
+ }
+ }
+ else if (hasRight) // no left or centre
+ {
+ if (hasWidth)
+ {
+ left = right - width;
+ hasLeft = true;
+ }
+ }
+ }
+ if (!hasWidth)
+ {
+ if (hasRight)
+ {
+ width = std::max(0.0f, right - left); // if left=0, this fills to size of parent
+ hasLeft = true;
+ hasWidth = true;
+ }
+ else if (hasCenter)
+ {
+ if (hasLeft)
+ {
+ width = std::max(0.0f, (center - left) * 2);
+ hasWidth = true;
+ }
+ else if (center > 0 && center < parentSize)
+ { // centre given, so fill to edge of parent
+ width = std::max(0.0f, std::min(parentSize - center, center) * 2);
+ left = center - width/2;
+ hasLeft = true;
+ hasWidth = true;
+ }
+ }
+ else if (hasLeft) // neither right nor center specified
+ {
+ width = std::max(0.0f, parentSize - left); // if left=0, this fills to parent
+ hasWidth = true;
+ }
+ }
+ return hasLeft && hasWidth;
+}
+
+bool CGUIControlFactory::GetAspectRatio(const TiXmlNode* pRootNode, const char* strTag, CAspectRatio &aspect)
+{
+ std::string ratio;
+ const TiXmlElement *node = pRootNode->FirstChildElement(strTag);
+ if (!node || !node->FirstChild())
+ return false;
+
+ ratio = node->FirstChild()->Value();
+ if (StringUtils::EqualsNoCase(ratio, "keep")) aspect.ratio = CAspectRatio::AR_KEEP;
+ else if (StringUtils::EqualsNoCase(ratio, "scale")) aspect.ratio = CAspectRatio::AR_SCALE;
+ else if (StringUtils::EqualsNoCase(ratio, "center")) aspect.ratio = CAspectRatio::AR_CENTER;
+ else if (StringUtils::EqualsNoCase(ratio, "stretch")) aspect.ratio = CAspectRatio::AR_STRETCH;
+
+ const char *attribute = node->Attribute("align");
+ if (attribute)
+ {
+ std::string align(attribute);
+ if (StringUtils::EqualsNoCase(align, "center")) aspect.align = ASPECT_ALIGN_CENTER | (aspect.align & ASPECT_ALIGNY_MASK);
+ else if (StringUtils::EqualsNoCase(align, "right")) aspect.align = ASPECT_ALIGN_RIGHT | (aspect.align & ASPECT_ALIGNY_MASK);
+ else if (StringUtils::EqualsNoCase(align, "left")) aspect.align = ASPECT_ALIGN_LEFT | (aspect.align & ASPECT_ALIGNY_MASK);
+ }
+ attribute = node->Attribute("aligny");
+ if (attribute)
+ {
+ std::string align(attribute);
+ if (StringUtils::EqualsNoCase(align, "center")) aspect.align = ASPECT_ALIGNY_CENTER | (aspect.align & ASPECT_ALIGN_MASK);
+ else if (StringUtils::EqualsNoCase(align, "bottom")) aspect.align = ASPECT_ALIGNY_BOTTOM | (aspect.align & ASPECT_ALIGN_MASK);
+ else if (StringUtils::EqualsNoCase(align, "top")) aspect.align = ASPECT_ALIGNY_TOP | (aspect.align & ASPECT_ALIGN_MASK);
+ }
+ attribute = node->Attribute("scalediffuse");
+ if (attribute)
+ {
+ std::string scale(attribute);
+ if (StringUtils::EqualsNoCase(scale, "true") || StringUtils::EqualsNoCase(scale, "yes"))
+ aspect.scaleDiffuse = true;
+ else
+ aspect.scaleDiffuse = false;
+ }
+ return true;
+}
+
+bool CGUIControlFactory::GetInfoTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image, GUIINFO::CGUIInfoLabel &info, int parentID)
+{
+ GetTexture(pRootNode, strTag, image);
+ image.filename = "";
+ GetInfoLabel(pRootNode, strTag, info, parentID);
+ return true;
+}
+
+bool CGUIControlFactory::GetTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image)
+{
+ const TiXmlElement* pNode = pRootNode->FirstChildElement(strTag);
+ if (!pNode) return false;
+ const char *border = pNode->Attribute("border");
+ if (border)
+ {
+ GetRectFromString(border, image.border);
+ const char* borderinfill = pNode->Attribute("infill");
+ image.m_infill = (!borderinfill || !StringUtils::EqualsNoCase(borderinfill, "false"));
+ }
+ image.orientation = 0;
+ const char *flipX = pNode->Attribute("flipx");
+ if (flipX && StringUtils::CompareNoCase(flipX, "true") == 0)
+ image.orientation = 1;
+ const char *flipY = pNode->Attribute("flipy");
+ if (flipY && StringUtils::CompareNoCase(flipY, "true") == 0)
+ image.orientation = 3 - image.orientation; // either 3 or 2
+ image.diffuse = XMLUtils::GetAttribute(pNode, "diffuse");
+ image.diffuseColor.Parse(XMLUtils::GetAttribute(pNode, "colordiffuse"), 0);
+ const char *background = pNode->Attribute("background");
+ if (background && StringUtils::CompareNoCase(background, "true", 4) == 0)
+ image.useLarge = true;
+ image.filename = pNode->FirstChild() ? pNode->FirstChild()->Value() : "";
+ return true;
+}
+
+void CGUIControlFactory::GetRectFromString(const std::string &string, CRect &rect)
+{
+ // format is rect="left[,top,right,bottom]"
+ std::vector<std::string> strRect = StringUtils::Split(string, ',');
+ if (strRect.size() == 1)
+ {
+ rect.x1 = (float)atof(strRect[0].c_str());
+ rect.y1 = rect.x1;
+ rect.x2 = rect.x1;
+ rect.y2 = rect.x1;
+ }
+ else if (strRect.size() == 4)
+ {
+ rect.x1 = (float)atof(strRect[0].c_str());
+ rect.y1 = (float)atof(strRect[1].c_str());
+ rect.x2 = (float)atof(strRect[2].c_str());
+ rect.y2 = (float)atof(strRect[3].c_str());
+ }
+}
+
+bool CGUIControlFactory::GetAlignment(const TiXmlNode* pRootNode, const char* strTag, uint32_t& alignment)
+{
+ const TiXmlNode* pNode = pRootNode->FirstChild(strTag);
+ if (!pNode || !pNode->FirstChild()) return false;
+
+ std::string strAlign = pNode->FirstChild()->Value();
+ if (strAlign == "right" || strAlign == "bottom") alignment = XBFONT_RIGHT;
+ else if (strAlign == "center") alignment = XBFONT_CENTER_X;
+ else if (strAlign == "justify") alignment = XBFONT_JUSTIFIED;
+ else alignment = XBFONT_LEFT;
+ return true;
+}
+
+bool CGUIControlFactory::GetAlignmentY(const TiXmlNode* pRootNode, const char* strTag, uint32_t& alignment)
+{
+ const TiXmlNode* pNode = pRootNode->FirstChild(strTag );
+ if (!pNode || !pNode->FirstChild())
+ {
+ return false;
+ }
+
+ std::string strAlign = pNode->FirstChild()->Value();
+
+ alignment = 0;
+ if (strAlign == "center")
+ {
+ alignment = XBFONT_CENTER_Y;
+ }
+
+ return true;
+}
+
+bool CGUIControlFactory::GetConditionalVisibility(const TiXmlNode* control, std::string &condition, std::string &allowHiddenFocus)
+{
+ const TiXmlElement* node = control->FirstChildElement("visible");
+ if (!node) return false;
+ std::vector<std::string> conditions;
+ while (node)
+ {
+ const char *hidden = node->Attribute("allowhiddenfocus");
+ if (hidden)
+ allowHiddenFocus = hidden;
+ // add to our condition string
+ if (!node->NoChildren())
+ conditions.emplace_back(node->FirstChild()->Value());
+ node = node->NextSiblingElement("visible");
+ }
+ if (!conditions.size())
+ return false;
+ if (conditions.size() == 1)
+ condition = conditions[0];
+ else
+ { // multiple conditions should be anded together
+ condition = "[";
+ for (unsigned int i = 0; i < conditions.size() - 1; i++)
+ condition += conditions[i] + "] + [";
+ condition += conditions[conditions.size() - 1] + "]";
+ }
+ return true;
+}
+
+bool CGUIControlFactory::GetConditionalVisibility(const TiXmlNode *control, std::string &condition)
+{
+ std::string allowHiddenFocus;
+ return GetConditionalVisibility(control, condition, allowHiddenFocus);
+}
+
+bool CGUIControlFactory::GetAnimations(TiXmlNode *control, const CRect &rect, int context, std::vector<CAnimation> &animations)
+{
+ TiXmlElement* node = control->FirstChildElement("animation");
+ bool ret = false;
+ if (node)
+ animations.clear();
+ while (node)
+ {
+ ret = true;
+ if (node->FirstChild())
+ {
+ CAnimation anim;
+ anim.Create(node, rect, context);
+ animations.push_back(anim);
+ if (StringUtils::CompareNoCase(node->FirstChild()->Value(), "VisibleChange") == 0)
+ { // add the hidden one as well
+ TiXmlElement hidden(*node);
+ hidden.FirstChild()->SetValue("hidden");
+ const char *start = hidden.Attribute("start");
+ const char *end = hidden.Attribute("end");
+ if (start && end)
+ {
+ std::string temp = end;
+ hidden.SetAttribute("end", start);
+ hidden.SetAttribute("start", temp.c_str());
+ }
+ else if (start)
+ hidden.SetAttribute("end", start);
+ else if (end)
+ hidden.SetAttribute("start", end);
+ CAnimation anim2;
+ anim2.Create(&hidden, rect, context);
+ animations.push_back(anim2);
+ }
+ }
+ node = node->NextSiblingElement("animation");
+ }
+ return ret;
+}
+
+bool CGUIControlFactory::GetActions(const TiXmlNode* pRootNode,
+ const char* strTag,
+ CGUIAction& actions)
+{
+ actions.Reset();
+ const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag);
+ while (pElement)
+ {
+ if (pElement->FirstChild())
+ {
+ actions.Append(
+ {XMLUtils::GetAttribute(pElement, "condition"), pElement->FirstChild()->Value()});
+ }
+ pElement = pElement->NextSiblingElement(strTag);
+ }
+ return actions.HasAnyActions();
+}
+
+bool CGUIControlFactory::GetHitRect(const TiXmlNode *control, CRect &rect, const CRect &parentRect)
+{
+ const TiXmlElement* node = control->FirstChildElement("hitrect");
+ if (node)
+ {
+ rect.x1 = ParsePosition(node->Attribute("x"), parentRect.Width());
+ rect.y1 = ParsePosition(node->Attribute("y"), parentRect.Height());
+ if (node->Attribute("w"))
+ rect.x2 = (float)atof(node->Attribute("w")) + rect.x1;
+ else if (node->Attribute("right"))
+ rect.x2 = std::min(ParsePosition(node->Attribute("right"), parentRect.Width()), rect.x1);
+ if (node->Attribute("h"))
+ rect.y2 = (float)atof(node->Attribute("h")) + rect.y1;
+ else if (node->Attribute("bottom"))
+ rect.y2 = std::min(ParsePosition(node->Attribute("bottom"), parentRect.Height()), rect.y1);
+ return true;
+ }
+ return false;
+}
+
+bool CGUIControlFactory::GetScroller(const TiXmlNode *control, const std::string &scrollerTag, CScroller& scroller)
+{
+ const TiXmlElement* node = control->FirstChildElement(scrollerTag);
+ if (node)
+ {
+ unsigned int scrollTime;
+ if (XMLUtils::GetUInt(control, scrollerTag.c_str(), scrollTime))
+ {
+ scroller = CScroller(scrollTime, CAnimEffect::GetTweener(node));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControlFactory::GetColor(const TiXmlNode* control,
+ const char* strTag,
+ UTILS::COLOR::Color& value)
+{
+ const TiXmlElement* node = control->FirstChildElement(strTag);
+ if (node && node->FirstChild())
+ {
+ value = CServiceBroker::GetGUI()->GetColorManager().GetColor(node->FirstChild()->Value());
+ return true;
+ }
+ return false;
+}
+
+bool CGUIControlFactory::GetInfoColor(const TiXmlNode *control, const char *strTag, GUIINFO::CGUIInfoColor &value,int parentID)
+{
+ const TiXmlElement* node = control->FirstChildElement(strTag);
+ if (node && node->FirstChild())
+ {
+ value.Parse(node->FirstChild()->ValueStr(), parentID);
+ return true;
+ }
+ return false;
+}
+
+void CGUIControlFactory::GetInfoLabel(const TiXmlNode *pControlNode, const std::string &labelTag, GUIINFO::CGUIInfoLabel &infoLabel, int parentID)
+{
+ std::vector<GUIINFO::CGUIInfoLabel> labels;
+ GetInfoLabels(pControlNode, labelTag, labels, parentID);
+ if (labels.size())
+ infoLabel = labels[0];
+}
+
+bool CGUIControlFactory::GetInfoLabelFromElement(const TiXmlElement *element, GUIINFO::CGUIInfoLabel &infoLabel, int parentID)
+{
+ if (!element || !element->FirstChild())
+ return false;
+
+ std::string label = element->FirstChild()->Value();
+ if (label.empty())
+ return false;
+
+ std::string fallback = XMLUtils::GetAttribute(element, "fallback");
+ if (StringUtils::IsNaturalNumber(label))
+ label = g_localizeStrings.Get(atoi(label.c_str()));
+ if (StringUtils::IsNaturalNumber(fallback))
+ fallback = g_localizeStrings.Get(atoi(fallback.c_str()));
+ else
+ g_charsetConverter.unknownToUTF8(fallback);
+ infoLabel.SetLabel(label, fallback, parentID);
+ return true;
+}
+
+void CGUIControlFactory::GetInfoLabels(const TiXmlNode *pControlNode, const std::string &labelTag, std::vector<GUIINFO::CGUIInfoLabel> &infoLabels, int parentID)
+{
+ // we can have the following infolabels:
+ // 1. <number>1234</number> -> direct number
+ // 2. <label>number</label> -> lookup in localizestrings
+ // 3. <label fallback="blah">$LOCALIZE(blah) $INFO(blah)</label> -> infolabel with given fallback
+ // 4. <info>ListItem.Album</info> (uses <label> as fallback)
+ int labelNumber = 0;
+ if (XMLUtils::GetInt(pControlNode, "number", labelNumber))
+ {
+ std::string label = std::to_string(labelNumber);
+ infoLabels.emplace_back(label);
+ return; // done
+ }
+ const TiXmlElement *labelNode = pControlNode->FirstChildElement(labelTag);
+ while (labelNode)
+ {
+ GUIINFO::CGUIInfoLabel label;
+ if (GetInfoLabelFromElement(labelNode, label, parentID))
+ infoLabels.push_back(label);
+ labelNode = labelNode->NextSiblingElement(labelTag);
+ }
+ const TiXmlNode *infoNode = pControlNode->FirstChild("info");
+ if (infoNode)
+ { // <info> nodes override <label>'s (backward compatibility)
+ std::string fallback;
+ if (infoLabels.size())
+ fallback = infoLabels[0].GetLabel(0);
+ infoLabels.clear();
+ while (infoNode)
+ {
+ if (infoNode->FirstChild())
+ {
+ std::string info = StringUtils::Format("$INFO[{}]", infoNode->FirstChild()->Value());
+ infoLabels.emplace_back(info, fallback, parentID);
+ }
+ infoNode = infoNode->NextSibling("info");
+ }
+ }
+}
+
+// Convert a string to a GUI label, by translating/parsing the label for localisable strings
+std::string CGUIControlFactory::FilterLabel(const std::string &label)
+{
+ std::string viewLabel = label;
+ if (StringUtils::IsNaturalNumber(viewLabel))
+ viewLabel = g_localizeStrings.Get(atoi(label.c_str()));
+ else
+ g_charsetConverter.unknownToUTF8(viewLabel);
+ return viewLabel;
+}
+
+bool CGUIControlFactory::GetString(const TiXmlNode* pRootNode, const char *strTag, std::string &text)
+{
+ if (!XMLUtils::GetString(pRootNode, strTag, text))
+ return false;
+ if (StringUtils::IsNaturalNumber(text))
+ text = g_localizeStrings.Get(atoi(text.c_str()));
+ return true;
+}
+
+std::string CGUIControlFactory::GetType(const TiXmlElement *pControlNode)
+{
+ std::string type = XMLUtils::GetAttribute(pControlNode, "type");
+ if (type.empty()) // backward compatibility - not desired
+ XMLUtils::GetString(pControlNode, "type", type);
+ return type;
+}
+
+bool CGUIControlFactory::GetMovingSpeedConfig(const TiXmlNode* pRootNode,
+ const char* strTag,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg)
+{
+ const TiXmlElement* msNode = pRootNode->FirstChildElement(strTag);
+ if (!msNode)
+ return false;
+
+ float globalAccel{StringUtils::ToFloat(XMLUtils::GetAttribute(msNode, "acceleration"))};
+ float globalMaxVel{StringUtils::ToFloat(XMLUtils::GetAttribute(msNode, "maxvelocity"))};
+ uint32_t globalResetTimeout{
+ StringUtils::ToUint32(XMLUtils::GetAttribute(msNode, "resettimeout"))};
+ float globalDelta{StringUtils::ToFloat(XMLUtils::GetAttribute(msNode, "delta"))};
+
+ const TiXmlElement* configElement{msNode->FirstChildElement("eventconfig")};
+ while (configElement)
+ {
+ const char* eventType = configElement->Attribute("type");
+ if (!eventType)
+ {
+ CLog::LogF(LOGERROR, "Failed to parse XML \"eventconfig\" tag missing \"type\" attribute");
+ continue;
+ }
+
+ const char* accelerationStr{configElement->Attribute("acceleration")};
+ float acceleration = accelerationStr ? StringUtils::ToFloat(accelerationStr) : globalAccel;
+
+ const char* maxVelocityStr{configElement->Attribute("maxvelocity")};
+ float maxVelocity = maxVelocityStr ? StringUtils::ToFloat(maxVelocityStr) : globalMaxVel;
+
+ const char* resetTimeoutStr{configElement->Attribute("resettimeout")};
+ uint32_t resetTimeout =
+ resetTimeoutStr ? StringUtils::ToUint32(resetTimeoutStr) : globalResetTimeout;
+
+ const char* deltaStr{configElement->Attribute("delta")};
+ float delta = deltaStr ? StringUtils::ToFloat(deltaStr) : globalDelta;
+
+ UTILS::MOVING_SPEED::EventCfg eventCfg{acceleration, maxVelocity, resetTimeout, delta};
+ movingSpeedCfg.emplace(UTILS::MOVING_SPEED::ParseEventType(eventType), eventCfg);
+
+ configElement = configElement->NextSiblingElement("eventconfig");
+ }
+ return true;
+}
+
+CGUIControl* CGUIControlFactory::Create(int parentID, const CRect &rect, TiXmlElement* pControlNode, bool insideContainer)
+{
+ // get the control type
+ std::string strType = GetType(pControlNode);
+ CGUIControl::GUICONTROLTYPES type = TranslateControlType(strType);
+
+ int id = 0;
+ float posX = 0, posY = 0;
+ float width = 0, height = 0;
+ float minHeight = 0, minWidth = 0;
+
+ CGUIControl::ActionMap actions;
+
+ int pageControl = 0;
+ GUIINFO::CGUIInfoColor colorDiffuse(0xFFFFFFFF);
+ GUIINFO::CGUIInfoColor colorBox(0xFF000000);
+ int defaultControl = 0;
+ bool defaultAlways = false;
+ std::string strTmp;
+ int singleInfo = 0;
+ int singleInfo2 = 0;
+ std::string strLabel;
+ int iUrlSet=0;
+ std::string toggleSelect;
+
+ float spinWidth = 16;
+ float spinHeight = 16;
+ float spinPosX = 0, spinPosY = 0;
+ std::string strSubType;
+ int iType = SPIN_CONTROL_TYPE_TEXT;
+ int iMin = 0;
+ int iMax = 100;
+ int iInterval = 1;
+ float fMin = 0.0f;
+ float fMax = 1.0f;
+ float fInterval = 0.1f;
+ bool bReverse = true;
+ bool bReveal = false;
+ CTextureInfo textureBackground, textureLeft, textureRight, textureMid, textureOverlay;
+ CTextureInfo textureNib, textureNibFocus, textureBar, textureBarFocus;
+ CTextureInfo textureUp, textureDown;
+ CTextureInfo textureUpFocus, textureDownFocus;
+ CTextureInfo textureUpDisabled, textureDownDisabled;
+ CTextureInfo texture, borderTexture;
+ GUIINFO::CGUIInfoLabel textureFile;
+ CTextureInfo textureFocus, textureNoFocus;
+ CTextureInfo textureAltFocus, textureAltNoFocus;
+ CTextureInfo textureRadioOnFocus, textureRadioOnNoFocus;
+ CTextureInfo textureRadioOffFocus, textureRadioOffNoFocus;
+ CTextureInfo textureRadioOnDisabled, textureRadioOffDisabled;
+ CTextureInfo textureProgressIndicator;
+ CTextureInfo textureColorMask, textureColorDisabledMask;
+
+ GUIINFO::CGUIInfoLabel texturePath;
+ CRect borderSize;
+
+ float sliderWidth = 150, sliderHeight = 16;
+ CPoint offset;
+
+ bool bHasPath = false;
+ CGUIAction clickActions;
+ CGUIAction altclickActions;
+ CGUIAction focusActions;
+ CGUIAction unfocusActions;
+ CGUIAction textChangeActions;
+ std::string strTitle = "";
+ std::string strRSSTags = "";
+
+ float buttonGap = 5;
+ int iMovementRange = 0;
+ CAspectRatio aspect;
+ std::string allowHiddenFocus;
+ std::string enableCondition;
+
+ std::vector<CAnimation> animations;
+
+ CGUIControl::GUISCROLLVALUE scrollValue = CGUIControl::FOCUS;
+ bool bPulse = true;
+ unsigned int timePerImage = 0;
+ unsigned int fadeTime = 0;
+ unsigned int timeToPauseAtEnd = 0;
+ bool randomized = false;
+ bool loop = true;
+ bool wrapMultiLine = false;
+ ORIENTATION orientation = VERTICAL;
+ bool showOnePage = true;
+ bool scrollOut = true;
+ int preloadItems = 0;
+
+ CLabelInfo labelInfo, labelInfoMono;
+
+ GUIINFO::CGUIInfoColor hitColor(0xFFFFFFFF);
+ GUIINFO::CGUIInfoColor textColor3;
+ GUIINFO::CGUIInfoColor headlineColor;
+
+ float radioWidth = 0;
+ float radioHeight = 0;
+ float radioPosX = 0;
+ float radioPosY = 0;
+
+ float colorWidth = 0;
+ float colorHeight = 0;
+ float colorPosX = 0;
+ float colorPosY = 0;
+
+ std::string altLabel;
+ std::string strLabel2;
+ std::string action;
+
+ int focusPosition = 0;
+ int scrollTime = 200;
+ int timeBlocks = 36;
+ int rulerUnit = 12;
+ bool useControlCoords = false;
+ bool renderFocusedLast = false;
+
+ CRect hitRect;
+ CPoint camera;
+ float stereo = 0.f;
+ bool hasCamera = false;
+ bool resetOnLabelChange = true;
+ bool bPassword = false;
+ std::string visibleCondition;
+
+ UTILS::MOVING_SPEED::MapEventConfig movingSpeedCfg;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Read control properties from XML
+ //
+
+ if (!pControlNode->Attribute("id", &id))
+ XMLUtils::GetInt(pControlNode, "id", id); // backward compatibility - not desired
+ //! @todo Perhaps we should check here whether id is valid for focusable controls
+ //! such as buttons etc. For labels/fadelabels/images it does not matter
+
+ GetAlignment(pControlNode, "align", labelInfo.align);
+ if (!GetDimensions(pControlNode, "left", "right", "centerleft", "centerright", "width", rect.Width(), posX, width, minWidth))
+ { // didn't get 2 dimensions, so test for old <posx> as well
+ if (GetPosition(pControlNode, "posx", rect.Width(), posX))
+ { // <posx> available, so use it along with any hacks we used to support
+ if (!insideContainer &&
+ type == CGUIControl::GUICONTROL_LABEL &&
+ (labelInfo.align & XBFONT_RIGHT))
+ posX -= width;
+ }
+ if (!width) // no width specified, so compute from parent
+ width = std::max(rect.Width() - posX, 0.0f);
+ }
+ if (!GetDimensions(pControlNode, "top", "bottom", "centertop", "centerbottom", "height", rect.Height(), posY, height, minHeight))
+ {
+ GetPosition(pControlNode, "posy", rect.Height(), posY);
+ if (!height)
+ height = std::max(rect.Height() - posY, 0.0f);
+ }
+
+ XMLUtils::GetFloat(pControlNode, "offsetx", offset.x);
+ XMLUtils::GetFloat(pControlNode, "offsety", offset.y);
+
+ hitRect.SetRect(posX, posY, posX + width, posY + height);
+ GetHitRect(pControlNode, hitRect, rect);
+
+ GetInfoColor(pControlNode, "hitrectcolor", hitColor, parentID);
+
+ GetActions(pControlNode, "onup", actions[ACTION_MOVE_UP]);
+ GetActions(pControlNode, "ondown", actions[ACTION_MOVE_DOWN]);
+ GetActions(pControlNode, "onleft", actions[ACTION_MOVE_LEFT]);
+ GetActions(pControlNode, "onright", actions[ACTION_MOVE_RIGHT]);
+ GetActions(pControlNode, "onnext", actions[ACTION_NEXT_CONTROL]);
+ GetActions(pControlNode, "onprev", actions[ACTION_PREV_CONTROL]);
+ GetActions(pControlNode, "onback", actions[ACTION_NAV_BACK]);
+ GetActions(pControlNode, "oninfo", actions[ACTION_SHOW_INFO]);
+
+ if (XMLUtils::GetInt(pControlNode, "defaultcontrol", defaultControl))
+ {
+ const char *always = pControlNode->FirstChildElement("defaultcontrol")->Attribute("always");
+ if (always && StringUtils::CompareNoCase(always, "true", 4) == 0)
+ defaultAlways = true;
+ }
+ XMLUtils::GetInt(pControlNode, "pagecontrol", pageControl);
+
+ GetInfoColor(pControlNode, "colordiffuse", colorDiffuse, parentID);
+ GetInfoColor(pControlNode, "colorbox", colorBox, parentID);
+
+ GetConditionalVisibility(pControlNode, visibleCondition, allowHiddenFocus);
+ XMLUtils::GetString(pControlNode, "enable", enableCondition);
+
+ CRect animRect(posX, posY, posX + width, posY + height);
+ GetAnimations(pControlNode, animRect, parentID, animations);
+
+ GetInfoColor(pControlNode, "textcolor", labelInfo.textColor, parentID);
+ GetInfoColor(pControlNode, "focusedcolor", labelInfo.focusedColor, parentID);
+ GetInfoColor(pControlNode, "disabledcolor", labelInfo.disabledColor, parentID);
+ GetInfoColor(pControlNode, "shadowcolor", labelInfo.shadowColor, parentID);
+ GetInfoColor(pControlNode, "selectedcolor", labelInfo.selectedColor, parentID);
+ GetInfoColor(pControlNode, "invalidcolor", labelInfo.invalidColor, parentID);
+ XMLUtils::GetFloat(pControlNode, "textoffsetx", labelInfo.offsetX);
+ XMLUtils::GetFloat(pControlNode, "textoffsety", labelInfo.offsetY);
+ int angle = 0; // use the negative angle to compensate for our vertically flipped cartesian plane
+ if (XMLUtils::GetInt(pControlNode, "angle", angle)) labelInfo.angle = (float)-angle;
+ std::string strFont, strMonoFont;
+ if (XMLUtils::GetString(pControlNode, "font", strFont))
+ labelInfo.font = g_fontManager.GetFont(strFont);
+ XMLUtils::GetString(pControlNode, "monofont", strMonoFont);
+ uint32_t alignY = 0;
+ if (GetAlignmentY(pControlNode, "aligny", alignY))
+ labelInfo.align |= alignY;
+ if (XMLUtils::GetFloat(pControlNode, "textwidth", labelInfo.width))
+ labelInfo.align |= XBFONT_TRUNCATED;
+
+ GetActions(pControlNode, "onclick", clickActions);
+ GetActions(pControlNode, "ontextchange", textChangeActions);
+ GetActions(pControlNode, "onfocus", focusActions);
+ GetActions(pControlNode, "onunfocus", unfocusActions);
+ focusActions.EnableSendThreadMessageMode();
+ unfocusActions.EnableSendThreadMessageMode();
+ GetActions(pControlNode, "altclick", altclickActions);
+
+ std::string infoString;
+ if (XMLUtils::GetString(pControlNode, "info", infoString))
+ singleInfo = CServiceBroker::GetGUI()->GetInfoManager().TranslateString(infoString);
+ if (XMLUtils::GetString(pControlNode, "info2", infoString))
+ singleInfo2 = CServiceBroker::GetGUI()->GetInfoManager().TranslateString(infoString);
+
+ GetTexture(pControlNode, "texturefocus", textureFocus);
+ GetTexture(pControlNode, "texturenofocus", textureNoFocus);
+ GetTexture(pControlNode, "alttexturefocus", textureAltFocus);
+ GetTexture(pControlNode, "alttexturenofocus", textureAltNoFocus);
+
+ XMLUtils::GetString(pControlNode, "usealttexture", toggleSelect);
+ XMLUtils::GetString(pControlNode, "selected", toggleSelect);
+
+ XMLUtils::GetBoolean(pControlNode, "haspath", bHasPath);
+
+ GetTexture(pControlNode, "textureup", textureUp);
+ GetTexture(pControlNode, "texturedown", textureDown);
+ GetTexture(pControlNode, "textureupfocus", textureUpFocus);
+ GetTexture(pControlNode, "texturedownfocus", textureDownFocus);
+ GetTexture(pControlNode, "textureupdisabled", textureUpDisabled);
+ GetTexture(pControlNode, "texturedowndisabled", textureDownDisabled);
+
+ XMLUtils::GetFloat(pControlNode, "spinwidth", spinWidth);
+ XMLUtils::GetFloat(pControlNode, "spinheight", spinHeight);
+ XMLUtils::GetFloat(pControlNode, "spinposx", spinPosX);
+ XMLUtils::GetFloat(pControlNode, "spinposy", spinPosY);
+
+ XMLUtils::GetFloat(pControlNode, "sliderwidth", sliderWidth);
+ XMLUtils::GetFloat(pControlNode, "sliderheight", sliderHeight);
+ if (!GetTexture(pControlNode, "textureradioonfocus", textureRadioOnFocus) || !GetTexture(pControlNode, "textureradioonnofocus", textureRadioOnNoFocus))
+ {
+ GetTexture(pControlNode, "textureradiofocus", textureRadioOnFocus); // backward compatibility
+ GetTexture(pControlNode, "textureradioon", textureRadioOnFocus);
+ textureRadioOnNoFocus = textureRadioOnFocus;
+ }
+ if (!GetTexture(pControlNode, "textureradioofffocus", textureRadioOffFocus) || !GetTexture(pControlNode, "textureradiooffnofocus", textureRadioOffNoFocus))
+ {
+ GetTexture(pControlNode, "textureradionofocus", textureRadioOffFocus); // backward compatibility
+ GetTexture(pControlNode, "textureradiooff", textureRadioOffFocus);
+ textureRadioOffNoFocus = textureRadioOffFocus;
+ }
+ GetTexture(pControlNode, "textureradioondisabled", textureRadioOnDisabled);
+ GetTexture(pControlNode, "textureradiooffdisabled", textureRadioOffDisabled);
+ GetTexture(pControlNode, "texturesliderbackground", textureBackground);
+ GetTexture(pControlNode, "texturesliderbar", textureBar);
+ GetTexture(pControlNode, "texturesliderbarfocus", textureBarFocus);
+ GetTexture(pControlNode, "textureslidernib", textureNib);
+ GetTexture(pControlNode, "textureslidernibfocus", textureNibFocus);
+
+ GetTexture(pControlNode, "texturecolormask", textureColorMask);
+ GetTexture(pControlNode, "texturecolordisabledmask", textureColorDisabledMask);
+
+ XMLUtils::GetString(pControlNode, "title", strTitle);
+ XMLUtils::GetString(pControlNode, "tagset", strRSSTags);
+ GetInfoColor(pControlNode, "headlinecolor", headlineColor, parentID);
+ GetInfoColor(pControlNode, "titlecolor", textColor3, parentID);
+
+ if (XMLUtils::GetString(pControlNode, "subtype", strSubType))
+ {
+ StringUtils::ToLower(strSubType);
+
+ if ( strSubType == "int")
+ iType = SPIN_CONTROL_TYPE_INT;
+ else if ( strSubType == "page")
+ iType = SPIN_CONTROL_TYPE_PAGE;
+ else if ( strSubType == "float")
+ iType = SPIN_CONTROL_TYPE_FLOAT;
+ else
+ iType = SPIN_CONTROL_TYPE_TEXT;
+ }
+
+ if (!GetIntRange(pControlNode, "range", iMin, iMax, iInterval))
+ {
+ GetFloatRange(pControlNode, "range", fMin, fMax, fInterval);
+ }
+
+ XMLUtils::GetBoolean(pControlNode, "reverse", bReverse);
+ XMLUtils::GetBoolean(pControlNode, "reveal", bReveal);
+
+ GetTexture(pControlNode, "texturebg", textureBackground);
+ GetTexture(pControlNode, "lefttexture", textureLeft);
+ GetTexture(pControlNode, "midtexture", textureMid);
+ GetTexture(pControlNode, "righttexture", textureRight);
+ GetTexture(pControlNode, "overlaytexture", textureOverlay);
+
+ // the <texture> tag can be overridden by the <info> tag
+ GetInfoTexture(pControlNode, "texture", texture, textureFile, parentID);
+
+ GetTexture(pControlNode, "bordertexture", borderTexture);
+
+ // fade label can have a whole bunch, but most just have one
+ std::vector<GUIINFO::CGUIInfoLabel> infoLabels;
+ GetInfoLabels(pControlNode, "label", infoLabels, parentID);
+
+ GetString(pControlNode, "label", strLabel);
+ GetString(pControlNode, "altlabel", altLabel);
+ GetString(pControlNode, "label2", strLabel2);
+
+ XMLUtils::GetBoolean(pControlNode, "wrapmultiline", wrapMultiLine);
+ XMLUtils::GetInt(pControlNode,"urlset",iUrlSet);
+
+ if ( XMLUtils::GetString(pControlNode, "orientation", strTmp) )
+ {
+ StringUtils::ToLower(strTmp);
+ if (strTmp == "horizontal")
+ orientation = HORIZONTAL;
+ }
+ XMLUtils::GetFloat(pControlNode, "itemgap", buttonGap);
+ XMLUtils::GetInt(pControlNode, "movement", iMovementRange);
+ GetAspectRatio(pControlNode, "aspectratio", aspect);
+
+ bool alwaysScroll;
+ if (XMLUtils::GetBoolean(pControlNode, "scroll", alwaysScroll))
+ scrollValue = alwaysScroll ? CGUIControl::ALWAYS : CGUIControl::NEVER;
+
+ XMLUtils::GetBoolean(pControlNode,"pulseonselect", bPulse);
+ XMLUtils::GetInt(pControlNode, "timeblocks", timeBlocks);
+ XMLUtils::GetInt(pControlNode, "rulerunit", rulerUnit);
+ GetTexture(pControlNode, "progresstexture", textureProgressIndicator);
+
+ GetInfoTexture(pControlNode, "imagepath", texture, texturePath, parentID);
+
+ XMLUtils::GetUInt(pControlNode,"timeperimage", timePerImage);
+ XMLUtils::GetUInt(pControlNode,"fadetime", fadeTime);
+ XMLUtils::GetUInt(pControlNode,"pauseatend", timeToPauseAtEnd);
+ XMLUtils::GetBoolean(pControlNode, "randomize", randomized);
+ XMLUtils::GetBoolean(pControlNode, "loop", loop);
+ XMLUtils::GetBoolean(pControlNode, "scrollout", scrollOut);
+
+ XMLUtils::GetFloat(pControlNode, "radiowidth", radioWidth);
+ XMLUtils::GetFloat(pControlNode, "radioheight", radioHeight);
+ XMLUtils::GetFloat(pControlNode, "radioposx", radioPosX);
+ XMLUtils::GetFloat(pControlNode, "radioposy", radioPosY);
+
+ XMLUtils::GetFloat(pControlNode, "colorwidth", colorWidth);
+ XMLUtils::GetFloat(pControlNode, "colorheight", colorHeight);
+ XMLUtils::GetFloat(pControlNode, "colorposx", colorPosX);
+ XMLUtils::GetFloat(pControlNode, "colorposy", colorPosY);
+
+ std::string borderStr;
+ if (XMLUtils::GetString(pControlNode, "bordersize", borderStr))
+ GetRectFromString(borderStr, borderSize);
+
+ XMLUtils::GetBoolean(pControlNode, "showonepage", showOnePage);
+ XMLUtils::GetInt(pControlNode, "focusposition", focusPosition);
+ XMLUtils::GetInt(pControlNode, "scrolltime", scrollTime);
+ XMLUtils::GetInt(pControlNode, "preloaditems", preloadItems, 0, 2);
+
+ XMLUtils::GetBoolean(pControlNode, "usecontrolcoords", useControlCoords);
+ XMLUtils::GetBoolean(pControlNode, "renderfocusedlast", renderFocusedLast);
+ XMLUtils::GetBoolean(pControlNode, "resetonlabelchange", resetOnLabelChange);
+
+ XMLUtils::GetBoolean(pControlNode, "password", bPassword);
+
+ // view type
+ VIEW_TYPE viewType = VIEW_TYPE_NONE;
+ std::string viewLabel;
+ if (type == CGUIControl::GUICONTAINER_PANEL)
+ {
+ viewType = VIEW_TYPE_ICON;
+ viewLabel = g_localizeStrings.Get(536);
+ }
+ else if (type == CGUIControl::GUICONTAINER_LIST)
+ {
+ viewType = VIEW_TYPE_LIST;
+ viewLabel = g_localizeStrings.Get(535);
+ }
+ else
+ {
+ viewType = VIEW_TYPE_WRAP;
+ viewLabel = g_localizeStrings.Get(541);
+ }
+ TiXmlElement *itemElement = pControlNode->FirstChildElement("viewtype");
+ if (itemElement && itemElement->FirstChild())
+ {
+ std::string type = itemElement->FirstChild()->Value();
+ if (type == "list")
+ viewType = VIEW_TYPE_LIST;
+ else if (type == "icon")
+ viewType = VIEW_TYPE_ICON;
+ else if (type == "biglist")
+ viewType = VIEW_TYPE_BIG_LIST;
+ else if (type == "bigicon")
+ viewType = VIEW_TYPE_BIG_ICON;
+ else if (type == "wide")
+ viewType = VIEW_TYPE_WIDE;
+ else if (type == "bigwide")
+ viewType = VIEW_TYPE_BIG_WIDE;
+ else if (type == "wrap")
+ viewType = VIEW_TYPE_WRAP;
+ else if (type == "bigwrap")
+ viewType = VIEW_TYPE_BIG_WRAP;
+ else if (type == "info")
+ viewType = VIEW_TYPE_INFO;
+ else if (type == "biginfo")
+ viewType = VIEW_TYPE_BIG_INFO;
+ const char *label = itemElement->Attribute("label");
+ if (label)
+ viewLabel = GUIINFO::CGUIInfoLabel::GetLabel(FilterLabel(label), INFO::DEFAULT_CONTEXT);
+ }
+
+ TiXmlElement *cam = pControlNode->FirstChildElement("camera");
+ if (cam)
+ {
+ hasCamera = true;
+ camera.x = ParsePosition(cam->Attribute("x"), width);
+ camera.y = ParsePosition(cam->Attribute("y"), height);
+ }
+
+ if (XMLUtils::GetFloat(pControlNode, "depth", stereo))
+ stereo = std::max(-1.f, std::min(1.f, stereo));
+
+ XMLUtils::GetInt(pControlNode, "scrollspeed", labelInfo.scrollSpeed);
+
+ GetString(pControlNode, "scrollsuffix", labelInfo.scrollSuffix);
+
+ XMLUtils::GetString(pControlNode, "action", action);
+
+ GetMovingSpeedConfig(pControlNode, "movingspeed", movingSpeedCfg);
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Instantiate a new control using the properties gathered above
+ //
+
+ CGUIControl *control = NULL;
+ switch (type)
+ {
+ case CGUIControl::GUICONTROL_GROUP:
+ {
+ if (insideContainer)
+ {
+ control = new CGUIListGroup(parentID, id, posX, posY, width, height);
+ }
+ else
+ {
+ control = new CGUIControlGroup(
+ parentID, id, posX, posY, width, height);
+ static_cast<CGUIControlGroup*>(control)->SetDefaultControl(defaultControl, defaultAlways);
+ static_cast<CGUIControlGroup*>(control)->SetRenderFocusedLast(renderFocusedLast);
+ }
+ }
+ break;
+ case CGUIControl::GUICONTROL_GROUPLIST:
+ {
+ CScroller scroller;
+ GetScroller(pControlNode, "scrolltime", scroller);
+
+ control = new CGUIControlGroupList(
+ parentID, id, posX, posY, width, height, buttonGap, pageControl, orientation, useControlCoords, labelInfo.align, scroller);
+ static_cast<CGUIControlGroup*>(control)->SetDefaultControl(defaultControl, defaultAlways);
+ static_cast<CGUIControlGroup*>(control)->SetRenderFocusedLast(renderFocusedLast);
+ static_cast<CGUIControlGroupList*>(control)->SetMinSize(minWidth, minHeight);
+ }
+ break;
+ case CGUIControl::GUICONTROL_LABEL:
+ {
+ static const GUIINFO::CGUIInfoLabel empty;
+ const GUIINFO::CGUIInfoLabel& content = !infoLabels.empty() ? infoLabels[0] : empty;
+ if (insideContainer)
+ { // inside lists we use CGUIListLabel
+ control = new CGUIListLabel(parentID, id, posX, posY, width, height, labelInfo, content, scrollValue);
+ }
+ else
+ {
+ control = new CGUILabelControl(
+ parentID, id, posX, posY, width, height,
+ labelInfo, wrapMultiLine, bHasPath);
+ static_cast<CGUILabelControl*>(control)->SetInfo(content);
+ static_cast<CGUILabelControl*>(control)->SetWidthControl(minWidth, (scrollValue == CGUIControl::ALWAYS));
+ }
+ }
+ break;
+ case CGUIControl::GUICONTROL_EDIT:
+ {
+ control = new CGUIEditControl(
+ parentID, id, posX, posY, width, height, textureFocus, textureNoFocus,
+ labelInfo, strLabel);
+
+ GUIINFO::CGUIInfoLabel hint_text;
+ GetInfoLabel(pControlNode, "hinttext", hint_text, parentID);
+ static_cast<CGUIEditControl*>(control)->SetHint(hint_text);
+
+ if (bPassword)
+ static_cast<CGUIEditControl*>(control)->SetInputType(CGUIEditControl::INPUT_TYPE_PASSWORD, 0);
+ static_cast<CGUIEditControl*>(control)->SetTextChangeActions(textChangeActions);
+ }
+ break;
+ case CGUIControl::GUICONTROL_VIDEO:
+ {
+ control = new CGUIVideoControl(
+ parentID, id, posX, posY, width, height);
+ }
+ break;
+ case CGUIControl::GUICONTROL_GAME:
+ {
+ control = new RETRO::CGUIGameControl(parentID, id, posX, posY, width, height);
+
+ GUIINFO::CGUIInfoLabel videoFilter;
+ GetInfoLabel(pControlNode, "videofilter", videoFilter, parentID);
+ static_cast<RETRO::CGUIGameControl*>(control)->SetVideoFilter(videoFilter);
+
+ GUIINFO::CGUIInfoLabel stretchMode;
+ GetInfoLabel(pControlNode, "stretchmode", stretchMode, parentID);
+ static_cast<RETRO::CGUIGameControl*>(control)->SetStretchMode(stretchMode);
+
+ GUIINFO::CGUIInfoLabel rotation;
+ GetInfoLabel(pControlNode, "rotation", rotation, parentID);
+ static_cast<RETRO::CGUIGameControl*>(control)->SetRotation(rotation);
+
+ GUIINFO::CGUIInfoLabel pixels;
+ GetInfoLabel(pControlNode, "pixels", pixels, parentID);
+ static_cast<RETRO::CGUIGameControl*>(control)->SetPixels(pixels);
+ }
+ break;
+ case CGUIControl::GUICONTROL_FADELABEL:
+ {
+ control = new CGUIFadeLabelControl(
+ parentID, id, posX, posY, width, height,
+ labelInfo, scrollOut, timeToPauseAtEnd, resetOnLabelChange, randomized);
+
+ static_cast<CGUIFadeLabelControl*>(control)->SetInfo(infoLabels);
+
+ // check whether or not a scroll tag was specified.
+ if (scrollValue != CGUIControl::FOCUS)
+ static_cast<CGUIFadeLabelControl*>(control)->SetScrolling(scrollValue == CGUIControl::ALWAYS);
+ }
+ break;
+ case CGUIControl::GUICONTROL_RSS:
+ {
+ control = new CGUIRSSControl(
+ parentID, id, posX, posY, width, height,
+ labelInfo, textColor3, headlineColor, strRSSTags);
+ RssUrls::const_iterator iter = CRssManager::GetInstance().GetUrls().find(iUrlSet);
+ if (iter != CRssManager::GetInstance().GetUrls().end())
+ static_cast<CGUIRSSControl*>(control)->SetUrlSet(iUrlSet);
+ }
+ break;
+ case CGUIControl::GUICONTROL_BUTTON:
+ {
+ control = new CGUIButtonControl(
+ parentID, id, posX, posY, width, height,
+ textureFocus, textureNoFocus,
+ labelInfo, wrapMultiLine);
+
+ CGUIButtonControl* bcontrol = static_cast<CGUIButtonControl*>(control);
+ bcontrol->SetLabel(strLabel);
+ bcontrol->SetLabel2(strLabel2);
+ bcontrol->SetMinWidth(minWidth);
+ bcontrol->SetClickActions(clickActions);
+ bcontrol->SetFocusActions(focusActions);
+ bcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTROL_TOGGLEBUTTON:
+ {
+ control = new CGUIToggleButtonControl(
+ parentID, id, posX, posY, width, height,
+ textureFocus, textureNoFocus,
+ textureAltFocus, textureAltNoFocus,
+ labelInfo, wrapMultiLine);
+
+ CGUIToggleButtonControl* tcontrol = static_cast<CGUIToggleButtonControl*>(control);
+ tcontrol->SetLabel(strLabel);
+ tcontrol->SetAltLabel(altLabel);
+ tcontrol->SetMinWidth(minWidth);
+ tcontrol->SetClickActions(clickActions);
+ tcontrol->SetAltClickActions(altclickActions);
+ tcontrol->SetFocusActions(focusActions);
+ tcontrol->SetUnFocusActions(unfocusActions);
+ tcontrol->SetToggleSelect(toggleSelect);
+ }
+ break;
+ case CGUIControl::GUICONTROL_RADIO:
+ {
+ control = new CGUIRadioButtonControl(
+ parentID, id, posX, posY, width, height,
+ textureFocus, textureNoFocus,
+ labelInfo,
+ textureRadioOnFocus, textureRadioOnNoFocus, textureRadioOffFocus, textureRadioOffNoFocus, textureRadioOnDisabled, textureRadioOffDisabled);
+
+ CGUIRadioButtonControl* rcontrol = static_cast<CGUIRadioButtonControl*>(control);
+ rcontrol->SetLabel(strLabel);
+ rcontrol->SetLabel2(strLabel2);
+ rcontrol->SetRadioDimensions(radioPosX, radioPosY, radioWidth, radioHeight);
+ rcontrol->SetToggleSelect(toggleSelect);
+ rcontrol->SetClickActions(clickActions);
+ rcontrol->SetFocusActions(focusActions);
+ rcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTROL_SPIN:
+ {
+ control = new CGUISpinControl(
+ parentID, id, posX, posY, width, height,
+ textureUp, textureDown, textureUpFocus, textureDownFocus,
+ textureUpDisabled, textureDownDisabled,
+ labelInfo, iType);
+
+ CGUISpinControl* scontrol = static_cast<CGUISpinControl*>(control);
+ scontrol->SetReverse(bReverse);
+
+ if (iType == SPIN_CONTROL_TYPE_INT)
+ {
+ scontrol->SetRange(iMin, iMax);
+ }
+ else if (iType == SPIN_CONTROL_TYPE_PAGE)
+ {
+ scontrol->SetRange(iMin, iMax);
+ scontrol->SetShowRange(true);
+ scontrol->SetReverse(false);
+ scontrol->SetShowOnePage(showOnePage);
+ }
+ else if (iType == SPIN_CONTROL_TYPE_FLOAT)
+ {
+ scontrol->SetFloatRange(fMin, fMax);
+ scontrol->SetFloatInterval(fInterval);
+ }
+ }
+ break;
+ case CGUIControl::GUICONTROL_SLIDER:
+ {
+ control = new CGUISliderControl(
+ parentID, id, posX, posY, width, height,
+ textureBar, textureNib, textureNibFocus, SLIDER_CONTROL_TYPE_PERCENTAGE, orientation);
+
+ static_cast<CGUISliderControl*>(control)->SetInfo(singleInfo);
+ static_cast<CGUISliderControl*>(control)->SetAction(action);
+ }
+ break;
+ case CGUIControl::GUICONTROL_SETTINGS_SLIDER:
+ {
+ control = new CGUISettingsSliderControl(
+ parentID, id, posX, posY, width, height, sliderWidth, sliderHeight, textureFocus, textureNoFocus,
+ textureBar, textureNib, textureNibFocus, labelInfo, SLIDER_CONTROL_TYPE_PERCENTAGE);
+
+ static_cast<CGUISettingsSliderControl*>(control)->SetText(strLabel);
+ static_cast<CGUISettingsSliderControl*>(control)->SetInfo(singleInfo);
+ }
+ break;
+ case CGUIControl::GUICONTROL_SCROLLBAR:
+ {
+ control = new GUIScrollBarControl(
+ parentID, id, posX, posY, width, height,
+ textureBackground, textureBar, textureBarFocus, textureNib, textureNibFocus, orientation, showOnePage);
+ }
+ break;
+ case CGUIControl::GUICONTROL_PROGRESS:
+ {
+ control = new CGUIProgressControl(
+ parentID, id, posX, posY, width, height,
+ textureBackground, textureLeft, textureMid, textureRight,
+ textureOverlay, bReveal);
+
+ static_cast<CGUIProgressControl*>(control)->SetInfo(singleInfo, singleInfo2);
+ }
+ break;
+ case CGUIControl::GUICONTROL_RANGES:
+ {
+ control = new CGUIRangesControl(
+ parentID, id, posX, posY, width, height,
+ textureBackground, textureLeft, textureMid, textureRight,
+ textureOverlay, singleInfo);
+ }
+ break;
+ case CGUIControl::GUICONTROL_IMAGE:
+ {
+ // use a bordered texture if we have <bordersize> or <bordertexture> specified.
+ if (borderTexture.filename.empty() && borderStr.empty())
+ control = new CGUIImage(
+ parentID, id, posX, posY, width, height, texture);
+ else
+ control = new CGUIBorderedImage(
+ parentID, id, posX, posY, width, height, texture, borderTexture, borderSize);
+ CGUIImage* icontrol = static_cast<CGUIImage*>(control);
+ icontrol->SetInfo(textureFile);
+ icontrol->SetAspectRatio(aspect);
+ icontrol->SetCrossFade(fadeTime);
+ }
+ break;
+ case CGUIControl::GUICONTROL_MULTI_IMAGE:
+ {
+ control = new CGUIMultiImage(
+ parentID, id, posX, posY, width, height, texture, timePerImage, fadeTime, randomized, loop, timeToPauseAtEnd);
+ static_cast<CGUIMultiImage*>(control)->SetInfo(texturePath);
+ static_cast<CGUIMultiImage*>(control)->SetAspectRatio(aspect);
+ }
+ break;
+ case CGUIControl::GUICONTAINER_LIST:
+ {
+ CScroller scroller;
+ GetScroller(pControlNode, "scrolltime", scroller);
+
+ control = new CGUIListContainer(parentID, id, posX, posY, width, height, orientation, scroller, preloadItems);
+ CGUIListContainer* lcontrol = static_cast<CGUIListContainer*>(control);
+ lcontrol->LoadLayout(pControlNode);
+ lcontrol->LoadListProvider(pControlNode, defaultControl, defaultAlways);
+ lcontrol->SetType(viewType, viewLabel);
+ lcontrol->SetPageControl(pageControl);
+ lcontrol->SetRenderOffset(offset);
+ lcontrol->SetAutoScrolling(pControlNode);
+ lcontrol->SetClickActions(clickActions);
+ lcontrol->SetFocusActions(focusActions);
+ lcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTAINER_WRAPLIST:
+ {
+ CScroller scroller;
+ GetScroller(pControlNode, "scrolltime", scroller);
+
+ control = new CGUIWrappingListContainer(parentID, id, posX, posY, width, height, orientation, scroller, preloadItems, focusPosition);
+ CGUIWrappingListContainer* wcontrol = static_cast<CGUIWrappingListContainer*>(control);
+ wcontrol->LoadLayout(pControlNode);
+ wcontrol->LoadListProvider(pControlNode, defaultControl, defaultAlways);
+ wcontrol->SetType(viewType, viewLabel);
+ wcontrol->SetPageControl(pageControl);
+ wcontrol->SetRenderOffset(offset);
+ wcontrol->SetAutoScrolling(pControlNode);
+ wcontrol->SetClickActions(clickActions);
+ wcontrol->SetFocusActions(focusActions);
+ wcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTAINER_EPGGRID:
+ {
+ CGUIEPGGridContainer *epgGridContainer = new CGUIEPGGridContainer(parentID, id, posX, posY, width, height, orientation, scrollTime, preloadItems, timeBlocks, rulerUnit, textureProgressIndicator);
+ control = epgGridContainer;
+ epgGridContainer->LoadLayout(pControlNode);
+ epgGridContainer->SetRenderOffset(offset);
+ epgGridContainer->SetType(viewType, viewLabel);
+ epgGridContainer->SetPageControl(pageControl);
+ }
+ break;
+ case CGUIControl::GUICONTAINER_FIXEDLIST:
+ {
+ CScroller scroller;
+ GetScroller(pControlNode, "scrolltime", scroller);
+
+ control = new CGUIFixedListContainer(parentID, id, posX, posY, width, height, orientation, scroller, preloadItems, focusPosition, iMovementRange);
+ CGUIFixedListContainer* fcontrol = static_cast<CGUIFixedListContainer*>(control);
+ fcontrol->LoadLayout(pControlNode);
+ fcontrol->LoadListProvider(pControlNode, defaultControl, defaultAlways);
+ fcontrol->SetType(viewType, viewLabel);
+ fcontrol->SetPageControl(pageControl);
+ fcontrol->SetRenderOffset(offset);
+ fcontrol->SetAutoScrolling(pControlNode);
+ fcontrol->SetClickActions(clickActions);
+ fcontrol->SetFocusActions(focusActions);
+ fcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTAINER_PANEL:
+ {
+ CScroller scroller;
+ GetScroller(pControlNode, "scrolltime", scroller);
+
+ control = new CGUIPanelContainer(parentID, id, posX, posY, width, height, orientation, scroller, preloadItems);
+ CGUIPanelContainer* pcontrol = static_cast<CGUIPanelContainer*>(control);
+ pcontrol->LoadLayout(pControlNode);
+ pcontrol->LoadListProvider(pControlNode, defaultControl, defaultAlways);
+ pcontrol->SetType(viewType, viewLabel);
+ pcontrol->SetPageControl(pageControl);
+ pcontrol->SetRenderOffset(offset);
+ pcontrol->SetAutoScrolling(pControlNode);
+ pcontrol->SetClickActions(clickActions);
+ pcontrol->SetFocusActions(focusActions);
+ pcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ case CGUIControl::GUICONTROL_TEXTBOX:
+ {
+ if (!strMonoFont.empty())
+ {
+ labelInfoMono = labelInfo;
+ labelInfoMono.font = g_fontManager.GetFont(strMonoFont);
+ }
+ control = new CGUITextBox(
+ parentID, id, posX, posY, width, height,
+ labelInfo, scrollTime, strMonoFont.empty() ? nullptr : &labelInfoMono);
+
+ CGUITextBox* tcontrol = static_cast<CGUITextBox*>(control);
+
+ tcontrol->SetPageControl(pageControl);
+ if (infoLabels.size())
+ tcontrol->SetInfo(infoLabels[0]);
+ tcontrol->SetAutoScrolling(pControlNode);
+ tcontrol->SetMinHeight(minHeight);
+ }
+ break;
+ case CGUIControl::GUICONTROL_MOVER:
+ {
+ control = new CGUIMoverControl(parentID, id, posX, posY, width, height, textureFocus,
+ textureNoFocus, movingSpeedCfg);
+ }
+ break;
+ case CGUIControl::GUICONTROL_RESIZE:
+ {
+ control = new CGUIResizeControl(parentID, id, posX, posY, width, height, textureFocus,
+ textureNoFocus, movingSpeedCfg);
+ }
+ break;
+ case CGUIControl::GUICONTROL_SPINEX:
+ {
+ control = new CGUISpinControlEx(
+ parentID, id, posX, posY, width, height, spinWidth, spinHeight,
+ labelInfo, textureFocus, textureNoFocus, textureUp, textureDown, textureUpFocus, textureDownFocus,
+ textureUpDisabled, textureDownDisabled, labelInfo, iType);
+
+ CGUISpinControlEx* scontrol = static_cast<CGUISpinControlEx*>(control);
+ scontrol->SetSpinPosition(spinPosX);
+ scontrol->SetText(strLabel);
+ scontrol->SetReverse(bReverse);
+ }
+ break;
+ case CGUIControl::GUICONTROL_VISUALISATION:
+ control = new CGUIVisualisationControl(parentID, id, posX, posY, width, height);
+ break;
+ case CGUIControl::GUICONTROL_RENDERADDON:
+ control = new CGUIRenderingControl(parentID, id, posX, posY, width, height);
+ break;
+ case CGUIControl::GUICONTROL_GAMECONTROLLER:
+ control = new GAME::CGUIGameController(parentID, id, posX, posY, width, height);
+ break;
+ case CGUIControl::GUICONTROL_COLORBUTTON:
+ {
+ control = new CGUIColorButtonControl(parentID, id, posX, posY, width, height, textureFocus,
+ textureNoFocus, labelInfo, textureColorMask,
+ textureColorDisabledMask);
+
+ CGUIColorButtonControl* rcontrol = static_cast<CGUIColorButtonControl*>(control);
+ rcontrol->SetLabel(strLabel);
+ rcontrol->SetImageBoxColor(colorBox);
+ rcontrol->SetColorDimensions(colorPosX, colorPosY, colorWidth, colorHeight);
+ rcontrol->SetClickActions(clickActions);
+ rcontrol->SetFocusActions(focusActions);
+ rcontrol->SetUnFocusActions(unfocusActions);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // things that apply to all controls
+ if (control)
+ {
+ control->SetHitRect(hitRect, hitColor);
+ control->SetVisibleCondition(visibleCondition, allowHiddenFocus);
+ control->SetEnableCondition(enableCondition);
+ control->SetAnimations(animations);
+ control->SetColorDiffuse(colorDiffuse);
+ control->SetActions(actions);
+ control->SetPulseOnSelect(bPulse);
+ if (hasCamera)
+ control->SetCamera(camera);
+ control->SetStereoFactor(stereo);
+ }
+ return control;
+}
diff --git a/xbmc/guilib/GUIControlFactory.h b/xbmc/guilib/GUIControlFactory.h
new file mode 100644
index 0000000..9cdf56c
--- /dev/null
+++ b/xbmc/guilib/GUIControlFactory.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIControlFactory.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "utils/ColorUtils.h"
+#include "utils/MovingSpeed.h"
+
+#include <string>
+#include <vector>
+
+class CTextureInfo; // forward
+class CAspectRatio;
+class TiXmlNode;
+class CGUIAction;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+ class CGUIInfoLabel;
+}
+}
+}
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIControlFactory
+{
+public:
+ CGUIControlFactory(void);
+ virtual ~CGUIControlFactory(void);
+ CGUIControl* Create(int parentID, const CRect &rect, TiXmlElement* pControlNode, bool insideContainer = false);
+
+ /*! \brief translate from control name to control type
+ \param type name of the control
+ \return type of control
+ */
+ static CGUIControl::GUICONTROLTYPES TranslateControlType(const std::string &type);
+
+ /*! \brief translate from control type to control name
+ \param type type of the control
+ \return name of control
+ */
+ static std::string TranslateControlType(CGUIControl::GUICONTROLTYPES type);
+
+ static bool GetAspectRatio(const TiXmlNode* pRootNode, const char* strTag, CAspectRatio &aspectRatio);
+ static bool GetInfoTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image, KODI::GUILIB::GUIINFO::CGUIInfoLabel &info, int parentID);
+ static bool GetTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image);
+ static bool GetAlignment(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwAlignment);
+ static bool GetAlignmentY(const TiXmlNode* pRootNode, const char* strTag, uint32_t& dwAlignment);
+ static bool GetAnimations(TiXmlNode *control, const CRect &rect, int context, std::vector<CAnimation> &animation);
+
+ /*! \brief Create an info label from an XML element
+ Processes XML elements of the form
+ <xmltag fallback="fallback_value">info_value</xmltag>
+ where info_value may use $INFO[], $LOCALIZE[], $NUMBER[] etc.
+ If either the fallback_value or info_value are natural numbers they are interpreted
+ as ids for lookup in strings.po. The fallback attribute is optional.
+ \param element XML element to process
+ \param infoLabel Returned infoLabel
+ \param parentID The parent id
+ \return true if a valid info label was read, false otherwise
+ */
+
+ /*! \brief Parse a position string
+ Handles strings of the form
+ ### number of pixels
+ ###r number of pixels measured from the right
+ ###% percentage of parent size
+ \param pos the string to parse.
+ \param parentSize the size of the parent.
+ \sa GetPosition
+ */
+ static float ParsePosition(const char* pos, const float parentSize);
+
+ static bool GetInfoLabelFromElement(const TiXmlElement *element, KODI::GUILIB::GUIINFO::CGUIInfoLabel &infoLabel, int parentID);
+ static void GetInfoLabel(const TiXmlNode *pControlNode, const std::string &labelTag, KODI::GUILIB::GUIINFO::CGUIInfoLabel &infoLabel, int parentID);
+ static void GetInfoLabels(const TiXmlNode *pControlNode, const std::string &labelTag, std::vector<KODI::GUILIB::GUIINFO::CGUIInfoLabel> &infoLabels, int parentID);
+ static bool GetColor(const TiXmlNode* pRootNode, const char* strTag, UTILS::COLOR::Color& value);
+ static bool GetInfoColor(const TiXmlNode* pRootNode, const char* strTag, KODI::GUILIB::GUIINFO::CGUIInfoColor &value, int parentID);
+ static std::string FilterLabel(const std::string &label);
+ static bool GetConditionalVisibility(const TiXmlNode* control, std::string &condition);
+ static bool GetActions(const TiXmlNode* pRootNode, const char* strTag, CGUIAction& actions);
+ static void GetRectFromString(const std::string &string, CRect &rect);
+ static bool GetHitRect(const TiXmlNode* pRootNode, CRect &rect, const CRect &parentRect);
+ static bool GetScroller(const TiXmlNode *pControlNode, const std::string &scrollerTag, CScroller& scroller);
+private:
+ static std::string GetType(const TiXmlElement *pControlNode);
+ static bool GetMovingSpeedConfig(const TiXmlNode* pRootNode,
+ const char* strTag,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg);
+ static bool GetConditionalVisibility(const TiXmlNode* control, std::string &condition, std::string &allowHiddenFocus);
+ bool GetString(const TiXmlNode* pRootNode, const char* strTag, std::string& strString);
+ static bool GetFloatRange(const TiXmlNode* pRootNode, const char* strTag, float& iMinValue, float& iMaxValue, float& iIntervalValue);
+ static bool GetIntRange(const TiXmlNode* pRootNode, const char* strTag, int& iMinValue, int& iMaxValue, int& iIntervalValue);
+
+ /*! \brief Get the value of a position tag from XML
+ Handles both absolute and relative values.
+ \param node the <control> XML node.
+ \param tag the XML node to parse.
+ \param parentSize the size of the parent, for relative positioning.
+ \param value [out] the returned value.
+ \sa ParsePosition, GetDimension, GetDimensions.
+ */
+ static bool GetPosition(const TiXmlNode *node, const char* tag, const float parentSize, float& value);
+
+ /*! \brief grab a dimension out of the XML
+
+ Supports plain reading of a number (or constant) and, in addition allows "auto" as the value
+ for the dimension, whereby value is set to the max attribute (if it exists) and min is set the min
+ attribute (if it exists) or 1. Auto values are thus detected by min != 0.
+
+ \param node the <control> XML node to read
+ \param strTag tag within node to read
+ \param parentSize the size of the parent for relative sizing.
+ \param value value to set, or maximum value if using auto
+ \param min minimum value - set != 0 if auto is used.
+ \return true if we found and read the tag.
+ \sa GetPosition, GetDimensions, ParsePosition.
+ */
+ static bool GetDimension(const TiXmlNode *node, const char* strTag, const float parentSize, float &value, float &min);
+
+ /*! \brief Retrieve the dimensions for a control.
+
+ Handles positioning based on at least 2 of left/right/center/width.
+
+ \param node the <control> node describing the control.
+ \param leftTag the tag that holds the left field.
+ \param rightTag the tag that holds the right field.
+ \param centerLeftTag the tag that holds the center left field.
+ \param centerRightTag the tag that holds the center right field.
+ \param widthTag the tag holding the width.
+ \param parentSize the size of the parent, for relative sizing.
+ \param pos [out] the discovered position.
+ \param width [out] the discovered width.
+ \param min_width [out] the discovered minimum width.
+ \return true if we can successfully derive the position and size, false otherwise.
+ \sa GetDimension, GetPosition, ParsePosition.
+ */
+ static bool GetDimensions(const TiXmlNode *node, const char *leftTag, const char *rightTag, const char *centerLeftTag,
+ const char *centerRightTag, const char *widthTag, const float parentSize, float &left,
+ float &width, float &min_width);
+};
+
diff --git a/xbmc/guilib/GUIControlGroup.cpp b/xbmc/guilib/GUIControlGroup.cpp
new file mode 100644
index 0000000..f737f9f
--- /dev/null
+++ b/xbmc/guilib/GUIControlGroup.cpp
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControlGroup.h"
+
+#include "GUIMessage.h"
+
+#include <cassert>
+#include <utility>
+
+CGUIControlGroup::CGUIControlGroup()
+{
+ m_defaultControl = 0;
+ m_defaultAlways = false;
+ m_focusedControl = 0;
+ m_renderFocusedLast = false;
+ ControlType = GUICONTROL_GROUP;
+}
+
+CGUIControlGroup::CGUIControlGroup(int parentID, int controlID, float posX, float posY, float width, float height)
+: CGUIControlLookup(parentID, controlID, posX, posY, width, height)
+{
+ m_defaultControl = 0;
+ m_defaultAlways = false;
+ m_focusedControl = 0;
+ m_renderFocusedLast = false;
+ ControlType = GUICONTROL_GROUP;
+}
+
+CGUIControlGroup::CGUIControlGroup(const CGUIControlGroup &from)
+: CGUIControlLookup(from)
+{
+ m_defaultControl = from.m_defaultControl;
+ m_defaultAlways = from.m_defaultAlways;
+ m_renderFocusedLast = from.m_renderFocusedLast;
+
+ // run through and add our controls
+ for (auto *i : from.m_children)
+ AddControl(i->Clone());
+
+ // defaults
+ m_focusedControl = 0;
+ ControlType = GUICONTROL_GROUP;
+}
+
+CGUIControlGroup::~CGUIControlGroup(void)
+{
+ ClearAll();
+}
+
+void CGUIControlGroup::AllocResources()
+{
+ CGUIControl::AllocResources();
+ for (auto *control : m_children)
+ {
+ if (!control->IsDynamicallyAllocated())
+ control->AllocResources();
+ }
+}
+
+void CGUIControlGroup::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ for (auto *control : m_children)
+ {
+ control->FreeResources(immediately);
+ }
+}
+
+void CGUIControlGroup::DynamicResourceAlloc(bool bOnOff)
+{
+ for (auto *control : m_children)
+ {
+ control->DynamicResourceAlloc(bOnOff);
+ }
+}
+
+void CGUIControlGroup::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CPoint pos(GetPosition());
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(pos.x, pos.y);
+
+ CRect rect;
+ for (auto *control : m_children)
+ {
+ control->UpdateVisibility(nullptr);
+ unsigned int oldDirty = dirtyregions.size();
+ control->DoProcess(currentTime, dirtyregions);
+ if (control->IsVisible() || (oldDirty != dirtyregions.size())) // visible or dirty (was visible?)
+ rect.Union(control->GetRenderRegion());
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+ CGUIControl::Process(currentTime, dirtyregions);
+ m_renderRegion = rect;
+}
+
+void CGUIControlGroup::Render()
+{
+ CPoint pos(GetPosition());
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(pos.x, pos.y);
+ CGUIControl *focusedControl = NULL;
+ for (auto *control : m_children)
+ {
+ if (m_renderFocusedLast && control->HasFocus())
+ focusedControl = control;
+ else
+ control->DoRender();
+ }
+ if (focusedControl)
+ focusedControl->DoRender();
+ CGUIControl::Render();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+void CGUIControlGroup::RenderEx()
+{
+ for (auto *control : m_children)
+ control->RenderEx();
+ CGUIControl::RenderEx();
+}
+
+bool CGUIControlGroup::OnAction(const CAction &action)
+{
+ assert(false); // unimplemented
+ return false;
+}
+
+bool CGUIControlGroup::HasFocus() const
+{
+ for (auto *control : m_children)
+ {
+ if (control->HasFocus())
+ return true;
+ }
+ return false;
+}
+
+bool CGUIControlGroup::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage() )
+ {
+ case GUI_MSG_ITEM_SELECT:
+ {
+ if (message.GetControlId() == GetID())
+ {
+ m_focusedControl = message.GetParam1();
+ return true;
+ }
+ break;
+ }
+ case GUI_MSG_ITEM_SELECTED:
+ {
+ if (message.GetControlId() == GetID())
+ {
+ message.SetParam1(m_focusedControl);
+ return true;
+ }
+ break;
+ }
+ case GUI_MSG_FOCUSED:
+ { // a control has been focused
+ m_focusedControl = message.GetControlId();
+ SetFocus(true);
+ // tell our parent thatwe have focus
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ return true;
+ }
+ case GUI_MSG_SETFOCUS:
+ {
+ // first try our last focused control...
+ if (!m_defaultAlways && m_focusedControl)
+ {
+ CGUIControl *control = GetFirstFocusableControl(m_focusedControl);
+ if (control)
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), control->GetID());
+ return control->OnMessage(msg);
+ }
+ }
+ // ok, no previously focused control, try the default control first
+ if (m_defaultControl)
+ {
+ CGUIControl *control = GetFirstFocusableControl(m_defaultControl);
+ if (control)
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), control->GetID());
+ return control->OnMessage(msg);
+ }
+ }
+ // no success with the default control, so just find one to focus
+ CGUIControl *control = GetFirstFocusableControl(0);
+ if (control)
+ {
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetParentID(), control->GetID());
+ return control->OnMessage(msg);
+ }
+ // unsuccessful
+ return false;
+ break;
+ }
+ case GUI_MSG_LOSTFOCUS:
+ {
+ // set all subcontrols unfocused
+ for (auto *control : m_children)
+ control->SetFocus(false);
+ if (!GetControl(message.GetParam1()))
+ { // we don't have the new id, so unfocus
+ SetFocus(false);
+ if (m_parentControl)
+ m_parentControl->OnMessage(message);
+ }
+ return true;
+ }
+ break;
+ case GUI_MSG_PAGE_CHANGE:
+ case GUI_MSG_REFRESH_THUMBS:
+ case GUI_MSG_REFRESH_LIST:
+ case GUI_MSG_WINDOW_RESIZE:
+ { // send to all child controls (make sure the target is the control id)
+ for (auto *control : m_children)
+ {
+ CGUIMessage msg(message.GetMessage(), message.GetSenderId(), control->GetID(), message.GetParam1());
+ control->OnMessage(msg);
+ }
+ return true;
+ }
+ break;
+ case GUI_MSG_REFRESH_TIMER:
+ if (!IsVisible() || !IsVisibleFromSkin())
+ return true;
+ break;
+ }
+ bool handled(false);
+ //not intended for any specific control, send to all childs and our base handler.
+ if (message.GetControlId() == 0)
+ {
+ for (auto *control : m_children)
+ {
+ handled |= control->OnMessage(message);
+ }
+ return CGUIControl::OnMessage(message) || handled;
+ }
+ // if it's intended for us, then so be it
+ if (message.GetControlId() == GetID())
+ return CGUIControl::OnMessage(message);
+
+ return SendControlMessage(message);
+}
+
+bool CGUIControlGroup::SendControlMessage(CGUIMessage &message)
+{
+ IDCollector collector(m_idCollector);
+
+ CGUIControl *ctrl(GetControl(message.GetControlId(), collector.m_collector));
+ // see if a child matches, and send to the child control if so
+ if (ctrl && ctrl->OnMessage(message))
+ return true;
+
+ // Unhandled - send to all matching invisible controls as well
+ bool handled(false);
+ for (auto *control : *collector.m_collector)
+ if (control->OnMessage(message))
+ handled = true;
+
+ return handled;
+}
+
+bool CGUIControlGroup::CanFocus() const
+{
+ if (!CGUIControl::CanFocus()) return false;
+ // see if we have any children that can be focused
+ for (auto *control : m_children)
+ {
+ if (control->CanFocus())
+ return true;
+ }
+ return false;
+}
+
+void CGUIControlGroup::SetInitialVisibility()
+{
+ CGUIControl::SetInitialVisibility();
+ for (auto *control : m_children)
+ control->SetInitialVisibility();
+}
+
+void CGUIControlGroup::QueueAnimation(ANIMATION_TYPE animType)
+{
+ CGUIControl::QueueAnimation(animType);
+ // send window level animations to our children as well
+ if (animType == ANIM_TYPE_WINDOW_OPEN || animType == ANIM_TYPE_WINDOW_CLOSE)
+ {
+ for (auto *control : m_children)
+ control->QueueAnimation(animType);
+ }
+}
+
+void CGUIControlGroup::ResetAnimation(ANIMATION_TYPE animType)
+{
+ CGUIControl::ResetAnimation(animType);
+ // send window level animations to our children as well
+ if (animType == ANIM_TYPE_WINDOW_OPEN || animType == ANIM_TYPE_WINDOW_CLOSE)
+ {
+ for (auto *control : m_children)
+ control->ResetAnimation(animType);
+ }
+}
+
+void CGUIControlGroup::ResetAnimations()
+{ // resets all animations, regardless of condition
+ CGUIControl::ResetAnimations();
+ for (auto *control : m_children)
+ control->ResetAnimations();
+}
+
+bool CGUIControlGroup::IsAnimating(ANIMATION_TYPE animType)
+{
+ if (CGUIControl::IsAnimating(animType))
+ return true;
+
+ if (IsVisible())
+ {
+ for (auto *control : m_children)
+ {
+ if (control->IsAnimating(animType))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIControlGroup::HasAnimation(ANIMATION_TYPE animType)
+{
+ if (CGUIControl::HasAnimation(animType))
+ return true;
+
+ if (IsVisible())
+ {
+ for (auto *control : m_children)
+ {
+ if (control->HasAnimation(animType))
+ return true;
+ }
+ }
+ return false;
+}
+
+EVENT_RESULT CGUIControlGroup::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ // transform our position into child coordinates
+ CPoint childPoint(point);
+ m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
+
+ if (CGUIControl::CanFocus())
+ {
+ CPoint pos(GetPosition());
+ // run through our controls in reverse order (so that last rendered is checked first)
+ for (rControls i = m_children.rbegin(); i != m_children.rend(); ++i)
+ {
+ CGUIControl *child = *i;
+ EVENT_RESULT ret = child->SendMouseEvent(childPoint - pos, event);
+ if (ret)
+ { // we've handled the action, and/or have focused an item
+ return ret;
+ }
+ }
+ // none of our children want the event, but we may want it.
+ EVENT_RESULT ret;
+ if (HitTest(childPoint) && (ret = OnMouseEvent(childPoint, event)))
+ return ret;
+ }
+ m_focusedControl = 0;
+ return EVENT_RESULT_UNHANDLED;
+}
+
+void CGUIControlGroup::UnfocusFromPoint(const CPoint &point)
+{
+ CPoint controlCoords(point);
+ m_transform.InverseTransformPosition(controlCoords.x, controlCoords.y);
+ controlCoords -= GetPosition();
+ for (auto *child : m_children)
+ {
+ child->UnfocusFromPoint(controlCoords);
+ }
+ CGUIControl::UnfocusFromPoint(point);
+}
+
+int CGUIControlGroup::GetFocusedControlID() const
+{
+ if (m_focusedControl) return m_focusedControl;
+ CGUIControl *control = GetFocusedControl();
+ if (control) return control->GetID();
+ return 0;
+}
+
+CGUIControl *CGUIControlGroup::GetFocusedControl() const
+{
+ // try lookup first
+ if (m_focusedControl)
+ {
+ // we may have multiple controls with same id - we pick first that has focus
+ std::pair<LookupMap::const_iterator, LookupMap::const_iterator> range = GetLookupControls(m_focusedControl);
+
+ for (LookupMap::const_iterator i = range.first; i != range.second; ++i)
+ {
+ if (i->second->HasFocus())
+ return i->second;
+ }
+ }
+
+ // if lookup didn't find focused control, iterate m_children to find it
+ for (auto *control : m_children)
+ {
+ // Avoid calling HasFocus() on control group as it will (possibly) recursively
+ // traverse entire group tree just to check if there is focused control.
+ // We are recursively traversing it here so no point in doing it twice.
+ CGUIControlGroup *groupControl(dynamic_cast<CGUIControlGroup*>(control));
+ if (groupControl)
+ {
+ CGUIControl* focusedControl = groupControl->GetFocusedControl();
+ if (focusedControl)
+ return focusedControl;
+ }
+ else if (control->HasFocus())
+ return control;
+ }
+ return NULL;
+}
+
+// in the case of id == 0, we don't match id
+CGUIControl *CGUIControlGroup::GetFirstFocusableControl(int id)
+{
+ if (!CanFocus()) return NULL;
+ if (id && id == GetID()) return this; // we're focusable and they want us
+ for (auto *pControl : m_children)
+ {
+ CGUIControlGroup *group(dynamic_cast<CGUIControlGroup*>(pControl));
+ if (group)
+ {
+ CGUIControl *control = group->GetFirstFocusableControl(id);
+ if (control) return control;
+ }
+ if ((!id || pControl->GetID() == id) && pControl->CanFocus())
+ return pControl;
+ }
+ return NULL;
+}
+
+void CGUIControlGroup::AddControl(CGUIControl *control, int position /* = -1*/)
+{
+ if (!control) return;
+ if (position < 0 || position > (int)m_children.size())
+ position = (int)m_children.size();
+ m_children.insert(m_children.begin() + position, control);
+ control->SetParentControl(this);
+ control->SetControlStats(m_controlStats);
+ control->SetPushUpdates(m_pushedUpdates);
+ AddLookup(control);
+ SetInvalid();
+}
+
+bool CGUIControlGroup::InsertControl(CGUIControl *control, const CGUIControl *insertPoint)
+{
+ // find our position
+ for (unsigned int i = 0; i < m_children.size(); i++)
+ {
+ CGUIControl *child = m_children[i];
+ CGUIControlGroup *group(dynamic_cast<CGUIControlGroup*>(child));
+ if (group && group->InsertControl(control, insertPoint))
+ return true;
+ else if (child == insertPoint)
+ {
+ AddControl(control, i);
+ return true;
+ }
+ }
+ return false;
+}
+
+void CGUIControlGroup::SaveStates(std::vector<CControlState> &states)
+{
+ // save our state, and that of our children
+ states.emplace_back(GetID(), m_focusedControl);
+ for (auto *control : m_children)
+ control->SaveStates(states);
+}
+
+// Note: This routine doesn't delete the control. It just removes it from the control list
+bool CGUIControlGroup::RemoveControl(const CGUIControl *control)
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *child = *it;
+ CGUIControlGroup *group(dynamic_cast<CGUIControlGroup*>(child));
+ if (group && group->RemoveControl(control))
+ return true;
+ if (control == child)
+ {
+ m_children.erase(it);
+ RemoveLookup(child);
+ SetInvalid();
+ return true;
+ }
+ }
+ return false;
+}
+
+void CGUIControlGroup::ClearAll()
+{
+ // first remove from the lookup table
+ RemoveLookup();
+
+ // and delete all our children
+ for (auto *control : m_children)
+ {
+ delete control;
+ }
+ m_focusedControl = 0;
+ m_children.clear();
+ ClearLookup();
+ SetInvalid();
+}
+
+#ifdef _DEBUG
+void CGUIControlGroup::DumpTextureUse()
+{
+ for (auto *control : m_children)
+ control->DumpTextureUse();
+}
+#endif
diff --git a/xbmc/guilib/GUIControlGroup.dox b/xbmc/guilib/GUIControlGroup.dox
new file mode 100644
index 0000000..66ac520
--- /dev/null
+++ b/xbmc/guilib/GUIControlGroup.dox
@@ -0,0 +1,108 @@
+/*!
+
+\page Group_Control Group Control
+\brief **Used to group controls together.**
+
+\tableofcontents
+
+The group control is one of the most important controls. It allows you to group
+controls together, applying attributes to all of them at once. It also remembers
+the last navigated button in the group, so you can set the <b>`<onup>`</b> of a control
+to a group of controls to have it always go back to the one you were at before.
+It also allows you to position controls more accurately relative to each other,
+as any controls within a group take their coordinates from the group's top left
+corner (or from elsewhere if you use the "r" attribute). You can have as many
+groups as you like within the skin, and groups within groups are handled with
+no issues.
+
+
+--------------------------------------------------------------------------------
+\section Group_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="group" id="17">
+ <description>My first group control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>30</height>
+ <defaultcontrol>2</defaultcontrol>
+ <visible>true</visible>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ ... more controls go here ...
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Group_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|------------------:|:--------------------------------------------------------------|
+| defaultcontrol | Specifies the default control that will be focused within the group when the group receives focus. Note that the group remembers it's previously focused item and will return to it.
+
+
+--------------------------------------------------------------------------------
+\section Group_Control_sect3 Notes on positioning of controls within groups
+
+All controls within a group take their positions relative to the group's placement.
+Thus, the group always requires its <b>`<posx>`</b>, <b>`<posy>`</b>,
+<b>`<width>`</b>, and <b>`<height>`</b> attributes to be defined.
+As this can be a pain to remember, anything that you don't specify will be
+inherited from it's parent group (or the main window).
+
+By way of example, consider the first group within a PAL full screen window
+(720x576), and suppose we have
+
+~~~~~~~~~~~~~
+<control type="group" id="15">
+ <posx>30</posx>
+ <posy>70</posy>
+ <width>400</width>
+ ... more controls go here ...
+</control>
+~~~~~~~~~~~~~
+
+so that the <b>`<height>`</b> hasn't been defined. Then Kodi will
+automatically set the <b>`<height>`</b> equal to 506 by inheriting this
+from the window's height of 576, less the <b>`<posy>`</b> amount.
+
+You can align controls within a group to the right edge of the group, by
+using the <b>"r"</b> modifier to the <b>`<posx>`</b> and <b>`<posy>`</b> fields
+
+~~~~~~~~~~~~~
+<control type="group" id="20">
+ <control type="button" id=2>
+ <posx>180r</posx>
+ <width>180</width>
+ </control>
+ <control type="button" id=3>
+ <posx>180r</posx>
+ <width>180</width>
+ </control>
+ <control type="button" id=4>
+ <posx>180r</posx>
+ <width>180</width>
+ </control>
+</control>
+~~~~~~~~~~~~~
+
+All the buttons have width 180, and are aligned 180 pixels from the right edge of the group they're within.
+
+
+--------------------------------------------------------------------------------
+\section Group_Control_sect4 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIControlGroup.h b/xbmc/guilib/GUIControlGroup.h
new file mode 100644
index 0000000..5f4a761
--- /dev/null
+++ b/xbmc/guilib/GUIControlGroup.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIControlGroup.h
+\brief
+*/
+
+#include "GUIControlLookup.h"
+
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief group of controls, useful for remembering last control + animating/hiding together
+ */
+class CGUIControlGroup : public CGUIControlLookup
+{
+public:
+ CGUIControlGroup();
+ CGUIControlGroup(int parentID, int controlID, float posX, float posY, float width, float height);
+ explicit CGUIControlGroup(const CGUIControlGroup& from);
+ ~CGUIControlGroup(void) override;
+ CGUIControlGroup* Clone() const override { return new CGUIControlGroup(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void RenderEx() override;
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ virtual bool SendControlMessage(CGUIMessage& message);
+ bool HasFocus() const override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ bool CanFocus() const override;
+
+ EVENT_RESULT SendMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ void UnfocusFromPoint(const CPoint &point) override;
+
+ void SetInitialVisibility() override;
+
+ bool IsAnimating(ANIMATION_TYPE anim) override;
+ bool HasAnimation(ANIMATION_TYPE anim) override;
+ void QueueAnimation(ANIMATION_TYPE anim) override;
+ void ResetAnimation(ANIMATION_TYPE anim) override;
+ void ResetAnimations() override;
+
+ int GetFocusedControlID() const;
+ CGUIControl *GetFocusedControl() const;
+ virtual CGUIControl *GetFirstFocusableControl(int id);
+
+ virtual void AddControl(CGUIControl *control, int position = -1);
+ bool InsertControl(CGUIControl *control, const CGUIControl *insertPoint);
+ virtual bool RemoveControl(const CGUIControl *control);
+ virtual void ClearAll();
+ void SetDefaultControl(int id, bool always)
+ {
+ m_defaultControl = id;
+ m_defaultAlways = always;
+ }
+ void SetRenderFocusedLast(bool renderLast) { m_renderFocusedLast = renderLast; }
+
+ void SaveStates(std::vector<CControlState> &states) override;
+
+ bool IsGroup() const override { return true; }
+
+#ifdef _DEBUG
+ void DumpTextureUse() override;
+#endif
+protected:
+ // sub controls
+ std::vector<CGUIControl *> m_children;
+
+ typedef std::vector<CGUIControl *>::iterator iControls;
+ typedef std::vector<CGUIControl *>::const_iterator ciControls;
+ typedef std::vector<CGUIControl *>::reverse_iterator rControls;
+ typedef std::vector<CGUIControl *>::const_reverse_iterator crControls;
+
+ int m_defaultControl;
+ bool m_defaultAlways;
+ int m_focusedControl;
+ bool m_renderFocusedLast;
+private:
+ typedef std::vector< std::vector<CGUIControl *> * > COLLECTORTYPE;
+
+ struct IDCollectorList
+ {
+ ~IDCollectorList()
+ {
+ for (auto item : m_items)
+ delete item;
+ }
+
+ std::vector<CGUIControl *> *Get() {
+ if (++m_stackDepth > m_items.size())
+ m_items.push_back(new std::vector<CGUIControl *>());
+ return m_items[m_stackDepth - 1];
+ }
+
+ void Release() { --m_stackDepth; }
+
+ COLLECTORTYPE m_items;
+ size_t m_stackDepth = 0;
+ }m_idCollector;
+
+ struct IDCollector
+ {
+ explicit IDCollector(IDCollectorList& list) : m_list(list), m_collector(list.Get()) {}
+
+ ~IDCollector() { m_list.Release(); }
+
+ IDCollectorList &m_list;
+ std::vector<CGUIControl *> *m_collector;
+ };
+};
+
diff --git a/xbmc/guilib/GUIControlGroupList.cpp b/xbmc/guilib/GUIControlGroupList.cpp
new file mode 100644
index 0000000..5252d67
--- /dev/null
+++ b/xbmc/guilib/GUIControlGroupList.cpp
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControlGroupList.h"
+
+#include "GUIAction.h"
+#include "GUIControlProfiler.h"
+#include "GUIFont.h" // for XBFONT_* definitions
+#include "GUIMessage.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "utils/StringUtils.h"
+
+CGUIControlGroupList::CGUIControlGroupList(int parentID, int controlID, float posX, float posY, float width, float height, float itemGap, int pageControl, ORIENTATION orientation, bool useControlPositions, uint32_t alignment, const CScroller& scroller)
+: CGUIControlGroup(parentID, controlID, posX, posY, width, height)
+, m_scroller(scroller)
+{
+ m_itemGap = itemGap;
+ m_pageControl = pageControl;
+ m_focusedPosition = 0;
+ m_totalSize = 0;
+ m_orientation = orientation;
+ m_alignment = alignment;
+ m_lastScrollerValue = -1;
+ m_useControlPositions = useControlPositions;
+ ControlType = GUICONTROL_GROUPLIST;
+ m_minSize = 0;
+}
+
+CGUIControlGroupList::~CGUIControlGroupList(void) = default;
+
+void CGUIControlGroupList::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_scroller.Update(currentTime))
+ MarkDirtyRegion();
+
+ // first we update visibility of all our items, to ensure our size and
+ // alignment computations are correct.
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ GUIPROFILER_VISIBILITY_BEGIN(control);
+ control->UpdateVisibility(nullptr);
+ GUIPROFILER_VISIBILITY_END(control);
+ }
+
+ // visibility status of some of the list items may have changed. Thus, the group list size
+ // may now be different and the scroller needs to be updated
+ int previousTotalSize = m_totalSize;
+ ValidateOffset(); // m_totalSize is updated here
+ bool sizeChanged = previousTotalSize != m_totalSize;
+
+ if (m_pageControl && (m_lastScrollerValue != m_scroller.GetValue() || sizeChanged))
+ {
+ CGUIMessage message(GUI_MSG_LABEL_RESET, GetParentID(), m_pageControl, (int)Size(), (int)m_totalSize);
+ SendWindowMessage(message);
+ CGUIMessage message2(GUI_MSG_ITEM_SELECT, GetParentID(), m_pageControl, (int)m_scroller.GetValue());
+ SendWindowMessage(message2);
+ m_lastScrollerValue = static_cast<int>(m_scroller.GetValue());
+ }
+ // we run through the controls, rendering as we go
+ int index = 0;
+ float pos = GetAlignOffset();
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ // note we render all controls, even if they're offscreen, as then they'll be updated
+ // with respect to animations
+ CGUIControl *control = *it;
+ if (m_orientation == VERTICAL)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX, m_posY + pos - m_scroller.GetValue());
+ else
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
+ control->DoProcess(currentTime, dirtyregions);
+
+ if (control->IsVisible())
+ {
+ if (IsControlOnScreen(pos, control))
+ {
+ if (control->HasFocus())
+ m_focusedPosition = index;
+ index++;
+ }
+
+ pos += Size(control) + m_itemGap;
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+ }
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIControlGroupList::Render()
+{
+ // we run through the controls, rendering as we go
+ bool render(CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height));
+ float pos = GetAlignOffset();
+ float focusedPos = 0;
+ CGUIControl *focusedControl = NULL;
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ // note we render all controls, even if they're offscreen, as then they'll be updated
+ // with respect to animations
+ CGUIControl *control = *it;
+ if (m_renderFocusedLast && control->HasFocus())
+ {
+ focusedControl = control;
+ focusedPos = pos;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX, m_posY + pos - m_scroller.GetValue());
+ else
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
+ control->DoRender();
+ }
+ if (control->IsVisible())
+ pos += Size(control) + m_itemGap;
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+ }
+ if (focusedControl)
+ {
+ if (m_orientation == VERTICAL)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX, m_posY + focusedPos - m_scroller.GetValue());
+ else
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX + focusedPos - m_scroller.GetValue(), m_posY);
+ focusedControl->DoRender();
+ }
+ if (render) CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ CGUIControl::Render();
+}
+
+bool CGUIControlGroupList::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage() )
+ {
+ case GUI_MSG_FOCUSED:
+ { // a control has been focused
+ // scroll if we need to and update our page control
+ ValidateOffset();
+ float offset = 0;
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ if (!control->IsVisible())
+ continue;
+ if (control->GetControl(message.GetControlId()))
+ {
+ // find out whether this is the first or last control
+ if (IsFirstFocusableControl(control))
+ ScrollTo(0);
+ else if (IsLastFocusableControl(control))
+ ScrollTo(m_totalSize - Size());
+ else if (offset < m_scroller.GetValue())
+ ScrollTo(offset);
+ else if (offset + Size(control) > m_scroller.GetValue() + Size())
+ ScrollTo(offset + Size(control) - Size());
+ break;
+ }
+ offset += Size(control) + m_itemGap;
+ }
+ }
+ break;
+ case GUI_MSG_SETFOCUS:
+ {
+ // we've been asked to focus. We focus the last control if it's on this page,
+ // else we'll focus the first focusable control from our offset (after verifying it)
+ ValidateOffset();
+ // now check the focusControl's offset
+ float offset = 0;
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ if (!control->IsVisible())
+ continue;
+ if (control->GetControl(m_focusedControl))
+ {
+ if (IsControlOnScreen(offset, control))
+ return CGUIControlGroup::OnMessage(message);
+ break;
+ }
+ offset += Size(control) + m_itemGap;
+ }
+ // find the first control on this page
+ offset = 0;
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ if (!control->IsVisible())
+ continue;
+ if (control->CanFocus() && IsControlOnScreen(offset, control))
+ {
+ m_focusedControl = control->GetID();
+ break;
+ }
+ offset += Size(control) + m_itemGap;
+ }
+ }
+ break;
+ case GUI_MSG_PAGE_CHANGE:
+ {
+ if (message.GetSenderId() == m_pageControl)
+ { // it's from our page control
+ ScrollTo((float)message.GetParam1());
+ return true;
+ }
+ }
+ break;
+ }
+ return CGUIControlGroup::OnMessage(message);
+}
+
+void CGUIControlGroupList::ValidateOffset()
+{
+ // calculate item gap. this needs to be done
+ // before fetching the total size
+ CalculateItemGap();
+ // calculate how many items we have on this page
+ m_totalSize = GetTotalSize();
+ // check our m_offset range
+ if (m_scroller.GetValue() > m_totalSize - Size())
+ m_scroller.SetValue(m_totalSize - Size());
+ if (m_scroller.GetValue() < 0) m_scroller.SetValue(0);
+}
+
+void CGUIControlGroupList::AddControl(CGUIControl *control, int position /*= -1*/)
+{
+ // NOTE: We override control navigation here, but we don't override the <onleft> etc. builtins
+ // if specified.
+ if (position < 0 || position > (int)m_children.size()) // add at the end
+ position = (int)m_children.size();
+
+ if (control)
+ { // set the navigation of items so that they form a list
+ CGUIAction beforeAction = GetAction((m_orientation == VERTICAL) ? ACTION_MOVE_UP : ACTION_MOVE_LEFT);
+ CGUIAction afterAction = GetAction((m_orientation == VERTICAL) ? ACTION_MOVE_DOWN : ACTION_MOVE_RIGHT);
+ if (m_children.size())
+ {
+ // we're inserting at the given position, so grab the items above and below and alter
+ // their navigation accordingly
+ CGUIControl *before = NULL;
+ CGUIControl *after = NULL;
+ if (position == 0)
+ { // inserting at the beginning
+ after = m_children[0];
+ if (!afterAction.HasActionsMeetingCondition() || afterAction.GetNavigation() == GetID()) // we're wrapping around bottom->top, so we have to update the last item
+ before = m_children[m_children.size() - 1];
+ if (!beforeAction.HasActionsMeetingCondition() || beforeAction.GetNavigation() == GetID()) // we're wrapping around top->bottom
+ beforeAction = CGUIAction(m_children[m_children.size() - 1]->GetID());
+ afterAction = CGUIAction(after->GetID());
+ }
+ else if (position == (int)m_children.size())
+ { // inserting at the end
+ before = m_children[m_children.size() - 1];
+ if (!beforeAction.HasActionsMeetingCondition() || beforeAction.GetNavigation() == GetID()) // we're wrapping around top->bottom, so we have to update the first item
+ after = m_children[0];
+ if (!afterAction.HasActionsMeetingCondition() || afterAction.GetNavigation() == GetID()) // we're wrapping around bottom->top
+ afterAction = CGUIAction(m_children[0]->GetID());
+ beforeAction = CGUIAction(before->GetID());
+ }
+ else
+ { // inserting somewhere in the middle
+ before = m_children[position - 1];
+ after = m_children[position];
+ beforeAction = CGUIAction(before->GetID());
+ afterAction = CGUIAction(after->GetID());
+ }
+ if (m_orientation == VERTICAL)
+ {
+ if (before) // update the DOWN action to point to us
+ before->SetAction(ACTION_MOVE_DOWN, CGUIAction(control->GetID()));
+ if (after) // update the UP action to point to us
+ after->SetAction(ACTION_MOVE_UP, CGUIAction(control->GetID()));
+ }
+ else
+ {
+ if (before) // update the RIGHT action to point to us
+ before->SetAction(ACTION_MOVE_RIGHT, CGUIAction(control->GetID()));
+ if (after) // update the LEFT action to point to us
+ after->SetAction(ACTION_MOVE_LEFT, CGUIAction(control->GetID()));
+ }
+ }
+ // now the control's nav
+ // set navigation path on orientation axis
+ // and try to apply other nav actions from grouplist
+ // don't override them if child have already defined actions
+ if (m_orientation == VERTICAL)
+ {
+ control->SetAction(ACTION_MOVE_UP, beforeAction);
+ control->SetAction(ACTION_MOVE_DOWN, afterAction);
+ control->SetAction(ACTION_MOVE_LEFT, GetAction(ACTION_MOVE_LEFT), false);
+ control->SetAction(ACTION_MOVE_RIGHT, GetAction(ACTION_MOVE_RIGHT), false);
+ }
+ else
+ {
+ control->SetAction(ACTION_MOVE_LEFT, beforeAction);
+ control->SetAction(ACTION_MOVE_RIGHT, afterAction);
+ control->SetAction(ACTION_MOVE_UP, GetAction(ACTION_MOVE_UP), false);
+ control->SetAction(ACTION_MOVE_DOWN, GetAction(ACTION_MOVE_DOWN), false);
+ }
+ control->SetAction(ACTION_NAV_BACK, GetAction(ACTION_NAV_BACK), false);
+
+ if (!m_useControlPositions)
+ control->SetPosition(0,0);
+ CGUIControlGroup::AddControl(control, position);
+ m_totalSize = GetTotalSize();
+ }
+}
+
+void CGUIControlGroupList::ClearAll()
+{
+ m_totalSize = 0;
+ CGUIControlGroup::ClearAll();
+ m_scroller.SetValue(0);
+}
+
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+float CGUIControlGroupList::GetWidth() const
+{
+ if (m_orientation == HORIZONTAL)
+ return CLAMP(m_totalSize, m_minSize, m_width);
+ return CGUIControlGroup::GetWidth();
+}
+
+float CGUIControlGroupList::GetHeight() const
+{
+ if (m_orientation == VERTICAL)
+ return CLAMP(m_totalSize, m_minSize, m_height);
+ return CGUIControlGroup::GetHeight();
+}
+
+void CGUIControlGroupList::SetMinSize(float minWidth, float minHeight)
+{
+ if (m_orientation == VERTICAL)
+ m_minSize = minHeight;
+ else
+ m_minSize = minWidth;
+}
+
+float CGUIControlGroupList::Size(const CGUIControl *control) const
+{
+ return (m_orientation == VERTICAL) ? control->GetYPosition() + control->GetHeight() : control->GetXPosition() + control->GetWidth();
+}
+
+inline float CGUIControlGroupList::Size() const
+{
+ return (m_orientation == VERTICAL) ? m_height : m_width;
+}
+
+void CGUIControlGroupList::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ // Force a message to the scrollbar
+ m_lastScrollerValue = -1;
+}
+
+void CGUIControlGroupList::ScrollTo(float offset)
+{
+ m_scroller.ScrollTo(offset);
+ if (m_scroller.IsScrolling())
+ SetInvalid();
+ MarkDirtyRegion();
+}
+
+EVENT_RESULT CGUIControlGroupList::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ // transform our position into child coordinates
+ CPoint childPoint(point);
+ m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
+ if (CGUIControl::CanFocus())
+ {
+ float pos = 0;
+ float alignOffset = GetAlignOffset();
+ for (ciControls i = m_children.begin(); i != m_children.end(); ++i)
+ {
+ CGUIControl *child = *i;
+ if (child->IsVisible())
+ {
+ if (IsControlOnScreen(pos, child))
+ { // we're on screen
+ float offsetX = m_orientation == VERTICAL ? m_posX : m_posX + alignOffset + pos - m_scroller.GetValue();
+ float offsetY = m_orientation == VERTICAL ? m_posY + alignOffset + pos - m_scroller.GetValue() : m_posY;
+ EVENT_RESULT ret = child->SendMouseEvent(childPoint - CPoint(offsetX, offsetY), event);
+ if (ret)
+ { // we've handled the action, and/or have focused an item
+ return ret;
+ }
+ }
+ pos += Size(child) + m_itemGap;
+ }
+ }
+ // none of our children want the event, but we may want it.
+ EVENT_RESULT ret;
+ if (HitTest(childPoint) && (ret = OnMouseEvent(childPoint, event)))
+ return ret;
+ }
+ m_focusedControl = 0;
+ return EVENT_RESULT_UNHANDLED;
+}
+
+void CGUIControlGroupList::UnfocusFromPoint(const CPoint &point)
+{
+ float pos = 0;
+ CPoint controlCoords(point);
+ m_transform.InverseTransformPosition(controlCoords.x, controlCoords.y);
+ float alignOffset = GetAlignOffset();
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *child = *it;
+ if (child->IsVisible())
+ {
+ if (IsControlOnScreen(pos, child))
+ { // we're on screen
+ CPoint offset = (m_orientation == VERTICAL) ? CPoint(m_posX, m_posY + alignOffset + pos - m_scroller.GetValue()) : CPoint(m_posX + alignOffset + pos - m_scroller.GetValue(), m_posY);
+ child->UnfocusFromPoint(controlCoords - offset);
+ }
+ pos += Size(child) + m_itemGap;
+ }
+ }
+ CGUIControl::UnfocusFromPoint(point);
+}
+
+bool CGUIControlGroupList::GetCondition(int condition, int data) const
+{
+ switch (condition)
+ {
+ case CONTAINER_HAS_NEXT:
+ return (m_totalSize >= Size() && m_scroller.GetValue() < m_totalSize - Size());
+ case CONTAINER_HAS_PREVIOUS:
+ return (m_scroller.GetValue() > 0);
+ case CONTAINER_POSITION:
+ return (m_focusedPosition == data);
+ default:
+ return false;
+ }
+}
+
+std::string CGUIControlGroupList::GetLabel(int info) const
+{
+ switch (info)
+ {
+ case CONTAINER_CURRENT_ITEM:
+ return std::to_string(GetSelectedItem());
+ case CONTAINER_NUM_ITEMS:
+ return std::to_string(GetNumItems());
+ case CONTAINER_POSITION:
+ return std::to_string(m_focusedPosition);
+ default:
+ break;
+ }
+ return "";
+}
+
+int CGUIControlGroupList::GetNumItems() const
+{
+ return std::count_if(m_children.begin(), m_children.end(), [&](const CGUIControl *child) {
+ return (child->IsVisible() && child->CanFocus());
+ });
+}
+
+int CGUIControlGroupList::GetSelectedItem() const
+{
+ int index = 1;
+ for (const auto& child : m_children)
+ {
+ if (child->IsVisible() && child->CanFocus())
+ {
+ if (child->HasFocus())
+ return index;
+ index++;
+ }
+ }
+ return -1;
+}
+
+bool CGUIControlGroupList::IsControlOnScreen(float pos, const CGUIControl *control) const
+{
+ return (pos >= m_scroller.GetValue() && pos + Size(control) <= m_scroller.GetValue() + Size());
+}
+
+bool CGUIControlGroupList::IsFirstFocusableControl(const CGUIControl *control) const
+{
+ for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *child = *it;
+ if (child->IsVisible() && child->CanFocus())
+ { // found first focusable
+ return child == control;
+ }
+ }
+ return false;
+}
+
+bool CGUIControlGroupList::IsLastFocusableControl(const CGUIControl *control) const
+{
+ for (crControls it = m_children.rbegin(); it != m_children.rend(); ++it)
+ {
+ CGUIControl *child = *it;
+ if (child->IsVisible() && child->CanFocus())
+ { // found first focusable
+ return child == control;
+ }
+ }
+ return false;
+}
+
+void CGUIControlGroupList::CalculateItemGap()
+{
+ if (m_alignment & XBFONT_JUSTIFIED)
+ {
+ int itemsCount = 0;
+ float itemsSize = 0;
+ for (const auto& child : m_children)
+ {
+ if (child->IsVisible())
+ {
+ itemsSize += Size(child);
+ itemsCount++;
+ }
+ }
+
+ if (itemsCount > 0)
+ m_itemGap = (Size() - itemsSize) / itemsCount;
+ }
+}
+
+float CGUIControlGroupList::GetAlignOffset() const
+{
+ if (m_totalSize < Size())
+ {
+ if (m_alignment & XBFONT_RIGHT)
+ return Size() - m_totalSize;
+ if (m_alignment & (XBFONT_CENTER_X | XBFONT_JUSTIFIED))
+ return (Size() - m_totalSize)*0.5f;
+ }
+ return 0.0f;
+}
+
+EVENT_RESULT CGUIControlGroupList::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_WHEEL_UP || event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ // find the current control and move to the next or previous
+ float offset = 0;
+ for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ if (!control->IsVisible()) continue;
+ float nextOffset = offset + Size(control) + m_itemGap;
+ if (event.m_id == ACTION_MOUSE_WHEEL_DOWN && nextOffset > m_scroller.GetValue() && m_scroller.GetValue() < m_totalSize - Size()) // past our current offset
+ {
+ ScrollTo(nextOffset);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP && nextOffset >= m_scroller.GetValue() && m_scroller.GetValue() > 0) // at least at our current offset
+ {
+ ScrollTo(offset);
+ return EVENT_RESULT_HANDLED;
+ }
+ offset = nextOffset;
+ }
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ { // do the drag and validate our offset (corrects for end of scroll)
+ m_scroller.SetValue(CLAMP(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY), 0, m_totalSize - Size()));
+ SetInvalid();
+ return EVENT_RESULT_HANDLED;
+ }
+
+ return EVENT_RESULT_UNHANDLED;
+}
+
+float CGUIControlGroupList::GetTotalSize() const
+{
+ float totalSize = 0;
+ for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ if (!control->IsVisible()) continue;
+ totalSize += Size(control) + m_itemGap;
+ }
+ if (totalSize > 0) totalSize -= m_itemGap;
+ return totalSize;
+}
diff --git a/xbmc/guilib/GUIControlGroupList.h b/xbmc/guilib/GUIControlGroupList.h
new file mode 100644
index 0000000..e50cf86
--- /dev/null
+++ b/xbmc/guilib/GUIControlGroupList.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIControlGroupList.h
+\brief
+*/
+
+#include "GUIControlGroup.h"
+
+/*!
+ \ingroup controls
+ \brief list of controls that is scrollable
+ */
+class CGUIControlGroupList : public CGUIControlGroup
+{
+public:
+ CGUIControlGroupList(int parentID, int controlID, float posX, float posY, float width, float height, float itemGap, int pageControl, ORIENTATION orientation, bool useControlPositions, uint32_t alignment, const CScroller& scroller);
+ ~CGUIControlGroupList(void) override;
+ CGUIControlGroupList* Clone() const override { return new CGUIControlGroupList(*this); }
+
+ float GetWidth() const override;
+ float GetHeight() const override;
+ virtual float Size() const;
+ void SetInvalid() override;
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnMessage(CGUIMessage& message) override;
+
+ EVENT_RESULT SendMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ void UnfocusFromPoint(const CPoint &point) override;
+
+ void AddControl(CGUIControl *control, int position = -1) override;
+ void ClearAll() override;
+
+ virtual std::string GetLabel(int info) const;
+ bool GetCondition(int condition, int data) const override;
+ /**
+ * Calculate total size of child controls area (including gaps between controls)
+ */
+ float GetTotalSize() const;
+ ORIENTATION GetOrientation() const { return m_orientation; }
+
+ // based on grouplist orientation pick one value as minSize;
+ void SetMinSize(float minWidth, float minHeight);
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool IsControlOnScreen(float pos, const CGUIControl* control) const;
+ bool IsFirstFocusableControl(const CGUIControl *control) const;
+ bool IsLastFocusableControl(const CGUIControl *control) const;
+ void ValidateOffset();
+ void CalculateItemGap();
+ inline float Size(const CGUIControl *control) const;
+ void ScrollTo(float offset);
+ float GetAlignOffset() const;
+
+ int GetNumItems() const;
+ int GetSelectedItem() const;
+
+ float m_itemGap;
+ int m_pageControl;
+ int m_focusedPosition;
+
+ float m_totalSize;
+
+ CScroller m_scroller;
+ int m_lastScrollerValue;
+
+ bool m_useControlPositions;
+ ORIENTATION m_orientation;
+ uint32_t m_alignment;
+
+ // for autosizing
+ float m_minSize;
+};
+
diff --git a/xbmc/guilib/GUIControlLookup.cpp b/xbmc/guilib/GUIControlLookup.cpp
new file mode 100644
index 0000000..8c572bb
--- /dev/null
+++ b/xbmc/guilib/GUIControlLookup.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControlLookup.h"
+
+CGUIControlLookup::CGUIControlLookup(const CGUIControlLookup& from) : CGUIControl(from)
+{
+}
+
+CGUIControl *CGUIControlLookup::GetControl(int iControl, std::vector<CGUIControl*> *idCollector)
+{
+ if (idCollector)
+ idCollector->clear();
+
+ CGUIControl* pPotential(nullptr);
+
+ LookupMap::const_iterator first = m_lookup.find(iControl);
+ if (first != m_lookup.end())
+ {
+ LookupMap::const_iterator last = m_lookup.upper_bound(iControl);
+ for (LookupMap::const_iterator i = first; i != last; ++i)
+ {
+ CGUIControl *control = i->second;
+ if (control->IsVisible())
+ return control;
+ else if (idCollector)
+ idCollector->push_back(control);
+ else if (!pPotential)
+ pPotential = control;
+ }
+ }
+ return pPotential;
+}
+
+bool CGUIControlLookup::IsValidControl(const CGUIControl *control) const
+{
+ if (control->GetID())
+ {
+ for (const auto &i : m_lookup)
+ {
+ if (control == i.second)
+ return true;
+ }
+ }
+ return false;
+}
+
+void CGUIControlLookup::AddLookup(CGUIControl *control)
+{
+ CGUIControlLookup *lookupControl(dynamic_cast<CGUIControlLookup*>(control));
+
+ if (lookupControl)
+ { // first add all the subitems of this group (if they exist)
+ const LookupMap &map(lookupControl->GetLookup());
+ for (const auto &i : map)
+ m_lookup.insert(m_lookup.upper_bound(i.first), std::make_pair(i.first, i.second));
+ }
+ if (control->GetID())
+ m_lookup.insert(m_lookup.upper_bound(control->GetID()), std::make_pair(control->GetID(), control));
+ // ensure that our size is what it should be
+ if (m_parentControl && (lookupControl = dynamic_cast<CGUIControlLookup*>(m_parentControl)))
+ lookupControl->AddLookup(control);
+}
+
+void CGUIControlLookup::RemoveLookup(CGUIControl *control)
+{
+ CGUIControlLookup *lookupControl(dynamic_cast<CGUIControlLookup*>(control));
+ if (lookupControl)
+ { // remove the group's lookup
+ const LookupMap &map(lookupControl->GetLookup());
+ for (const auto &i : map)
+ { // remove this control
+ for (LookupMap::iterator it = m_lookup.begin(); it != m_lookup.end(); ++it)
+ {
+ if (i.second == it->second)
+ {
+ m_lookup.erase(it);
+ break;
+ }
+ }
+ }
+ }
+ // remove the actual control
+ if (control->GetID())
+ {
+ for (LookupMap::iterator it = m_lookup.begin(); it != m_lookup.end(); ++it)
+ {
+ if (control == it->second)
+ {
+ m_lookup.erase(it);
+ break;
+ }
+ }
+ }
+ if (m_parentControl && (lookupControl = dynamic_cast<CGUIControlLookup*>(m_parentControl)))
+ lookupControl->RemoveLookup(control);
+}
+
+void CGUIControlLookup::RemoveLookup()
+{
+ CGUIControlLookup *lookupControl;
+ if (m_parentControl && (lookupControl = dynamic_cast<CGUIControlLookup*>(m_parentControl)))
+ {
+ const LookupMap map(m_lookup);
+ for (const auto &i : map)
+ lookupControl->RemoveLookup(i.second);
+ }
+}
diff --git a/xbmc/guilib/GUIControlLookup.h b/xbmc/guilib/GUIControlLookup.h
new file mode 100644
index 0000000..01175e4
--- /dev/null
+++ b/xbmc/guilib/GUIControlLookup.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+
+class CGUIControlLookup : public CGUIControl
+{
+public:
+ CGUIControlLookup() = default;
+ CGUIControlLookup(int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height) {}
+ explicit CGUIControlLookup(const CGUIControlLookup& from);
+ ~CGUIControlLookup(void) override = default;
+
+ CGUIControl *GetControl(int id, std::vector<CGUIControl*> *idCollector = nullptr) override;
+protected:
+ typedef std::multimap<int, CGUIControl *> LookupMap;
+
+ /*!
+ \brief Check whether a given control is valid
+ Runs through controls and returns whether this control is valid. Only functional
+ for controls with non-zero id.
+ \param control to check
+ \return true if the control is valid, false otherwise.
+ */
+ bool IsValidControl(const CGUIControl *control) const;
+ std::pair<LookupMap::const_iterator, LookupMap::const_iterator> GetLookupControls(int controlId) const
+ {
+ return m_lookup.equal_range(controlId);
+ };
+
+ // fast lookup by id
+ void AddLookup(CGUIControl *control);
+ void RemoveLookup(CGUIControl *control);
+ void RemoveLookup();
+ const LookupMap &GetLookup() const { return m_lookup; }
+ void ClearLookup() { m_lookup.clear(); }
+private:
+ LookupMap m_lookup;
+};
diff --git a/xbmc/guilib/GUIControlProfiler.cpp b/xbmc/guilib/GUIControlProfiler.cpp
new file mode 100644
index 0000000..a3605a2
--- /dev/null
+++ b/xbmc/guilib/GUIControlProfiler.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIControlProfiler.h"
+
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/XBMCTinyXML.h"
+
+bool CGUIControlProfiler::m_bIsRunning = false;
+
+CGUIControlProfilerItem::CGUIControlProfilerItem(CGUIControlProfiler *pProfiler, CGUIControlProfilerItem *pParent, CGUIControl *pControl)
+: m_pProfiler(pProfiler), m_pParent(pParent), m_pControl(pControl), m_visTime(0), m_renderTime(0), m_i64VisStart(0), m_i64RenderStart(0)
+{
+ if (m_pControl)
+ {
+ m_controlID = m_pControl->GetID();
+ m_ControlType = m_pControl->GetControlType();
+ m_strDescription = m_pControl->GetDescription();
+ }
+ else
+ {
+ m_controlID = 0;
+ m_ControlType = CGUIControl::GUICONTROL_UNKNOWN;
+ }
+}
+
+CGUIControlProfilerItem::~CGUIControlProfilerItem(void)
+{
+ Reset(NULL);
+}
+
+void CGUIControlProfilerItem::Reset(CGUIControlProfiler *pProfiler)
+{
+ m_controlID = 0;
+ m_ControlType = CGUIControl::GUICONTROL_UNKNOWN;
+ m_pControl = NULL;
+
+ m_visTime = 0;
+ m_renderTime = 0;
+ const unsigned int dwSize = m_vecChildren.size();
+ for (unsigned int i=0; i<dwSize; ++i)
+ delete m_vecChildren[i];
+ m_vecChildren.clear();
+
+ m_pProfiler = pProfiler;
+}
+
+void CGUIControlProfilerItem::BeginVisibility(void)
+{
+ m_i64VisStart = CurrentHostCounter();
+}
+
+void CGUIControlProfilerItem::EndVisibility(void)
+{
+ m_visTime += (unsigned int)(m_pProfiler->m_fPerfScale * (CurrentHostCounter() - m_i64VisStart));
+}
+
+void CGUIControlProfilerItem::BeginRender(void)
+{
+ m_i64RenderStart = CurrentHostCounter();
+}
+
+void CGUIControlProfilerItem::EndRender(void)
+{
+ m_renderTime += (unsigned int)(m_pProfiler->m_fPerfScale * (CurrentHostCounter() - m_i64RenderStart));
+}
+
+void CGUIControlProfilerItem::SaveToXML(TiXmlElement *parent)
+{
+ TiXmlElement *xmlControl = new TiXmlElement("control");
+ parent->LinkEndChild(xmlControl);
+
+ const char *lpszType = NULL;
+ switch (m_ControlType)
+ {
+ case CGUIControl::GUICONTROL_BUTTON:
+ lpszType = "button"; break;
+ case CGUIControl::GUICONTROL_FADELABEL:
+ lpszType = "fadelabel"; break;
+ case CGUIControl::GUICONTROL_IMAGE:
+ case CGUIControl::GUICONTROL_BORDEREDIMAGE:
+ lpszType = "image"; break;
+ case CGUIControl::GUICONTROL_LABEL:
+ lpszType = "label"; break;
+ case CGUIControl::GUICONTROL_LISTGROUP:
+ lpszType = "group"; break;
+ case CGUIControl::GUICONTROL_PROGRESS:
+ lpszType = "progress"; break;
+ case CGUIControl::GUICONTROL_RADIO:
+ lpszType = "radiobutton"; break;
+ case CGUIControl::GUICONTROL_RSS:
+ lpszType = "rss"; break;
+ case CGUIControl::GUICONTROL_SLIDER:
+ lpszType = "slider"; break;
+ case CGUIControl::GUICONTROL_SETTINGS_SLIDER:
+ lpszType = "sliderex"; break;
+ case CGUIControl::GUICONTROL_SPIN:
+ lpszType = "spincontrol"; break;
+ case CGUIControl::GUICONTROL_SPINEX:
+ lpszType = "spincontrolex"; break;
+ case CGUIControl::GUICONTROL_TEXTBOX:
+ lpszType = "textbox"; break;
+ case CGUIControl::GUICONTROL_TOGGLEBUTTON:
+ lpszType = "togglebutton"; break;
+ case CGUIControl::GUICONTROL_VIDEO:
+ lpszType = "videowindow"; break;
+ case CGUIControl::GUICONTROL_MOVER:
+ lpszType = "mover"; break;
+ case CGUIControl::GUICONTROL_RESIZE:
+ lpszType = "resize"; break;
+ case CGUIControl::GUICONTROL_EDIT:
+ lpszType = "edit"; break;
+ case CGUIControl::GUICONTROL_VISUALISATION:
+ lpszType = "visualisation"; break;
+ case CGUIControl::GUICONTROL_MULTI_IMAGE:
+ lpszType = "multiimage"; break;
+ case CGUIControl::GUICONTROL_GROUP:
+ lpszType = "group"; break;
+ case CGUIControl::GUICONTROL_GROUPLIST:
+ lpszType = "grouplist"; break;
+ case CGUIControl::GUICONTROL_SCROLLBAR:
+ lpszType = "scrollbar"; break;
+ case CGUIControl::GUICONTROL_LISTLABEL:
+ lpszType = "label"; break;
+ case CGUIControl::GUICONTAINER_LIST:
+ lpszType = "list"; break;
+ case CGUIControl::GUICONTAINER_WRAPLIST:
+ lpszType = "wraplist"; break;
+ case CGUIControl::GUICONTAINER_FIXEDLIST:
+ lpszType = "fixedlist"; break;
+ case CGUIControl::GUICONTAINER_PANEL:
+ lpszType = "panel"; break;
+ case CGUIControl::GUICONTROL_COLORBUTTON:
+ lpszType = "colorbutton";
+ break;
+ //case CGUIControl::GUICONTROL_UNKNOWN:
+ default:
+ break;
+ }
+
+ if (lpszType)
+ xmlControl->SetAttribute("type", lpszType);
+ if (m_controlID != 0)
+ {
+ std::string str = std::to_string(m_controlID);
+ xmlControl->SetAttribute("id", str.c_str());
+ }
+
+ float pct = (float)GetTotalTime() / (float)m_pProfiler->GetTotalTime();
+ if (pct > 0.01f)
+ {
+ std::string str = StringUtils::Format("{:.0f}", pct * 100.0f);
+ xmlControl->SetAttribute("percent", str.c_str());
+ }
+
+ if (!m_strDescription.empty())
+ {
+ TiXmlElement *elem = new TiXmlElement("description");
+ xmlControl->LinkEndChild(elem);
+ TiXmlText *text = new TiXmlText(m_strDescription.c_str());
+ elem->LinkEndChild(text);
+ }
+
+ // Note time is stored in 1/100 milliseconds but reported in ms
+ unsigned int vis = m_visTime / 100;
+ unsigned int rend = m_renderTime / 100;
+ if (vis || rend)
+ {
+ std::string val;
+ TiXmlElement *elem = new TiXmlElement("rendertime");
+ xmlControl->LinkEndChild(elem);
+ val = std::to_string(rend);
+ TiXmlText *text = new TiXmlText(val.c_str());
+ elem->LinkEndChild(text);
+
+ elem = new TiXmlElement("visibletime");
+ xmlControl->LinkEndChild(elem);
+ val = std::to_string(vis);
+ text = new TiXmlText(val.c_str());
+ elem->LinkEndChild(text);
+ }
+
+ if (m_vecChildren.size())
+ {
+ TiXmlElement *xmlChilds = new TiXmlElement("children");
+ xmlControl->LinkEndChild(xmlChilds);
+ const unsigned int dwSize = m_vecChildren.size();
+ for (unsigned int i=0; i<dwSize; ++i)
+ m_vecChildren[i]->SaveToXML(xmlChilds);
+ }
+}
+
+CGUIControlProfilerItem *CGUIControlProfilerItem::AddControl(CGUIControl *pControl)
+{
+ m_vecChildren.push_back(new CGUIControlProfilerItem(m_pProfiler, this, pControl));
+ return m_vecChildren.back();
+}
+
+CGUIControlProfilerItem *CGUIControlProfilerItem::FindOrAddControl(CGUIControl *pControl, bool recurse)
+{
+ const unsigned int dwSize = m_vecChildren.size();
+ for (unsigned int i=0; i<dwSize; ++i)
+ {
+ CGUIControlProfilerItem *p = m_vecChildren[i];
+ if (p->m_pControl == pControl)
+ return p;
+ if (recurse && (p = p->FindOrAddControl(pControl, true)))
+ return p;
+ }
+
+ if (pControl->GetParentControl() == m_pControl)
+ return AddControl(pControl);
+
+ return NULL;
+}
+
+CGUIControlProfiler::CGUIControlProfiler(void)
+: m_ItemHead(NULL, NULL, NULL), m_pLastItem(NULL)
+// m_bIsRunning(false), no isRunning because it is static
+{
+ m_fPerfScale = 100000.0f / CurrentHostFrequency();
+}
+
+CGUIControlProfiler &CGUIControlProfiler::Instance(void)
+{
+ static CGUIControlProfiler _instance;
+ return _instance;
+}
+
+bool CGUIControlProfiler::IsRunning(void)
+{
+ return m_bIsRunning;
+}
+
+void CGUIControlProfiler::Start(void)
+{
+ m_iFrameCount = 0;
+ m_bIsRunning = true;
+ m_pLastItem = NULL;
+ m_ItemHead.Reset(this);
+}
+
+void CGUIControlProfiler::BeginVisibility(CGUIControl *pControl)
+{
+ CGUIControlProfilerItem *item = FindOrAddControl(pControl);
+ item->BeginVisibility();
+}
+
+void CGUIControlProfiler::EndVisibility(CGUIControl *pControl)
+{
+ CGUIControlProfilerItem *item = FindOrAddControl(pControl);
+ item->EndVisibility();
+}
+
+void CGUIControlProfiler::BeginRender(CGUIControl *pControl)
+{
+ CGUIControlProfilerItem *item = FindOrAddControl(pControl);
+ item->BeginRender();
+}
+
+void CGUIControlProfiler::EndRender(CGUIControl *pControl)
+{
+ CGUIControlProfilerItem *item = FindOrAddControl(pControl);
+ item->EndRender();
+}
+
+CGUIControlProfilerItem *CGUIControlProfiler::FindOrAddControl(CGUIControl *pControl)
+{
+ if (m_pLastItem)
+ {
+ // Typically calls come in pairs so the last control we found is probably
+ // the one we want again next time
+ if (m_pLastItem->m_pControl == pControl)
+ return m_pLastItem;
+ // If that control is not a match, usually the one we want is the next
+ // sibling of that control, or the parent of that control so check
+ // the parent first as it is more convenient
+ m_pLastItem = m_pLastItem->m_pParent;
+ if (m_pLastItem && m_pLastItem->m_pControl == pControl)
+ return m_pLastItem;
+ // continued from above, this searches the original control's siblings
+ if (m_pLastItem)
+ m_pLastItem = m_pLastItem->FindOrAddControl(pControl, false);
+ if (m_pLastItem)
+ return m_pLastItem;
+ }
+
+ m_pLastItem = m_ItemHead.FindOrAddControl(pControl, true);
+ if (!m_pLastItem)
+ m_pLastItem = m_ItemHead.AddControl(pControl);
+
+ return m_pLastItem;
+}
+
+void CGUIControlProfiler::EndFrame(void)
+{
+ m_iFrameCount++;
+ if (m_iFrameCount >= m_iMaxFrameCount)
+ {
+ const unsigned int dwSize = m_ItemHead.m_vecChildren.size();
+ for (unsigned int i=0; i<dwSize; ++i)
+ {
+ CGUIControlProfilerItem *p = m_ItemHead.m_vecChildren[i];
+ m_ItemHead.m_visTime += p->m_visTime;
+ m_ItemHead.m_renderTime += p->m_renderTime;
+ }
+
+ m_bIsRunning = false;
+ if (SaveResults())
+ m_ItemHead.Reset(this);
+ }
+}
+
+bool CGUIControlProfiler::SaveResults(void)
+{
+ if (m_strOutputFile.empty())
+ return false;
+
+ CXBMCTinyXML doc;
+ TiXmlDeclaration decl("1.0", "", "yes");
+ doc.InsertEndChild(decl);
+
+ TiXmlElement *root = new TiXmlElement("guicontrolprofiler");
+ std::string str = std::to_string(m_iFrameCount);
+ root->SetAttribute("framecount", str.c_str());
+ root->SetAttribute("timeunit", "ms");
+ doc.LinkEndChild(root);
+
+ m_ItemHead.SaveToXML(root);
+ return doc.SaveFile(m_strOutputFile);
+}
diff --git a/xbmc/guilib/GUIControlProfiler.h b/xbmc/guilib/GUIControlProfiler.h
new file mode 100644
index 0000000..006493a
--- /dev/null
+++ b/xbmc/guilib/GUIControlProfiler.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+
+#include <vector>
+
+class CGUIControlProfiler;
+class TiXmlElement;
+
+class CGUIControlProfilerItem
+{
+public:
+ CGUIControlProfiler *m_pProfiler;
+ CGUIControlProfilerItem * m_pParent;
+ CGUIControl *m_pControl;
+ std::vector<CGUIControlProfilerItem *> m_vecChildren;
+ std::string m_strDescription;
+ int m_controlID;
+ CGUIControl::GUICONTROLTYPES m_ControlType;
+ unsigned int m_visTime;
+ unsigned int m_renderTime;
+ int64_t m_i64VisStart;
+ int64_t m_i64RenderStart;
+
+ CGUIControlProfilerItem(CGUIControlProfiler *pProfiler, CGUIControlProfilerItem *pParent, CGUIControl *pControl);
+ ~CGUIControlProfilerItem(void);
+
+ void Reset(CGUIControlProfiler *pProfiler);
+ void BeginVisibility(void);
+ void EndVisibility(void);
+ void BeginRender(void);
+ void EndRender(void);
+ void SaveToXML(TiXmlElement *parent);
+ unsigned int GetTotalTime(void) const { return m_visTime + m_renderTime; }
+
+ CGUIControlProfilerItem *AddControl(CGUIControl *pControl);
+ CGUIControlProfilerItem *FindOrAddControl(CGUIControl *pControl, bool recurse);
+};
+
+class CGUIControlProfiler
+{
+public:
+ static CGUIControlProfiler &Instance(void);
+ static bool IsRunning(void);
+
+ void Start(void);
+ void EndFrame(void);
+ void BeginVisibility(CGUIControl *pControl);
+ void EndVisibility(CGUIControl *pControl);
+ void BeginRender(CGUIControl *pControl);
+ void EndRender(CGUIControl *pControl);
+ int GetMaxFrameCount(void) const { return m_iMaxFrameCount; }
+ void SetMaxFrameCount(int iMaxFrameCount) { m_iMaxFrameCount = iMaxFrameCount; }
+ void SetOutputFile(const std::string& strOutputFile) { m_strOutputFile = strOutputFile; }
+ const std::string& GetOutputFile(void) const { return m_strOutputFile; }
+ bool SaveResults(void);
+ unsigned int GetTotalTime(void) const { return m_ItemHead.GetTotalTime(); }
+
+ float m_fPerfScale;
+private:
+ CGUIControlProfiler(void);
+ ~CGUIControlProfiler(void) = default;
+ CGUIControlProfiler(const CGUIControlProfiler &that) = delete;
+ CGUIControlProfiler &operator=(const CGUIControlProfiler &that) = delete;
+
+ CGUIControlProfilerItem m_ItemHead;
+ CGUIControlProfilerItem *m_pLastItem;
+ CGUIControlProfilerItem *FindOrAddControl(CGUIControl *pControl);
+
+ static bool m_bIsRunning;
+ std::string m_strOutputFile;
+ int m_iMaxFrameCount = 200;
+ int m_iFrameCount = 0;
+};
+
+#define GUIPROFILER_VISIBILITY_BEGIN(x) { if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().BeginVisibility(x); }
+#define GUIPROFILER_VISIBILITY_END(x) { if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().EndVisibility(x); }
+#define GUIPROFILER_RENDER_BEGIN(x) { if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().BeginRender(x); }
+#define GUIPROFILER_RENDER_END(x) { if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().EndRender(x); }
+
diff --git a/xbmc/guilib/GUIDialog.cpp b/xbmc/guilib/GUIDialog.cpp
new file mode 100644
index 0000000..a3d0e02
--- /dev/null
+++ b/xbmc/guilib/GUIDialog.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIDialog.h"
+
+#include "GUIComponent.h"
+#include "GUIControlFactory.h"
+#include "GUILabelControl.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "input/Key.h"
+#include "messaging/ApplicationMessenger.h"
+#include "threads/SingleLock.h"
+#include "utils/TimeUtils.h"
+
+CGUIDialog::CGUIDialog(int id, const std::string &xmlFile, DialogModalityType modalityType /* = DialogModalityType::MODAL */)
+ : CGUIWindow(id, xmlFile)
+{
+ m_modalityType = modalityType;
+ m_wasRunning = false;
+ m_renderOrder = RENDER_ORDER_DIALOG;
+ m_autoClosing = false;
+ m_showStartTime = 0;
+ m_showDuration = 0;
+ m_enableSound = true;
+ m_bAutoClosed = false;
+}
+
+CGUIDialog::~CGUIDialog(void) = default;
+
+bool CGUIDialog::Load(TiXmlElement* pRootElement)
+{
+ return CGUIWindow::Load(pRootElement);
+}
+
+void CGUIDialog::OnWindowLoaded()
+{
+ CGUIWindow::OnWindowLoaded();
+
+ // Clip labels to extents
+ if (m_children.size())
+ {
+ CGUIControl* pBase = m_children[0];
+
+ for (iControls p = m_children.begin() + 1; p != m_children.end(); ++p)
+ {
+ if ((*p)->GetControlType() == CGUIControl::GUICONTROL_LABEL)
+ {
+ CGUILabelControl* pLabel = (CGUILabelControl*)(*p);
+
+ if (!pLabel->GetWidth())
+ {
+ float spacing = (pLabel->GetXPosition() - pBase->GetXPosition()) * 2;
+ pLabel->SetWidth(pBase->GetWidth() - spacing);
+ }
+ }
+ }
+ }
+}
+
+bool CGUIDialog::OnAction(const CAction &action)
+{
+ // keyboard or controller movement should prevent autoclosing
+ if (!action.IsMouse() && m_autoClosing)
+ SetAutoClose(m_showDuration);
+
+ return CGUIWindow::OnAction(action);
+}
+
+bool CGUIDialog::OnBack(int actionID)
+{
+ Close();
+ return true;
+}
+
+bool CGUIDialog::OnMessage(CGUIMessage& message)
+{
+ switch ( message.GetMessage() )
+ {
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ CGUIWindow::OnMessage(message);
+ return true;
+ }
+ case GUI_MSG_WINDOW_INIT:
+ {
+ CGUIWindow::OnMessage(message);
+ m_showStartTime = 0;
+ return true;
+ }
+ }
+
+ return CGUIWindow::OnMessage(message);
+}
+
+void CGUIDialog::OnDeinitWindow(int nextWindowID)
+{
+ if (m_active)
+ {
+ CServiceBroker::GetGUI()->GetWindowManager().RemoveDialog(GetID());
+ m_autoClosing = false;
+ }
+ CGUIWindow::OnDeinitWindow(nextWindowID);
+}
+
+void CGUIDialog::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ UpdateVisibility();
+
+ // if we were running but now we're not, mark us dirty
+ if (!m_active && m_wasRunning)
+ dirtyregions.push_back(CDirtyRegion(m_renderRegion));
+
+ if (m_active)
+ CGUIWindow::DoProcess(currentTime, dirtyregions);
+
+ m_wasRunning = m_active;
+}
+
+void CGUIDialog::UpdateVisibility()
+{
+ if (m_visibleCondition)
+ {
+ if (m_visibleCondition->Get(INFO::DEFAULT_CONTEXT))
+ Open();
+ else
+ Close();
+ }
+
+ if (m_autoClosing)
+ { // check if our timer is running
+ if (!m_showStartTime)
+ {
+ if (HasProcessed()) // start timer
+ m_showStartTime = CTimeUtils::GetFrameTime();
+ }
+ else
+ {
+ if (m_showStartTime + m_showDuration < CTimeUtils::GetFrameTime() && !m_closing)
+ {
+ m_bAutoClosed = true;
+ Close();
+ }
+ }
+ }
+}
+
+void CGUIDialog::Open_Internal(bool bProcessRenderLoop, const std::string &param /* = "" */)
+{
+ if (!CServiceBroker::GetGUI()->GetWindowManager().Initialized() ||
+ (m_active && !m_closing && !IsAnimating(ANIM_TYPE_WINDOW_CLOSE)))
+ return;
+
+ // set running before it's added to the window manager, else the auto-show code
+ // could show it as well if we are in a different thread from the main rendering
+ // thread (this should really be handled via a thread message though IMO)
+ m_active = true;
+ m_closing = false;
+ CServiceBroker::GetGUI()->GetWindowManager().RegisterDialog(this);
+
+ // active this window
+ CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0);
+ msg.SetStringParam(param);
+ OnMessage(msg);
+
+ // process render loop
+ if (bProcessRenderLoop)
+ {
+ if (!m_windowLoaded)
+ Close(true);
+
+ while (m_active)
+ {
+ if (!CServiceBroker::GetGUI()->GetWindowManager().ProcessRenderLoop(false))
+ break;
+ }
+ }
+}
+
+void CGUIDialog::Open(const std::string &param /* = "" */)
+{
+ CGUIDialog::Open(m_modalityType != DialogModalityType::MODELESS, param);
+}
+
+
+void CGUIDialog::Open(bool bProcessRenderLoop, const std::string& param /* = "" */)
+{
+ if (!CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ // make sure graphics lock is not held
+ CSingleExit leaveIt(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_DIALOG_OPEN, -1, bProcessRenderLoop,
+ static_cast<void*>(this), param);
+ }
+ else
+ Open_Internal(bProcessRenderLoop, param);
+}
+
+void CGUIDialog::Render()
+{
+ if (!m_active)
+ return;
+
+ CGUIWindow::Render();
+}
+
+void CGUIDialog::SetDefaults()
+{
+ CGUIWindow::SetDefaults();
+ m_renderOrder = RENDER_ORDER_DIALOG;
+}
+
+void CGUIDialog::SetAutoClose(unsigned int timeoutMs)
+{
+ m_autoClosing = true;
+ m_showDuration = timeoutMs;
+ ResetAutoClose();
+}
+
+void CGUIDialog::ResetAutoClose(void)
+{
+ if (m_autoClosing && m_active)
+ m_showStartTime = CTimeUtils::GetFrameTime();
+}
+
+void CGUIDialog::CancelAutoClose(void)
+{
+ m_autoClosing = false;
+}
+
+void CGUIDialog::ProcessRenderLoop(bool renderOnly)
+{
+ CServiceBroker::GetGUI()->GetWindowManager().ProcessRenderLoop(renderOnly);
+}
diff --git a/xbmc/guilib/GUIDialog.h b/xbmc/guilib/GUIDialog.h
new file mode 100644
index 0000000..7300c1f
--- /dev/null
+++ b/xbmc/guilib/GUIDialog.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIDialog.h
+\brief
+*/
+
+#include "GUIWindow.h"
+#include "WindowIDs.h"
+
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(push, 8)
+#endif
+enum class DialogModalityType
+{
+ MODELESS,
+ MODAL
+};
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(pop)
+#endif
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+class CGUIDialog :
+ public CGUIWindow
+{
+public:
+ CGUIDialog(int id, const std::string &xmlFile, DialogModalityType modalityType = DialogModalityType::MODAL);
+ ~CGUIDialog(void) override;
+
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+
+ void Open(const std::string &param = "");
+ void Open(bool bProcessRenderLoop, const std::string& param = "");
+
+ bool OnBack(int actionID) override;
+
+ bool IsDialogRunning() const override { return m_active; }
+ bool IsDialog() const override { return true; }
+ bool IsModalDialog() const override { return m_modalityType == DialogModalityType::MODAL; }
+ virtual DialogModalityType GetModalityType() const { return m_modalityType; }
+
+ void SetAutoClose(unsigned int timeoutMs);
+ void ResetAutoClose(void);
+ void CancelAutoClose(void);
+ bool IsAutoClosed(void) const { return m_bAutoClosed; }
+ void SetSound(bool OnOff) { m_enableSound = OnOff; }
+ bool IsSoundEnabled() const override { return m_enableSound; }
+
+protected:
+ bool Load(TiXmlElement *pRootElement) override;
+ void SetDefaults() override;
+ void OnWindowLoaded() override;
+ using CGUIWindow::UpdateVisibility;
+ virtual void UpdateVisibility();
+
+ virtual void Open_Internal(bool bProcessRenderLoop, const std::string &param = "");
+ void OnDeinitWindow(int nextWindowID) override;
+
+ void ProcessRenderLoop(bool renderOnly = false);
+
+ bool m_wasRunning; ///< \brief true if we were running during the last DoProcess()
+ bool m_autoClosing;
+ bool m_enableSound;
+ unsigned int m_showStartTime;
+ unsigned int m_showDuration;
+ bool m_bAutoClosed;
+ DialogModalityType m_modalityType;
+};
diff --git a/xbmc/guilib/GUIEditControl.cpp b/xbmc/guilib/GUIEditControl.cpp
new file mode 100644
index 0000000..325a703
--- /dev/null
+++ b/xbmc/guilib/GUIEditControl.cpp
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIEditControl.h"
+
+#include "GUIFont.h"
+#include "GUIKeyboardFactory.h"
+#include "GUIUserMessages.h"
+#include "GUIWindowManager.h"
+#include "LocalizeStrings.h"
+#include "ServiceBroker.h"
+#include "XBDateTime.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "input/Key.h"
+#include "input/XBMC_vkeys.h"
+#include "utils/CharsetConverter.h"
+#include "utils/ColorUtils.h"
+#include "utils/Digest.h"
+#include "utils/Variant.h"
+#include "windowing/WinSystem.h"
+
+using namespace KODI::GUILIB;
+
+using KODI::UTILITY::CDigest;
+
+const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2ABC", "def3DEF", "ghi4GHI", "jkl5JKL", "mno6MNO", "pqrs7PQRS", "tuv8TUV", "wxyz9WXYZ" };
+const unsigned int CGUIEditControl::smsDelay = 1000;
+
+#ifdef TARGET_WINDOWS
+extern HWND g_hWnd;
+#endif
+
+CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
+ float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
+ const CLabelInfo& labelInfo, const std::string &text)
+ : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
+{
+ DefaultConstructor();
+ SetLabel(text);
+}
+
+void CGUIEditControl::DefaultConstructor()
+{
+ ControlType = GUICONTROL_EDIT;
+ m_textOffset = 0;
+ m_textWidth = GetWidth();
+ m_cursorPos = 0;
+ m_cursorBlink = 0;
+ m_inputHeading = g_localizeStrings.Get(16028);
+ m_inputType = INPUT_TYPE_TEXT;
+ m_smsLastKey = 0;
+ m_smsKeyIndex = 0;
+ m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
+ m_label2.GetLabelInfo().offsetX = 0;
+ m_isMD5 = false;
+ m_invalidInput = false;
+ m_inputValidator = NULL;
+ m_inputValidatorData = NULL;
+ m_editLength = 0;
+ m_editOffset = 0;
+}
+
+CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
+ : CGUIButtonControl(button)
+{
+ DefaultConstructor();
+}
+
+CGUIEditControl::~CGUIEditControl(void) = default;
+
+bool CGUIEditControl::OnMessage(CGUIMessage &message)
+{
+ if (message.GetMessage() == GUI_MSG_SET_TYPE)
+ {
+ SetInputType((INPUT_TYPE)message.GetParam1(), message.GetParam2());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
+ {
+ message.SetLabel(GetLabel2());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_SET_TEXT &&
+ ((message.GetControlId() <= 0 && HasFocus()) || (message.GetControlId() == GetID())))
+ {
+ SetLabel2(message.GetLabel());
+ UpdateText();
+ }
+ return CGUIButtonControl::OnMessage(message);
+}
+
+bool CGUIEditControl::OnAction(const CAction &action)
+{
+ ValidateCursor();
+
+ if (m_inputType != INPUT_TYPE_READONLY)
+ {
+ if (action.GetID() == ACTION_BACKSPACE)
+ {
+ // backspace
+ if (m_cursorPos)
+ {
+ if (!ClearMD5())
+ m_text2.erase(--m_cursorPos, 1);
+ UpdateText();
+ }
+ return true;
+ }
+ else if (action.GetID() == ACTION_MOVE_LEFT ||
+ action.GetID() == ACTION_CURSOR_LEFT)
+ {
+ if (m_cursorPos > 0)
+ {
+ m_cursorPos--;
+ UpdateText(false);
+ return true;
+ }
+ }
+ else if (action.GetID() == ACTION_MOVE_RIGHT ||
+ action.GetID() == ACTION_CURSOR_RIGHT)
+ {
+ if (m_cursorPos < m_text2.size())
+ {
+ m_cursorPos++;
+ UpdateText(false);
+ return true;
+ }
+ }
+ else if (action.GetID() == ACTION_PASTE)
+ {
+ ClearMD5();
+ OnPasteClipboard();
+ return true;
+ }
+ else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_UNICODE && m_edit.empty())
+ {
+ // input from the keyboard (vkey, not ascii)
+ unsigned char b = action.GetID() & 0xFF;
+ if (b == XBMCVK_HOME)
+ {
+ m_cursorPos = 0;
+ UpdateText(false);
+ return true;
+ }
+ else if (b == XBMCVK_END)
+ {
+ m_cursorPos = m_text2.length();
+ UpdateText(false);
+ return true;
+ }
+ if (b == XBMCVK_LEFT && m_cursorPos > 0)
+ {
+ m_cursorPos--;
+ UpdateText(false);
+ return true;
+ }
+ if (b == XBMCVK_RIGHT && m_cursorPos < m_text2.length())
+ {
+ m_cursorPos++;
+ UpdateText(false);
+ return true;
+ }
+ if (b == XBMCVK_DELETE)
+ {
+ if (m_cursorPos < m_text2.length())
+ {
+ if (!ClearMD5())
+ m_text2.erase(m_cursorPos, 1);
+ UpdateText();
+ return true;
+ }
+ }
+ if (b == XBMCVK_BACK)
+ {
+ if (m_cursorPos > 0)
+ {
+ if (!ClearMD5())
+ m_text2.erase(--m_cursorPos, 1);
+ UpdateText();
+ }
+ return true;
+ }
+ else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER)
+ {
+ // enter - send click message, but otherwise ignore
+ SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
+ return true;
+ }
+ else if (b == XBMCVK_ESCAPE)
+ { // escape - fallthrough to default action
+ return CGUIButtonControl::OnAction(action);
+ }
+ }
+ else if (action.GetID() == KEY_UNICODE)
+ {
+ // input from the keyboard
+ int ch = action.GetUnicode();
+ // ignore non-printing characters
+ if ( !((0 <= ch && ch < 0x8) || (0xE <= ch && ch < 0x1B) || (0x1C <= ch && ch < 0x20)) )
+ {
+ switch (ch)
+ {
+ case 9: // tab, ignore
+ case 11: // Non-printing character, ignore
+ case 12: // Non-printing character, ignore
+ break;
+ case 10:
+ case 13:
+ {
+ // enter - send click message, but otherwise ignore
+ SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
+ return true;
+ }
+ case 27:
+ { // escape - fallthrough to default action
+ return CGUIButtonControl::OnAction(action);
+ }
+ case 8:
+ {
+ // backspace
+ if (m_cursorPos)
+ {
+ if (!ClearMD5())
+ m_text2.erase(--m_cursorPos, 1);
+ }
+ break;
+ }
+ case 127:
+ { // delete
+ if (m_cursorPos < m_text2.length())
+ {
+ if (!ClearMD5())
+ m_text2.erase(m_cursorPos, 1);
+ }
+ break;
+ }
+ default:
+ {
+ ClearMD5();
+ m_edit.clear();
+ m_text2.insert(m_text2.begin() + m_cursorPos++, action.GetUnicode());
+ break;
+ }
+ }
+ UpdateText();
+ return true;
+ }
+ }
+ else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
+ { // input from the remote
+ ClearMD5();
+ m_edit.clear();
+ OnSMSCharacter(action.GetID() - REMOTE_0);
+ return true;
+ }
+ else if (action.GetID() == ACTION_INPUT_TEXT)
+ {
+ m_edit.clear();
+ std::wstring str;
+ g_charsetConverter.utf8ToW(action.GetText(), str, false);
+ m_text2.insert(m_cursorPos, str);
+ m_cursorPos += str.size();
+ UpdateText();
+ return true;
+ }
+ }
+ return CGUIButtonControl::OnAction(action);
+}
+
+void CGUIEditControl::OnClick()
+{
+ // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
+ // that is where we reside!
+ if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
+ return;
+
+ std::string utf8;
+ g_charsetConverter.wToUTF8(m_text2, utf8);
+ bool textChanged = false;
+ switch (m_inputType)
+ {
+ case INPUT_TYPE_READONLY:
+ textChanged = false;
+ break;
+ case INPUT_TYPE_NUMBER:
+ textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, m_inputHeading);
+ break;
+ case INPUT_TYPE_SECONDS:
+ textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
+ break;
+ case INPUT_TYPE_TIME:
+ {
+ CDateTime dateTime;
+ dateTime.SetFromDBTime(utf8);
+ KODI::TIME::SystemTime time;
+ dateTime.GetAsSystemTime(time);
+ if (CGUIDialogNumeric::ShowAndGetTime(time, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
+ {
+ dateTime = CDateTime(time);
+ utf8 = dateTime.GetAsLocalizedTime("", false);
+ textChanged = true;
+ }
+ break;
+ }
+ case INPUT_TYPE_DATE:
+ {
+ CDateTime dateTime;
+ dateTime.SetFromDBDate(utf8);
+ if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
+ dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
+ KODI::TIME::SystemTime date;
+ dateTime.GetAsSystemTime(date);
+ if (CGUIDialogNumeric::ShowAndGetDate(date, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
+ {
+ dateTime = CDateTime(date);
+ utf8 = dateTime.GetAsDBDate();
+ textChanged = true;
+ }
+ break;
+ }
+ case INPUT_TYPE_IPADDRESS:
+ textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, m_inputHeading);
+ break;
+ case INPUT_TYPE_SEARCH:
+ textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, true);
+ break;
+ case INPUT_TYPE_FILTER:
+ textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, false);
+ break;
+ case INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW:
+ textChanged = CGUIDialogNumeric::ShowAndVerifyNewPassword(utf8);
+ break;
+ case INPUT_TYPE_PASSWORD_MD5:
+ utf8 = ""; //! @todo Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
+ // fallthrough
+ [[fallthrough]];
+ case INPUT_TYPE_TEXT:
+ default:
+ textChanged = CGUIKeyboardFactory::ShowAndGetInput(utf8, m_inputHeading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
+ break;
+ }
+ if (textChanged)
+ {
+ ClearMD5();
+ m_edit.clear();
+ g_charsetConverter.utf8ToW(utf8, m_text2, false);
+ m_cursorPos = m_text2.size();
+ UpdateText();
+ m_cursorPos = m_text2.size();
+ }
+}
+
+void CGUIEditControl::UpdateText(bool sendUpdate)
+{
+ m_smsTimer.Stop();
+ if (sendUpdate)
+ {
+ ValidateInput();
+
+ SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
+
+ m_textChangeActions.ExecuteActions(GetID(), GetParentID());
+ }
+ SetInvalid();
+}
+
+void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, const CVariant& heading)
+{
+ m_inputType = type;
+ if (heading.isString())
+ m_inputHeading = heading.asString();
+ else if (heading.isInteger() && heading.asInteger())
+ m_inputHeading = g_localizeStrings.Get(static_cast<uint32_t>(heading.asInteger()));
+ //! @todo Verify the current input string?
+}
+
+void CGUIEditControl::RecalcLabelPosition()
+{
+ // ensure that our cursor is within our width
+ ValidateCursor();
+
+ std::wstring text = GetDisplayedText();
+ m_textWidth = m_label.CalcTextWidth(text + L'|');
+ float beforeCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos));
+ float afterCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos) + L'|');
+ float leftTextWidth = m_label.GetRenderRect().Width();
+ float maxTextWidth = m_label.GetMaxWidth();
+ if (leftTextWidth > 0)
+ maxTextWidth -= leftTextWidth + spaceWidth;
+
+ // if skinner forgot to set height :p
+ if (m_height == 0 && m_label.GetLabelInfo().font)
+ m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
+
+ if (m_textWidth > maxTextWidth)
+ { // we render taking up the full width, so make sure our cursor position is
+ // within the render window
+ if (m_textOffset + afterCursorWidth > maxTextWidth)
+ {
+ // move the position to the left (outside of the viewport)
+ m_textOffset = maxTextWidth - afterCursorWidth;
+ }
+ else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
+ {
+ // otherwise use original position
+ m_textOffset = -beforeCursorWidth;
+ }
+ else if (m_textOffset + m_textWidth < maxTextWidth)
+ { // we have more text than we're allowed, but we aren't filling all the space
+ m_textOffset = maxTextWidth - m_textWidth;
+ }
+ }
+ else
+ m_textOffset = 0;
+}
+
+void CGUIEditControl::ProcessText(unsigned int currentTime)
+{
+ if (m_smsTimer.IsRunning() && m_smsTimer.GetElapsedMilliseconds() > smsDelay)
+ UpdateText();
+
+ if (m_bInvalidated)
+ {
+ m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
+ m_label.SetText(m_info.GetLabel(GetParentID()));
+ RecalcLabelPosition();
+ }
+
+ bool changed = false;
+
+ m_clipRect.x1 = m_label.GetRenderRect().x1;
+ m_clipRect.x2 = m_clipRect.x1 + m_label.GetMaxWidth();
+ m_clipRect.y1 = m_posY;
+ m_clipRect.y2 = m_posY + m_height;
+
+ // start by rendering the normal text
+ float leftTextWidth = m_label.GetRenderRect().Width();
+ if (leftTextWidth > 0)
+ {
+ // render the text on the left
+ changed |= m_label.SetColor(GetTextColor());
+ changed |= m_label.Process(currentTime);
+
+ m_clipRect.x1 += leftTextWidth + spaceWidth;
+ }
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
+ {
+ uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
+ if (m_label2.GetTextWidth() < m_clipRect.Width())
+ { // align text as our text fits
+ if (leftTextWidth > 0)
+ { // right align as we have 2 labels
+ align |= XBFONT_RIGHT;
+ }
+ else
+ { // align by whatever the skinner requests
+ align |= (m_label2.GetLabelInfo().align & 3);
+ }
+ }
+ changed |= m_label2.SetMaxRect(m_clipRect.x1 + m_textOffset, m_posY, m_clipRect.Width() - m_textOffset, m_height);
+
+ std::wstring text = GetDisplayedText();
+ std::string hint = m_hintInfo.GetLabel(GetParentID());
+
+ if (!HasFocus() && text.empty() && !hint.empty())
+ {
+ changed |= m_label2.SetText(hint);
+ }
+ else if ((HasFocus() || GetParentID() == WINDOW_DIALOG_KEYBOARD) &&
+ m_inputType != INPUT_TYPE_READONLY)
+ {
+ changed |= SetStyledText(text);
+ }
+ else
+ changed |= m_label2.SetTextW(text);
+
+ changed |= m_label2.SetAlign(align);
+ changed |= m_label2.SetColor(GetTextColor());
+ changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
+ changed |= m_label2.Process(currentTime);
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ if (changed)
+ MarkDirtyRegion();
+}
+
+void CGUIEditControl::RenderText()
+{
+ m_label.Render();
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
+ {
+ m_label2.Render();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+}
+
+CGUILabel::COLOR CGUIEditControl::GetTextColor() const
+{
+ CGUILabel::COLOR color = CGUIButtonControl::GetTextColor();
+ if (color != CGUILabel::COLOR_DISABLED && HasInvalidInput())
+ return CGUILabel::COLOR_INVALID;
+
+ return color;
+}
+
+void CGUIEditControl::SetHint(const GUIINFO::CGUIInfoLabel& hint)
+{
+ m_hintInfo = hint;
+}
+
+std::wstring CGUIEditControl::GetDisplayedText() const
+{
+ std::wstring text(m_text2);
+ if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
+ {
+ text.clear();
+ if (m_smsTimer.IsRunning())
+ { // using the remove to input, so display the last key input
+ text.append(m_cursorPos - 1, L'*');
+ text.append(1, m_text2[m_cursorPos - 1]);
+ text.append(m_text2.size() - m_cursorPos, L'*');
+ }
+ else
+ text.append(m_text2.size(), L'*');
+ }
+ else if (!m_edit.empty())
+ text.insert(m_editOffset, m_edit);
+ return text;
+}
+
+bool CGUIEditControl::SetStyledText(const std::wstring &text)
+{
+ vecText styled;
+ styled.reserve(text.size() + 1);
+
+ std::vector<UTILS::COLOR::Color> colors;
+ colors.push_back(m_label.GetLabelInfo().textColor);
+ colors.push_back(m_label.GetLabelInfo().disabledColor);
+ UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor;
+ if (!select)
+ select = 0xFFFF0000;
+ colors.push_back(select);
+ colors.push_back(0x00FFFFFF);
+
+ unsigned int startHighlight = m_cursorPos;
+ unsigned int endHighlight = m_cursorPos + m_edit.size();
+ unsigned int startSelection = m_cursorPos + m_editOffset;
+ unsigned int endSelection = m_cursorPos + m_editOffset + m_editLength;
+
+ CGUIFont* font = m_label2.GetLabelInfo().font;
+ uint32_t style = (font ? font->GetStyle() : (FONT_STYLE_NORMAL & FONT_STYLE_MASK)) << 24;
+
+ for (unsigned int i = 0; i < text.size(); i++)
+ {
+ uint32_t ch = text[i] | style;
+ if (m_editLength > 0 && startSelection <= i && i < endSelection)
+ ch |= (2 << 16); // highlight the letters we're playing with
+ else if (!m_edit.empty() && (i < startHighlight || i >= endHighlight))
+ ch |= (1 << 16); // dim the bits we're not editing
+ styled.push_back(ch);
+ }
+
+ // show the cursor
+ uint32_t ch = L'|' | style;
+ if ((++m_cursorBlink % 64) > 32)
+ ch |= (3 << 16);
+ styled.insert(styled.begin() + m_cursorPos, ch);
+
+ return m_label2.SetStyledText(styled, colors);
+}
+
+void CGUIEditControl::ValidateCursor()
+{
+ if (m_cursorPos > m_text2.size())
+ m_cursorPos = m_text2.size();
+}
+
+void CGUIEditControl::SetLabel(const std::string &text)
+{
+ CGUIButtonControl::SetLabel(text);
+ SetInvalid();
+}
+
+void CGUIEditControl::SetLabel2(const std::string &text)
+{
+ m_edit.clear();
+ std::wstring newText;
+ g_charsetConverter.utf8ToW(text, newText, false);
+ if (newText != m_text2)
+ {
+ m_isMD5 = (m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW);
+ m_text2 = newText;
+ m_cursorPos = m_text2.size();
+ ValidateInput();
+ SetInvalid();
+ }
+}
+
+std::string CGUIEditControl::GetLabel2() const
+{
+ std::string text;
+ g_charsetConverter.wToUTF8(m_text2, text);
+ if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
+ return CDigest::Calculate(CDigest::Type::MD5, text);
+ return text;
+}
+
+bool CGUIEditControl::ClearMD5()
+{
+ if (!(m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW) || !m_isMD5)
+ return false;
+
+ m_text2.clear();
+ m_cursorPos = 0;
+ if (m_inputType != INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
+ m_isMD5 = false;
+ return true;
+}
+
+unsigned int CGUIEditControl::GetCursorPosition() const
+{
+ return m_cursorPos;
+}
+
+void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
+{
+ m_cursorPos = iPosition;
+}
+
+void CGUIEditControl::OnSMSCharacter(unsigned int key)
+{
+ assert(key < 10);
+ if (m_smsTimer.IsRunning())
+ {
+ // we're already entering an SMS character
+ if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
+ { // a different key was clicked than last time, or we have timed out
+ m_smsLastKey = key;
+ m_smsKeyIndex = 0;
+ }
+ else
+ { // same key as last time within the appropriate time period
+ m_smsKeyIndex++;
+ if (m_cursorPos)
+ m_text2.erase(--m_cursorPos, 1);
+ }
+ }
+ else
+ { // key is pressed for the first time
+ m_smsLastKey = key;
+ m_smsKeyIndex = 0;
+ }
+
+ m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
+
+ m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
+ UpdateText();
+ m_smsTimer.StartZero();
+}
+
+void CGUIEditControl::OnPasteClipboard()
+{
+ std::wstring unicode_text;
+ std::string utf8_text;
+
+ // Get text from the clipboard
+ utf8_text = CServiceBroker::GetWinSystem()->GetClipboardText();
+ g_charsetConverter.utf8ToW(utf8_text, unicode_text, false);
+
+ // Insert the pasted text at the current cursor position.
+ if (unicode_text.length() > 0)
+ {
+ std::wstring left_end = m_text2.substr(0, m_cursorPos);
+ std::wstring right_end = m_text2.substr(m_cursorPos);
+
+ m_text2 = left_end;
+ m_text2.append(unicode_text);
+ m_text2.append(right_end);
+ m_cursorPos += unicode_text.length();
+ UpdateText();
+ }
+}
+
+void CGUIEditControl::SetInputValidation(StringValidation::Validator inputValidator, void *data /* = NULL */)
+{
+ if (m_inputValidator == inputValidator)
+ return;
+
+ m_inputValidator = inputValidator;
+ m_inputValidatorData = data;
+ // the input validator has changed, so re-validate the current data
+ ValidateInput();
+}
+
+bool CGUIEditControl::ValidateInput(const std::wstring &data) const
+{
+ if (m_inputValidator == NULL)
+ return true;
+
+ return m_inputValidator(GetLabel2(), m_inputValidatorData != NULL ? m_inputValidatorData : const_cast<void*>((const void*)this));
+}
+
+void CGUIEditControl::ValidateInput()
+{
+ // validate the input
+ bool invalid = !ValidateInput(m_text2);
+ // nothing to do if still valid/invalid
+ if (invalid != m_invalidInput)
+ {
+ // the validity state has changed so we need to update the control
+ m_invalidInput = invalid;
+
+ // let the window/dialog know that the validity has changed
+ CGUIMessage msg(GUI_MSG_VALIDITY_CHANGED, GetID(), GetID(), m_invalidInput ? 0 : 1);
+ SendWindowMessage(msg);
+
+ SetInvalid();
+ }
+}
+
+void CGUIEditControl::SetFocus(bool focus)
+{
+ m_smsTimer.Stop();
+ CGUIControl::SetFocus(focus);
+ SetInvalid();
+}
+
+std::string CGUIEditControl::GetDescriptionByIndex(int index) const
+{
+ if (index == 0)
+ return GetDescription();
+ else if(index == 1)
+ return GetLabel2();
+
+ return "";
+}
diff --git a/xbmc/guilib/GUIEditControl.dox b/xbmc/guilib/GUIEditControl.dox
new file mode 100644
index 0000000..9cad4d0
--- /dev/null
+++ b/xbmc/guilib/GUIEditControl.dox
@@ -0,0 +1,60 @@
+/*!
+
+\page skin_Edit_control Edit control
+\brief **Used as an input control for the osd keyboard and other input fields.**
+
+\tableofcontents
+
+The edit control allows a user to input text in Kodi. You can choose the font,
+size, colour, location and header of the text to be displayed.
+
+--------------------------------------------------------------------------------
+\section skin_Edit_control_sect1 Example
+
+~~~~~~~~~~~~~
+ <control type="edit" id="1">
+ <description>My First edit control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <visible>true</visible>
+ <aligny>center</aligny>
+ <label>Search</label>
+ <hinttext>Enter search string</hinttext>
+ <font>font14</font>
+ <textoffsetx>10</textoffsetx>
+ <textcolor>FFB2D4F5</textcolor>
+ <disabledcolor>FF000000</disabledcolor>
+ <invalidcolor>FFFFFFFF</invalidcolor>
+ <texturefocus>button-focus.png</texturefocus>
+ <texturenofocus>button-nofocus.png</texturenofocus>
+ <pulseonselect>no</pulseonselect>
+ </control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section skin_Edit_control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| aligny | Can be top or center. Aligns the text within its given control <b>`<height>`</b>. Defaults to top
+| label | Specifies the header text which should be shown. You should specify an entry from the **strings.po** here (either the Kodi strings.po or your skin's strings.po file), however you may also hardcode a piece of text also if you wish, though of course it will not be localized. You can use the full [label formatting syntax](http://kodi.wiki/view/Label_Formatting) and [you may also specify more than one piece of information here by using the $INFO and $LOCALIZE formats.strings.po](http://kodi.wiki/view/Label_Parsing))
+| hinttext | Specifies the text which should be displayed in the edit label control, until the user enters some text. It can be used to provide a clue as to what a user should enter in this control.
+| font | Specifies the font to use from the **font.xml** file.
+| textcolor | Specifies the color the text should be, in hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| textwidth | Will truncate any text that's too long.
+
+
+--------------------------------------------------------------------------------
+\section skin_Edit_control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIEditControl.h b/xbmc/guilib/GUIEditControl.h
new file mode 100644
index 0000000..9f44f8c
--- /dev/null
+++ b/xbmc/guilib/GUIEditControl.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIEditControl.h
+\brief
+*/
+
+#include "GUIButtonControl.h"
+#include "utils/Stopwatch.h"
+#include "utils/StringValidation.h"
+#include "utils/Variant.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+
+class CGUIEditControl : public CGUIButtonControl
+{
+public:
+ enum INPUT_TYPE {
+ INPUT_TYPE_READONLY = -1,
+ INPUT_TYPE_TEXT = 0,
+ INPUT_TYPE_NUMBER,
+ INPUT_TYPE_SECONDS,
+ INPUT_TYPE_TIME,
+ INPUT_TYPE_DATE,
+ INPUT_TYPE_IPADDRESS,
+ INPUT_TYPE_PASSWORD,
+ INPUT_TYPE_PASSWORD_MD5,
+ INPUT_TYPE_SEARCH,
+ INPUT_TYPE_FILTER,
+ INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW
+ };
+
+ CGUIEditControl(int parentID, int controlID, float posX, float posY,
+ float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
+ const CLabelInfo& labelInfo, const std::string &text);
+ explicit CGUIEditControl(const CGUIButtonControl &button);
+ ~CGUIEditControl(void) override;
+ CGUIEditControl* Clone() const override { return new CGUIEditControl(*this); }
+
+ bool OnMessage(CGUIMessage &message) override;
+ bool OnAction(const CAction &action) override;
+ void OnClick() override;
+
+ void SetLabel(const std::string &text) override;
+ void SetLabel2(const std::string &text) override;
+ void SetHint(const KODI::GUILIB::GUIINFO::CGUIInfoLabel& hint);
+
+ std::string GetLabel2() const override;
+
+ unsigned int GetCursorPosition() const;
+ void SetCursorPosition(unsigned int iPosition);
+
+ void SetInputType(INPUT_TYPE type, const CVariant& heading);
+
+ void SetTextChangeActions(const CGUIAction& textChangeActions)
+ {
+ m_textChangeActions = textChangeActions;
+ }
+
+ bool HasTextChangeActions() const { return m_textChangeActions.HasActionsMeetingCondition(); }
+
+ virtual bool HasInvalidInput() const { return m_invalidInput; }
+ virtual void SetInputValidation(StringValidation::Validator inputValidator, void *data = NULL);
+
+protected:
+ void SetFocus(bool focus) override;
+ void ProcessText(unsigned int currentTime) override;
+ void RenderText() override;
+ CGUILabel::COLOR GetTextColor() const override;
+ std::wstring GetDisplayedText() const;
+ std::string GetDescriptionByIndex(int index) const override;
+ bool SetStyledText(const std::wstring &text);
+ void RecalcLabelPosition();
+ void ValidateCursor();
+ void UpdateText(bool sendUpdate = true);
+ void OnPasteClipboard();
+ void OnSMSCharacter(unsigned int key);
+ void DefaultConstructor();
+
+ virtual bool ValidateInput(const std::wstring &data) const;
+ void ValidateInput();
+
+ /*! \brief Clear out the current text input if it's an MD5 password.
+ \return true if the password is cleared, false otherwise.
+ */
+ bool ClearMD5();
+
+ std::wstring m_text2;
+ std::string m_text;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_hintInfo;
+ float m_textOffset;
+ float m_textWidth;
+ CRect m_clipRect; ///< clipping rect for the second label
+
+ static const int spaceWidth = 5;
+
+ unsigned int m_cursorPos;
+ unsigned int m_cursorBlink;
+
+ std::string m_inputHeading;
+ INPUT_TYPE m_inputType;
+ bool m_isMD5;
+
+ CGUIAction m_textChangeActions;
+
+ bool m_invalidInput;
+ StringValidation::Validator m_inputValidator;
+ void *m_inputValidatorData;
+
+ unsigned int m_smsKeyIndex;
+ unsigned int m_smsLastKey;
+ CStopWatch m_smsTimer;
+
+ std::wstring m_edit;
+ int m_editOffset;
+ int m_editLength;
+
+ static const char* smsLetters[10];
+ static const unsigned int smsDelay;
+};
diff --git a/xbmc/guilib/GUIFadeLabelControl.cpp b/xbmc/guilib/GUIFadeLabelControl.cpp
new file mode 100644
index 0000000..5855f3c
--- /dev/null
+++ b/xbmc/guilib/GUIFadeLabelControl.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFadeLabelControl.h"
+
+#include "GUIMessage.h"
+#include "utils/Random.h"
+
+using namespace KODI::GUILIB;
+
+CGUIFadeLabelControl::CGUIFadeLabelControl(int parentID, int controlID, float posX, float posY, float width, float height, const CLabelInfo& labelInfo, bool scrollOut, unsigned int timeToDelayAtEnd, bool resetOnLabelChange, bool randomized)
+ : CGUIControl(parentID, controlID, posX, posY, width, height), m_label(labelInfo), m_scrollInfo(50, labelInfo.offsetX, labelInfo.scrollSpeed)
+ , m_textLayout(labelInfo.font, false)
+ , m_fadeAnim(CAnimation::CreateFader(100, 0, timeToDelayAtEnd, 200))
+{
+ m_currentLabel = 0;
+ ControlType = GUICONTROL_FADELABEL;
+ m_scrollOut = scrollOut;
+ m_fadeAnim.ApplyAnimation();
+ m_lastLabel = -1;
+ m_scrollSpeed = labelInfo.scrollSpeed; // save it for later
+ m_resetOnLabelChange = resetOnLabelChange;
+ m_shortText = true;
+ m_scroll = true;
+ m_randomized = randomized;
+}
+
+CGUIFadeLabelControl::CGUIFadeLabelControl(const CGUIFadeLabelControl &from)
+: CGUIControl(from), m_infoLabels(from.m_infoLabels), m_label(from.m_label), m_scrollInfo(from.m_scrollInfo), m_textLayout(from.m_textLayout),
+ m_fadeAnim(from.m_fadeAnim)
+{
+ m_scrollOut = from.m_scrollOut;
+ m_scrollSpeed = from.m_scrollSpeed;
+ m_resetOnLabelChange = from.m_resetOnLabelChange;
+
+ m_fadeAnim.ApplyAnimation();
+ m_currentLabel = 0;
+ m_lastLabel = -1;
+ ControlType = GUICONTROL_FADELABEL;
+ m_shortText = from.m_shortText;
+ m_scroll = from.m_scroll;
+ m_randomized = from.m_randomized;
+ m_allLabelsShown = from.m_allLabelsShown;
+}
+
+CGUIFadeLabelControl::~CGUIFadeLabelControl(void) = default;
+
+void CGUIFadeLabelControl::SetInfo(const std::vector<GUIINFO::CGUIInfoLabel> &infoLabels)
+{
+ m_lastLabel = -1;
+ m_infoLabels = infoLabels;
+ m_allLabelsShown = m_infoLabels.empty();
+ if (m_randomized)
+ KODI::UTILS::RandomShuffle(m_infoLabels.begin(), m_infoLabels.end());
+}
+
+void CGUIFadeLabelControl::AddLabel(const std::string &label)
+{
+ m_infoLabels.emplace_back(label, "", GetParentID());
+ m_allLabelsShown = false;
+}
+
+void CGUIFadeLabelControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_infoLabels.empty() || !m_label.font)
+ {
+ CGUIControl::Process(currentTime, dirtyregions);
+ return;
+ }
+
+ if (m_currentLabel >= m_infoLabels.size() )
+ m_currentLabel = 0;
+
+ if (m_textLayout.Update(GetLabel()))
+ { // changed label - update our suffix based on length of available text
+ float width, height;
+ m_textLayout.GetTextExtent(width, height);
+ float spaceWidth = m_label.font->GetCharWidth(L' ');
+ unsigned int numSpaces = (unsigned int)(m_width / spaceWidth) + 1;
+ if (width < m_width) // append spaces for scrolling
+ numSpaces += (unsigned int)((m_width - width) / spaceWidth) + 1;
+ m_shortText = (width + m_label.offsetX) < m_width;
+ m_scrollInfo.m_suffix.assign(numSpaces, ' ');
+ if (m_resetOnLabelChange)
+ {
+ m_scrollInfo.Reset();
+ m_fadeAnim.ResetAnimation();
+ }
+ MarkDirtyRegion();
+ }
+
+ if (m_shortText && m_infoLabels.size() == 1)
+ m_allLabelsShown = true;
+
+ if (m_currentLabel != m_lastLabel)
+ { // new label - reset scrolling
+ m_scrollInfo.Reset();
+ m_fadeAnim.QueueAnimation(ANIM_PROCESS_REVERSE);
+ m_lastLabel = m_currentLabel;
+ MarkDirtyRegion();
+ }
+
+ if (m_infoLabels.size() > 1 || !m_shortText)
+ { // have scrolling text
+ bool moveToNextLabel = false;
+ if (!m_scrollOut)
+ {
+ if (m_scrollInfo.m_pixelPos + m_width > m_scrollInfo.m_textWidth)
+ {
+ if (m_fadeAnim.GetProcess() != ANIM_PROCESS_NORMAL)
+ m_fadeAnim.QueueAnimation(ANIM_PROCESS_NORMAL);
+ moveToNextLabel = true;
+ }
+ }
+ else if (m_scrollInfo.m_pixelPos > m_scrollInfo.m_textWidth)
+ moveToNextLabel = true;
+
+ if (m_scrollInfo.m_pixelSpeed || m_fadeAnim.GetState() == ANIM_STATE_IN_PROCESS)
+ MarkDirtyRegion();
+
+ // apply the fading animation
+ TransformMatrix matrix;
+ m_fadeAnim.Animate(currentTime, true);
+ m_fadeAnim.RenderAnimation(matrix);
+ m_fadeMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(matrix);
+
+ if (m_fadeAnim.GetState() == ANIM_STATE_APPLIED)
+ m_fadeAnim.ResetAnimation();
+
+ m_scrollInfo.SetSpeed((m_fadeAnim.GetProcess() == ANIM_PROCESS_NONE) ? m_scrollSpeed : 0);
+
+ if (moveToNextLabel)
+ { // increment the label and reset scrolling
+ if (m_fadeAnim.GetProcess() != ANIM_PROCESS_NORMAL)
+ {
+ if (++m_currentLabel >= m_infoLabels.size())
+ {
+ m_currentLabel = 0;
+ m_allLabelsShown = true;
+ }
+ m_scrollInfo.Reset();
+ m_fadeAnim.QueueAnimation(ANIM_PROCESS_REVERSE);
+ }
+ }
+
+ if (m_scroll)
+ {
+ m_textLayout.UpdateScrollinfo(m_scrollInfo);
+ MarkDirtyRegion();
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+bool CGUIFadeLabelControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+
+ return changed;
+}
+
+void CGUIFadeLabelControl::Render()
+{
+ if (!m_label.font)
+ { // nothing to render
+ CGUIControl::Render();
+ return ;
+ }
+
+ float posY = m_posY;
+ if (m_label.align & XBFONT_CENTER_Y)
+ posY += m_height * 0.5f;
+ if (m_infoLabels.size() == 1 && m_shortText)
+ { // single label set and no scrolling required - just display
+ float posX = m_posX + m_label.offsetX;
+ if (m_label.align & XBFONT_CENTER_X)
+ posX = m_posX + m_width * 0.5f;
+ else if (m_label.align & XBFONT_RIGHT)
+ posX = m_posX + m_width;
+ m_textLayout.Render(posX, posY, m_label.angle, m_label.textColor, m_label.shadowColor, m_label.align, m_width - m_label.offsetX);
+ CGUIControl::Render();
+ return;
+ }
+
+ // render the scrolling text
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_fadeMatrix);
+ if (!m_scroll || (!m_scrollOut && m_shortText))
+ {
+ float posX = m_posX + m_label.offsetX;
+ if (m_label.align & XBFONT_CENTER_X)
+ posX = m_posX + m_width * 0.5f;
+ else if (m_label.align & XBFONT_RIGHT)
+ posX = m_posX + m_width;
+ m_textLayout.Render(posX, posY, 0, m_label.textColor, m_label.shadowColor, m_label.align, m_width);
+ }
+ else
+ m_textLayout.RenderScrolling(m_posX, posY, 0, m_label.textColor, m_label.shadowColor, (m_label.align & ~3), m_width, m_scrollInfo);
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ CGUIControl::Render();
+}
+
+
+bool CGUIFadeLabelControl::CanFocus() const
+{
+ return false;
+}
+
+
+bool CGUIFadeLabelControl::OnMessage(CGUIMessage& message)
+{
+ if ( message.GetControlId() == GetID() )
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_ADD)
+ {
+ AddLabel(message.GetLabel());
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ m_lastLabel = -1;
+ m_infoLabels.clear();
+ m_allLabelsShown = true;
+ m_scrollInfo.Reset();
+ return true;
+ }
+ if (message.GetMessage() == GUI_MSG_LABEL_SET)
+ {
+ m_lastLabel = -1;
+ m_infoLabels.clear();
+ m_allLabelsShown = true;
+ m_scrollInfo.Reset();
+ AddLabel(message.GetLabel());
+ return true;
+ }
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+std::string CGUIFadeLabelControl::GetDescription() const
+{
+ return (m_currentLabel < m_infoLabels.size()) ? m_infoLabels[m_currentLabel].GetLabel(m_parentID) : "";
+}
+
+std::string CGUIFadeLabelControl::GetLabel()
+{
+ if (m_currentLabel > m_infoLabels.size())
+ m_currentLabel = 0;
+
+ unsigned int numTries = 0;
+ std::string label(m_infoLabels[m_currentLabel].GetLabel(m_parentID));
+ while (label.empty() && ++numTries < m_infoLabels.size())
+ {
+ if (++m_currentLabel >= m_infoLabels.size())
+ m_currentLabel = 0;
+ label = m_infoLabels[m_currentLabel].GetLabel(m_parentID);
+ }
+ return label;
+}
diff --git a/xbmc/guilib/GUIFadeLabelControl.dox b/xbmc/guilib/GUIFadeLabelControl.dox
new file mode 100644
index 0000000..a64ab30
--- /dev/null
+++ b/xbmc/guilib/GUIFadeLabelControl.dox
@@ -0,0 +1,72 @@
+/*!
+
+\page Fade_Label_Control Fade label control
+\brief **Used to show multiple pieces of text in the same position, by fading from one to the other.**
+
+\tableofcontents
+
+The fade label control is used for displaying multiple pieces of text in the
+same space in Kodi. You can choose the font, size, colour, location and contents
+of the text to be displayed. The first piece of information to display fades in
+over 50 frames, then scrolls off to the left. Once it is finished scrolling off
+screen, the second piece of information fades in and the process repeats. A fade
+label control is not supported in a list container.
+
+--------------------------------------------------------------------------------
+\section Fade_Label_Control_sect1 Example
+
+~~~~~~~~~~~~~
+ <control type="fadelabel" id="1">
+ <description>My First fadelabel</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <visible>true</visible>
+ <scrollout>true</scrollout>
+ <pauseatend>200</pauseatend>
+ <label>6</label>
+ <info>MusicPlayer.Genre</info>
+ <info>MusicPlayer.Artist</info>
+ <info>MusicPlayer.Album</info>
+ <info>MusicPlayer.Year</info>
+ <font>font14</font>
+ <textcolor>FFB2D4F5</textcolor>
+ <textoffsetx>20</textoffsetx>
+ <scroll>true</scroll>
+ <randomize>true</randomize>
+ </control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Fade_Label_Control_sect2 Tag descriptions
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------------:|:--------------------------------------------------------------|
+| label | Specifies the text which should be drawn. You should specify an entry from the strings.po here, however you may also specify a piece of text yourself if you wish, though of course it will not be localisable. [You may also specify more than one piece of information here by using the $INFO and $LOCALIZE formats](http://kodi.wiki/view/Label_Parsing).
+| info | Specifies the information that should be presented. Kodi will auto-fill in this info in place of the <b>`<label>`</b>. [See here for more information](http://kodi.wiki/view/InfoLabels).
+| font | Specifies the font to use from the font.xml file.
+| textcolor | Specified the color the text should be, in hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| textoffsetx | Specify the offset from the left edge that the text should be rendered at when static (not scrolling). The scrolling text will still scroll using the full <b>`<width>`</b> of the control.
+| shadowcolor | Specifies the color of the drop shadow on the text, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| angle | Specifies the angle at which the text should be drawn, measured counter clockwise from the horizontal.
+| scrollout | If set to False the fadelabel will only scroll until the last char is to the right side of the width of the fadelabel instead of all the way out to the left.
+| pauseatend | Specifies the time that the text will wait until it fades away before it scrolls again or moves to the next item.
+| resetonlabelchange | If set to false the fadelabel will not reset the scrolling offset when the label's content changes. Useful if you have things such as the play time (in seconds) inside a fadelabel. Defaults to true.
+| scrollspeed | Scroll speed of text in pixels per second. Defaults to 60.
+| scroll | If set to false, the labels won't scroll. Defaults to true.
+| randomize | If set to true, the labels will be displayed in a random order. Defaults to false.
+
+
+--------------------------------------------------------------------------------
+\section Fade_Label_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIFadeLabelControl.h b/xbmc/guilib/GUIFadeLabelControl.h
new file mode 100644
index 0000000..9f5f7ab
--- /dev/null
+++ b/xbmc/guilib/GUIFadeLabelControl.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFadeLabelControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIFadeLabelControl : public CGUIControl
+{
+public:
+ CGUIFadeLabelControl(int parentID, int controlID, float posX, float posY, float width, float height, const CLabelInfo& labelInfo, bool scrollOut, unsigned int timeToDelayAtEnd, bool resetOnLabelChange, bool randomized);
+ CGUIFadeLabelControl(const CGUIFadeLabelControl &from);
+ ~CGUIFadeLabelControl(void) override;
+ CGUIFadeLabelControl* Clone() const override { return new CGUIFadeLabelControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool CanFocus() const override;
+ bool OnMessage(CGUIMessage& message) override;
+
+ void SetInfo(const std::vector<KODI::GUILIB::GUIINFO::CGUIInfoLabel> &vecInfo);
+ void SetScrolling(bool scroll) { m_scroll = scroll; }
+
+ bool AllLabelsShown() const { return m_allLabelsShown; }
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ std::string GetDescription() const override;
+ void AddLabel(const std::string &label);
+
+ /*! \brief retrieve the current label for display
+
+ The fadelabel has multiple labels which it cycles through. This routine retrieves the current label.
+ It first checks the current label and if non-empty returns it. Otherwise it will iterate through all labels
+ until it has a non-empty label to return.
+
+ \return the label that should be displayed. If empty, there is no label available.
+ */
+ std::string GetLabel();
+
+ std::vector<KODI::GUILIB::GUIINFO::CGUIInfoLabel> m_infoLabels;
+ unsigned int m_currentLabel;
+ unsigned int m_lastLabel;
+
+ CLabelInfo m_label;
+
+ bool m_scroll; // true if we scroll the text
+ bool m_scrollOut; // true if we scroll the text all the way to the left before fading in the next label
+ bool m_shortText; // true if the text we have is shorter than the width of the control
+
+ CScrollInfo m_scrollInfo;
+ CGUITextLayout m_textLayout;
+ CAnimation m_fadeAnim;
+ TransformMatrix m_fadeMatrix;
+ unsigned int m_scrollSpeed;
+ bool m_resetOnLabelChange;
+ bool m_randomized;
+ bool m_allLabelsShown = true;
+};
+
diff --git a/xbmc/guilib/GUIFixedListContainer.cpp b/xbmc/guilib/GUIFixedListContainer.cpp
new file mode 100644
index 0000000..0295736
--- /dev/null
+++ b/xbmc/guilib/GUIFixedListContainer.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFixedListContainer.h"
+
+#include "GUIListItemLayout.h"
+#include "input/Key.h"
+
+CGUIFixedListContainer::CGUIFixedListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition, int cursorRange)
+ : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
+{
+ ControlType = GUICONTAINER_FIXEDLIST;
+ m_type = VIEW_TYPE_LIST;
+ m_fixedCursor = fixedPosition;
+ m_cursorRange = std::max(0, cursorRange);
+ SetCursor(m_fixedCursor);
+}
+
+CGUIFixedListContainer::~CGUIFixedListContainer(void) = default;
+
+bool CGUIFixedListContainer::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PAGE_UP:
+ {
+ Scroll(-m_itemsPerPage);
+ return true;
+ }
+ break;
+ case ACTION_PAGE_DOWN:
+ {
+ Scroll(m_itemsPerPage);
+ return true;
+ }
+ break;
+ // smooth scrolling (for analog controls)
+ case ACTION_SCROLL_UP:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ Scroll(-1);
+ }
+ return handled;
+ }
+ break;
+ case ACTION_SCROLL_DOWN:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ Scroll(1);
+ }
+ return handled;
+ }
+ break;
+ }
+ return CGUIBaseContainer::OnAction(action);
+}
+
+bool CGUIFixedListContainer::MoveUp(bool wrapAround)
+{
+ int item = GetSelectedItem();
+ if (item > 0)
+ SelectItem(item - 1);
+ else if (wrapAround)
+ {
+ SelectItem((int)m_items.size() - 1);
+ SetContainerMoving(-1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIFixedListContainer::MoveDown(bool wrapAround)
+{
+ int item = GetSelectedItem();
+ if (item < (int)m_items.size() - 1)
+ SelectItem(item + 1);
+ else if (wrapAround)
+ { // move first item in list
+ SelectItem(0);
+ SetContainerMoving(1);
+ }
+ else
+ return false;
+ return true;
+}
+
+void CGUIFixedListContainer::Scroll(int amount)
+{
+ // increase or decrease the offset within [-minCursor, m_items.size() - maxCursor]
+ int minCursor, maxCursor;
+ GetCursorRange(minCursor, maxCursor);
+ const int nextCursor = GetCursor() + amount;
+ int offset = GetOffset() + amount;
+ if (offset < -minCursor)
+ {
+ offset = -minCursor;
+ SetCursor(nextCursor < minCursor ? minCursor : nextCursor);
+ }
+ if (offset > (int)m_items.size() - 1 - maxCursor)
+ {
+ offset = m_items.size() - 1 - maxCursor;
+ SetCursor(nextCursor > maxCursor ? maxCursor : nextCursor);
+ }
+ ScrollToOffset(offset);
+}
+
+bool CGUIFixedListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
+{
+ GetCursorRange(minOffset, maxOffset);
+ minOffset = -minOffset;
+ maxOffset = m_items.size() - maxOffset - 1;
+ return true;
+}
+
+void CGUIFixedListContainer::ValidateOffset()
+{
+ if (!m_layout) return;
+ // ensure our fixed cursor position is valid
+ if (m_fixedCursor >= m_itemsPerPage)
+ m_fixedCursor = m_itemsPerPage - 1;
+ if (m_fixedCursor < 0)
+ m_fixedCursor = 0;
+ // compute our minimum and maximum cursor positions
+ int minCursor, maxCursor;
+ GetCursorRange(minCursor, maxCursor);
+ // assure our cursor is between these limits
+ SetCursor(std::max(GetCursor(), minCursor));
+ SetCursor(std::min(GetCursor(), maxCursor));
+ int minOffset, maxOffset;
+ GetOffsetRange(minOffset, maxOffset);
+ // and finally ensure our offset is valid
+ // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
+ if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
+ {
+ SetOffset(std::max(-minCursor, maxOffset));
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+ }
+ if (GetOffset() < minOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() < minOffset * m_layout->Size(m_orientation)))
+ {
+ SetOffset(minOffset);
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+ }
+}
+
+int CGUIFixedListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
+{
+ if (!m_focusedLayout || !m_layout)
+ return -1;
+ int minCursor, maxCursor;
+ GetCursorRange(minCursor, maxCursor);
+ // see if the point is either side of our focus range
+ float start = (minCursor + 0.2f) * m_layout->Size(m_orientation);
+ float end = (maxCursor - 0.2f) * m_layout->Size(m_orientation) + m_focusedLayout->Size(m_orientation);
+ float pos = (m_orientation == VERTICAL) ? point.y : point.x;
+ if (pos >= start && pos <= end)
+ { // select the appropriate item
+ pos -= minCursor * m_layout->Size(m_orientation);
+ for (int row = minCursor; row <= maxCursor; row++)
+ {
+ const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
+ if (pos < layout->Size(m_orientation))
+ {
+ if (!InsideLayout(layout, point))
+ return -1;
+ return row;
+ }
+ pos -= layout->Size(m_orientation);
+ }
+ }
+ return -1;
+}
+
+bool CGUIFixedListContainer::SelectItemFromPoint(const CPoint &point)
+{
+ if (!m_focusedLayout || !m_layout)
+ return false;
+
+ MarkDirtyRegion();
+
+ const float mouse_scroll_speed = 0.25f;
+ const float mouse_max_amount = 1.5f;
+ float sizeOfItem = m_layout->Size(m_orientation);
+ int minCursor, maxCursor;
+ GetCursorRange(minCursor, maxCursor);
+ // see if the point is either side of our focus range
+ float start = (minCursor + 0.2f) * sizeOfItem;
+ float end = (maxCursor - 0.2f) * sizeOfItem + m_focusedLayout->Size(m_orientation);
+ float pos = (m_orientation == VERTICAL) ? point.y : point.x;
+ if (pos < start && GetOffset() > -minCursor)
+ { // scroll backward
+ if (!InsideLayout(m_layout, point))
+ return false;
+ float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
+ m_analogScrollCount += amount * amount * mouse_scroll_speed;
+ if (m_analogScrollCount > 1)
+ {
+ ScrollToOffset(GetOffset() - 1);
+ m_analogScrollCount = 0;
+ }
+ return true;
+ }
+ else if (pos > end && GetOffset() + maxCursor < (int)m_items.size() - 1)
+ {
+ if (!InsideLayout(m_layout, point))
+ return false;
+ // scroll forward
+ float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
+ m_analogScrollCount += amount * amount * mouse_scroll_speed;
+ if (m_analogScrollCount > 1)
+ {
+ ScrollToOffset(GetOffset() + 1);
+ m_analogScrollCount = 0;
+ }
+ return true;
+ }
+ else
+ { // select the appropriate item
+ int cursor = GetCursorFromPoint(point);
+ if (cursor < 0)
+ return false;
+ // calling SelectItem() here will focus the item and scroll, which isn't really what we're after
+ SetCursor(cursor);
+ return true;
+ }
+}
+
+void CGUIFixedListContainer::SelectItem(int item)
+{
+ // Check that GetOffset() is valid
+ ValidateOffset();
+ // only select an item if it's in a valid range
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ // Select the item requested - we first set the cursor position
+ // which may be different at either end of the list, then the offset
+ int minCursor, maxCursor;
+ GetCursorRange(minCursor, maxCursor);
+
+ int cursor;
+ if ((int)m_items.size() - 1 - item <= maxCursor - m_fixedCursor)
+ cursor = std::max(m_fixedCursor, maxCursor + item - (int)m_items.size() + 1);
+ else if (item <= m_fixedCursor - minCursor)
+ cursor = std::min(m_fixedCursor, minCursor + item);
+ else
+ cursor = m_fixedCursor;
+ if (cursor != GetCursor())
+ SetContainerMoving(cursor - GetCursor());
+ SetCursor(cursor);
+ ScrollToOffset(item - GetCursor());
+ MarkDirtyRegion();
+ }
+}
+
+bool CGUIFixedListContainer::HasPreviousPage() const
+{
+ return (GetOffset() > 0);
+}
+
+bool CGUIFixedListContainer::HasNextPage() const
+{
+ return (GetOffset() < (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
+}
+
+int CGUIFixedListContainer::GetCurrentPage() const
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page
+ return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
+ return offset / m_itemsPerPage + 1;
+}
+
+void CGUIFixedListContainer::GetCursorRange(int &minCursor, int &maxCursor) const
+{
+ minCursor = std::max(m_fixedCursor - m_cursorRange, 0);
+ maxCursor = std::min(m_fixedCursor + m_cursorRange, m_itemsPerPage);
+
+ if (!m_items.size())
+ {
+ minCursor = m_fixedCursor;
+ maxCursor = m_fixedCursor;
+ return;
+ }
+
+ while (maxCursor - minCursor > (int)m_items.size() - 1)
+ {
+ if (maxCursor - m_fixedCursor > m_fixedCursor - minCursor)
+ maxCursor--;
+ else
+ minCursor++;
+ }
+}
+
diff --git a/xbmc/guilib/GUIFixedListContainer.dox b/xbmc/guilib/GUIFixedListContainer.dox
new file mode 100644
index 0000000..b9e7726
--- /dev/null
+++ b/xbmc/guilib/GUIFixedListContainer.dox
@@ -0,0 +1,142 @@
+/*!
+
+\page Fixed_List_Container Fixed List Container
+\brief <b>Used for a list of items with a fixed focus. Same as the
+\ref Wrap_List_Container "Wrap List Container" except it doesn't wrap.</b>
+
+\tableofcontents
+
+The fixed list container is one of several containers used to display items from
+file lists in various ways. The fixed list container is the same as the
+[List Container](http://kodi.wiki/view/List_Container), with one exception: the
+focused item is fixed. Thus, moving up or down scrolls the items, not the focused
+position. As with all container controls, the layout of the items within the
+control is very flexible.
+
+~~~~~~~~~~~~~
+ <control type="fixedlist" id="50">
+ <description>My first fixed list container</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ <viewtype label="3D list">list</viewtype>
+ <orientation>vertical</orientation>
+ <pagecontrol>25</pagecontrol>
+ <scrolltime tween="sine" easing="out">200</scrolltime>
+ <autoscroll>true</autoscroll>
+ <focusposition>4</focusposition>
+ <movement>6</movement>
+ <itemlayout width="250" height="29">
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </itemlayout>
+ <focusedlayout height="29" width="250">
+ <control type="image">
+ <width>485</width>
+ <height>29</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <visible>Control.HasFocus(50)</visible>
+ <texture>list-focus.png</texture>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </focusedlayout>
+ </control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Fixed_List_Container_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| viewtype | The type of view. Choices are list, icon, wide, wrap, biglist, bigicon, bigwide, bigwrap, info and biginfo. The label attribute indicates the label that will be used in the "View As" control within the GUI. It is localizable via **strings.po**. *viewtype* has no effect on the view itself. It is used by kodi when switching skin to automatically select a view with a similar layout. Skinners should try to set viewtype to describe the layout as best as possible.
+| orientation | The orientation of the list. Defaults to vertical.
+| pagecontrol | Used to set the <b>`<id>`</b> of the page control used to control this list.
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is 200ms. The list will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling. The scroll movement can be further adjusted by selecting one of the available [tween](http://kodi.wiki/view/Tweeners) methods.
+| focusposition | Specifies the focus position (from 0 -> number of displayable items - 1). The focused position doesn't change - instead, the items move up or down (or left and right) as focus changes.
+| movement | This will make the focused position scroll again at the end of the list. (ie. <b>`<movement>6</movement>`</b> if there are only 6 items below the focus, the focus moves down to the bottom)
+| itemlayout | Specifies the layout of items in the list. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<itemlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| focusedlayout | Specifies the layout of items in the list that have focus. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<focusedlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| content | Used to set the item content that this list will contain. Allows the skinner to setup a list anywhere they want with a static set of content, as a useful alternative to the grouplist control. [See here for more information](http://kodi.wiki/view/Static_List_Content).
+| preloaditems | Used in association with the background image loader. [See here for more information](http://kodi.wiki/view/Background_Image_Loader).
+| autoscroll | Used to make the container scroll automatically
+
+
+--------------------------------------------------------------------------------
+\section Fixed_List_Container_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+
+*/
diff --git a/xbmc/guilib/GUIFixedListContainer.h b/xbmc/guilib/GUIFixedListContainer.h
new file mode 100644
index 0000000..b75ef11
--- /dev/null
+++ b/xbmc/guilib/GUIFixedListContainer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFixedListContainer.h
+\brief
+*/
+
+#include "GUIBaseContainer.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIFixedListContainer : public CGUIBaseContainer
+{
+public:
+ CGUIFixedListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition, int cursorRange);
+ ~CGUIFixedListContainer(void) override;
+ CGUIFixedListContainer* Clone() const override { return new CGUIFixedListContainer(*this); }
+
+ bool OnAction(const CAction &action) override;
+
+protected:
+ void Scroll(int amount) override;
+ bool MoveDown(bool wrapAround) override;
+ bool MoveUp(bool wrapAround) override;
+ bool GetOffsetRange(int &minOffset, int &maxOffset) const override;
+ void ValidateOffset() override;
+ bool SelectItemFromPoint(const CPoint &point) override;
+ int GetCursorFromPoint(const CPoint &point, CPoint *itemPoint = NULL) const override;
+ void SelectItem(int item) override;
+ bool HasNextPage() const override;
+ bool HasPreviousPage() const override;
+ int GetCurrentPage() const override;
+
+private:
+ /*!
+ \brief Get the minimal and maximal cursor positions in the list
+
+ As the fixed list cursor position may vary at the ends of the list based
+ on the cursor range, this function computes the minimal and maximal positions
+ based on the number of items in the list
+ \param minCursor the minimal cursor position
+ \param maxCursor the maximal cursor position
+ \sa m_fixedCursor, m_cursorRange
+ */
+ void GetCursorRange(int &minCursor, int &maxCursor) const;
+
+ int m_fixedCursor; ///< default position the skinner wishes to use for the focused item
+ int m_cursorRange; ///< range that the focused item can vary when at the ends of the list
+};
+
diff --git a/xbmc/guilib/GUIFont.cpp b/xbmc/guilib/GUIFont.cpp
new file mode 100644
index 0000000..e39a5b0
--- /dev/null
+++ b/xbmc/guilib/GUIFont.cpp
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFont.h"
+
+#include "GUIFontTTF.h"
+#include "utils/CharsetConverter.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+
+#define ROUND(x) (float)(MathUtils::round_int(x))
+
+CScrollInfo::CScrollInfo(unsigned int wait /* = 50 */,
+ float pos /* = 0 */,
+ int speed /* = defaultSpeed */,
+ const std::string& scrollSuffix /* = " | " */)
+ : m_initialWait(wait), m_initialPos(pos)
+
+{
+ SetSpeed(speed ? speed : defaultSpeed);
+ std::wstring wsuffix;
+ g_charsetConverter.utf8ToW(scrollSuffix, wsuffix);
+ m_suffix.clear();
+ m_suffix.reserve(wsuffix.size());
+ m_suffix = vecText(wsuffix.begin(), wsuffix.end());
+ Reset();
+}
+
+float CScrollInfo::GetPixelsPerFrame()
+{
+ static const float alphaEMA = 0.05f;
+
+ if (0 == m_pixelSpeed)
+ return 0; // not scrolling
+
+ unsigned int currentTime = CTimeUtils::GetFrameTime();
+ float delta =
+ m_lastFrameTime ? static_cast<float>(currentTime - m_lastFrameTime) : m_averageFrameTime;
+ if (delta > 100)
+ delta = 100; // assume a minimum of 10 fps
+ m_lastFrameTime = currentTime;
+ // do an exponential moving average of the frame time
+ if (delta)
+ m_averageFrameTime = m_averageFrameTime + (delta - m_averageFrameTime) * alphaEMA;
+ // and multiply by pixel speed (per ms) to get number of pixels to move this frame
+ return m_pixelSpeed * m_averageFrameTime;
+}
+
+CGUIFont::CGUIFont(const std::string& strFontName,
+ uint32_t style,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ float lineSpacing,
+ float origHeight,
+ CGUIFontTTF* font)
+ : m_strFontName(strFontName)
+{
+ m_style = style & FONT_STYLE_MASK;
+ m_textColor = textColor;
+ m_shadowColor = shadowColor;
+ m_lineSpacing = lineSpacing;
+ m_origHeight = origHeight;
+ m_font = font;
+
+ if (m_font)
+ m_font->AddReference();
+}
+
+CGUIFont::~CGUIFont()
+{
+ if (m_font)
+ m_font->RemoveReference();
+}
+
+std::string& CGUIFont::GetFontName()
+{
+ return m_strFontName;
+}
+
+void CGUIFont::DrawText(float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ UTILS::COLOR::Color shadowColor,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return;
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ bool clip = maxPixelWidth > 0;
+ if (clip && ClippedRegionIsEmpty(context, x, y, maxPixelWidth, alignment))
+ return;
+
+ maxPixelWidth = ROUND(static_cast<double>(maxPixelWidth / context.GetGUIScaleX()));
+ std::vector<UTILS::COLOR::Color> renderColors;
+ renderColors.reserve(colors.size());
+ for (const auto& color : colors)
+ renderColors.emplace_back(context.MergeColor(color ? color : m_textColor));
+ if (!shadowColor)
+ shadowColor = m_shadowColor;
+ if (shadowColor)
+ {
+ shadowColor = context.MergeColor(shadowColor);
+ std::vector<UTILS::COLOR::Color> shadowColors;
+ shadowColors.reserve(renderColors.size());
+ for (const auto& renderColor : renderColors)
+ shadowColors.emplace_back((renderColor & 0xff000000) != 0 ? shadowColor : 0);
+ m_font->DrawTextInternal(context, x + 1, y + 1, shadowColors, text, alignment, maxPixelWidth,
+ false);
+ }
+ m_font->DrawTextInternal(context, x, y, renderColors, text, alignment, maxPixelWidth, false);
+
+ if (clip)
+ context.RestoreClipRegion();
+}
+
+bool CGUIFont::UpdateScrollInfo(const vecText& text, CScrollInfo& scrollInfo)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return false;
+
+ // draw at our scroll position
+ // we handle the scrolling as follows:
+ // We scroll on a per-pixel basis (eschewing the use of character indices
+ // which were also in use previously). The complete string, including suffix,
+ // is plotted to achieve the desired effect - normally just the one time, but
+ // if there is a wrap point within the viewport then it will be plotted twice.
+ // If the string is smaller than the viewport, then it may be plotted even
+ // more times than that.
+ //
+ if (scrollInfo.m_waitTime)
+ {
+ scrollInfo.m_waitTime--;
+ return true;
+ }
+
+ if (text.empty())
+ return false;
+
+ CScrollInfo old(scrollInfo);
+
+ // move along by the appropriate scroll amount
+ float scrollAmount =
+ fabs(scrollInfo.GetPixelsPerFrame() * winSystem->GetGfxContext().GetGUIScaleX());
+
+ if (!scrollInfo.m_widthValid)
+ {
+ /* Calculate the pixel width of the complete string */
+ scrollInfo.m_textWidth = GetTextWidth(text);
+ scrollInfo.m_totalWidth = scrollInfo.m_textWidth + GetTextWidth(scrollInfo.m_suffix);
+ scrollInfo.m_widthValid = true;
+ }
+ scrollInfo.m_pixelPos += scrollAmount;
+ assert(scrollInfo.m_totalWidth != 0);
+ while (scrollInfo.m_pixelPos >= scrollInfo.m_totalWidth)
+ scrollInfo.m_pixelPos -= scrollInfo.m_totalWidth;
+
+ if (scrollInfo.m_pixelPos < old.m_pixelPos)
+ ++scrollInfo.m_loopCount;
+
+ if (scrollInfo.m_pixelPos != old.m_pixelPos)
+ return true;
+
+ return false;
+}
+
+void CGUIFont::DrawScrollingText(float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ UTILS::COLOR::Color shadowColor,
+ const vecText& text,
+ uint32_t alignment,
+ float maxWidth,
+ const CScrollInfo& scrollInfo)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return;
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ if (!shadowColor)
+ shadowColor = m_shadowColor;
+
+ if (!text.size() || ClippedRegionIsEmpty(context, x, y, maxWidth, alignment))
+ return; // nothing to render
+
+ if (!scrollInfo.m_widthValid)
+ {
+ /* Calculate the pixel width of the complete string */
+ scrollInfo.m_textWidth = GetTextWidth(text);
+ scrollInfo.m_totalWidth = scrollInfo.m_textWidth + GetTextWidth(scrollInfo.m_suffix);
+ scrollInfo.m_widthValid = true;
+ }
+
+ assert(scrollInfo.m_totalWidth != 0);
+
+ float textPixelWidth =
+ ROUND(static_cast<double>(scrollInfo.m_textWidth / context.GetGUIScaleX()));
+ float suffixPixelWidth = ROUND(static_cast<double>(
+ (scrollInfo.m_totalWidth - scrollInfo.m_textWidth) / context.GetGUIScaleX()));
+
+ float offset;
+ if (scrollInfo.m_pixelSpeed >= 0)
+ offset = scrollInfo.m_pixelPos;
+ else
+ offset = scrollInfo.m_totalWidth - scrollInfo.m_pixelPos;
+
+ std::vector<UTILS::COLOR::Color> renderColors;
+ renderColors.reserve(colors.size());
+ for (const auto& color : colors)
+ renderColors.emplace_back(context.MergeColor(color ? color : m_textColor));
+
+ bool scroll = !scrollInfo.m_waitTime && scrollInfo.m_pixelSpeed;
+ if (shadowColor)
+ {
+ shadowColor = context.MergeColor(shadowColor);
+ std::vector<UTILS::COLOR::Color> shadowColors;
+ shadowColors.reserve(renderColors.size());
+ for (const auto& renderColor : renderColors)
+ shadowColors.emplace_back((renderColor & 0xff000000) != 0 ? shadowColor : 0);
+ for (float dx = -offset; dx < maxWidth; dx += scrollInfo.m_totalWidth)
+ {
+ m_font->DrawTextInternal(context, x + dx + 1, y + 1, shadowColors, text, alignment,
+ textPixelWidth, scroll);
+ m_font->DrawTextInternal(context, x + dx + scrollInfo.m_textWidth + 1, y + 1, shadowColors,
+ scrollInfo.m_suffix, alignment, suffixPixelWidth, scroll);
+ }
+ }
+ for (float dx = -offset; dx < maxWidth; dx += scrollInfo.m_totalWidth)
+ {
+ m_font->DrawTextInternal(context, x + dx, y, renderColors, text, alignment, textPixelWidth,
+ scroll);
+ m_font->DrawTextInternal(context, x + dx + scrollInfo.m_textWidth, y, renderColors,
+ scrollInfo.m_suffix, alignment, suffixPixelWidth, scroll);
+ }
+
+ context.RestoreClipRegion();
+}
+
+bool CGUIFont::ClippedRegionIsEmpty(
+ CGraphicContext& context, float x, float y, float width, uint32_t alignment) const
+{
+ if (alignment & XBFONT_CENTER_X)
+ x -= width * 0.5f;
+ else if (alignment & XBFONT_RIGHT)
+ x -= width;
+ if (alignment & XBFONT_CENTER_Y)
+ y -= m_font->GetLineHeight(m_lineSpacing);
+
+ return !context.SetClipRegion(x, y, width, m_font->GetTextHeight(1, 2) * context.GetGUIScaleY());
+}
+
+float CGUIFont::GetTextWidth(const vecText& text)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return 0;
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ std::unique_lock<CCriticalSection> lock(context);
+ return m_font->GetTextWidthInternal(text) * context.GetGUIScaleX();
+}
+
+float CGUIFont::GetCharWidth(character_t ch)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return 0;
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ std::unique_lock<CCriticalSection> lock(context);
+ return m_font->GetCharWidthInternal(ch) * context.GetGUIScaleX();
+}
+
+float CGUIFont::GetTextHeight(int numLines) const
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return 0;
+
+ return m_font->GetTextHeight(m_lineSpacing, numLines) * winSystem->GetGfxContext().GetGUIScaleY();
+}
+
+float CGUIFont::GetTextBaseLine() const
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return 0;
+
+ return m_font->GetTextBaseLine() * winSystem->GetGfxContext().GetGUIScaleY();
+}
+
+float CGUIFont::GetLineHeight() const
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!m_font || !winSystem)
+ return 0;
+
+ return m_font->GetLineHeight(m_lineSpacing) * winSystem->GetGfxContext().GetGUIScaleY();
+}
+
+float CGUIFont::GetScaleFactor() const
+{
+ if (!m_font)
+ return 1.0f;
+
+ return m_font->GetFontHeight() / m_origHeight;
+}
+
+void CGUIFont::Begin()
+{
+ if (!m_font)
+ return;
+
+ m_font->Begin();
+}
+
+void CGUIFont::End()
+{
+ if (!m_font)
+ return;
+
+ m_font->End();
+}
+
+void CGUIFont::SetFont(CGUIFontTTF* font)
+{
+ if (m_font == font)
+ return; // no need to update the font if we already have it
+
+ if (m_font)
+ m_font->RemoveReference();
+
+ m_font = font;
+ if (m_font)
+ m_font->AddReference();
+}
diff --git a/xbmc/guilib/GUIFont.h b/xbmc/guilib/GUIFont.h
new file mode 100644
index 0000000..672a395
--- /dev/null
+++ b/xbmc/guilib/GUIFont.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2003-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFont.h
+\brief
+*/
+
+#include "utils/ColorUtils.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+typedef uint32_t character_t;
+typedef std::vector<character_t> vecText;
+
+class CGUIFontTTF;
+class CGraphicContext;
+
+///
+/// \defgroup kodi_gui_font_alignment Font alignment flags
+/// \ingroup python_xbmcgui_control_radiobutton
+/// @{
+/// @brief Flags for alignment
+///
+/// Flags are used as bits to have several together, e.g. `XBFONT_LEFT | XBFONT_CENTER_Y`
+///
+constexpr int XBFONT_LEFT = 0; ///< Align X left
+constexpr int XBFONT_RIGHT = (1 << 0); ///< Align X right
+constexpr int XBFONT_CENTER_X = (1 << 1); ///< Align X center
+constexpr int XBFONT_CENTER_Y = (1 << 2); ///< Align Y center
+constexpr int XBFONT_TRUNCATED = (1 << 3); ///< Truncated text
+constexpr int XBFONT_JUSTIFIED = (1 << 4); ///< Justify text
+/// @}
+
+// flags for font style. lower 16 bits are the unicode code
+// points, 16-24 are color bits and 24-32 are style bits
+constexpr int FONT_STYLE_NORMAL = 0;
+constexpr int FONT_STYLE_BOLD = (1 << 0);
+constexpr int FONT_STYLE_ITALICS = (1 << 1);
+constexpr int FONT_STYLE_LIGHT = (1 << 2);
+constexpr int FONT_STYLE_UPPERCASE = (1 << 3);
+constexpr int FONT_STYLE_LOWERCASE = (1 << 4);
+constexpr int FONT_STYLE_CAPITALIZE = (1 << 5);
+constexpr int FONT_STYLE_MASK = 0xFF;
+constexpr int FONT_STYLES_COUNT = 7;
+
+class CScrollInfo
+{
+public:
+ CScrollInfo(unsigned int wait = 50,
+ float pos = 0,
+ int speed = defaultSpeed,
+ const std::string& scrollSuffix = " | ");
+
+ void SetSpeed(int speed) { m_pixelSpeed = speed * 0.001f; }
+ void Reset()
+ {
+ m_waitTime = m_initialWait;
+ // pixelPos is where we start the current letter, so is measured
+ // to the left of the text rendering's left edge. Thus, a negative
+ // value will mean the text starts to the right
+ m_pixelPos = -m_initialPos;
+ // privates:
+ m_averageFrameTime = 1000.f / fabs((float)defaultSpeed);
+ m_lastFrameTime = 0;
+ m_textWidth = 0;
+ m_totalWidth = 0;
+ m_widthValid = false;
+ m_loopCount = 0;
+ }
+ float GetPixelsPerFrame();
+
+ float m_pixelPos;
+ float m_pixelSpeed;
+ unsigned int m_waitTime;
+ unsigned int m_initialWait;
+ float m_initialPos;
+ vecText m_suffix;
+ mutable float m_textWidth;
+ mutable float m_totalWidth;
+ mutable bool m_widthValid;
+
+ unsigned int m_loopCount;
+
+ static const int defaultSpeed = 60;
+
+private:
+ float m_averageFrameTime;
+ uint32_t m_lastFrameTime;
+};
+
+/*!
+ \ingroup textures
+ \brief
+ */
+class CGUIFont
+{
+public:
+ CGUIFont(const std::string& strFontName,
+ uint32_t style,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ float lineSpacing,
+ float origHeight,
+ CGUIFontTTF* font);
+ virtual ~CGUIFont();
+
+ std::string& GetFontName();
+
+ void DrawText(float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth)
+ {
+ std::vector<UTILS::COLOR::Color> colors;
+ colors.push_back(color);
+ DrawText(x, y, colors, shadowColor, text, alignment, maxPixelWidth);
+ };
+
+ void DrawText(float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ UTILS::COLOR::Color shadowColor,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth);
+
+ void DrawScrollingText(float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ UTILS::COLOR::Color shadowColor,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ const CScrollInfo& scrollInfo);
+
+ bool UpdateScrollInfo(const vecText& text, CScrollInfo& scrollInfo);
+
+ float GetTextWidth(const vecText& text);
+ float GetCharWidth(character_t ch);
+ float GetTextHeight(int numLines) const;
+ float GetTextBaseLine() const;
+ float GetLineHeight() const;
+
+ //! get font scale factor (rendered height / original height)
+ float GetScaleFactor() const;
+
+ void Begin();
+ void End();
+
+ uint32_t GetStyle() const { return m_style; }
+
+ CGUIFontTTF* GetFont() const { return m_font; }
+
+ void SetFont(CGUIFontTTF* font);
+
+protected:
+ std::string m_strFontName;
+ uint32_t m_style;
+ UTILS::COLOR::Color m_shadowColor;
+ UTILS::COLOR::Color m_textColor;
+ float m_lineSpacing;
+ float m_origHeight;
+ CGUIFontTTF* m_font; // the font object has the size information
+
+private:
+ bool ClippedRegionIsEmpty(
+ CGraphicContext& context, float x, float y, float width, uint32_t alignment) const;
+};
diff --git a/xbmc/guilib/GUIFontCache.cpp b/xbmc/guilib/GUIFontCache.cpp
new file mode 100644
index 0000000..70f17ac
--- /dev/null
+++ b/xbmc/guilib/GUIFontCache.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontTTF.h"
+#include "windowing/GraphicContext.h"
+
+#include <stdint.h>
+#include <vector>
+
+using namespace std::chrono_literals;
+
+namespace
+{
+constexpr auto FONT_CACHE_TIME_LIMIT = 1000ms;
+}
+
+template<class Position, class Value>
+class CGUIFontCacheImpl
+{
+ struct EntryList
+ {
+ using HashMap = std::multimap<size_t, std::unique_ptr<CGUIFontCacheEntry<Position, Value>>>;
+ using HashIter = typename HashMap::iterator;
+ using AgeMap = std::multimap<std::chrono::steady_clock::time_point, HashIter>;
+
+ ~EntryList() { Flush(); }
+
+ HashIter Insert(size_t hash, std::unique_ptr<CGUIFontCacheEntry<Position, Value>> v)
+ {
+ auto r(hashMap.insert(typename HashMap::value_type(hash, std::move(v))));
+ if (r->second)
+ ageMap.insert(typename AgeMap::value_type(r->second->m_lastUsed, r));
+
+ return r;
+ }
+ void Flush()
+ {
+ ageMap.clear();
+ hashMap.clear();
+ }
+ typename HashMap::iterator FindKey(CGUIFontCacheKey<Position> key)
+ {
+ CGUIFontCacheHash<Position> hashGen;
+ CGUIFontCacheKeysMatch<Position> keyMatch;
+ auto range = hashMap.equal_range(hashGen(key));
+ for (auto ret = range.first; ret != range.second; ++ret)
+ {
+ if (keyMatch(ret->second->m_key, key))
+ {
+ return ret;
+ }
+ }
+
+ return hashMap.end();
+ }
+ void UpdateAge(HashIter it, std::chrono::steady_clock::time_point now)
+ {
+ auto range = ageMap.equal_range(it->second->m_lastUsed);
+ for (auto ageit = range.first; ageit != range.second; ++ageit)
+ {
+ if (ageit->second == it)
+ {
+ ageMap.erase(ageit);
+ ageMap.insert(typename AgeMap::value_type(now, it));
+ it->second->m_lastUsed = now;
+ return;
+ }
+ }
+ }
+
+ HashMap hashMap;
+ AgeMap ageMap;
+ };
+
+ EntryList m_list;
+ CGUIFontCache<Position, Value>* m_parent;
+
+public:
+ explicit CGUIFontCacheImpl(CGUIFontCache<Position, Value>* parent) : m_parent(parent) {}
+ Value& Lookup(const CGraphicContext& context,
+ Position& pos,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling,
+ std::chrono::steady_clock::time_point now,
+ bool& dirtyCache);
+ void Flush();
+};
+
+template<class Position, class Value>
+CGUIFontCacheEntry<Position, Value>::~CGUIFontCacheEntry()
+{
+ delete &m_key.m_colors;
+ delete &m_key.m_text;
+ m_value.clear();
+}
+
+template<class Position, class Value>
+void CGUIFontCacheEntry<Position, Value>::Assign(const CGUIFontCacheKey<Position>& key,
+ std::chrono::steady_clock::time_point now)
+{
+ m_key.m_pos = key.m_pos;
+ m_key.m_colors.assign(key.m_colors.begin(), key.m_colors.end());
+ m_key.m_text.assign(key.m_text.begin(), key.m_text.end());
+ m_key.m_alignment = key.m_alignment;
+ m_key.m_maxPixelWidth = key.m_maxPixelWidth;
+ m_key.m_scrolling = key.m_scrolling;
+ m_matrix = key.m_matrix;
+ m_key.m_scaleX = key.m_scaleX;
+ m_key.m_scaleY = key.m_scaleY;
+ m_lastUsed = now;
+ m_value.clear();
+}
+
+template<class Position, class Value>
+CGUIFontCache<Position, Value>::CGUIFontCache(CGUIFontTTF& font)
+ : m_impl(std::make_unique<CGUIFontCacheImpl<Position, Value>>(this)), m_font(font)
+{
+}
+
+template<class Position, class Value>
+CGUIFontCache<Position, Value>::~CGUIFontCache() = default;
+
+template<class Position, class Value>
+Value& CGUIFontCache<Position, Value>::Lookup(const CGraphicContext& context,
+ Position& pos,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling,
+ std::chrono::steady_clock::time_point now,
+ bool& dirtyCache)
+{
+ if (!m_impl)
+ m_impl = std::make_unique<CGUIFontCacheImpl<Position, Value>>(this);
+
+ return m_impl->Lookup(context, pos, colors, text, alignment, maxPixelWidth, scrolling, now,
+ dirtyCache);
+}
+
+template<class Position, class Value>
+Value& CGUIFontCacheImpl<Position, Value>::Lookup(const CGraphicContext& context,
+ Position& pos,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling,
+ std::chrono::steady_clock::time_point now,
+ bool& dirtyCache)
+{
+ const CGUIFontCacheKey<Position> key(pos, const_cast<std::vector<UTILS::COLOR::Color>&>(colors),
+ const_cast<vecText&>(text), alignment, maxPixelWidth,
+ scrolling, context.GetGUIMatrix(), context.GetGUIScaleX(),
+ context.GetGUIScaleY());
+
+ auto i = m_list.FindKey(key);
+ if (i == m_list.hashMap.end())
+ {
+ // Cache miss
+ dirtyCache = true;
+ std::unique_ptr<CGUIFontCacheEntry<Position, Value>> entry;
+
+ if (!m_list.ageMap.empty())
+ {
+ const auto duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - m_list.ageMap.begin()->first);
+ if (duration > FONT_CACHE_TIME_LIMIT)
+ {
+ entry = std::move(m_list.ageMap.begin()->second->second);
+ m_list.hashMap.erase(m_list.ageMap.begin()->second);
+ m_list.ageMap.erase(m_list.ageMap.begin());
+ }
+ }
+
+ // add new entry
+ CGUIFontCacheHash<Position> hashgen;
+ if (!entry)
+ entry = std::make_unique<CGUIFontCacheEntry<Position, Value>>(*m_parent, key, now);
+ else
+ entry->Assign(key, now);
+ return m_list.Insert(hashgen(key), std::move(entry))->second->m_value;
+ }
+ else
+ {
+ // Cache hit
+ // Update the translation arguments so that they hold the offset to apply
+ // to the cached values (but only in the dynamic case)
+ pos.UpdateWithOffsets(i->second->m_key.m_pos, scrolling);
+
+ // Update time in entry and move to the back of the list
+ m_list.UpdateAge(i, now);
+
+ dirtyCache = false;
+
+ return i->second->m_value;
+ }
+}
+
+template<class Position, class Value>
+void CGUIFontCache<Position, Value>::Flush()
+{
+ m_impl->Flush();
+}
+
+template<class Position, class Value>
+void CGUIFontCacheImpl<Position, Value>::Flush()
+{
+ m_list.Flush();
+}
+
+template CGUIFontCache<CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue>::CGUIFontCache(
+ CGUIFontTTF& font);
+template CGUIFontCache<CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue>::~CGUIFontCache();
+template CGUIFontCacheEntry<CGUIFontCacheStaticPosition,
+ CGUIFontCacheStaticValue>::~CGUIFontCacheEntry();
+template CGUIFontCacheStaticValue& CGUIFontCache<
+ CGUIFontCacheStaticPosition,
+ CGUIFontCacheStaticValue>::Lookup(const CGraphicContext& context,
+ CGUIFontCacheStaticPosition&,
+ const std::vector<UTILS::COLOR::Color>&,
+ const vecText&,
+ uint32_t,
+ float,
+ bool,
+ std::chrono::steady_clock::time_point,
+ bool&);
+template void CGUIFontCache<CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue>::Flush();
+
+template CGUIFontCache<CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue>::CGUIFontCache(
+ CGUIFontTTF& font);
+template CGUIFontCache<CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue>::~CGUIFontCache();
+template CGUIFontCacheEntry<CGUIFontCacheDynamicPosition,
+ CGUIFontCacheDynamicValue>::~CGUIFontCacheEntry();
+template CGUIFontCacheDynamicValue& CGUIFontCache<
+ CGUIFontCacheDynamicPosition,
+ CGUIFontCacheDynamicValue>::Lookup(const CGraphicContext& context,
+ CGUIFontCacheDynamicPosition&,
+ const std::vector<UTILS::COLOR::Color>&,
+ const vecText&,
+ uint32_t,
+ float,
+ bool,
+ std::chrono::steady_clock::time_point,
+ bool&);
+template void CGUIFontCache<CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue>::Flush();
+
+void CVertexBuffer::clear()
+{
+ if (m_font)
+ m_font->DestroyVertexBuffer(*this);
+}
diff --git a/xbmc/guilib/GUIFontCache.h b/xbmc/guilib/GUIFontCache.h
new file mode 100644
index 0000000..8a967fa
--- /dev/null
+++ b/xbmc/guilib/GUIFontCache.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFontCache.h
+\brief
+*/
+
+#include "utils/ColorUtils.h"
+#include "utils/TransformMatrix.h"
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <cstddef>
+#include <cstring>
+#include <memory>
+#include <stdint.h>
+#include <vector>
+
+constexpr float FONT_CACHE_DIST_LIMIT = 0.01f;
+
+class CGraphicContext;
+
+template<class Position, class Value>
+class CGUIFontCache;
+class CGUIFontTTF;
+
+template<class Position, class Value>
+class CGUIFontCacheImpl;
+
+template<class Position>
+struct CGUIFontCacheKey
+{
+ Position m_pos;
+ std::vector<UTILS::COLOR::Color>& m_colors;
+ vecText& m_text;
+ uint32_t m_alignment;
+ float m_maxPixelWidth;
+ bool m_scrolling;
+ const TransformMatrix& m_matrix;
+ float m_scaleX;
+ float m_scaleY;
+
+ CGUIFontCacheKey(Position pos,
+ std::vector<UTILS::COLOR::Color>& colors,
+ vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling,
+ const TransformMatrix& matrix,
+ float scaleX,
+ float scaleY)
+ : m_pos(pos),
+ m_colors(colors),
+ m_text(text),
+ m_alignment(alignment),
+ m_maxPixelWidth(maxPixelWidth),
+ m_scrolling(scrolling),
+ m_matrix(matrix),
+ m_scaleX(scaleX),
+ m_scaleY(scaleY)
+ {
+ }
+};
+
+template<class Position, class Value>
+struct CGUIFontCacheEntry
+{
+ const CGUIFontCache<Position, Value>& m_cache;
+ CGUIFontCacheKey<Position> m_key;
+ TransformMatrix m_matrix;
+ std::chrono::steady_clock::time_point m_lastUsed;
+ Value m_value;
+
+ CGUIFontCacheEntry(const CGUIFontCache<Position, Value>& cache,
+ const CGUIFontCacheKey<Position>& key,
+ std::chrono::steady_clock::time_point now)
+ : m_cache(cache),
+ m_key(key.m_pos,
+ *new std::vector<UTILS::COLOR::Color>,
+ *new vecText,
+ key.m_alignment,
+ key.m_maxPixelWidth,
+ key.m_scrolling,
+ m_matrix,
+ key.m_scaleX,
+ key.m_scaleY),
+ m_lastUsed(now)
+ {
+ m_key.m_colors.assign(key.m_colors.begin(), key.m_colors.end());
+ m_key.m_text.assign(key.m_text.begin(), key.m_text.end());
+ m_matrix = key.m_matrix;
+ }
+
+ ~CGUIFontCacheEntry();
+
+ void Assign(const CGUIFontCacheKey<Position>& key, std::chrono::steady_clock::time_point now);
+};
+
+template<class Position>
+struct CGUIFontCacheHash
+{
+ size_t operator()(const CGUIFontCacheKey<Position>& key) const
+ {
+ /* Not much effort has gone into choosing this hash function */
+ size_t hash = 0, i;
+ for (i = 0; i < 3 && i < key.m_text.size(); ++i)
+ hash += key.m_text[i];
+ if (key.m_colors.size())
+ hash += key.m_colors[0];
+ hash += static_cast<size_t>(MatrixHashContribution(key)); // horrible
+ return hash;
+ }
+};
+
+template<class Position>
+struct CGUIFontCacheKeysMatch
+{
+ bool operator()(const CGUIFontCacheKey<Position>& a, const CGUIFontCacheKey<Position>& b) const
+ {
+ // clang-format off
+ return a.m_text == b.m_text &&
+ a.m_colors == b.m_colors &&
+ a.m_alignment == b.m_alignment &&
+ a.m_scrolling == b.m_scrolling &&
+ a.m_maxPixelWidth == b.m_maxPixelWidth &&
+ Match(a.m_pos, a.m_matrix, b.m_pos, b.m_matrix, a.m_scrolling) &&
+ a.m_scaleX == b.m_scaleX &&
+ a.m_scaleY == b.m_scaleY;
+ // clang-format on
+ }
+};
+
+
+template<class Position, class Value>
+class CGUIFontCache
+{
+ std::unique_ptr<CGUIFontCacheImpl<Position, Value>> m_impl;
+
+ CGUIFontCache(const CGUIFontCache<Position, Value>&) = delete;
+ const CGUIFontCache<Position, Value>& operator=(const CGUIFontCache<Position, Value>&) = delete;
+
+public:
+ const CGUIFontTTF& m_font;
+
+ explicit CGUIFontCache(CGUIFontTTF& font);
+
+ ~CGUIFontCache();
+
+ Value& Lookup(const CGraphicContext& context,
+ Position& pos,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling,
+ std::chrono::steady_clock::time_point now,
+ bool& dirtyCache);
+ void Flush();
+};
+
+struct CGUIFontCacheStaticPosition
+{
+ float m_x;
+ float m_y;
+ CGUIFontCacheStaticPosition(float x, float y) : m_x(x), m_y(y) {}
+ void UpdateWithOffsets(const CGUIFontCacheStaticPosition& cached, bool scrolling) {}
+};
+
+struct CGUIFontCacheStaticValue : public std::shared_ptr<std::vector<SVertex>>
+{
+ void clear()
+ {
+ if (*this)
+ (*this)->clear();
+ }
+};
+
+inline bool Match(const CGUIFontCacheStaticPosition& a,
+ const TransformMatrix& a_m,
+ const CGUIFontCacheStaticPosition& b,
+ const TransformMatrix& b_m,
+ bool scrolling)
+{
+ return a.m_x == b.m_x && a.m_y == b.m_y && a_m == b_m;
+}
+
+inline float MatrixHashContribution(const CGUIFontCacheKey<CGUIFontCacheStaticPosition>& a)
+{
+ /* Ensure horizontally translated versions end up in different buckets */
+ return a.m_matrix.m[0][3];
+}
+
+struct CGUIFontCacheDynamicPosition
+{
+ float m_x;
+ float m_y;
+ float m_z;
+ CGUIFontCacheDynamicPosition() = default;
+ CGUIFontCacheDynamicPosition(float x, float y, float z) : m_x(x), m_y(y), m_z(z) {}
+ void UpdateWithOffsets(const CGUIFontCacheDynamicPosition& cached, bool scrolling)
+ {
+ if (scrolling)
+ m_x = m_x - cached.m_x;
+ else
+ m_x = floorf(m_x - cached.m_x + FONT_CACHE_DIST_LIMIT);
+ m_y = floorf(m_y - cached.m_y + FONT_CACHE_DIST_LIMIT);
+ m_z = floorf(m_z - cached.m_z + FONT_CACHE_DIST_LIMIT);
+ }
+};
+
+struct CVertexBuffer
+{
+#if defined(HAS_GL) || defined(HAS_GLES)
+ typedef unsigned int BufferHandleType;
+#define BUFFER_HANDLE_INIT 0
+#elif defined(HAS_DX)
+ typedef void* BufferHandleType;
+#define BUFFER_HANDLE_INIT nullptr
+#endif
+ BufferHandleType bufferHandle = BUFFER_HANDLE_INIT; // this is really a GLuint
+ size_t size = 0;
+ CVertexBuffer() : m_font(nullptr) {}
+ CVertexBuffer(BufferHandleType bufferHandle, size_t size, const CGUIFontTTF* font)
+ : bufferHandle(bufferHandle), size(size), m_font(font)
+ {
+ }
+ CVertexBuffer(const CVertexBuffer& other)
+ : bufferHandle(other.bufferHandle), size(other.size), m_font(other.m_font)
+ {
+ /* In practice, the copy constructor is only called before a vertex buffer
+ * has been attached. If this should ever change, we'll need another support
+ * function in GUIFontTTFGL/DX to duplicate a buffer, given its handle. */
+ assert(other.bufferHandle == 0);
+ }
+ CVertexBuffer& operator=(CVertexBuffer& other)
+ {
+ /* This is used with move-assignment semantics for initialising the object in the font cache */
+ assert(bufferHandle == 0);
+ bufferHandle = other.bufferHandle;
+ other.bufferHandle = 0;
+ size = other.size;
+ m_font = other.m_font;
+ return *this;
+ }
+ void clear();
+
+private:
+ const CGUIFontTTF* m_font;
+};
+
+typedef CVertexBuffer CGUIFontCacheDynamicValue;
+
+inline bool Match(const CGUIFontCacheDynamicPosition& a,
+ const TransformMatrix& a_m,
+ const CGUIFontCacheDynamicPosition& b,
+ const TransformMatrix& b_m,
+ bool scrolling)
+{
+ float diffX = a.m_x - b.m_x + FONT_CACHE_DIST_LIMIT;
+ float diffY = a.m_y - b.m_y + FONT_CACHE_DIST_LIMIT;
+ float diffZ = a.m_z - b.m_z + FONT_CACHE_DIST_LIMIT;
+ // clang-format off
+ return (scrolling ||
+ diffX - floorf(diffX) < 2 * FONT_CACHE_DIST_LIMIT) &&
+ diffY - floorf(diffY) < 2 * FONT_CACHE_DIST_LIMIT &&
+ diffZ - floorf(diffZ) < 2 * FONT_CACHE_DIST_LIMIT &&
+ a_m.m[0][0] == b_m.m[0][0] &&
+ a_m.m[1][1] == b_m.m[1][1] &&
+ a_m.m[2][2] == b_m.m[2][2];
+ // clang-format on
+ // We already know the first 3 columns of both matrices are diagonal, so no need to check the other elements
+}
+
+inline float MatrixHashContribution(const CGUIFontCacheKey<CGUIFontCacheDynamicPosition>& a)
+{
+ return 0;
+}
diff --git a/xbmc/guilib/GUIFontManager.cpp b/xbmc/guilib/GUIFontManager.cpp
new file mode 100644
index 0000000..605b9c8
--- /dev/null
+++ b/xbmc/guilib/GUIFontManager.cpp
@@ -0,0 +1,671 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontManager.h"
+
+#include "GUIComponent.h"
+#include "GUIFontTTF.h"
+#include "GUIWindowManager.h"
+#include "addons/AddonManager.h"
+#include "addons/FontResource.h"
+#include "addons/Skin.h"
+#include "addons/addoninfo/AddonType.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+#if defined(HAS_GLES) || defined(HAS_GL)
+#include "GUIFontTTFGL.h"
+#endif
+#include "FileItem.h"
+#include "GUIControlFactory.h"
+#include "GUIFont.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingDefinitions.h"
+#include "utils/FileUtils.h"
+#include "utils/FontUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <set>
+
+#ifdef TARGET_POSIX
+#include "filesystem/SpecialProtocol.h"
+#endif
+
+using namespace ADDON;
+
+namespace
+{
+constexpr const char* XML_FONTCACHE_FILENAME = "fontcache.xml";
+
+bool LoadXMLData(const std::string& filepath, CXBMCTinyXML& xmlDoc)
+{
+ if (!CFileUtils::Exists(filepath))
+ {
+ CLog::LogF(LOGDEBUG, "Couldn't load '{}' the file don't exists", filepath);
+ return false;
+ }
+ if (!xmlDoc.LoadFile(filepath))
+ {
+ CLog::LogF(LOGERROR, "Couldn't load '{}'", filepath);
+ return false;
+ }
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (!pRootElement || pRootElement->ValueStr() != "fonts")
+ {
+ CLog::LogF(LOGERROR, "Couldn't load '{}' XML content doesn't start with <fonts>", filepath);
+ return false;
+ }
+ return true;
+}
+} // unnamed namespace
+
+
+GUIFontManager::GUIFontManager() = default;
+
+GUIFontManager::~GUIFontManager()
+{
+ Clear();
+}
+
+void GUIFontManager::RescaleFontSizeAndAspect(CGraphicContext& context,
+ float* size,
+ float* aspect,
+ const RESOLUTION_INFO& sourceRes,
+ bool preserveAspect)
+{
+ // get the UI scaling constants so that we can scale our font sizes correctly
+ // as fonts aren't scaled at render time (due to aliasing) we must scale
+ // the size of the fonts before they are drawn to bitmaps
+ float scaleX, scaleY;
+ context.GetGUIScaling(sourceRes, scaleX, scaleY);
+
+ if (preserveAspect)
+ {
+ // font always displayed in the aspect specified by the aspect parameter
+ *aspect /= context.GetResInfo().fPixelRatio;
+ }
+ else
+ {
+ // font stretched like the rest of the UI, aspect parameter being the original aspect
+
+ // adjust aspect ratio
+ *aspect *= sourceRes.fPixelRatio;
+
+ *aspect *= scaleY / scaleX;
+ }
+
+ *size /= scaleY;
+}
+
+static bool CheckFont(std::string& strPath, const std::string& newPath, const std::string& filename)
+{
+ if (!CFileUtils::Exists(strPath))
+ {
+ strPath = URIUtils::AddFileToFolder(newPath, filename);
+#ifdef TARGET_POSIX
+ strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
+#endif
+ return false;
+ }
+
+ return true;
+}
+
+CGUIFont* GUIFontManager::LoadTTF(const std::string& strFontName,
+ const std::string& strFilename,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ const int iSize,
+ const int iStyle,
+ bool border,
+ float lineSpacing,
+ float aspect,
+ const RESOLUTION_INFO* sourceRes,
+ bool preserveAspect)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ {
+ CLog::Log(LOGFATAL,
+ "GUIFontManager::{}: Something tries to call function without an available GUI "
+ "window system",
+ __func__);
+ return nullptr;
+ }
+
+ CGraphicContext& context = winSystem->GetGfxContext();
+
+ float originalAspect = aspect;
+
+ //check if font already exists
+ CGUIFont* pFont = GetFont(strFontName, false);
+ if (pFont)
+ return pFont;
+
+ if (!sourceRes) // no source res specified, so assume the skin res
+ sourceRes = &m_skinResolution;
+
+ float newSize = static_cast<float>(iSize);
+ RescaleFontSizeAndAspect(context, &newSize, &aspect, *sourceRes, preserveAspect);
+
+ // First try to load the font from the skin
+ std::string strPath;
+ if (!CURL::IsFullPath(strFilename))
+ {
+ strPath = URIUtils::AddFileToFolder(context.GetMediaDir(), "fonts", strFilename);
+ }
+ else
+ strPath = strFilename;
+
+#ifdef TARGET_POSIX
+ strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
+#endif
+
+ // Check if the file exists, otherwise try loading it from the global media dir
+ std::string file = URIUtils::GetFileName(strFilename);
+ if (!CheckFont(strPath, "special://home/media/Fonts", file) &&
+ !CheckFont(strPath, "special://xbmc/media/Fonts", file))
+ {
+ VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::RESOURCE_FONT);
+ for (auto& it : addons)
+ {
+ std::shared_ptr<CFontResource> font(std::static_pointer_cast<CFontResource>(it));
+ if (font->GetFont(file, strPath))
+ break;
+ }
+ }
+
+ // check if we already have this font file loaded (font object could differ only by color or style)
+ const std::string fontIdent =
+ StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize, aspect, border ? "_border" : "");
+
+ CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
+ if (!pFontFile)
+ {
+ pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
+ bool bFontLoaded = pFontFile->Load(strPath, newSize, aspect, 1.0f, border);
+
+ if (!bFontLoaded)
+ {
+ delete pFontFile;
+
+ // font could not be loaded - try Arial.ttf, which we distribute
+ if (strFilename != "arial.ttf")
+ {
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name: {}({}), trying arial.ttf",
+ __func__, strFontName, strFilename);
+ return LoadTTF(strFontName, "arial.ttf", textColor, shadowColor, iSize, iStyle, border,
+ lineSpacing, originalAspect);
+ }
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't load font name:{} file:{}", __func__,
+ strFontName, strPath);
+
+ return nullptr;
+ }
+
+ m_vecFontFiles.emplace_back(pFontFile);
+ }
+
+ // font file is loaded, create our CGUIFont
+ CGUIFont* pNewFont = new CGUIFont(strFontName, iStyle, textColor, shadowColor, lineSpacing,
+ static_cast<float>(iSize), pFontFile);
+ m_vecFonts.emplace_back(pNewFont);
+
+ // Store the original TTF font info in case we need to reload it in a different resolution
+ OrigFontInfo fontInfo;
+ fontInfo.size = iSize;
+ fontInfo.aspect = originalAspect;
+ fontInfo.fontFilePath = strPath;
+ fontInfo.fileName = strFilename;
+ fontInfo.sourceRes = *sourceRes;
+ fontInfo.preserveAspect = preserveAspect;
+ fontInfo.border = border;
+ m_vecFontInfo.emplace_back(fontInfo);
+
+ return pNewFont;
+}
+
+bool GUIFontManager::OnMessage(CGUIMessage& message)
+{
+ if (message.GetMessage() != GUI_MSG_NOTIFY_ALL)
+ return false;
+
+ if (message.GetParam1() == GUI_MSG_RENDERER_LOST)
+ {
+ m_canReload = false;
+ return true;
+ }
+
+ if (message.GetParam1() == GUI_MSG_RENDERER_RESET)
+ { // our device has been reset - we have to reload our ttf fonts, and send
+ // a message to controls that we have done so
+ ReloadTTFFonts();
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0,
+ GUI_MSG_WINDOW_RESIZE);
+ m_canReload = true;
+ return true;
+ }
+
+ if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
+ { // we need to reload our fonts
+ if (m_canReload)
+ {
+ ReloadTTFFonts();
+ // no need to send a resize message, as this message will do the rounds
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GUIFontManager::ReloadTTFFonts(void)
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (m_vecFonts.empty() || !winSystem)
+ return; // we haven't even loaded fonts in yet
+
+ for (size_t i = 0; i < m_vecFonts.size(); ++i)
+ {
+ const auto& font = m_vecFonts[i];
+ OrigFontInfo fontInfo = m_vecFontInfo[i];
+
+ float aspect = fontInfo.aspect;
+ float newSize = static_cast<float>(fontInfo.size);
+ std::string& strPath = fontInfo.fontFilePath;
+ std::string& strFilename = fontInfo.fileName;
+
+ RescaleFontSizeAndAspect(winSystem->GetGfxContext(), &newSize, &aspect, fontInfo.sourceRes,
+ fontInfo.preserveAspect);
+
+ const std::string fontIdent = StringUtils::Format("{}_{:f}_{:f}{}", strFilename, newSize,
+ aspect, fontInfo.border ? "_border" : "");
+ CGUIFontTTF* pFontFile = GetFontFile(fontIdent);
+ if (!pFontFile)
+ {
+ pFontFile = CGUIFontTTF::CreateGUIFontTTF(fontIdent);
+ if (!pFontFile || !pFontFile->Load(strPath, newSize, aspect, 1.0f, fontInfo.border))
+ {
+ delete pFontFile;
+ // font could not be loaded
+ CLog::Log(LOGERROR, "GUIFontManager::{}: Couldn't re-load font file: '{}'", __func__,
+ strPath);
+ return;
+ }
+
+ m_vecFontFiles.emplace_back(pFontFile);
+ }
+
+ font->SetFont(pFontFile);
+ }
+}
+
+void GUIFontManager::Unload(const std::string& strFontName)
+{
+ for (auto iFont = m_vecFonts.begin(); iFont != m_vecFonts.end(); ++iFont)
+ {
+ if (StringUtils::EqualsNoCase((*iFont)->GetFontName(), strFontName))
+ {
+ m_vecFonts.erase(iFont);
+ return;
+ }
+ }
+}
+
+void GUIFontManager::FreeFontFile(CGUIFontTTF* pFont)
+{
+ for (auto it = m_vecFontFiles.begin(); it != m_vecFontFiles.end(); ++it)
+ {
+ if (pFont == it->get())
+ {
+ m_vecFontFiles.erase(it);
+ return;
+ }
+ }
+}
+
+CGUIFontTTF* GUIFontManager::GetFontFile(const std::string& fontIdent)
+{
+ for (const auto& it : m_vecFontFiles)
+ {
+ if (StringUtils::EqualsNoCase(it->GetFontIdent(), fontIdent))
+ return it.get();
+ }
+
+ return nullptr;
+}
+
+CGUIFont* GUIFontManager::GetFont(const std::string& strFontName, bool fallback /*= true*/)
+{
+ for (const auto& it : m_vecFonts)
+ {
+ CGUIFont* pFont = it.get();
+ if (StringUtils::EqualsNoCase(pFont->GetFontName(), strFontName))
+ return pFont;
+ }
+
+ // fall back to "font13" if we have none
+ if (fallback && !strFontName.empty() && !StringUtils::EqualsNoCase(strFontName, "font13"))
+ return GetFont("font13");
+
+ return nullptr;
+}
+
+CGUIFont* GUIFontManager::GetDefaultFont(bool border)
+{
+ // first find "font13" or "__defaultborder__"
+ size_t font13index = m_vecFonts.size();
+ CGUIFont* font13border = nullptr;
+ for (size_t i = 0; i < m_vecFonts.size(); i++)
+ {
+ CGUIFont* font = m_vecFonts[i].get();
+ if (font->GetFontName() == "font13")
+ font13index = i;
+ else if (font->GetFontName() == "__defaultborder__")
+ font13border = font;
+ }
+ // no "font13" means no default font is found - use the first font found.
+ if (font13index == m_vecFonts.size())
+ {
+ if (m_vecFonts.empty())
+ return nullptr;
+
+ font13index = 0;
+ }
+
+ if (border)
+ {
+ if (!font13border)
+ { // create it
+ const auto& font13 = m_vecFonts[font13index];
+ OrigFontInfo fontInfo = m_vecFontInfo[font13index];
+ font13border = LoadTTF("__defaultborder__", fontInfo.fileName, UTILS::COLOR::BLACK, 0,
+ fontInfo.size, font13->GetStyle(), true, 1.0f, fontInfo.aspect,
+ &fontInfo.sourceRes, fontInfo.preserveAspect);
+ }
+ return font13border;
+ }
+
+ return m_vecFonts[font13index].get();
+}
+
+void GUIFontManager::Clear()
+{
+ m_vecFonts.clear();
+ m_vecFontFiles.clear();
+ m_vecFontInfo.clear();
+
+#if defined(HAS_GLES) || defined(HAS_GL)
+ CGUIFontTTFGL::DestroyStaticVertexBuffers();
+#endif
+}
+
+void GUIFontManager::LoadFonts(const std::string& fontSet)
+{
+ // Get the file to load fonts from:
+ const std::string filePath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution);
+ CLog::LogF(LOGINFO, "Loading fonts from '{}'", filePath);
+
+ CXBMCTinyXML xmlDoc;
+ if (!LoadXMLData(filePath, xmlDoc))
+ return;
+
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ // Resolve includes in Font.xml
+ g_SkinInfo->ResolveIncludes(pRootElement);
+ // take note of the first font available in case we can't load the one specified
+ std::string firstFont;
+ const TiXmlElement* pChild = pRootElement->FirstChildElement("fontset");
+ while (pChild)
+ {
+ const char* idAttr = pChild->Attribute("id");
+ if (idAttr)
+ {
+ if (firstFont.empty())
+ firstFont = idAttr;
+
+ if (StringUtils::EqualsNoCase(fontSet, idAttr))
+ {
+ LoadFonts(pChild->FirstChild("font"));
+ return;
+ }
+ }
+ pChild = pChild->NextSiblingElement("fontset");
+ }
+
+ // no fontset was loaded, try the first
+ if (!firstFont.empty())
+ {
+ CLog::Log(LOGWARNING,
+ "GUIFontManager::{}: File doesn't have <fontset> with name '{}', defaulting to first "
+ "fontset",
+ __func__, fontSet);
+ LoadFonts(firstFont);
+ }
+ else
+ CLog::LogF(LOGERROR, "File '{}' doesn't have a valid <fontset>", filePath);
+}
+
+void GUIFontManager::LoadFonts(const TiXmlNode* fontNode)
+{
+ while (fontNode)
+ {
+ std::string fontName;
+ std::string fileName;
+ int iSize = 20;
+ float aspect = 1.0f;
+ float lineSpacing = 1.0f;
+ UTILS::COLOR::Color shadowColor = 0;
+ UTILS::COLOR::Color textColor = 0;
+ int iStyle = FONT_STYLE_NORMAL;
+
+ XMLUtils::GetString(fontNode, "name", fontName);
+ XMLUtils::GetInt(fontNode, "size", iSize);
+ XMLUtils::GetFloat(fontNode, "linespacing", lineSpacing);
+ XMLUtils::GetFloat(fontNode, "aspect", aspect);
+ CGUIControlFactory::GetColor(fontNode, "shadow", shadowColor);
+ CGUIControlFactory::GetColor(fontNode, "color", textColor);
+ XMLUtils::GetString(fontNode, "filename", fileName);
+ GetStyle(fontNode, iStyle);
+
+ if (!fontName.empty() && URIUtils::HasExtension(fileName, ".ttf"))
+ {
+ //! @todo Why do we tolower() this shit?
+ std::string strFontFileName = fileName;
+ StringUtils::ToLower(strFontFileName);
+ LoadTTF(fontName, strFontFileName, textColor, shadowColor, iSize, iStyle, false, lineSpacing,
+ aspect);
+ }
+ fontNode = fontNode->NextSibling("font");
+ }
+}
+
+void GUIFontManager::GetStyle(const TiXmlNode* fontNode, int& iStyle)
+{
+ std::string style;
+ iStyle = FONT_STYLE_NORMAL;
+ if (XMLUtils::GetString(fontNode, "style", style))
+ {
+ std::vector<std::string> styles = StringUtils::Tokenize(style, " ");
+ for (const std::string& i : styles)
+ {
+ if (i == "bold")
+ iStyle |= FONT_STYLE_BOLD;
+ else if (i == "italics")
+ iStyle |= FONT_STYLE_ITALICS;
+ else if (i == "bolditalics") // backward compatibility
+ iStyle |= (FONT_STYLE_BOLD | FONT_STYLE_ITALICS);
+ else if (i == "uppercase")
+ iStyle |= FONT_STYLE_UPPERCASE;
+ else if (i == "lowercase")
+ iStyle |= FONT_STYLE_LOWERCASE;
+ else if (i == "capitalize")
+ iStyle |= FONT_STYLE_CAPITALIZE;
+ else if (i == "lighten")
+ iStyle |= FONT_STYLE_LIGHT;
+ }
+ }
+}
+
+void GUIFontManager::SettingOptionsFontsFiller(const SettingConstPtr& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ CFileItemList itemsRoot;
+ CFileItemList items;
+
+ // Find font files
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::SYSTEM, itemsRoot,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, items,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+
+ for (auto itItem = itemsRoot.rbegin(); itItem != itemsRoot.rend(); ++itItem)
+ items.AddFront(*itItem, 0);
+
+ for (const auto& item : items)
+ {
+ if (item->m_bIsFolder)
+ continue;
+
+ list.emplace_back(item->GetLabel(), item->GetLabel());
+ }
+}
+
+void GUIFontManager::Initialize()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ LoadUserFonts();
+}
+
+void GUIFontManager::LoadUserFonts()
+{
+ if (!XFILE::CDirectory::Exists(UTILS::FONT::FONTPATH::USER))
+ return;
+
+ CLog::LogF(LOGDEBUG, "Updating user fonts cache...");
+ CXBMCTinyXML xmlDoc;
+ std::string userFontCacheFilepath =
+ URIUtils::AddFileToFolder(UTILS::FONT::FONTPATH::USER, XML_FONTCACHE_FILENAME);
+ if (LoadXMLData(userFontCacheFilepath, xmlDoc))
+ {
+ // Load in cache the fonts metadata previously stored in the XML
+ TiXmlElement* pRootElement = xmlDoc.RootElement();
+ if (pRootElement)
+ {
+ const TiXmlNode* fontNode = pRootElement->FirstChild("font");
+ while (fontNode)
+ {
+ std::string filename;
+ std::string familyName;
+ XMLUtils::GetString(fontNode, "filename", filename);
+ XMLUtils::GetString(fontNode, "familyname", familyName);
+ m_userFontsCache.emplace_back(filename, familyName);
+ fontNode = fontNode->NextSibling("font");
+ }
+ }
+ }
+
+ bool isCacheChanged{false};
+ size_t previousCacheSize = m_userFontsCache.size();
+ CFileItemList dirItems;
+ // Get the current files list from user fonts folder
+ XFILE::CDirectory::GetDirectory(UTILS::FONT::FONTPATH::USER, dirItems,
+ UTILS::FONT::SUPPORTED_EXTENSIONS_MASK,
+ XFILE::DIR_FLAG_NO_FILE_DIRS | XFILE::DIR_FLAG_NO_FILE_INFO);
+ dirItems.SetFastLookup(true);
+
+ // Remove files that no longer exist from cache
+ auto it = m_userFontsCache.begin();
+ while (it != m_userFontsCache.end())
+ {
+ const std::string filePath = UTILS::FONT::FONTPATH::USER + (*it).m_filename;
+ if (!dirItems.Contains(filePath))
+ {
+ it = m_userFontsCache.erase(it);
+ }
+ else
+ {
+ auto item = dirItems.Get(filePath);
+ dirItems.Remove(item.get());
+ ++it;
+ }
+ }
+ isCacheChanged = previousCacheSize != m_userFontsCache.size();
+ previousCacheSize = m_userFontsCache.size();
+
+ // Add new files in cache and generate the metadata
+ //!@todo FIXME: this "for" loop should be replaced with the more performant
+ //! parallel execution std::for_each(std::execution::par, ...
+ //! to halving loading times of fonts list, maybe with C++17 with appropriate
+ //! fix to include parallel execution or future C++20
+ for (auto& item : dirItems)
+ {
+ std::string filepath = item->GetPath();
+ if (item->m_bIsFolder)
+ continue;
+
+ std::string familyName = UTILS::FONT::GetFontFamily(filepath);
+ if (!familyName.empty())
+ {
+ m_userFontsCache.emplace_back(item->GetLabel(), familyName);
+ }
+ }
+ isCacheChanged = isCacheChanged || previousCacheSize != m_userFontsCache.size();
+
+ // If the cache is changed save an updated XML cache file
+ if (isCacheChanged)
+ {
+ CXBMCTinyXML xmlDoc;
+ TiXmlDeclaration decl("1.0", "UTF-8", "yes");
+ xmlDoc.InsertEndChild(decl);
+ TiXmlElement xmlMainElement("fonts");
+ TiXmlNode* fontsNode = xmlDoc.InsertEndChild(xmlMainElement);
+ if (fontsNode)
+ {
+ for (auto& fontMetadata : m_userFontsCache)
+ {
+ TiXmlElement fontElement("font");
+ TiXmlNode* fontNode = fontsNode->InsertEndChild(fontElement);
+ XMLUtils::SetString(fontNode, "filename", fontMetadata.m_filename);
+ XMLUtils::SetString(fontNode, "familyname", fontMetadata.m_familyName);
+ }
+ if (!xmlDoc.SaveFile(userFontCacheFilepath))
+ CLog::LogF(LOGERROR, "Failed to save fonts cache file '{}'", userFontCacheFilepath);
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Failed to create XML 'fonts' node");
+ }
+ }
+ CLog::LogF(LOGDEBUG, "Updating user fonts cache... DONE");
+}
+
+std::vector<std::string> GUIFontManager::GetUserFontsFamilyNames()
+{
+ // We ensure to have unique font family names and sorted alphabetically
+ // Duplicated family names can happens for example when a font have each style
+ // on different files
+ std::set<std::string, sortstringbyname> familyNames;
+ for (auto& fontMetadata : m_userFontsCache)
+ {
+ familyNames.insert(fontMetadata.m_familyName);
+ }
+ return std::vector<std::string>(familyNames.begin(), familyNames.end());
+}
diff --git a/xbmc/guilib/GUIFontManager.h b/xbmc/guilib/GUIFontManager.h
new file mode 100644
index 0000000..5923517
--- /dev/null
+++ b/xbmc/guilib/GUIFontManager.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFontManager.h
+\brief
+*/
+
+#include "IMsgTargetCallback.h"
+#include "threads/CriticalSection.h"
+#include "threads/SingleLock.h"
+#include "utils/ColorUtils.h"
+#include "utils/GlobalsHandling.h"
+#include "windowing/GraphicContext.h"
+
+#include <utility>
+#include <vector>
+
+// Forward
+class CGUIFont;
+class CGUIFontTTF;
+class CXBMCTinyXML;
+class TiXmlNode;
+class CSetting;
+struct StringSettingOption;
+
+struct OrigFontInfo
+{
+ int size;
+ float aspect;
+ std::string fontFilePath;
+ std::string fileName;
+ RESOLUTION_INFO sourceRes;
+ bool preserveAspect;
+ bool border;
+};
+
+struct FontMetadata
+{
+ FontMetadata(const std::string& filename, const std::string& familyName)
+ : m_filename{filename}, m_familyName{familyName}
+ {
+ }
+
+ std::string m_filename;
+ std::string m_familyName;
+};
+
+/*!
+ \ingroup textures
+ \brief
+ */
+class GUIFontManager : public IMsgTargetCallback
+{
+public:
+ GUIFontManager();
+ ~GUIFontManager() override;
+
+ /*!
+ * \brief Initialize the font manager.
+ * Checks that fonts cache are up to date, otherwise update it.
+ */
+ void Initialize();
+
+ bool IsUpdating() const { return m_critSection.IsLocked(); }
+
+ bool OnMessage(CGUIMessage& message) override;
+
+ void Unload(const std::string& strFontName);
+ void LoadFonts(const std::string& fontSet);
+ CGUIFont* LoadTTF(const std::string& strFontName,
+ const std::string& strFilename,
+ UTILS::COLOR::Color textColor,
+ UTILS::COLOR::Color shadowColor,
+ const int iSize,
+ const int iStyle,
+ bool border = false,
+ float lineSpacing = 1.0f,
+ float aspect = 1.0f,
+ const RESOLUTION_INFO* res = nullptr,
+ bool preserveAspect = false);
+ CGUIFont* GetFont(const std::string& strFontName, bool fallback = true);
+
+ /*! \brief return a default font
+ \param border whether the font should be a font with an outline
+ \return the font. `nullptr` if no default font can be found.
+ */
+ CGUIFont* GetDefaultFont(bool border = false);
+
+ void Clear();
+ void FreeFontFile(CGUIFontTTF* pFont);
+
+ static void SettingOptionsFontsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
+
+ /*!
+ * \brief Get the list of user fonts as family names from cache
+ * \return The list of available fonts family names
+ */
+ std::vector<std::string> GetUserFontsFamilyNames();
+
+protected:
+ void ReloadTTFFonts();
+ static void RescaleFontSizeAndAspect(CGraphicContext& context,
+ float* size,
+ float* aspect,
+ const RESOLUTION_INFO& sourceRes,
+ bool preserveAspect);
+ void LoadFonts(const TiXmlNode* fontNode);
+ CGUIFontTTF* GetFontFile(const std::string& fontIdent);
+ static void GetStyle(const TiXmlNode* fontNode, int& iStyle);
+
+ std::vector<std::unique_ptr<CGUIFont>> m_vecFonts;
+ std::vector<std::unique_ptr<CGUIFontTTF>> m_vecFontFiles;
+ std::vector<OrigFontInfo> m_vecFontInfo;
+ RESOLUTION_INFO m_skinResolution;
+ bool m_canReload{true};
+
+private:
+ void LoadUserFonts();
+
+ mutable CCriticalSection m_critSection;
+ std::vector<FontMetadata> m_userFontsCache;
+};
+
+/*!
+ \ingroup textures
+ \brief
+ */
+XBMC_GLOBAL_REF(GUIFontManager, g_fontManager);
+#define g_fontManager XBMC_GLOBAL_USE(GUIFontManager)
diff --git a/xbmc/guilib/GUIFontTTF.cpp b/xbmc/guilib/GUIFontTTF.cpp
new file mode 100644
index 0000000..6b38fb0
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTF.cpp
@@ -0,0 +1,1166 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontTTF.h"
+
+#include "GUIFontManager.h"
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "URL.h"
+#include "filesystem/File.h"
+#include "filesystem/SpecialProtocol.h"
+#include "rendering/RenderSystem.h"
+#include "threads/SystemClock.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <math.h>
+#include <memory>
+#include <queue>
+#include <utility>
+
+// stuff for freetype
+#include <ft2build.h>
+#include <harfbuzz/hb-ft.h>
+#if defined(HAS_GL) || defined(HAS_GLES)
+#include "utils/GLUtils.h"
+
+#include "system_gl.h"
+#endif
+
+#if defined(HAS_DX)
+#include "guilib/D3DResource.h"
+#endif
+
+#ifdef TARGET_WINDOWS_STORE
+#define generic GenericFromFreeTypeLibrary
+#endif
+
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+#include FT_STROKER_H
+
+namespace
+{
+constexpr int VERTEX_PER_GLYPH = 4; // number of vertex for each glyph
+constexpr int CHARS_PER_TEXTURE_LINE = 20; // number characters to cache per texture line
+constexpr int MAX_TRANSLATED_VERTEX = 32; // max number of structs CTranslatedVertices expect to use
+constexpr int MAX_GLYPHS_PER_TEXT_LINE = 1024; // max number of glyphs per text line expect to use
+constexpr unsigned int SPACING_BETWEEN_CHARACTERS_IN_TEXTURE = 1;
+constexpr int CHAR_CHUNK = 64; // 64 chars allocated at a time (2048 bytes)
+constexpr int GLYPH_STRENGTH_BOLD = 24;
+constexpr int GLYPH_STRENGTH_LIGHT = -48;
+constexpr int TAB_SPACE_LENGTH = 4;
+} /* namespace */
+
+class CFreeTypeLibrary
+{
+public:
+ CFreeTypeLibrary() = default;
+ virtual ~CFreeTypeLibrary()
+ {
+ if (m_library)
+ FT_Done_FreeType(m_library);
+ }
+
+ FT_Face GetFont(const std::string& filename,
+ float size,
+ float aspect,
+ std::vector<uint8_t>& memoryBuf)
+ {
+ // don't have it yet - create it
+ if (!m_library)
+ FT_Init_FreeType(&m_library);
+ if (!m_library)
+ {
+ CLog::LogF(LOGERROR, "Unable to initialize freetype library");
+ return nullptr;
+ }
+
+ FT_Face face;
+
+ // ok, now load the font face
+ CURL realFile(CSpecialProtocol::TranslatePath(filename));
+ if (realFile.GetFileName().empty())
+ return nullptr;
+
+ memoryBuf.clear();
+#ifndef TARGET_WINDOWS
+ if (!realFile.GetProtocol().empty())
+#endif // ! TARGET_WINDOWS
+ {
+ // load file into memory if it is not on local drive
+ // in case of win32: always load file into memory as filename is in UTF-8,
+ // but freetype expect filename in ANSI encoding
+ XFILE::CFile f;
+ if (f.LoadFile(realFile, memoryBuf) <= 0)
+ return nullptr;
+
+ if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(memoryBuf.data()),
+ memoryBuf.size(), 0, &face) != 0)
+ return nullptr;
+ }
+#ifndef TARGET_WINDOWS
+ else if (FT_New_Face(m_library, realFile.GetFileName().c_str(), 0, &face))
+ return nullptr;
+#endif // ! TARGET_WINDOWS
+
+ unsigned int ydpi = 72; // 72 points to the inch is the freetype default
+ unsigned int xdpi =
+ static_cast<unsigned int>(MathUtils::round_int(static_cast<double>(ydpi * aspect)));
+
+ // we set our screen res currently to 96dpi in both directions (windows default)
+ // we cache our characters (for rendering speed) so it's probably
+ // not a good idea to allow free scaling of fonts - rather, just
+ // scaling to pixel ratio on screen perhaps?
+ if (FT_Set_Char_Size(face, 0, static_cast<int>(size * 64 + 0.5f), xdpi, ydpi))
+ {
+ FT_Done_Face(face);
+ return nullptr;
+ }
+
+ return face;
+ };
+
+ FT_Stroker GetStroker()
+ {
+ if (!m_library)
+ return nullptr;
+
+ FT_Stroker stroker;
+ if (FT_Stroker_New(m_library, &stroker))
+ return nullptr;
+
+ return stroker;
+ };
+
+ static void ReleaseFont(FT_Face face)
+ {
+ assert(face);
+ FT_Done_Face(face);
+ };
+
+ static void ReleaseStroker(FT_Stroker stroker)
+ {
+ assert(stroker);
+ FT_Stroker_Done(stroker);
+ }
+
+private:
+ FT_Library m_library{nullptr};
+};
+
+XBMC_GLOBAL_REF(CFreeTypeLibrary, g_freeTypeLibrary); // our freetype library
+#define g_freeTypeLibrary XBMC_GLOBAL_USE(CFreeTypeLibrary)
+
+CGUIFontTTF::CGUIFontTTF(const std::string& fontIdent)
+ : m_fontIdent(fontIdent),
+ m_staticCache(*this),
+ m_dynamicCache(*this),
+ m_renderSystem(CServiceBroker::GetRenderSystem())
+{
+}
+
+CGUIFontTTF::~CGUIFontTTF(void)
+{
+ Clear();
+}
+
+void CGUIFontTTF::AddReference()
+{
+ m_referenceCount++;
+}
+
+void CGUIFontTTF::RemoveReference()
+{
+ // delete this object when it's reference count hits zero
+ m_referenceCount--;
+ if (!m_referenceCount)
+ g_fontManager.FreeFontFile(this);
+}
+
+
+void CGUIFontTTF::ClearCharacterCache()
+{
+ m_texture.reset();
+
+ DeleteHardwareTexture();
+
+ m_texture = nullptr;
+ m_char.clear();
+ m_char.reserve(CHAR_CHUNK);
+ memset(m_charquick, 0, sizeof(m_charquick));
+ // set the posX and posY so that our texture will be created on first character write.
+ m_posX = m_textureWidth;
+ m_posY = -static_cast<int>(GetTextureLineHeight());
+ m_textureHeight = 0;
+}
+
+void CGUIFontTTF::Clear()
+{
+ m_texture.reset();
+ m_texture = nullptr;
+ memset(m_charquick, 0, sizeof(m_charquick));
+ m_posX = 0;
+ m_posY = 0;
+ m_nestedBeginCount = 0;
+
+ if (m_hbFont)
+ hb_font_destroy(m_hbFont);
+ m_hbFont = nullptr;
+ if (m_face)
+ g_freeTypeLibrary.ReleaseFont(m_face);
+ m_face = nullptr;
+ if (m_stroker)
+ g_freeTypeLibrary.ReleaseStroker(m_stroker);
+ m_stroker = nullptr;
+
+ m_vertexTrans.clear();
+ m_vertex.clear();
+
+ m_fontFileInMemory.clear();
+}
+
+bool CGUIFontTTF::Load(
+ const std::string& strFilename, float height, float aspect, float lineSpacing, bool border)
+{
+ // we now know that this object is unique - only the GUIFont objects are non-unique, so no need
+ // for reference tracking these fonts
+ m_face = g_freeTypeLibrary.GetFont(strFilename, height, aspect, m_fontFileInMemory);
+ if (!m_face)
+ return false;
+
+ m_hbFont = hb_ft_font_create(m_face, 0);
+ if (!m_hbFont)
+ return false;
+ /*
+ the values used are described below
+
+ XBMC coords Freetype coords
+
+ 0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bbox.yMax, ascender
+ A \
+ A A |
+ A A |
+ AAAAA pppp cellAscender
+ A A p p |
+ A A p p |
+ m_cellBaseLine _ _A_ _A_ pppp_ _ _ _ _/_ _ _ _ _ 0, base line.
+ p \
+ p cellDescender
+ m_cellHeight _ _ _ _ _ p _ _ _ _ _ _/_ _ _ _ _ bbox.yMin, descender
+
+ */
+ int cellDescender = std::min<int>(m_face->bbox.yMin, m_face->descender);
+ int cellAscender = std::max<int>(m_face->bbox.yMax, m_face->ascender);
+
+ if (border)
+ {
+ /*
+ add on the strength of any border - the non-bordered font needs
+ aligning with the bordered font by utilising GetTextBaseLine()
+ */
+ FT_Pos strength = FT_MulFix(m_face->units_per_EM, m_face->size->metrics.y_scale) / 12;
+ if (strength < 128)
+ strength = 128;
+
+ cellDescender -= strength;
+ cellAscender += strength;
+
+ m_stroker = g_freeTypeLibrary.GetStroker();
+ if (m_stroker)
+ FT_Stroker_Set(m_stroker, strength, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
+ }
+
+ // scale to pixel sizing, rounding so that maximal extent is obtained
+ float scaler = height / m_face->units_per_EM;
+ cellDescender =
+ MathUtils::round_int(cellDescender * static_cast<double>(scaler) - 0.5); // round down
+ cellAscender = MathUtils::round_int(cellAscender * static_cast<double>(scaler) + 0.5); // round up
+
+ m_cellBaseLine = cellAscender;
+ m_cellHeight = cellAscender - cellDescender;
+
+ m_height = height;
+
+ m_texture.reset();
+ m_texture = nullptr;
+
+ m_textureHeight = 0;
+ m_textureWidth = ((m_cellHeight * CHARS_PER_TEXTURE_LINE) & ~63) + 64;
+
+ m_textureWidth = CTexture::PadPow2(m_textureWidth);
+
+ if (m_textureWidth > m_renderSystem->GetMaxTextureSize())
+ m_textureWidth = m_renderSystem->GetMaxTextureSize();
+ m_textureScaleX = 1.0f / m_textureWidth;
+
+ // set the posX and posY so that our texture will be created on first character write.
+ m_posX = m_textureWidth;
+ m_posY = -static_cast<int>(GetTextureLineHeight());
+
+ return true;
+}
+
+void CGUIFontTTF::Begin()
+{
+ if (m_nestedBeginCount == 0 && m_texture && FirstBegin())
+ {
+ m_vertexTrans.clear();
+ m_vertex.clear();
+ }
+ // Keep track of the nested begin/end calls.
+ m_nestedBeginCount++;
+}
+
+void CGUIFontTTF::End()
+{
+ if (m_nestedBeginCount == 0)
+ return;
+
+ if (--m_nestedBeginCount > 0)
+ return;
+
+ LastEnd();
+}
+
+void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
+ float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling)
+{
+ if (text.empty())
+ {
+ return;
+ }
+
+ Begin();
+ uint32_t rawAlignment = alignment;
+ bool dirtyCache(false);
+ const bool hardwareClipping = m_renderSystem->ScissorsCanEffectClipping();
+ CGUIFontCacheStaticPosition staticPos(x, y);
+ CGUIFontCacheDynamicPosition dynamicPos;
+ if (hardwareClipping)
+ {
+ dynamicPos =
+ CGUIFontCacheDynamicPosition(context.ScaleFinalXCoord(x, y), context.ScaleFinalYCoord(x, y),
+ context.ScaleFinalZCoord(x, y));
+ }
+ CVertexBuffer unusedVertexBuffer;
+ CVertexBuffer& vertexBuffer =
+ hardwareClipping
+ ? m_dynamicCache.Lookup(context, dynamicPos, colors, text, alignment, maxPixelWidth,
+ scrolling, std::chrono::steady_clock::now(), dirtyCache)
+ : unusedVertexBuffer;
+ std::shared_ptr<std::vector<SVertex>> tempVertices = std::make_shared<std::vector<SVertex>>();
+ std::shared_ptr<std::vector<SVertex>>& vertices =
+ hardwareClipping ? tempVertices
+ : static_cast<std::shared_ptr<std::vector<SVertex>>&>(m_staticCache.Lookup(
+ context, staticPos, colors, text, alignment, maxPixelWidth, scrolling,
+ std::chrono::steady_clock::now(), dirtyCache));
+
+ // reserves vertex vector capacity, only the ones that are going to be used
+ if (hardwareClipping)
+ {
+ if (m_vertexTrans.capacity() == 0)
+ m_vertexTrans.reserve(MAX_TRANSLATED_VERTEX);
+ }
+ else
+ {
+ if (m_vertex.capacity() == 0)
+ m_vertex.reserve(VERTEX_PER_GLYPH * MAX_GLYPHS_PER_TEXT_LINE);
+ }
+
+ if (dirtyCache)
+ {
+ const std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text);
+ // save the origin, which is scaled separately
+ m_originX = x;
+ m_originY = y;
+
+ // cache the ellipses width
+ if (!m_ellipseCached)
+ {
+ m_ellipseCached = true;
+ Character* ellipse = GetCharacter(L'.', 0);
+ if (ellipse)
+ m_ellipsesWidth = ellipse->m_advance;
+ }
+
+ // Check if we will really need to truncate or justify the text
+ if (alignment & XBFONT_TRUNCATED)
+ {
+ if (maxPixelWidth <= 0.0f || GetTextWidthInternal(text, glyphs) <= maxPixelWidth)
+ alignment &= ~XBFONT_TRUNCATED;
+ }
+ else if (alignment & XBFONT_JUSTIFIED)
+ {
+ if (maxPixelWidth <= 0.0f)
+ alignment &= ~XBFONT_JUSTIFIED;
+ }
+
+ // calculate sizing information
+ float startX = 0;
+ float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f * m_cellHeight : 0; // vertical centering
+
+ if (alignment & (XBFONT_RIGHT | XBFONT_CENTER_X))
+ {
+ // Get the extent of this line
+ float w = GetTextWidthInternal(text, glyphs);
+
+ if (alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f) // + 0.5f due to rounding issues
+ w = maxPixelWidth;
+
+ if (alignment & XBFONT_CENTER_X)
+ w *= 0.5f;
+ // Offset this line's starting position
+ startX -= w;
+ }
+
+ float spacePerSpaceCharacter = 0; // for justification effects
+ if (alignment & XBFONT_JUSTIFIED)
+ {
+ // first compute the size of the text to render in both characters and pixels
+ unsigned int numSpaces = 0;
+ float linePixels = 0;
+ for (const auto& glyph : glyphs)
+ {
+ Character* ch = GetCharacter(text[glyph.m_glyphInfo.cluster], glyph.m_glyphInfo.codepoint);
+ if (ch)
+ {
+ if ((text[glyph.m_glyphInfo.cluster] & 0xffff) == L' ')
+ numSpaces += 1;
+ linePixels += ch->m_advance;
+ }
+ }
+ if (numSpaces > 0)
+ spacePerSpaceCharacter = (maxPixelWidth - linePixels) / numSpaces;
+ }
+
+ float cursorX = 0; // current position along the line
+ float offsetX = 0;
+ float offsetY = 0;
+
+ // Collect all the Character info in a first pass, in case any of them
+ // are not currently cached and cause the texture to be enlarged, which
+ // would invalidate the texture coordinates.
+ std::queue<Character> characters;
+ if (alignment & XBFONT_TRUNCATED)
+ GetCharacter(L'.', 0);
+ for (const auto& glyph : glyphs)
+ {
+ Character* ch = GetCharacter(text[glyph.m_glyphInfo.cluster], glyph.m_glyphInfo.codepoint);
+ if (!ch)
+ {
+ Character null = {};
+ characters.push(null);
+ continue;
+ }
+ characters.push(*ch);
+
+ if (maxPixelWidth > 0 &&
+ cursorX + ((alignment & XBFONT_TRUNCATED) ? ch->m_advance + 3 * m_ellipsesWidth : 0) >
+ maxPixelWidth)
+ break;
+ cursorX += ch->m_advance;
+ }
+
+ // Reserve vector space: 4 vertex for each glyph
+ tempVertices->reserve(VERTEX_PER_GLYPH * glyphs.size());
+ cursorX = 0;
+
+ for (const auto& glyph : glyphs)
+ {
+ // If starting text on a new line, determine justification effects
+ // Get the current letter in the CStdString
+ UTILS::COLOR::Color color = (text[glyph.m_glyphInfo.cluster] & 0xff0000) >> 16;
+ if (color >= colors.size())
+ color = 0;
+ color = colors[color];
+
+ // grab the next character
+ Character* ch = &characters.front();
+
+ if ((text[glyph.m_glyphInfo.cluster] & 0xffff) == static_cast<character_t>('\t'))
+ {
+ const float tabwidth = GetTabSpaceLength();
+ const float a = cursorX / tabwidth;
+ cursorX += tabwidth - ((a - floorf(a)) * tabwidth);
+ characters.pop();
+ continue;
+ }
+
+ if (alignment & XBFONT_TRUNCATED)
+ {
+ // Check if we will be exceeded the max allowed width
+ if (cursorX + ch->m_advance + 3 * m_ellipsesWidth > maxPixelWidth)
+ {
+ // Yup. Let's draw the ellipses, then bail
+ // Perhaps we should really bail to the next line in this case??
+ Character* period = GetCharacter(L'.', 0);
+ if (!period)
+ break;
+
+ for (int i = 0; i < 3; i++)
+ {
+ RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
+ *tempVertices);
+ cursorX += period->m_advance;
+ }
+ break;
+ }
+ }
+ else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
+ break; // exceeded max allowed width - stop rendering
+
+ offsetX = static_cast<float>(
+ MathUtils::round_int(static_cast<double>(glyph.m_glyphPosition.x_offset) / 64));
+ offsetY = static_cast<float>(
+ MathUtils::round_int(static_cast<double>(glyph.m_glyphPosition.y_offset) / 64));
+ RenderCharacter(context, startX + cursorX + offsetX, startY - offsetY, ch, color, !scrolling,
+ *tempVertices);
+ if (alignment & XBFONT_JUSTIFIED)
+ {
+ if ((text[glyph.m_glyphInfo.cluster] & 0xffff) == L' ')
+ cursorX += ch->m_advance + spacePerSpaceCharacter;
+ else
+ cursorX += ch->m_advance;
+ }
+ else
+ cursorX += ch->m_advance;
+ characters.pop();
+ }
+ if (hardwareClipping)
+ {
+ CVertexBuffer& vertexBuffer =
+ m_dynamicCache.Lookup(context, dynamicPos, colors, text, rawAlignment, maxPixelWidth,
+ scrolling, std::chrono::steady_clock::now(), dirtyCache);
+ CVertexBuffer newVertexBuffer = CreateVertexBuffer(*tempVertices);
+ vertexBuffer = newVertexBuffer;
+ m_vertexTrans.emplace_back(.0f, .0f, .0f, &vertexBuffer, context.GetClipRegion());
+ }
+ else
+ {
+ m_staticCache.Lookup(context, staticPos, colors, text, rawAlignment, maxPixelWidth, scrolling,
+ std::chrono::steady_clock::now(), dirtyCache) =
+ *static_cast<CGUIFontCacheStaticValue*>(&tempVertices);
+ /* Append the new vertices to the set collected since the first Begin() call */
+ m_vertex.insert(m_vertex.end(), tempVertices->begin(), tempVertices->end());
+ }
+ }
+ else
+ {
+ if (hardwareClipping)
+ m_vertexTrans.emplace_back(dynamicPos.m_x, dynamicPos.m_y, dynamicPos.m_z, &vertexBuffer,
+ context.GetClipRegion());
+ else
+ /* Append the vertices from the cache to the set collected since the first Begin() call */
+ m_vertex.insert(m_vertex.end(), vertices->begin(), vertices->end());
+ }
+
+ End();
+}
+
+
+float CGUIFontTTF::GetTextWidthInternal(const vecText& text)
+{
+ const std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text);
+ return GetTextWidthInternal(text, glyphs);
+}
+
+// this routine assumes a single line (i.e. it was called from GUITextLayout)
+float CGUIFontTTF::GetTextWidthInternal(const vecText& text, const std::vector<Glyph>& glyphs)
+{
+ float width = 0;
+ for (auto it = glyphs.begin(); it != glyphs.end(); it++)
+ {
+ const character_t ch = text[(*it).m_glyphInfo.cluster];
+ Character* c = GetCharacter(ch, (*it).m_glyphInfo.codepoint);
+ if (c)
+ {
+ // If last character in line, we want to add render width
+ // and not advance distance - this makes sure that italic text isn't
+ // choped on the end (as render width is larger than advance then).
+ if (std::next(it) == glyphs.end())
+ width += std::max(c->m_right - c->m_left + c->m_offsetX, c->m_advance);
+ else if ((ch & 0xffff) == static_cast<character_t>('\t'))
+ width += GetTabSpaceLength();
+ else
+ width += c->m_advance;
+ }
+ }
+
+ return width;
+}
+
+float CGUIFontTTF::GetCharWidthInternal(character_t ch)
+{
+ Character* c = GetCharacter(ch, 0);
+ if (c)
+ {
+ if ((ch & 0xffff) == static_cast<character_t>('\t'))
+ return GetTabSpaceLength();
+ else
+ return c->m_advance;
+ }
+
+ return 0;
+}
+
+float CGUIFontTTF::GetTextHeight(float lineSpacing, int numLines) const
+{
+ return static_cast<float>(numLines - 1) * GetLineHeight(lineSpacing) + m_cellHeight;
+}
+
+float CGUIFontTTF::GetLineHeight(float lineSpacing) const
+{
+ if (!m_face)
+ return 0.0f;
+
+ return lineSpacing * m_face->size->metrics.height / 64.0f;
+}
+
+unsigned int CGUIFontTTF::GetTextureLineHeight() const
+{
+ return m_cellHeight + SPACING_BETWEEN_CHARACTERS_IN_TEXTURE;
+}
+
+unsigned int CGUIFontTTF::GetMaxFontHeight() const
+{
+ return m_maxFontHeight + SPACING_BETWEEN_CHARACTERS_IN_TEXTURE;
+}
+
+std::vector<CGUIFontTTF::Glyph> CGUIFontTTF::GetHarfBuzzShapedGlyphs(const vecText& text)
+{
+ std::vector<Glyph> glyphs;
+ if (text.empty())
+ {
+ return glyphs;
+ }
+
+ std::vector<hb_script_t> scripts;
+ std::vector<RunInfo> runs;
+ hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_get_default();
+ hb_script_t lastScript;
+ int lastScriptIndex = -1;
+ int lastSetIndex = -1;
+
+ for (const auto& character : text)
+ {
+ scripts.emplace_back(hb_unicode_script(ufuncs, static_cast<wchar_t>(0xffff & character)));
+ }
+
+ // HB_SCRIPT_COMMON or HB_SCRIPT_INHERITED should be replaced with previous script
+ for (size_t i = 0; i < scripts.size(); ++i)
+ {
+ if (scripts[i] == HB_SCRIPT_COMMON || scripts[i] == HB_SCRIPT_INHERITED)
+ {
+ if (lastScriptIndex != -1)
+ {
+ scripts[i] = lastScript;
+ lastSetIndex = i;
+ }
+ }
+ else
+ {
+ for (size_t j = lastSetIndex + 1; j < i; ++j)
+ scripts[j] = scripts[i];
+ lastScript = scripts[i];
+ lastScriptIndex = i;
+ lastSetIndex = i;
+ }
+ }
+
+ lastScript = scripts[0];
+ int lastRunStart = 0;
+
+ for (unsigned int i = 0; i <= static_cast<unsigned int>(scripts.size()); ++i)
+ {
+ if (i == scripts.size() || scripts[i] != lastScript)
+ {
+ RunInfo run{};
+ run.m_startOffset = lastRunStart;
+ run.m_endOffset = i;
+ run.m_script = lastScript;
+ runs.emplace_back(run);
+
+ if (i < scripts.size())
+ {
+ lastScript = scripts[i];
+ lastRunStart = i;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ for (auto& run : runs)
+ {
+ run.m_buffer = hb_buffer_create();
+ hb_buffer_set_direction(run.m_buffer, static_cast<hb_direction_t>(HB_DIRECTION_LTR));
+ hb_buffer_set_script(run.m_buffer, run.m_script);
+
+ for (unsigned int j = run.m_startOffset; j < run.m_endOffset; j++)
+ {
+ hb_buffer_add(run.m_buffer, static_cast<wchar_t>(0xffff & text[j]), j);
+ }
+
+ hb_buffer_set_content_type(run.m_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
+ hb_shape(m_hbFont, run.m_buffer, nullptr, 0);
+ unsigned int glyphCount;
+ run.m_glyphInfos = hb_buffer_get_glyph_infos(run.m_buffer, &glyphCount);
+ run.m_glyphPositions = hb_buffer_get_glyph_positions(run.m_buffer, &glyphCount);
+
+ for (size_t k = 0; k < glyphCount; k++)
+ {
+ glyphs.emplace_back(run.m_glyphInfos[k], run.m_glyphPositions[k]);
+ }
+
+ hb_buffer_destroy(run.m_buffer);
+ }
+
+ return glyphs;
+}
+
+CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr, FT_UInt glyphIndex)
+{
+ const wchar_t letter = static_cast<wchar_t>(chr & 0xffff);
+
+ // ignore linebreaks
+ if (letter == L'\r')
+ return nullptr;
+
+ const character_t style = (chr & 0x7000000) >> 24; // style = 0 - 6
+
+ if (!glyphIndex)
+ glyphIndex = FT_Get_Char_Index(m_face, letter);
+
+ // quick access to the most frequently used glyphs
+ if (glyphIndex < MAX_GLYPH_IDX)
+ {
+ character_t ch = (style << 12) | glyphIndex; // 2^12 = 4096
+
+ if (ch < LOOKUPTABLE_SIZE && m_charquick[ch])
+ return m_charquick[ch];
+ }
+
+ // letters are stored based on style and glyph
+ character_t ch = (style << 16) | glyphIndex;
+
+ // perform binary search on sorted array by m_glyphAndStyle and
+ // if not found obtains position to insert the new m_char to keep sorted
+ int low = 0;
+ int high = m_char.size() - 1;
+ while (low <= high)
+ {
+ int mid = (low + high) >> 1;
+ if (ch > m_char[mid].m_glyphAndStyle)
+ low = mid + 1;
+ else if (ch < m_char[mid].m_glyphAndStyle)
+ high = mid - 1;
+ else
+ return &m_char[mid];
+ }
+ // if we get to here, then low is where we should insert the new character
+
+ int startIndex = low;
+
+ // increase the size of the buffer if we need it
+ if (m_char.size() == m_char.capacity())
+ {
+ m_char.reserve(m_char.capacity() + CHAR_CHUNK);
+ startIndex = 0;
+ }
+
+ // render the character to our texture
+ // must End() as we can't render text to our texture during a Begin(), End() block
+ unsigned int nestedBeginCount = m_nestedBeginCount;
+ m_nestedBeginCount = 1;
+ if (nestedBeginCount)
+ End();
+
+ m_char.emplace(m_char.begin() + low);
+ if (!CacheCharacter(glyphIndex, style, m_char.data() + low))
+ { // unable to cache character - try clearing them all out and starting over
+ CLog::LogF(LOGDEBUG, "Unable to cache character. Clearing character cache of {} characters",
+ m_char.size());
+ ClearCharacterCache();
+ low = 0;
+ startIndex = 0;
+ m_char.emplace(m_char.begin());
+ if (!CacheCharacter(glyphIndex, style, m_char.data()))
+ {
+ CLog::LogF(LOGERROR, "Unable to cache character (out of memory?)");
+ if (nestedBeginCount)
+ Begin();
+ m_nestedBeginCount = nestedBeginCount;
+ return nullptr;
+ }
+ }
+
+ if (nestedBeginCount)
+ Begin();
+ m_nestedBeginCount = nestedBeginCount;
+
+ // update the lookup table with only the m_char addresses that have changed
+ for (size_t i = startIndex; i < m_char.size(); ++i)
+ {
+ if (m_char[i].m_glyphIndex < MAX_GLYPH_IDX)
+ {
+ // >> 16 is style (0-6), then 16 - 12 (>> 4) is equivalent to style * 4096
+ character_t ch = ((m_char[i].m_glyphAndStyle & 0xffff0000) >> 4) | m_char[i].m_glyphIndex;
+
+ if (ch < LOOKUPTABLE_SIZE)
+ m_charquick[ch] = m_char.data() + i;
+ }
+ }
+
+ return m_char.data() + low;
+}
+
+bool CGUIFontTTF::CacheCharacter(FT_UInt glyphIndex, uint32_t style, Character* ch)
+{
+ FT_Glyph glyph = nullptr;
+ if (FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_TARGET_LIGHT))
+ {
+ CLog::LogF(LOGDEBUG, "Failed to load glyph {:x}", glyphIndex);
+ return false;
+ }
+
+ // make bold if applicable
+ if (style & FONT_STYLE_BOLD)
+ SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_BOLD);
+ // and italics if applicable
+ if (style & FONT_STYLE_ITALICS)
+ ObliqueGlyph(m_face->glyph);
+ // and light if applicable
+ if (style & FONT_STYLE_LIGHT)
+ SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_LIGHT);
+ // grab the glyph
+ if (FT_Get_Glyph(m_face->glyph, &glyph))
+ {
+ CLog::LogF(LOGDEBUG, "Failed to get glyph {:x}", glyphIndex);
+ return false;
+ }
+ if (m_stroker)
+ FT_Glyph_StrokeBorder(&glyph, m_stroker, 0, 1);
+ // render the glyph
+ if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, 1))
+ {
+ CLog::LogF(LOGDEBUG, "Failed to render glyph {:x} to a bitmap", glyphIndex);
+ return false;
+ }
+
+ FT_BitmapGlyph bitGlyph = (FT_BitmapGlyph)glyph;
+ FT_Bitmap bitmap = bitGlyph->bitmap;
+ bool isEmptyGlyph = (bitmap.width == 0 || bitmap.rows == 0);
+
+ if (!isEmptyGlyph)
+ {
+ if (bitGlyph->left < 0)
+ m_posX += -bitGlyph->left;
+
+ // check we have enough room for the character.
+ // cast-fest is here to avoid warnings due to freeetype version differences (signedness of width).
+ if (static_cast<int>(m_posX + bitGlyph->left + bitmap.width +
+ SPACING_BETWEEN_CHARACTERS_IN_TEXTURE) > static_cast<int>(m_textureWidth))
+ { // no space - gotta drop to the next line (which means creating a new texture and copying it across)
+ m_posX = 1;
+ m_posY += GetTextureLineHeight();
+ if (bitGlyph->left < 0)
+ m_posX += -bitGlyph->left;
+
+ if (m_posY + GetTextureLineHeight() >= m_textureHeight)
+ {
+ // create the new larger texture
+ unsigned int newHeight = m_posY + GetTextureLineHeight();
+ // check for max height
+ if (newHeight > m_renderSystem->GetMaxTextureSize())
+ {
+ CLog::LogF(LOGDEBUG, "New cache texture is too large ({} > {} pixels long)", newHeight,
+ m_renderSystem->GetMaxTextureSize());
+ FT_Done_Glyph(glyph);
+ return false;
+ }
+
+ std::unique_ptr<CTexture> newTexture = ReallocTexture(newHeight);
+ if (!newTexture)
+ {
+ FT_Done_Glyph(glyph);
+ CLog::LogF(LOGDEBUG, "Failed to allocate new texture of height {}", newHeight);
+ return false;
+ }
+ m_texture = std::move(newTexture);
+ }
+ m_posY = GetMaxFontHeight();
+ }
+
+ if (!m_texture)
+ {
+ FT_Done_Glyph(glyph);
+ CLog::LogF(LOGDEBUG, "no texture to cache character to");
+ return false;
+ }
+ }
+
+ // set the character in our table
+ ch->m_glyphAndStyle = (style << 16) | glyphIndex;
+ ch->m_glyphIndex = glyphIndex;
+ ch->m_offsetX = static_cast<short>(bitGlyph->left);
+ ch->m_offsetY = static_cast<short>(m_cellBaseLine - bitGlyph->top);
+ ch->m_left = isEmptyGlyph ? 0.0f : (static_cast<float>(m_posX));
+ ch->m_top = isEmptyGlyph ? 0.0f : (static_cast<float>(m_posY));
+ ch->m_right = ch->m_left + bitmap.width;
+ ch->m_bottom = ch->m_top + bitmap.rows;
+ ch->m_advance =
+ static_cast<float>(MathUtils::round_int(static_cast<double>(m_face->glyph->advance.x) / 64));
+
+ // we need only render if we actually have some pixels
+ if (!isEmptyGlyph)
+ {
+ // ensure our rect will stay inside the texture (it *should* but we need to be certain)
+ unsigned int x1 = std::max(m_posX, 0);
+ unsigned int y1 = std::max(m_posY, 0);
+ unsigned int x2 = std::min(x1 + bitmap.width, m_textureWidth);
+ unsigned int y2 = std::min(y1 + bitmap.rows, m_textureHeight);
+ m_maxFontHeight = std::max(m_maxFontHeight, y2);
+ CopyCharToTexture(bitGlyph, x1, y1, x2, y2);
+
+ m_posX += SPACING_BETWEEN_CHARACTERS_IN_TEXTURE +
+ static_cast<unsigned short>(ch->m_right - ch->m_left);
+ }
+
+ // free the glyph
+ FT_Done_Glyph(glyph);
+
+ return true;
+}
+
+void CGUIFontTTF::RenderCharacter(CGraphicContext& context,
+ float posX,
+ float posY,
+ const Character* ch,
+ UTILS::COLOR::Color color,
+ bool roundX,
+ std::vector<SVertex>& vertices)
+{
+ // actual image width isn't same as the character width as that is
+ // just baseline width and height should include the descent
+ const float width = ch->m_right - ch->m_left;
+ const float height = ch->m_bottom - ch->m_top;
+
+ // return early if nothing to render
+ if (width == 0 || height == 0)
+ return;
+
+ // posX and posY are relative to our origin, and the textcell is offset
+ // from our (posX, posY). Plus, these are unscaled quantities compared to the underlying GUI resolution
+ CRect vertex((posX + ch->m_offsetX) * context.GetGUIScaleX(),
+ (posY + ch->m_offsetY) * context.GetGUIScaleY(),
+ (posX + ch->m_offsetX + width) * context.GetGUIScaleX(),
+ (posY + ch->m_offsetY + height) * context.GetGUIScaleY());
+ vertex += CPoint(m_originX, m_originY);
+ CRect texture(ch->m_left, ch->m_top, ch->m_right, ch->m_bottom);
+ if (!m_renderSystem->ScissorsCanEffectClipping())
+ context.ClipRect(vertex, texture);
+
+ // transform our positions - note, no scaling due to GUI calibration/resolution occurs
+ float x[VERTEX_PER_GLYPH] = {context.ScaleFinalXCoord(vertex.x1, vertex.y1),
+ context.ScaleFinalXCoord(vertex.x2, vertex.y1),
+ context.ScaleFinalXCoord(vertex.x2, vertex.y2),
+ context.ScaleFinalXCoord(vertex.x1, vertex.y2)};
+
+ if (roundX)
+ {
+ // We only round the "left" side of the character, and then use the direction of rounding to
+ // move the "right" side of the character. This ensures that a constant width is kept when rendering
+ // the same letter at the same size at different places of the screen, avoiding the problem
+ // of the "left" side rounding one way while the "right" side rounds the other way, thus getting
+ // altering the width of thin characters substantially. This only really works for positive
+ // coordinates (due to the direction of truncation for negatives) but this is the only case that
+ // really interests us anyway.
+ float rx0 = static_cast<float>(MathUtils::round_int(static_cast<double>(x[0])));
+ float rx3 = static_cast<float>(MathUtils::round_int(static_cast<double>(x[3])));
+ x[1] = static_cast<float>(MathUtils::truncate_int(static_cast<double>(x[1])));
+ x[2] = static_cast<float>(MathUtils::truncate_int(static_cast<double>(x[2])));
+ if (x[0] > 0.0f && rx0 > x[0])
+ x[1] += 1;
+ else if (x[0] < 0.0f && rx0 < x[0])
+ x[1] -= 1;
+ if (x[3] > 0.0f && rx3 > x[3])
+ x[2] += 1;
+ else if (x[3] < 0.0f && rx3 < x[3])
+ x[2] -= 1;
+ x[0] = rx0;
+ x[3] = rx3;
+ }
+
+ const float y[VERTEX_PER_GLYPH] = {
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalYCoord(vertex.x1, vertex.y1)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalYCoord(vertex.x2, vertex.y1)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalYCoord(vertex.x2, vertex.y2)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalYCoord(vertex.x1, vertex.y2))))};
+
+ const float z[VERTEX_PER_GLYPH] = {
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalZCoord(vertex.x1, vertex.y1)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalZCoord(vertex.x2, vertex.y1)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalZCoord(vertex.x2, vertex.y2)))),
+ static_cast<float>(MathUtils::round_int(
+ static_cast<double>(context.ScaleFinalZCoord(vertex.x1, vertex.y2))))};
+
+ // tex coords converted to 0..1 range
+ const float tl = texture.x1 * m_textureScaleX;
+ const float tr = texture.x2 * m_textureScaleX;
+ const float tt = texture.y1 * m_textureScaleY;
+ const float tb = texture.y2 * m_textureScaleY;
+
+ vertices.resize(vertices.size() + VERTEX_PER_GLYPH);
+ SVertex* v = &vertices[vertices.size() - VERTEX_PER_GLYPH];
+ m_color = color;
+
+#if !defined(HAS_DX)
+ uint8_t r = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ uint8_t g = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ uint8_t b = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ uint8_t a = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+#endif
+
+ for (int i = 0; i < VERTEX_PER_GLYPH; i++)
+ {
+#ifdef HAS_DX
+ CD3DHelper::XMStoreColor(&v[i].col, color);
+#else
+ v[i].r = r;
+ v[i].g = g;
+ v[i].b = b;
+ v[i].a = a;
+#endif
+ }
+
+#if defined(HAS_DX)
+ for (int i = 0; i < VERTEX_PER_GLYPH; i++)
+ {
+ v[i].x = x[i];
+ v[i].y = y[i];
+ v[i].z = z[i];
+ }
+
+ v[0].u = tl;
+ v[0].v = tt;
+
+ v[1].u = tr;
+ v[1].v = tt;
+
+ v[2].u = tr;
+ v[2].v = tb;
+
+ v[3].u = tl;
+ v[3].v = tb;
+#else
+ // GL / GLES uses triangle strips, not quads, so have to rearrange the vertex order
+ v[0].u = tl;
+ v[0].v = tt;
+ v[0].x = x[0];
+ v[0].y = y[0];
+ v[0].z = z[0];
+
+ v[1].u = tl;
+ v[1].v = tb;
+ v[1].x = x[3];
+ v[1].y = y[3];
+ v[1].z = z[3];
+
+ v[2].u = tr;
+ v[2].v = tt;
+ v[2].x = x[1];
+ v[2].y = y[1];
+ v[2].z = z[1];
+
+ v[3].u = tr;
+ v[3].v = tb;
+ v[3].x = x[2];
+ v[3].y = y[2];
+ v[3].z = z[2];
+#endif
+}
+
+// Oblique code - original taken from freetype2 (ftsynth.c)
+void CGUIFontTTF::ObliqueGlyph(FT_GlyphSlot slot)
+{
+ /* only oblique outline glyphs */
+ if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
+ return;
+
+ /* we don't touch the advance width */
+
+ /* For italic, simply apply a shear transform, with an angle */
+ /* of about 12 degrees. */
+
+ FT_Matrix transform;
+ transform.xx = 0x10000L;
+ transform.yx = 0x00000L;
+
+ transform.xy = 0x06000L;
+ transform.yy = 0x10000L;
+
+ FT_Outline_Transform(&slot->outline, &transform);
+}
+
+// Embolden code - original taken from freetype2 (ftsynth.c)
+void CGUIFontTTF::SetGlyphStrength(FT_GlyphSlot slot, int glyphStrength)
+{
+ if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
+ return;
+
+ /* some reasonable strength */
+ FT_Pos strength = FT_MulFix(m_face->units_per_EM, m_face->size->metrics.y_scale) / glyphStrength;
+
+ FT_BBox bbox_before, bbox_after;
+ FT_Outline_Get_CBox(&slot->outline, &bbox_before);
+ FT_Outline_Embolden(&slot->outline, strength); // ignore error
+ FT_Outline_Get_CBox(&slot->outline, &bbox_after);
+
+ FT_Pos dx = bbox_after.xMax - bbox_before.xMax;
+ FT_Pos dy = bbox_after.yMax - bbox_before.yMax;
+
+ if (slot->advance.x)
+ slot->advance.x += dx;
+
+ if (slot->advance.y)
+ slot->advance.y += dy;
+
+ slot->metrics.width += dx;
+ slot->metrics.height += dy;
+ slot->metrics.horiBearingY += dy;
+ slot->metrics.horiAdvance += dx;
+ slot->metrics.vertBearingX -= dx / 2;
+ slot->metrics.vertBearingY += dy;
+ slot->metrics.vertAdvance += dy;
+}
+
+float CGUIFontTTF::GetTabSpaceLength()
+{
+ const Character* c = GetCharacter(static_cast<character_t>('X'), 0);
+ return c ? c->m_advance * TAB_SPACE_LENGTH : 28.0f * TAB_SPACE_LENGTH;
+}
diff --git a/xbmc/guilib/GUIFontTTF.h b/xbmc/guilib/GUIFontTTF.h
new file mode 100644
index 0000000..2ed2703
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTF.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIFont.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h"
+
+#include <memory>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include <harfbuzz/hb.h>
+
+#ifdef HAS_DX
+#include <DirectXMath.h>
+#include <DirectXPackedVector.h>
+
+using namespace DirectX;
+using namespace DirectX::PackedVector;
+#endif
+
+class CGraphicContext;
+class CTexture;
+class CRenderSystemBase;
+
+struct FT_FaceRec_;
+struct FT_LibraryRec_;
+struct FT_GlyphSlotRec_;
+struct FT_BitmapGlyphRec_;
+struct FT_StrokerRec_;
+
+typedef struct FT_FaceRec_* FT_Face;
+typedef struct FT_LibraryRec_* FT_Library;
+typedef struct FT_GlyphSlotRec_* FT_GlyphSlot;
+typedef struct FT_BitmapGlyphRec_* FT_BitmapGlyph;
+typedef struct FT_StrokerRec_* FT_Stroker;
+
+typedef uint32_t character_t;
+typedef std::vector<character_t> vecText;
+
+/*!
+ \ingroup textures
+ \brief
+ */
+
+#ifdef HAS_DX
+struct SVertex
+{
+ float x, y, z;
+ XMFLOAT4 col;
+ float u, v;
+ float u2, v2;
+};
+#else
+struct SVertex
+{
+ float x, y, z;
+ unsigned char r, g, b, a;
+ float u, v;
+};
+#endif
+
+#include "GUIFontCache.h"
+
+
+class CGUIFontTTF
+{
+ // use lookup table for the first 4096 glyphs (almost any letter or symbol) to
+ // speed up GUI rendering and decrease CPU usage and less memory reallocations
+ static constexpr int MAX_GLYPH_IDX = 4096;
+ static constexpr size_t LOOKUPTABLE_SIZE = MAX_GLYPH_IDX * FONT_STYLES_COUNT;
+
+ friend class CGUIFont;
+
+public:
+ virtual ~CGUIFontTTF();
+
+ static CGUIFontTTF* CreateGUIFontTTF(const std::string& fontIdent);
+
+ void Clear();
+
+ bool Load(const std::string& strFilename,
+ float height = 20.0f,
+ float aspect = 1.0f,
+ float lineSpacing = 1.0f,
+ bool border = false);
+
+ void Begin();
+ void End();
+ /* The next two should only be called if we've declared we can do hardware clipping */
+ virtual CVertexBuffer CreateVertexBuffer(const std::vector<SVertex>& vertices) const
+ {
+ assert(false);
+ return CVertexBuffer();
+ }
+ virtual void DestroyVertexBuffer(CVertexBuffer& bufferHandle) const {}
+
+ const std::string& GetFontIdent() const { return m_fontIdent; }
+
+protected:
+ explicit CGUIFontTTF(const std::string& fontIdent);
+
+ struct Glyph
+ {
+ hb_glyph_info_t m_glyphInfo{};
+ hb_glyph_position_t m_glyphPosition{};
+
+ Glyph(const hb_glyph_info_t& glyphInfo, const hb_glyph_position_t& glyphPosition)
+ : m_glyphInfo(glyphInfo), m_glyphPosition(glyphPosition)
+ {
+ }
+ };
+
+ struct Character
+ {
+ short m_offsetX;
+ short m_offsetY;
+ float m_left;
+ float m_top;
+ float m_right;
+ float m_bottom;
+ float m_advance;
+ FT_UInt m_glyphIndex;
+ character_t m_glyphAndStyle;
+ };
+
+ struct RunInfo
+ {
+ unsigned int m_startOffset;
+ unsigned int m_endOffset;
+ hb_buffer_t* m_buffer;
+ hb_script_t m_script;
+ hb_glyph_info_t* m_glyphInfos;
+ hb_glyph_position_t* m_glyphPositions;
+ };
+
+ void AddReference();
+ void RemoveReference();
+
+ std::vector<Glyph> GetHarfBuzzShapedGlyphs(const vecText& text);
+
+ float GetTextWidthInternal(const vecText& text);
+ float GetTextWidthInternal(const vecText& text, const std::vector<Glyph>& glyph);
+ float GetCharWidthInternal(character_t ch);
+ float GetTextHeight(float lineSpacing, int numLines) const;
+ float GetTextBaseLine() const { return static_cast<float>(m_cellBaseLine); }
+ float GetLineHeight(float lineSpacing) const;
+ float GetFontHeight() const { return m_height; }
+
+ void DrawTextInternal(CGraphicContext& context,
+ float x,
+ float y,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ const vecText& text,
+ uint32_t alignment,
+ float maxPixelWidth,
+ bool scrolling);
+
+ float m_height{0.0f};
+
+ // Stuff for pre-rendering for speed
+ Character* GetCharacter(character_t letter, FT_UInt glyphIndex);
+ bool CacheCharacter(FT_UInt glyphIndex, uint32_t style, Character* ch);
+ void RenderCharacter(CGraphicContext& context,
+ float posX,
+ float posY,
+ const Character* ch,
+ UTILS::COLOR::Color color,
+ bool roundX,
+ std::vector<SVertex>& vertices);
+ void ClearCharacterCache();
+
+ virtual std::unique_ptr<CTexture> ReallocTexture(unsigned int& newHeight) = 0;
+ virtual bool CopyCharToTexture(FT_BitmapGlyph bitGlyph,
+ unsigned int x1,
+ unsigned int y1,
+ unsigned int x2,
+ unsigned int y2) = 0;
+ virtual void DeleteHardwareTexture() = 0;
+
+ // modifying glyphs
+ void SetGlyphStrength(FT_GlyphSlot slot, int glyphStrength);
+ static void ObliqueGlyph(FT_GlyphSlot slot);
+
+ std::unique_ptr<CTexture>
+ m_texture; // texture that holds our rendered characters (8bit alpha only)
+
+ unsigned int m_textureWidth{0}; // width of our texture
+ unsigned int m_textureHeight{0}; // height of our texture
+ int m_posX{0}; // current position in the texture
+ int m_posY{0};
+
+ /*! \brief the height of each line in the texture.
+ Accounts for spacing between lines to avoid characters overlapping.
+ */
+ unsigned int GetTextureLineHeight() const;
+ unsigned int GetMaxFontHeight() const;
+
+ UTILS::COLOR::Color m_color{UTILS::COLOR::NONE};
+
+ std::vector<Character> m_char; // our characters
+
+ // room for the first MAX_GLYPH_IDX glyphs in 7 styles
+ Character* m_charquick[LOOKUPTABLE_SIZE]{nullptr};
+
+ bool m_ellipseCached{false};
+ float m_ellipsesWidth{0.0f}; // this is used every character (width of '.')
+
+ unsigned int m_cellBaseLine{0};
+ unsigned int m_cellHeight{0};
+ unsigned int m_maxFontHeight{0};
+
+ unsigned int m_nestedBeginCount{0}; // speedups
+
+ // freetype stuff
+ FT_Face m_face{nullptr};
+ FT_Stroker m_stroker{nullptr};
+
+ hb_font_t* m_hbFont{nullptr};
+
+ float m_originX{0.0f};
+ float m_originY{0.0f};
+
+ unsigned int m_nTexture{0};
+
+ struct CTranslatedVertices
+ {
+ float m_translateX;
+ float m_translateY;
+ float m_translateZ;
+ const CVertexBuffer* m_vertexBuffer;
+ CRect m_clip;
+ CTranslatedVertices(float translateX,
+ float translateY,
+ float translateZ,
+ const CVertexBuffer* vertexBuffer,
+ const CRect& clip)
+ : m_translateX(translateX),
+ m_translateY(translateY),
+ m_translateZ(translateZ),
+ m_vertexBuffer(vertexBuffer),
+ m_clip(clip)
+ {
+ }
+ };
+ std::vector<CTranslatedVertices> m_vertexTrans;
+ std::vector<SVertex> m_vertex;
+
+ float m_textureScaleX{0.0f};
+ float m_textureScaleY{0.0f};
+
+ const std::string m_fontIdent;
+ std::vector<uint8_t>
+ m_fontFileInMemory; // used only in some cases, see CFreeTypeLibrary::GetFont()
+
+ CGUIFontCache<CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue> m_staticCache;
+ CGUIFontCache<CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue> m_dynamicCache;
+
+ CRenderSystemBase* m_renderSystem;
+
+private:
+ float GetTabSpaceLength();
+
+ virtual bool FirstBegin() = 0;
+ virtual void LastEnd() = 0;
+ CGUIFontTTF(const CGUIFontTTF&) = delete;
+ CGUIFontTTF& operator=(const CGUIFontTTF&) = delete;
+ int m_referenceCount{0};
+};
diff --git a/xbmc/guilib/GUIFontTTFDX.cpp b/xbmc/guilib/GUIFontTTFDX.cpp
new file mode 100644
index 0000000..0f4a6b0
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTFDX.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontTTFDX.h"
+
+#include "GUIFontManager.h"
+#include "GUIShaderDX.h"
+#include "TextureDX.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+// stuff for freetype
+#include <ft2build.h>
+
+using namespace Microsoft::WRL;
+
+#ifdef TARGET_WINDOWS_STORE
+#define generic GenericFromFreeTypeLibrary
+#endif
+
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+namespace
+{
+constexpr size_t ELEMENT_ARRAY_MAX_CHAR_INDEX = 2000;
+} /* namespace */
+
+CGUIFontTTF* CGUIFontTTF::CreateGUIFontTTF(const std::string& fontIdent)
+{
+ return new CGUIFontTTFDX(fontIdent);
+}
+
+CGUIFontTTFDX::CGUIFontTTFDX(const std::string& fontIdent) : CGUIFontTTF(fontIdent)
+{
+ DX::Windowing()->Register(this);
+}
+
+CGUIFontTTFDX::~CGUIFontTTFDX(void)
+{
+ DX::Windowing()->Unregister(this);
+
+ m_vertexBuffer = nullptr;
+ m_staticIndexBuffer = nullptr;
+ if (!m_buffers.empty())
+ {
+ std::for_each(m_buffers.begin(), m_buffers.end(), [](CD3DBuffer* buf) {
+ if (buf)
+ delete buf;
+ });
+ }
+ m_buffers.clear();
+ m_staticIndexBufferCreated = false;
+ m_vertexWidth = 0;
+}
+
+bool CGUIFontTTFDX::FirstBegin()
+{
+ if (!DX::DeviceResources::Get()->GetD3DContext())
+ return false;
+
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+ pGUIShader->Begin(SHADER_METHOD_RENDER_FONT);
+
+ return true;
+}
+
+void CGUIFontTTFDX::LastEnd()
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ if (!pContext || !winSystem)
+ return;
+
+ typedef CGUIFontTTF::CTranslatedVertices trans;
+ bool transIsEmpty = std::all_of(m_vertexTrans.begin(), m_vertexTrans.end(),
+ [](trans& _) { return _.m_vertexBuffer->size <= 0; });
+ // no chars to render
+ if (m_vertex.empty() && transIsEmpty)
+ return;
+
+ CreateStaticIndexBuffer();
+
+ unsigned int offset = 0;
+ unsigned int stride = sizeof(SVertex);
+
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+ // Set font texture as shader resource
+ pGUIShader->SetShaderViews(1, m_speedupTexture->GetAddressOfSRV());
+ // Enable alpha blend
+ DX::Windowing()->SetAlphaBlendEnable(true);
+ // Set our static index buffer
+ pContext->IASetIndexBuffer(m_staticIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
+ // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
+ pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ if (!m_vertex.empty())
+ {
+ // Deal with vertices that had to use software clipping
+ if (!UpdateDynamicVertexBuffer(&m_vertex[0], m_vertex.size()))
+ return;
+
+ // Set the dynamic vertex buffer to active in the input assembler
+ pContext->IASetVertexBuffers(0, 1, m_vertexBuffer.GetAddressOf(), &stride, &offset);
+
+ // Do the actual drawing operation, split into groups of characters no
+ // larger than the pre-determined size of the element array
+ size_t size = m_vertex.size() / 4;
+ for (size_t character = 0; size > character; character += ELEMENT_ARRAY_MAX_CHAR_INDEX)
+ {
+ size_t count = size - character;
+ count = std::min<size_t>(count, ELEMENT_ARRAY_MAX_CHAR_INDEX);
+
+ // 6 indices and 4 vertices per character
+ pGUIShader->DrawIndexed(count * 6, 0, character * 4);
+ }
+ }
+
+ if (!transIsEmpty)
+ {
+ // Deal with the vertices that can be hardware clipped and therefore translated
+
+ // Store current GPU transform
+ XMMATRIX view = pGUIShader->GetView();
+ // Store current scissor
+ CGraphicContext& context = winSystem->GetGfxContext();
+ CRect scissor = context.StereoCorrection(context.GetScissors());
+
+ for (size_t i = 0; i < m_vertexTrans.size(); i++)
+ {
+ // ignore empty buffers
+ if (m_vertexTrans[i].m_vertexBuffer->size == 0)
+ continue;
+
+ // Apply the clip rectangle
+ CRect clip = DX::Windowing()->ClipRectToScissorRect(m_vertexTrans[i].m_clip);
+ // Intersect with current scissors
+ clip.Intersect(scissor);
+
+ // skip empty clip, a little improvement to not render invisible text
+ if (clip.IsEmpty())
+ continue;
+
+ DX::Windowing()->SetScissors(clip);
+
+ // Apply the translation to the model view matrix
+ XMMATRIX translation =
+ XMMatrixTranslation(m_vertexTrans[i].m_translateX, m_vertexTrans[i].m_translateY,
+ m_vertexTrans[i].m_translateZ);
+ pGUIShader->SetView(XMMatrixMultiply(translation, view));
+
+ CD3DBuffer* vbuffer =
+ reinterpret_cast<CD3DBuffer*>(m_vertexTrans[i].m_vertexBuffer->bufferHandle);
+ // Set the static vertex buffer to active in the input assembler
+ ID3D11Buffer* buffers[1] = {vbuffer->Get()};
+ pContext->IASetVertexBuffers(0, 1, buffers, &stride, &offset);
+
+ // Do the actual drawing operation, split into groups of characters no
+ // larger than the pre-determined size of the element array
+ for (size_t character = 0; m_vertexTrans[i].m_vertexBuffer->size > character;
+ character += ELEMENT_ARRAY_MAX_CHAR_INDEX)
+ {
+ size_t count = m_vertexTrans[i].m_vertexBuffer->size - character;
+ count = std::min<size_t>(count, ELEMENT_ARRAY_MAX_CHAR_INDEX);
+
+ // 6 indices and 4 vertices per character
+ pGUIShader->DrawIndexed(count * 6, 0, character * 4);
+ }
+ }
+
+ // restore scissor
+ DX::Windowing()->SetScissors(scissor);
+
+ // Restore the original transform
+ pGUIShader->SetView(view);
+ }
+
+ pGUIShader->RestoreBuffers();
+}
+
+CVertexBuffer CGUIFontTTFDX::CreateVertexBuffer(const std::vector<SVertex>& vertices) const
+{
+ CD3DBuffer* buffer = nullptr;
+ // do not create empty buffers, leave buffer as nullptr, it will be ignored on drawing stage
+ if (!vertices.empty())
+ {
+ buffer = new CD3DBuffer();
+ if (!buffer->Create(D3D11_BIND_VERTEX_BUFFER, vertices.size(), sizeof(SVertex),
+ DXGI_FORMAT_UNKNOWN, D3D11_USAGE_IMMUTABLE, &vertices[0]))
+ CLog::LogF(LOGERROR, "Failed to create vertex buffer.");
+ else
+ AddReference((CGUIFontTTFDX*)this, buffer);
+ }
+
+ return CVertexBuffer(reinterpret_cast<void*>(buffer), vertices.size() / 4, this);
+}
+
+void CGUIFontTTFDX::AddReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer)
+{
+ font->m_buffers.emplace_back(pBuffer);
+}
+
+void CGUIFontTTFDX::DestroyVertexBuffer(CVertexBuffer& buffer) const
+{
+ if (nullptr != buffer.bufferHandle)
+ {
+ CD3DBuffer* vbuffer = reinterpret_cast<CD3DBuffer*>(buffer.bufferHandle);
+ ClearReference((CGUIFontTTFDX*)this, vbuffer);
+ if (vbuffer)
+ delete vbuffer;
+ buffer.bufferHandle = 0;
+ }
+}
+
+void CGUIFontTTFDX::ClearReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer)
+{
+ std::list<CD3DBuffer*>::iterator it =
+ std::find(font->m_buffers.begin(), font->m_buffers.end(), pBuffer);
+ if (it != font->m_buffers.end())
+ font->m_buffers.erase(it);
+}
+
+std::unique_ptr<CTexture> CGUIFontTTFDX::ReallocTexture(unsigned int& newHeight)
+{
+ assert(newHeight != 0);
+ assert(m_textureWidth != 0);
+ if (m_textureHeight == 0)
+ {
+ m_texture.reset();
+ m_speedupTexture.reset();
+ }
+ m_staticCache.Flush();
+ m_dynamicCache.Flush();
+
+ std::unique_ptr<CDXTexture> pNewTexture =
+ std::make_unique<CDXTexture>(m_textureWidth, newHeight, XB_FMT_A8);
+ std::unique_ptr<CD3DTexture> newSpeedupTexture = std::make_unique<CD3DTexture>();
+ if (!newSpeedupTexture->Create(m_textureWidth, newHeight, 1, D3D11_USAGE_DEFAULT,
+ DXGI_FORMAT_R8_UNORM))
+ {
+ return nullptr;
+ }
+
+ // There might be data to copy from the previous texture
+ if (newSpeedupTexture && m_speedupTexture)
+ {
+ CD3D11_BOX rect(0, 0, 0, m_textureWidth, m_textureHeight, 1);
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+ pContext->CopySubresourceRegion(newSpeedupTexture->Get(), 0, 0, 0, 0, m_speedupTexture->Get(),
+ 0, &rect);
+ }
+
+ m_texture.reset();
+
+ m_textureHeight = newHeight;
+ m_textureScaleY = 1.0f / m_textureHeight;
+ m_speedupTexture = std::move(newSpeedupTexture);
+
+ return pNewTexture;
+}
+
+bool CGUIFontTTFDX::CopyCharToTexture(
+ FT_BitmapGlyph bitGlyph, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2)
+{
+ FT_Bitmap bitmap = bitGlyph->bitmap;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetImmediateContext();
+ if (m_speedupTexture && m_speedupTexture->Get() && pContext && bitmap.buffer)
+ {
+ CD3D11_BOX dstBox(x1, y1, 0, x2, y2, 1);
+ pContext->UpdateSubresource(m_speedupTexture->Get(), 0, &dstBox, bitmap.buffer, bitmap.pitch,
+ 0);
+ return true;
+ }
+
+ return false;
+}
+
+void CGUIFontTTFDX::DeleteHardwareTexture()
+{
+}
+
+bool CGUIFontTTFDX::UpdateDynamicVertexBuffer(const SVertex* pSysMem, unsigned int vertex_count)
+{
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+
+ if (!pDevice || !pContext)
+ return false;
+
+ unsigned width = sizeof(SVertex) * vertex_count;
+ if (width > m_vertexWidth) // create or re-create
+ {
+ CD3D11_BUFFER_DESC bufferDesc(width, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC,
+ D3D11_CPU_ACCESS_WRITE);
+ D3D11_SUBRESOURCE_DATA initData = {};
+ initData.pSysMem = pSysMem;
+
+ if (FAILED(
+ pDevice->CreateBuffer(&bufferDesc, &initData, m_vertexBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to create the vertex buffer.");
+ return false;
+ }
+
+ m_vertexWidth = width;
+ }
+ else
+ {
+ D3D11_MAPPED_SUBRESOURCE resource;
+ if (FAILED(pContext->Map(m_vertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &resource)))
+ {
+ CLog::LogF(LOGERROR, "Failed to update the vertex buffer.");
+ return false;
+ }
+
+ memcpy(resource.pData, pSysMem, width);
+ pContext->Unmap(m_vertexBuffer.Get(), 0);
+ }
+
+ return true;
+}
+
+void CGUIFontTTFDX::CreateStaticIndexBuffer(void)
+{
+ if (m_staticIndexBufferCreated)
+ return;
+
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+ if (!pDevice)
+ return;
+
+ uint16_t index[ELEMENT_ARRAY_MAX_CHAR_INDEX][6];
+ for (size_t i = 0; i < ELEMENT_ARRAY_MAX_CHAR_INDEX; i++)
+ {
+ index[i][0] = 4 * i;
+ index[i][1] = 4 * i + 1;
+ index[i][2] = 4 * i + 2;
+ index[i][3] = 4 * i + 2;
+ index[i][4] = 4 * i + 3;
+ index[i][5] = 4 * i + 0;
+ }
+
+ CD3D11_BUFFER_DESC desc(sizeof(index), D3D11_BIND_INDEX_BUFFER, D3D11_USAGE_IMMUTABLE);
+ D3D11_SUBRESOURCE_DATA initData = {};
+ initData.pSysMem = index;
+
+ if (SUCCEEDED(
+ pDevice->CreateBuffer(&desc, &initData, m_staticIndexBuffer.ReleaseAndGetAddressOf())))
+ m_staticIndexBufferCreated = true;
+}
+
+bool CGUIFontTTFDX::m_staticIndexBufferCreated = false;
+ComPtr<ID3D11Buffer> CGUIFontTTFDX::m_staticIndexBuffer = nullptr;
+
+void CGUIFontTTFDX::OnDestroyDevice(bool fatal)
+{
+ m_staticIndexBufferCreated = false;
+ m_vertexWidth = 0;
+ m_staticIndexBuffer = nullptr;
+ m_vertexBuffer = nullptr;
+}
+
+void CGUIFontTTFDX::OnCreateDevice(void)
+{
+}
diff --git a/xbmc/guilib/GUIFontTTFDX.h b/xbmc/guilib/GUIFontTTFDX.h
new file mode 100644
index 0000000..ab44ce2
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTFDX.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIFont.h
+\brief
+*/
+
+#include "D3DResource.h"
+#include "GUIFontTTF.h"
+
+#include <list>
+#include <memory>
+#include <vector>
+
+#include <wrl/client.h>
+
+/*!
+ \ingroup textures
+ \brief
+ */
+class CGUIFontTTFDX : public CGUIFontTTF, public ID3DResource
+{
+public:
+ explicit CGUIFontTTFDX(const std::string& fontIdent);
+ virtual ~CGUIFontTTFDX(void);
+
+ bool FirstBegin() override;
+ void LastEnd() override;
+ CVertexBuffer CreateVertexBuffer(const std::vector<SVertex>& vertices) const override;
+ void DestroyVertexBuffer(CVertexBuffer& bufferHandle) const override;
+
+ void OnDestroyDevice(bool fatal) override;
+ void OnCreateDevice() override;
+
+ static void CreateStaticIndexBuffer(void);
+ static void DestroyStaticIndexBuffer(void);
+
+protected:
+ std::unique_ptr<CTexture> ReallocTexture(unsigned int& newHeight) override;
+ bool CopyCharToTexture(FT_BitmapGlyph bitGlyph,
+ unsigned int x1,
+ unsigned int y1,
+ unsigned int x2,
+ unsigned int y2) override;
+ void DeleteHardwareTexture() override;
+
+private:
+ bool UpdateDynamicVertexBuffer(const SVertex* pSysMem, unsigned int count);
+ static void AddReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer);
+ static void ClearReference(CGUIFontTTFDX* font, CD3DBuffer* pBuffer);
+
+ unsigned m_vertexWidth{0};
+ std::unique_ptr<CD3DTexture> m_speedupTexture; // extra texture to speed up reallocations
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_vertexBuffer;
+ std::list<CD3DBuffer*> m_buffers;
+
+ static bool m_staticIndexBufferCreated;
+ static Microsoft::WRL::ComPtr<ID3D11Buffer> m_staticIndexBuffer;
+};
diff --git a/xbmc/guilib/GUIFontTTFGL.cpp b/xbmc/guilib/GUIFontTTFGL.cpp
new file mode 100644
index 0000000..e6dbc03
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTFGL.cpp
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIFontTTFGL.h"
+
+#include "GUIFont.h"
+#include "GUIFontManager.h"
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "TextureManager.h"
+#include "gui3d.h"
+#include "utils/GLUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#ifdef HAS_GL
+#include "rendering/gl/RenderSystemGL.h"
+#elif HAS_GLES
+#include "rendering/gles/RenderSystemGLES.h"
+#endif
+#include "rendering/MatrixGL.h"
+
+#include <cassert>
+#include <memory>
+
+// stuff for freetype
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+
+namespace
+{
+constexpr size_t ELEMENT_ARRAY_MAX_CHAR_INDEX = 1000;
+} /* namespace */
+
+CGUIFontTTF* CGUIFontTTF::CreateGUIFontTTF(const std::string& fontIdent)
+{
+ return new CGUIFontTTFGL(fontIdent);
+}
+
+CGUIFontTTFGL::CGUIFontTTFGL(const std::string& fontIdent) : CGUIFontTTF(fontIdent)
+{
+}
+
+CGUIFontTTFGL::~CGUIFontTTFGL(void)
+{
+ // It's important that all the CGUIFontCacheEntry objects are
+ // destructed before the CGUIFontTTFGL goes out of scope, because
+ // our virtual methods won't be accessible after this point
+ m_dynamicCache.Flush();
+ DeleteHardwareTexture();
+}
+
+bool CGUIFontTTFGL::FirstBegin()
+{
+#if defined(HAS_GL)
+ GLenum pixformat = GL_RED;
+ GLenum internalFormat;
+ unsigned int major, minor;
+ CRenderSystemGL* renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+ renderSystem->GetRenderVersion(major, minor);
+ if (major >= 3)
+ internalFormat = GL_R8;
+ else
+ internalFormat = GL_LUMINANCE;
+ renderSystem->EnableShader(ShaderMethodGL::SM_FONTS);
+#else
+ CRenderSystemGLES* renderSystem =
+ dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_FONTS);
+ GLenum pixformat = GL_ALPHA; // deprecated
+ GLenum internalFormat = GL_ALPHA;
+#endif
+
+ if (m_textureStatus == TEXTURE_REALLOCATED)
+ {
+ if (glIsTexture(m_nTexture))
+ CServiceBroker::GetGUI()->GetTextureManager().ReleaseHwTexture(m_nTexture);
+ m_textureStatus = TEXTURE_VOID;
+ }
+
+ if (m_textureStatus == TEXTURE_VOID)
+ {
+ // Have OpenGL generate a texture object handle for us
+ glGenTextures(1, static_cast<GLuint*>(&m_nTexture));
+
+ // Bind the texture object
+ glBindTexture(GL_TEXTURE_2D, m_nTexture);
+
+ // Set the texture's stretching properties
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ // Set the texture image -- THIS WORKS, so the pixels must be wrong.
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, m_texture->GetWidth(), m_texture->GetHeight(), 0,
+ pixformat, GL_UNSIGNED_BYTE, 0);
+
+ VerifyGLState();
+ m_textureStatus = TEXTURE_UPDATED;
+ }
+
+ if (m_textureStatus == TEXTURE_UPDATED)
+ {
+ // Copies one more line in case we have to sample from there
+ m_updateY2 = std::min(m_updateY2 + 1, m_texture->GetHeight());
+
+ glBindTexture(GL_TEXTURE_2D, m_nTexture);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, m_updateY1, m_texture->GetWidth(), m_updateY2 - m_updateY1,
+ pixformat, GL_UNSIGNED_BYTE,
+ m_texture->GetPixels() + m_updateY1 * m_texture->GetPitch());
+
+ m_updateY1 = m_updateY2 = 0;
+ m_textureStatus = TEXTURE_READY;
+ }
+
+ // Turn Blending On
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE);
+ glEnable(GL_BLEND);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_nTexture);
+
+ return true;
+}
+
+void CGUIFontTTFGL::LastEnd()
+{
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+#ifdef HAS_GL
+ CRenderSystemGL* renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+
+ GLint posLoc = renderSystem->ShaderGetPos();
+ GLint colLoc = renderSystem->ShaderGetCol();
+ GLint tex0Loc = renderSystem->ShaderGetCoord0();
+ GLint modelLoc = renderSystem->ShaderGetModel();
+
+ CreateStaticVertexBuffers();
+
+ // Enable the attributes used by this shader
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(colLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ if (!m_vertex.empty())
+ {
+
+ // Deal with vertices that had to use software clipping
+ std::vector<SVertex> vecVertices(6 * (m_vertex.size() / 4));
+ SVertex* vertices = &vecVertices[0];
+ for (size_t i = 0; i < m_vertex.size(); i += 4)
+ {
+ *vertices++ = m_vertex[i];
+ *vertices++ = m_vertex[i + 1];
+ *vertices++ = m_vertex[i + 2];
+
+ *vertices++ = m_vertex[i + 1];
+ *vertices++ = m_vertex[i + 3];
+ *vertices++ = m_vertex[i + 2];
+ }
+ vertices = &vecVertices[0];
+
+ GLuint VertexVBO;
+
+ glGenBuffers(1, &VertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, VertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(SVertex) * vecVertices.size(), &vecVertices[0],
+ GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(SVertex, x)));
+ glVertexAttribPointer(colLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(SVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(SVertex, r)));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(SVertex, u)));
+
+ glDrawArrays(GL_TRIANGLES, 0, vecVertices.size());
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &VertexVBO);
+ }
+
+#else
+ // GLES 2.0 version.
+ CRenderSystemGLES* renderSystem =
+ dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint colLoc = renderSystem->GUIShaderGetCol();
+ GLint tex0Loc = renderSystem->GUIShaderGetCoord0();
+ GLint modelLoc = renderSystem->GUIShaderGetModel();
+
+
+ CreateStaticVertexBuffers();
+
+ // Enable the attributes used by this shader
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(colLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ if (!m_vertex.empty())
+ {
+ // Deal with vertices that had to use software clipping
+ std::vector<SVertex> vecVertices(6 * (m_vertex.size() / 4));
+ SVertex* vertices = &vecVertices[0];
+
+ for (size_t i = 0; i < m_vertex.size(); i += 4)
+ {
+ *vertices++ = m_vertex[i];
+ *vertices++ = m_vertex[i + 1];
+ *vertices++ = m_vertex[i + 2];
+
+ *vertices++ = m_vertex[i + 1];
+ *vertices++ = m_vertex[i + 3];
+ *vertices++ = m_vertex[i + 2];
+ }
+
+ vertices = &vecVertices[0];
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<char*>(vertices) + offsetof(SVertex, x));
+ glVertexAttribPointer(colLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(SVertex),
+ reinterpret_cast<char*>(vertices) + offsetof(SVertex, r));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<char*>(vertices) + offsetof(SVertex, u));
+
+ glDrawArrays(GL_TRIANGLES, 0, vecVertices.size());
+ }
+#endif
+
+ if (!m_vertexTrans.empty())
+ {
+ // Deal with the vertices that can be hardware clipped and therefore translated
+
+ // Bind our pre-calculated array to GL_ELEMENT_ARRAY_BUFFER
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementArrayHandle);
+ // Store current scissor
+ CGraphicContext& context = winSystem->GetGfxContext();
+ CRect scissor = context.StereoCorrection(context.GetScissors());
+
+ for (size_t i = 0; i < m_vertexTrans.size(); i++)
+ {
+ if (m_vertexTrans[i].m_vertexBuffer->bufferHandle == 0)
+ {
+ continue;
+ }
+
+ // Apply the clip rectangle
+ CRect clip = renderSystem->ClipRectToScissorRect(m_vertexTrans[i].m_clip);
+ if (!clip.IsEmpty())
+ {
+ // intersect with current scissor
+ clip.Intersect(scissor);
+ // skip empty clip
+ if (clip.IsEmpty())
+ continue;
+ renderSystem->SetScissors(clip);
+ }
+
+ // Apply the translation to the currently active (top-of-stack) model view matrix
+ glMatrixModview.Push();
+ glMatrixModview.Get().Translatef(m_vertexTrans[i].m_translateX, m_vertexTrans[i].m_translateY,
+ m_vertexTrans[i].m_translateZ);
+ glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glMatrixModview.Get());
+
+ // Bind the buffer to the OpenGL context's GL_ARRAY_BUFFER binding point
+ glBindBuffer(GL_ARRAY_BUFFER, m_vertexTrans[i].m_vertexBuffer->bufferHandle);
+
+ // Do the actual drawing operation, split into groups of characters no
+ // larger than the pre-determined size of the element array
+ for (size_t character = 0; m_vertexTrans[i].m_vertexBuffer->size > character;
+ character += ELEMENT_ARRAY_MAX_CHAR_INDEX)
+ {
+ size_t count = m_vertexTrans[i].m_vertexBuffer->size - character;
+ count = std::min<size_t>(count, ELEMENT_ARRAY_MAX_CHAR_INDEX);
+
+ // Set up the offsets of the various vertex attributes within the buffer
+ // object bound to GL_ARRAY_BUFFER
+ glVertexAttribPointer(
+ posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<GLvoid*>(character * sizeof(SVertex) * 4 + offsetof(SVertex, x)));
+ glVertexAttribPointer(
+ colLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(SVertex),
+ reinterpret_cast<GLvoid*>(character * sizeof(SVertex) * 4 + offsetof(SVertex, r)));
+ glVertexAttribPointer(
+ tex0Loc, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex),
+ reinterpret_cast<GLvoid*>(character * sizeof(SVertex) * 4 + offsetof(SVertex, u)));
+
+ glDrawElements(GL_TRIANGLES, 6 * count, GL_UNSIGNED_SHORT, 0);
+ }
+
+ glMatrixModview.Pop();
+ }
+ // Restore the original scissor rectangle
+ renderSystem->SetScissors(scissor);
+ // Restore the original model view matrix
+ glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glMatrixModview.Get());
+ // Unbind GL_ARRAY_BUFFER and GL_ELEMENT_ARRAY_BUFFER
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ // Disable the attributes used by this shader
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(colLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+#ifdef HAS_GL
+ renderSystem->DisableShader();
+#else
+ renderSystem->DisableGUIShader();
+#endif
+}
+
+CVertexBuffer CGUIFontTTFGL::CreateVertexBuffer(const std::vector<SVertex>& vertices) const
+{
+ assert(vertices.size() % 4 == 0);
+ GLuint bufferHandle = 0;
+
+ // Do not create empty buffers, leave buffer as 0, it will be ignored in drawing stage
+ if (!vertices.empty())
+ {
+ // Generate a unique buffer object name and put it in bufferHandle
+ glGenBuffers(1, &bufferHandle);
+ // Bind the buffer to the OpenGL context's GL_ARRAY_BUFFER binding point
+ glBindBuffer(GL_ARRAY_BUFFER, bufferHandle);
+ // Create a data store for the buffer object bound to the GL_ARRAY_BUFFER
+ // binding point (i.e. our buffer object) and initialise it from the
+ // specified client-side pointer
+ glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(SVertex), vertices.data(),
+ GL_STATIC_DRAW);
+ // Unbind GL_ARRAY_BUFFER
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ return CVertexBuffer(bufferHandle, vertices.size() / 4, this);
+}
+
+void CGUIFontTTFGL::DestroyVertexBuffer(CVertexBuffer& buffer) const
+{
+ if (buffer.bufferHandle != 0)
+ {
+ // Release the buffer name for reuse
+ glDeleteBuffers(1, static_cast<GLuint*>(&buffer.bufferHandle));
+ buffer.bufferHandle = 0;
+ }
+}
+
+std::unique_ptr<CTexture> CGUIFontTTFGL::ReallocTexture(unsigned int& newHeight)
+{
+ newHeight = CTexture::PadPow2(newHeight);
+
+ std::unique_ptr<CTexture> newTexture =
+ CTexture::CreateTexture(m_textureWidth, newHeight, XB_FMT_A8);
+
+ if (!newTexture || !newTexture->GetPixels())
+ {
+ CLog::Log(LOGERROR, "GUIFontTTFGL::{}: Error creating new cache texture for size {:f}",
+ __func__, m_height);
+ return nullptr;
+ }
+
+ m_textureHeight = newTexture->GetHeight();
+ m_textureScaleY = 1.0f / m_textureHeight;
+ m_textureWidth = newTexture->GetWidth();
+ m_textureScaleX = 1.0f / m_textureWidth;
+ if (m_textureHeight < newHeight)
+ CLog::Log(LOGWARNING, "GUIFontTTFGL::{}: allocated new texture with height of {}, requested {}",
+ __func__, m_textureHeight, newHeight);
+ m_staticCache.Flush();
+ m_dynamicCache.Flush();
+
+ memset(newTexture->GetPixels(), 0, m_textureHeight * newTexture->GetPitch());
+ if (m_texture)
+ {
+ m_updateY1 = 0;
+ m_updateY2 = m_texture->GetHeight();
+
+ unsigned char* src = m_texture->GetPixels();
+ unsigned char* dst = newTexture->GetPixels();
+ for (unsigned int y = 0; y < m_texture->GetHeight(); y++)
+ {
+ memcpy(dst, src, m_texture->GetPitch());
+ src += m_texture->GetPitch();
+ dst += newTexture->GetPitch();
+ }
+ }
+
+ m_textureStatus = TEXTURE_REALLOCATED;
+
+ return newTexture;
+}
+
+bool CGUIFontTTFGL::CopyCharToTexture(
+ FT_BitmapGlyph bitGlyph, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2)
+{
+ FT_Bitmap bitmap = bitGlyph->bitmap;
+
+ unsigned char* source = bitmap.buffer;
+ unsigned char* target = m_texture->GetPixels() + y1 * m_texture->GetPitch() + x1;
+
+ for (unsigned int y = y1; y < y2; y++)
+ {
+ memcpy(target, source, x2 - x1);
+ source += bitmap.width;
+ target += m_texture->GetPitch();
+ }
+
+ switch (m_textureStatus)
+ {
+ case TEXTURE_UPDATED:
+ {
+ m_updateY1 = std::min(m_updateY1, y1);
+ m_updateY2 = std::max(m_updateY2, y2);
+ }
+ break;
+
+ case TEXTURE_READY:
+ {
+ m_updateY1 = y1;
+ m_updateY2 = y2;
+ m_textureStatus = TEXTURE_UPDATED;
+ }
+ break;
+
+ case TEXTURE_REALLOCATED:
+ {
+ m_updateY2 = std::max(m_updateY2, y2);
+ }
+ break;
+
+ case TEXTURE_VOID:
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void CGUIFontTTFGL::DeleteHardwareTexture()
+{
+ if (m_textureStatus != TEXTURE_VOID)
+ {
+ if (glIsTexture(m_nTexture))
+ CServiceBroker::GetGUI()->GetTextureManager().ReleaseHwTexture(m_nTexture);
+
+ m_textureStatus = TEXTURE_VOID;
+ m_updateY1 = m_updateY2 = 0;
+ }
+}
+
+void CGUIFontTTFGL::CreateStaticVertexBuffers(void)
+{
+ if (m_staticVertexBufferCreated)
+ return;
+
+ // Bind a new buffer to the OpenGL context's GL_ELEMENT_ARRAY_BUFFER binding point
+ glGenBuffers(1, &m_elementArrayHandle);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementArrayHandle);
+
+ // Create an array holding the mesh indices to convert quads to triangles
+ GLushort index[ELEMENT_ARRAY_MAX_CHAR_INDEX][6];
+ for (size_t i = 0; i < ELEMENT_ARRAY_MAX_CHAR_INDEX; i++)
+ {
+ index[i][0] = 4 * i;
+ index[i][1] = 4 * i + 1;
+ index[i][2] = 4 * i + 2;
+ index[i][3] = 4 * i + 1;
+ index[i][4] = 4 * i + 3;
+ index[i][5] = 4 * i + 2;
+ }
+
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof index, index, GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ m_staticVertexBufferCreated = true;
+}
+
+void CGUIFontTTFGL::DestroyStaticVertexBuffers(void)
+{
+ if (!m_staticVertexBufferCreated)
+ return;
+
+ glDeleteBuffers(1, &m_elementArrayHandle);
+ m_staticVertexBufferCreated = false;
+}
+
+GLuint CGUIFontTTFGL::m_elementArrayHandle{0};
+bool CGUIFontTTFGL::m_staticVertexBufferCreated{false};
diff --git a/xbmc/guilib/GUIFontTTFGL.h b/xbmc/guilib/GUIFontTTFGL.h
new file mode 100644
index 0000000..22fbe64
--- /dev/null
+++ b/xbmc/guilib/GUIFontTTFGL.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIFontTTF.h"
+
+#include <string>
+#include <vector>
+
+#include "system_gl.h"
+
+class CGUIFontTTFGL : public CGUIFontTTF
+{
+public:
+ explicit CGUIFontTTFGL(const std::string& fontIdent);
+ ~CGUIFontTTFGL(void) override;
+
+ bool FirstBegin() override;
+ void LastEnd() override;
+
+ CVertexBuffer CreateVertexBuffer(const std::vector<SVertex>& vertices) const override;
+ void DestroyVertexBuffer(CVertexBuffer& bufferHandle) const override;
+ static void CreateStaticVertexBuffers(void);
+ static void DestroyStaticVertexBuffers(void);
+
+protected:
+ std::unique_ptr<CTexture> ReallocTexture(unsigned int& newHeight) override;
+ bool CopyCharToTexture(FT_BitmapGlyph bitGlyph,
+ unsigned int x1,
+ unsigned int y1,
+ unsigned int x2,
+ unsigned int y2) override;
+ void DeleteHardwareTexture() override;
+
+ static GLuint m_elementArrayHandle;
+
+private:
+ unsigned int m_updateY1{0};
+ unsigned int m_updateY2{0};
+
+ enum TextureStatus
+ {
+ TEXTURE_VOID = 0,
+ TEXTURE_READY,
+ TEXTURE_REALLOCATED,
+ TEXTURE_UPDATED,
+ };
+
+ TextureStatus m_textureStatus{TEXTURE_VOID};
+
+ static bool m_staticVertexBufferCreated;
+};
diff --git a/xbmc/guilib/GUIImage.cpp b/xbmc/guilib/GUIImage.cpp
new file mode 100644
index 0000000..9b7ad23
--- /dev/null
+++ b/xbmc/guilib/GUIImage.cpp
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIImage.h"
+
+#include "GUIMessage.h"
+#include "utils/log.h"
+
+#include <cassert>
+
+using namespace KODI::GUILIB;
+
+CGUIImage::CGUIImage(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& texture)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_texture(CGUITexture::CreateTexture(posX, posY, width, height, texture))
+{
+ m_crossFadeTime = 0;
+ m_currentFadeTime = 0;
+ m_lastRenderTime = 0;
+ ControlType = GUICONTROL_IMAGE;
+ m_bDynamicResourceAlloc=false;
+}
+
+CGUIImage::CGUIImage(const CGUIImage& left)
+ : CGUIControl(left),
+ m_image(left.m_image),
+ m_info(left.m_info),
+ m_texture(left.m_texture->Clone()),
+ m_fadingTextures(),
+ m_currentTexture(),
+ m_currentFallback()
+{
+ m_crossFadeTime = left.m_crossFadeTime;
+ // defaults
+ m_currentFadeTime = 0;
+ m_lastRenderTime = 0;
+ ControlType = GUICONTROL_IMAGE;
+ m_bDynamicResourceAlloc=false;
+}
+
+CGUIImage::~CGUIImage(void) = default;
+
+void CGUIImage::UpdateVisibility(const CGUIListItem *item)
+{
+ CGUIControl::UpdateVisibility(item);
+
+ // now that we've checked for conditional info, we can
+ // check for allocation
+ AllocateOnDemand();
+}
+
+void CGUIImage::UpdateDiffuseColor(const CGUIListItem* item)
+{
+ if (m_texture->SetDiffuseColor(m_diffuseColor, item))
+ {
+ MarkDirtyRegion();
+ }
+}
+
+void CGUIImage::UpdateInfo(const CGUIListItem *item)
+{
+ // The texture may also depend on info conditions. Update the diffuse color in that case.
+ if (m_texture->GetDiffuseColor().HasInfo())
+ UpdateDiffuseColor(item);
+
+ if (m_info.IsConstant())
+ return; // nothing to do
+
+ // don't allow image to change while animating out
+ if (HasProcessed() && IsAnimating(ANIM_TYPE_HIDDEN) && !IsVisibleFromSkin())
+ return;
+
+ if (item)
+ SetFileName(m_info.GetItemLabel(item, true, &m_currentFallback));
+ else
+ SetFileName(m_info.GetLabel(m_parentID, true, &m_currentFallback));
+}
+
+void CGUIImage::AllocateOnDemand()
+{
+ // if we're hidden, we can free our resources and return
+ if (!IsVisible() && m_visible != DELAYED)
+ {
+ if (m_bDynamicResourceAlloc && m_texture->IsAllocated())
+ FreeResourcesButNotAnims();
+ return;
+ }
+
+ // either visible or delayed - we need the resources allocated in either case
+ if (!m_texture->IsAllocated())
+ AllocResources();
+}
+
+void CGUIImage::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // check whether our image failed to allocate, and if so drop back to the fallback image
+ if (m_texture->FailedToAlloc() && m_texture->GetFileName() != m_info.GetFallback())
+ {
+ if (!m_currentFallback.empty() && m_texture->GetFileName() != m_currentFallback)
+ m_texture->SetFileName(m_currentFallback);
+ else
+ m_texture->SetFileName(m_info.GetFallback());
+ }
+
+ if (m_crossFadeTime)
+ {
+ // make sure our texture has started allocating
+ if (m_texture->AllocResources())
+ MarkDirtyRegion();
+
+ // compute the frame time
+ unsigned int frameTime = 0;
+ if (m_lastRenderTime)
+ frameTime = currentTime - m_lastRenderTime;
+ if (!frameTime)
+ frameTime = (unsigned int)(1000 / CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS());
+ m_lastRenderTime = currentTime;
+
+ if (m_fadingTextures.size()) // have some fading images
+ { // anything other than the last old texture needs to be faded out as per usual
+ for (std::vector<CFadingTexture *>::iterator i = m_fadingTextures.begin(); i != m_fadingTextures.end() - 1;)
+ {
+ if (!ProcessFading(*i, frameTime, currentTime))
+ i = m_fadingTextures.erase(i);
+ else
+ ++i;
+ }
+
+ if (m_texture->ReadyToRender() || m_texture->GetFileName().empty())
+ { // fade out the last one as well
+ if (!ProcessFading(m_fadingTextures[m_fadingTextures.size() - 1], frameTime, currentTime))
+ m_fadingTextures.erase(m_fadingTextures.end() - 1);
+ }
+ else
+ { // keep the last one fading in
+ CFadingTexture *texture = m_fadingTextures[m_fadingTextures.size() - 1];
+ texture->m_fadeTime += frameTime;
+ if (texture->m_fadeTime > m_crossFadeTime)
+ texture->m_fadeTime = m_crossFadeTime;
+
+ if (texture->m_texture->SetAlpha(GetFadeLevel(texture->m_fadeTime)))
+ MarkDirtyRegion();
+ if (texture->m_texture->SetDiffuseColor(m_diffuseColor))
+ MarkDirtyRegion();
+ if (texture->m_texture->Process(currentTime))
+ MarkDirtyRegion();
+ }
+ }
+
+ if (m_texture->ReadyToRender() || m_texture->GetFileName().empty())
+ { // fade the new one in
+ m_currentFadeTime += frameTime;
+ if (m_currentFadeTime > m_crossFadeTime || frameTime == 0) // for if we allocate straight away on creation
+ m_currentFadeTime = m_crossFadeTime;
+ }
+ if (m_texture->SetAlpha(GetFadeLevel(m_currentFadeTime)))
+ MarkDirtyRegion();
+ }
+
+ if (!m_texture->GetDiffuseColor().HasInfo())
+ UpdateDiffuseColor(nullptr);
+
+ if (m_texture->Process(currentTime))
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIImage::Render()
+{
+ if (!IsVisible()) return;
+
+ for (auto& itr : m_fadingTextures)
+ itr->m_texture->Render();
+
+ m_texture->Render();
+
+ CGUIControl::Render();
+}
+
+bool CGUIImage::ProcessFading(CGUIImage::CFadingTexture *texture, unsigned int frameTime, unsigned int currentTime)
+{
+ assert(texture);
+ if (texture->m_fadeTime <= frameTime)
+ { // time to kill off the texture
+ MarkDirtyRegion();
+ delete texture;
+ return false;
+ }
+ // render this texture
+ texture->m_fadeTime -= frameTime;
+
+ if (texture->m_texture->SetAlpha(GetFadeLevel(texture->m_fadeTime)))
+ MarkDirtyRegion();
+ if (texture->m_texture->SetDiffuseColor(m_diffuseColor))
+ MarkDirtyRegion();
+ if (texture->m_texture->Process(currentTime))
+ MarkDirtyRegion();
+
+ return true;
+}
+
+bool CGUIImage::OnAction(const CAction &action)
+{
+ return false;
+}
+
+bool CGUIImage::OnMessage(CGUIMessage& message)
+{
+ if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
+ {
+ if (!m_info.IsConstant())
+ FreeTextures(true); // true as we want to free the texture immediately
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_SET_FILENAME)
+ {
+ SetFileName(message.GetLabel());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_GET_FILENAME)
+ {
+ message.SetLabel(GetFileName());
+ return true;
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIImage::AllocResources()
+{
+ if (m_texture->GetFileName().empty())
+ return;
+
+ CGUIControl::AllocResources();
+ m_texture->AllocResources();
+}
+
+void CGUIImage::FreeTextures(bool immediately /* = false */)
+{
+ m_texture->FreeResources(immediately);
+ for (unsigned int i = 0; i < m_fadingTextures.size(); i++)
+ delete m_fadingTextures[i];
+ m_fadingTextures.clear();
+ m_currentTexture.clear();
+ if (!m_info.IsConstant()) // constant textures never change
+ m_texture->SetFileName("");
+}
+
+void CGUIImage::FreeResources(bool immediately)
+{
+ FreeTextures(immediately);
+ CGUIControl::FreeResources(immediately);
+}
+
+void CGUIImage::SetInvalid()
+{
+ m_texture->SetInvalid();
+ CGUIControl::SetInvalid();
+}
+
+// WORKAROUND - we are currently resetting all animations when this is called, which shouldn't be the case
+// see CGUIControl::FreeResources() - this needs remedying.
+void CGUIImage::FreeResourcesButNotAnims()
+{
+ FreeTextures();
+ m_bAllocated=false;
+ m_hasProcessed = false;
+}
+
+void CGUIImage::DynamicResourceAlloc(bool bOnOff)
+{
+ m_bDynamicResourceAlloc = bOnOff;
+ m_texture->DynamicResourceAlloc(bOnOff);
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+}
+
+bool CGUIImage::CanFocus() const
+{
+ return false;
+}
+
+float CGUIImage::GetTextureWidth() const
+{
+ return m_texture->GetTextureWidth();
+}
+
+float CGUIImage::GetTextureHeight() const
+{
+ return m_texture->GetTextureHeight();
+}
+
+CRect CGUIImage::CalcRenderRegion() const
+{
+ CRect region = m_texture->GetRenderRect();
+
+ for (const auto& itr : m_fadingTextures)
+ region.Union(itr->m_texture->GetRenderRect());
+
+ return CGUIControl::CalcRenderRegion().Intersect(region);
+}
+
+const std::string &CGUIImage::GetFileName() const
+{
+ return m_texture->GetFileName();
+}
+
+void CGUIImage::SetAspectRatio(const CAspectRatio &aspect)
+{
+ m_texture->SetAspectRatio(aspect);
+}
+
+void CGUIImage::SetCrossFade(unsigned int time)
+{
+ m_crossFadeTime = time;
+ if (!m_crossFadeTime && m_texture->IsLazyLoaded() && !m_info.GetFallback().empty())
+ m_crossFadeTime = 1;
+}
+
+void CGUIImage::SetFileName(const std::string& strFileName, bool setConstant, const bool useCache)
+{
+ if (setConstant)
+ m_info.SetLabel(strFileName, "", GetParentID());
+
+ // Set whether or not to use cache
+ m_texture->SetUseCache(useCache);
+
+ if (m_crossFadeTime)
+ {
+ // set filename on the next texture
+ if (m_currentTexture == strFileName)
+ return; // nothing to do - we already have this image
+
+ if (m_texture->ReadyToRender() || m_texture->GetFileName().empty())
+ { // save the current image
+ m_fadingTextures.push_back(new CFadingTexture(m_texture.get(), m_currentFadeTime));
+ MarkDirtyRegion();
+ }
+ m_currentFadeTime = 0;
+ }
+ if (m_currentTexture != strFileName)
+ { // texture is changing - attempt to load it, and save the name in m_currentTexture.
+ // we'll check whether it loaded or not in Render()
+ m_currentTexture = strFileName;
+ if (m_texture->SetFileName(m_currentTexture))
+ MarkDirtyRegion();
+ }
+}
+
+#ifdef _DEBUG
+void CGUIImage::DumpTextureUse()
+{
+ if (m_texture->IsAllocated())
+ {
+ if (GetID())
+ CLog::Log(LOGDEBUG, "Image control {} using texture {}", GetID(), m_texture->GetFileName());
+ else
+ CLog::Log(LOGDEBUG, "Using texture {}", m_texture->GetFileName());
+ }
+}
+#endif
+
+void CGUIImage::SetWidth(float width)
+{
+ m_texture->SetWidth(width);
+ CGUIControl::SetWidth(m_texture->GetWidth());
+}
+
+void CGUIImage::SetHeight(float height)
+{
+ m_texture->SetHeight(height);
+ CGUIControl::SetHeight(m_texture->GetHeight());
+}
+
+void CGUIImage::SetPosition(float posX, float posY)
+{
+ m_texture->SetPosition(posX, posY);
+ CGUIControl::SetPosition(posX, posY);
+}
+
+void CGUIImage::SetInfo(const GUIINFO::CGUIInfoLabel &info)
+{
+ m_info = info;
+ // a constant image never needs updating
+ if (m_info.IsConstant())
+ m_texture->SetFileName(m_info.GetLabel(0));
+}
+
+unsigned char CGUIImage::GetFadeLevel(unsigned int time) const
+{
+ float amount = (float)time / m_crossFadeTime;
+ // we want a semi-transparent image, so we need to use a more complicated
+ // fade technique. Assuming a black background (not generally true, but still...)
+ // we have
+ // b(t) = [a - b(1-t)*a] / a*(1-b(1-t)*a),
+ // where a = alpha, and b(t):[0,1] -> [0,1] is the blend function.
+ // solving, we get
+ // b(t) = [1 - (1-a)^t] / a
+ const float alpha = 0.7f;
+ return (unsigned char)(255.0f * (1 - pow(1-alpha, amount))/alpha);
+}
+
+std::string CGUIImage::GetDescription(void) const
+{
+ return GetFileName();
+}
+
diff --git a/xbmc/guilib/GUIImage.dox b/xbmc/guilib/GUIImage.dox
new file mode 100644
index 0000000..cd56fe7
--- /dev/null
+++ b/xbmc/guilib/GUIImage.dox
@@ -0,0 +1,98 @@
+/*!
+
+\page Image_Control Image Control
+\brief **Used to show an image.**
+
+\tableofcontents
+
+The image control is used for displaying images in Kodi. You can choose the
+position, size, transparency and contents of the image to be displayed.
+
+--------------------------------------------------------------------------------
+\section Image_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="image" id="1">
+ <description>My first image control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <fadetime>200</fadetime>
+ <texture border="5" flipy="true" flipx="false">mytexture.png</texture>
+ <bordertexture border="5">mybordertexture.png</bordertexture>
+ <bordersize>5</bordersize>
+ <texture>$INFO[MusicPlayer.Cover]</texture>
+ <aspectratio>keep</aspectratio>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Image_Control_sect2 Image Size and Type Restrictions
+For the <b>`<texture>`</b> tags, and for all <b>`<texture>`</b> tags in
+other controls, there is a small set of rules that you should follow if at all
+possible:
+
+\subsection Image_Control_sect2_1 Size
+Images can be any size, though some graphics cards allow only power of 2 textures,
+so this may be a consideration. For the most case, it doesn't really matter what
+size things are - Kodi will quite happily load most files.
+
+\subsection Image_Control_sect2_2 Formats
+If you wish to use transparency, then use PNG. It is suggested that you use PNG
+and JPG as much as possible. Note that once the images are injected into
+Textures.xbt, they are not stored as JPG or PNG – rather they are stored in
+a native format used for GPUs for faster loading, such as ARGB or DXTc textures.
+The size of the images (in kb) is therefore not as important as the size of the
+images in pixels – so feel free to store them in a lossless (eg PNG) manner if
+you wish.
+
+The only exception to this is if you require an animated texture. In this case,
+we support only animated GIF. There are also some animated gifs that may not
+work. Use ImageReady CS and make sure you set the gif-anim to “restore to
+background” and they should work fine.
+
+--------------------------------------------------------------------------------
+\section Image_Control_sect3 Available tags and attributes
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| aspectratio | This specifies how the image will be drawn inside the box defined by <b>`<width>`</b> and <b>`<height>`</b>. [See here for more information](http://kodi.wiki/view/Aspect_Ratio).
+| texture | Specifies the image file which should be displayed. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| bordertexture | Specifies the image file which should be displayed as a border around the image. Use the <b>`<bordersize>`</b> to specify the size of the border. The <b>`<width>`</b>, <b>`<height>`</b> box specifies the size of the image plus border.
+| bordersize | Specifies the size of the border. A single number specifies the border should be the same size all the way around the image, whereas a comma separated list of 4 values indicates left,top,right,bottom values.
+| info | Specifies the information that this image control is presenting. Kodi will select the texture to use based on this tag. [See here for more information](http://kodi.wiki/view/InfoLabels).
+| fadetime | This specifies a crossfade time that will be used whenever the underlying <b>`<texture>`</b> filename changes. The previous image will be held until the new image is ready, and then they will be crossfaded. This is particularly useful for a large thumbnail image showing the focused item in a list, or for fanart or other large backdrops.
+| background | For images inside a container, you can specify background="true" to load the textures in a background worker thread. [See here for additional information about background loading](http://kodi.wiki/view/Background_Image_Loader).
+
+For the tags "texture" and "bordertexture", the following attributes are available.
+
+| Attribute | Description |
+|--------------:|:--------------------------------------------------------------|
+| border | Used to specify a region of the texture to be considered a border that should not be scaled when the texture is scaled to fit a control's dimensions. The portion in the border region will remain unscaled. Particularly useful to achieve rounded corners that do not change size when a control is resized. Note that zoom animations and GUI rescaling will still affect the border region - it is just the scaling of the texture to the control size which is unaffected. Using border="5" will give a 5 pixel border all around the texture. You can specify each of the border amounts on each edge individually using border="left,top,right,bottom".
+| infill | Available if the "border" attribute is used. Hint for the GUI drawing routine if the center portion or just the outer frame should be drawn. Useful if the texture center is fully transparent. Accepts boolean values "true" (default) and "false".
+| flipx | Specifies that the texture should be flipped in the horizontal direction, useful for reflections.
+| flipy | Specifies that the texture should be flipped in the vertical direction, useful for reflections.
+| background | Used on the <b>`<imagepath>`</b> tag. When set to "true", this forces the image to be loaded via the large texture manager (except for textures located in Textures.xbt).
+| diffuse | Specifies a diffuse texture which is "modulated" or multiplied with the texture in order to give specific effects, such as making the image gain transparency, or tint it in various ways. As it's applied per-pixel many effects are possible, the most popular being by a transparent reflection which slowly gets more and more opaque. This is achieved using a WHITE image where the transparency increases as you move down the texture.
+| colordiffuse | This specifies the color to be used for the texture basis. It's is specified in hexadecimal notation using the AARRGGBB format. If you define <b>`<texture colordiffuse="FFFFAAFF">texture.png</texture>`</b> (magenta), the image will be given a magenta tint when rendered. You can also specify this as a name from the colour theme, or you can also use $VAR and $INFO for dynamic values.
+
+@skinning_v20 <b>infill</b> New texture attribute added.
+
+--------------------------------------------------------------------------------
+\section Image_Control_sect4 See also
+
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIImage.h b/xbmc/guilib/GUIImage.h
new file mode 100644
index 0000000..e9e9347
--- /dev/null
+++ b/xbmc/guilib/GUIImage.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file guiImage.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief
+ */
+
+class CGUIImage : public CGUIControl
+{
+public:
+ class CFadingTexture
+ {
+ public:
+ CFadingTexture(const CGUITexture* texture, unsigned int fadeTime)
+ {
+ // create a copy of our texture, and allocate resources
+ m_texture.reset(texture->Clone());
+ m_texture->AllocResources();
+ m_fadeTime = fadeTime;
+ m_fading = false;
+ };
+ ~CFadingTexture()
+ {
+ m_texture->FreeResources();
+ };
+
+ std::unique_ptr<CGUITexture> m_texture; ///< texture to fade out
+ unsigned int m_fadeTime; ///< time to fade out (ms)
+ bool m_fading; ///< whether we're fading out
+
+ private:
+ CFadingTexture(const CFadingTexture&) = delete;
+ CFadingTexture& operator=(const CFadingTexture&) = delete;
+ };
+
+ CGUIImage(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& texture);
+ CGUIImage(const CGUIImage &left);
+ ~CGUIImage(void) override;
+ CGUIImage* Clone() const override { return new CGUIImage(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+ bool OnAction(const CAction &action) override ;
+ bool OnMessage(CGUIMessage& message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ bool IsDynamicallyAllocated() override { return m_bDynamicResourceAlloc; }
+ void SetInvalid() override;
+ bool CanFocus() const override;
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+
+ virtual void SetInfo(const KODI::GUILIB::GUIINFO::CGUIInfoLabel &info);
+ virtual void SetFileName(const std::string& strFileName, bool setConstant = false, const bool useCache = true);
+ virtual void SetAspectRatio(const CAspectRatio &aspect);
+ void SetWidth(float width) override;
+ void SetHeight(float height) override;
+ void SetPosition(float posX, float posY) override;
+ std::string GetDescription() const override;
+ void SetCrossFade(unsigned int time);
+
+ const std::string& GetFileName() const;
+ float GetTextureWidth() const;
+ float GetTextureHeight() const;
+
+ CRect CalcRenderRegion() const override;
+
+#ifdef _DEBUG
+ void DumpTextureUse() override;
+#endif
+protected:
+ virtual void AllocateOnDemand();
+ virtual void FreeTextures(bool immediately = false);
+ void FreeResourcesButNotAnims();
+ unsigned char GetFadeLevel(unsigned int time) const;
+ bool ProcessFading(CFadingTexture *texture, unsigned int frameTime, unsigned int currentTime);
+
+ /*!
+ * \brief Update the diffuse color based on the current item infos
+ * \param item the item to for info resolution
+ */
+ void UpdateDiffuseColor(const CGUIListItem* item);
+
+ bool m_bDynamicResourceAlloc;
+
+ // border + conditional info
+ CTextureInfo m_image;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info;
+
+ std::unique_ptr<CGUITexture> m_texture;
+ std::vector<CFadingTexture *> m_fadingTextures;
+ std::string m_currentTexture;
+ std::string m_currentFallback;
+
+ unsigned int m_crossFadeTime;
+ unsigned int m_currentFadeTime;
+ unsigned int m_lastRenderTime;
+};
+
diff --git a/xbmc/guilib/GUIIncludes.cpp b/xbmc/guilib/GUIIncludes.cpp
new file mode 100644
index 0000000..d950c2d
--- /dev/null
+++ b/xbmc/guilib/GUIIncludes.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIIncludes.h"
+
+#include "GUIInfoManager.h"
+#include "addons/Skin.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+#include "interfaces/info/SkinVariable.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB;
+
+CGUIIncludes::CGUIIncludes()
+{
+ m_constantAttributes.insert("x");
+ m_constantAttributes.insert("y");
+ m_constantAttributes.insert("width");
+ m_constantAttributes.insert("height");
+ m_constantAttributes.insert("center");
+ m_constantAttributes.insert("max");
+ m_constantAttributes.insert("min");
+ m_constantAttributes.insert("w");
+ m_constantAttributes.insert("h");
+ m_constantAttributes.insert("time");
+ m_constantAttributes.insert("acceleration");
+ m_constantAttributes.insert("delay");
+ m_constantAttributes.insert("start");
+ m_constantAttributes.insert("end");
+ m_constantAttributes.insert("center");
+ m_constantAttributes.insert("border");
+ m_constantAttributes.insert("repeat");
+
+ m_constantNodes.insert("posx");
+ m_constantNodes.insert("posy");
+ m_constantNodes.insert("left");
+ m_constantNodes.insert("centerleft");
+ m_constantNodes.insert("right");
+ m_constantNodes.insert("centerright");
+ m_constantNodes.insert("top");
+ m_constantNodes.insert("centertop");
+ m_constantNodes.insert("bottom");
+ m_constantNodes.insert("centerbottom");
+ m_constantNodes.insert("width");
+ m_constantNodes.insert("height");
+ m_constantNodes.insert("offsetx");
+ m_constantNodes.insert("offsety");
+ m_constantNodes.insert("textoffsetx");
+ m_constantNodes.insert("textoffsety");
+ m_constantNodes.insert("textwidth");
+ m_constantNodes.insert("spinposx");
+ m_constantNodes.insert("spinposy");
+ m_constantNodes.insert("spinwidth");
+ m_constantNodes.insert("spinheight");
+ m_constantNodes.insert("radioposx");
+ m_constantNodes.insert("radioposy");
+ m_constantNodes.insert("radiowidth");
+ m_constantNodes.insert("radioheight");
+ m_constantNodes.insert("sliderwidth");
+ m_constantNodes.insert("sliderheight");
+ m_constantNodes.insert("itemgap");
+ m_constantNodes.insert("bordersize");
+ m_constantNodes.insert("timeperimage");
+ m_constantNodes.insert("fadetime");
+ m_constantNodes.insert("pauseatend");
+ m_constantNodes.insert("depth");
+ m_constantNodes.insert("movement");
+ m_constantNodes.insert("focusposition");
+
+ m_expressionAttributes.insert("condition");
+
+ m_expressionNodes.insert("visible");
+ m_expressionNodes.insert("enable");
+ m_expressionNodes.insert("usealttexture");
+ m_expressionNodes.insert("selected");
+}
+
+CGUIIncludes::~CGUIIncludes() = default;
+
+void CGUIIncludes::Clear()
+{
+ m_includes.clear();
+ m_defaults.clear();
+ m_constants.clear();
+ m_skinvariables.clear();
+ m_files.clear();
+ m_expressions.clear();
+}
+
+void CGUIIncludes::Load(const std::string &file)
+{
+ if (!Load_Internal(file))
+ return;
+ FlattenExpressions();
+ FlattenSkinVariableConditions();
+}
+
+bool CGUIIncludes::Load_Internal(const std::string &file)
+{
+ // check to see if we already have this loaded
+ if (HasLoaded(file))
+ return true;
+
+ CXBMCTinyXML doc;
+ if (!doc.LoadFile(file))
+ {
+ CLog::Log(LOGINFO, "Error loading include file {}: {} (row: {}, col: {})", file,
+ doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol());
+ return false;
+ }
+
+ TiXmlElement *root = doc.RootElement();
+ if (!root || !StringUtils::EqualsNoCase(root->Value(), "includes"))
+ {
+ CLog::Log(LOGERROR, "Error loading include file {}: Root element <includes> required.", file);
+ return false;
+ }
+
+ // load components
+ LoadDefaults(root);
+ LoadConstants(root);
+ LoadExpressions(root);
+ LoadVariables(root);
+ LoadIncludes(root);
+
+ m_files.push_back(file);
+
+ return true;
+}
+
+void CGUIIncludes::LoadDefaults(const TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ const TiXmlElement* child = node->FirstChildElement("default");
+ while (child)
+ {
+ const char *type = child->Attribute("type");
+ if (type && child->FirstChild())
+ m_defaults.insert(std::make_pair(type, *child));
+
+ child = child->NextSiblingElement("default");
+ }
+}
+
+void CGUIIncludes::LoadExpressions(const TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ const TiXmlElement* child = node->FirstChildElement("expression");
+ while (child)
+ {
+ const char *tagName = child->Attribute("name");
+ if (tagName && child->FirstChild())
+ m_expressions.insert(std::make_pair(tagName, "[" + child->FirstChild()->ValueStr() + "]"));
+
+ child = child->NextSiblingElement("expression");
+ }
+}
+
+
+void CGUIIncludes::LoadConstants(const TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ const TiXmlElement* child = node->FirstChildElement("constant");
+ while (child)
+ {
+ const char *tagName = child->Attribute("name");
+ if (tagName && child->FirstChild())
+ m_constants.insert(std::make_pair(tagName, child->FirstChild()->ValueStr()));
+
+ child = child->NextSiblingElement("constant");
+ }
+}
+
+void CGUIIncludes::LoadVariables(const TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ const TiXmlElement* child = node->FirstChildElement("variable");
+ while (child)
+ {
+ const char *tagName = child->Attribute("name");
+ if (tagName && child->FirstChild())
+ m_skinvariables.insert(std::make_pair(tagName, *child));
+
+ child = child->NextSiblingElement("variable");
+ }
+}
+
+void CGUIIncludes::LoadIncludes(const TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ const TiXmlElement* child = node->FirstChildElement("include");
+ while (child)
+ {
+ const char *tagName = child->Attribute("name");
+ if (tagName && child->FirstChild())
+ {
+ // we'll parse and store parameter list with defaults when include definition is first encountered
+ // if there's a <definition> tag only use its body as the actually included part
+ const TiXmlElement *definitionTag = child->FirstChildElement("definition");
+ const TiXmlElement *includeBody = definitionTag ? definitionTag : child;
+
+ // if there's a <param> tag there also must be a <definition> tag
+ Params defaultParams;
+ bool haveParamTags = GetParameters(child, "default", defaultParams);
+ if (haveParamTags && !definitionTag)
+ CLog::Log(LOGWARNING, "Skin has invalid include definition: {}", tagName);
+ else
+ m_includes.insert({ tagName, { *includeBody, std::move(defaultParams) } });
+ }
+ else if (child->Attribute("file"))
+ {
+ std::string file = g_SkinInfo->GetSkinPath(child->Attribute("file"));
+ const char *condition = child->Attribute("condition");
+
+ if (condition)
+ { // load include file if condition evals to true
+ if (CServiceBroker::GetGUI()->GetInfoManager().Register(condition)->Get(
+ INFO::DEFAULT_CONTEXT))
+ Load_Internal(file);
+ }
+ else
+ Load_Internal(file);
+ }
+ child = child->NextSiblingElement("include");
+ }
+}
+
+void CGUIIncludes::FlattenExpressions()
+{
+ for (auto& expression : m_expressions)
+ {
+ std::vector<std::string> resolved = std::vector<std::string>();
+ resolved.push_back(expression.first);
+ FlattenExpression(expression.second, resolved);
+ }
+}
+
+void CGUIIncludes::FlattenExpression(std::string &expression, const std::vector<std::string> &resolved)
+{
+ std::string original(expression);
+ GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(expression, "EXP", [&](const std::string &expressionName) -> std::string {
+ if (std::find(resolved.begin(), resolved.end(), expressionName) != resolved.end())
+ {
+ CLog::Log(LOGERROR, "Skin has a circular expression \"{}\": {}", resolved.back(), original);
+ return std::string();
+ }
+ auto it = m_expressions.find(expressionName);
+ if (it == m_expressions.end())
+ return std::string();
+
+ std::vector<std::string> rescopy = resolved;
+ rescopy.push_back(expressionName);
+ FlattenExpression(it->second, rescopy);
+
+ return it->second;
+ });
+}
+
+void CGUIIncludes::FlattenSkinVariableConditions()
+{
+ for (auto& variable : m_skinvariables)
+ {
+ TiXmlElement* valueNode = variable.second.FirstChildElement("value");
+ while (valueNode)
+ {
+ const char *condition = valueNode->Attribute("condition");
+ if (condition)
+ valueNode->SetAttribute("condition", ResolveExpressions(condition));
+
+ valueNode = valueNode->NextSiblingElement("value");
+ }
+ }
+}
+
+bool CGUIIncludes::HasLoaded(const std::string &file) const
+{
+ for (const auto& loadedFile : m_files)
+ {
+ if (loadedFile == file)
+ return true;
+ }
+ return false;
+}
+
+void CGUIIncludes::Resolve(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
+{
+ if (!node)
+ return;
+
+ SetDefaults(node);
+ ResolveConstants(node);
+ ResolveExpressions(node);
+ ResolveIncludes(node, xmlIncludeConditions);
+
+ TiXmlElement *child = node->FirstChildElement();
+ while (child)
+ {
+ // recursive call
+ Resolve(child, xmlIncludeConditions);
+ child = child->NextSiblingElement();
+ }
+}
+
+void CGUIIncludes::SetDefaults(TiXmlElement *node)
+{
+ if (node->ValueStr() != "control")
+ return;
+
+ std::string type = XMLUtils::GetAttribute(node, "type");
+ const auto it = m_defaults.find(type);
+ if (it != m_defaults.end())
+ {
+ // we don't insert <left> et. al. if <posx> or <posy> is specified
+ bool hasPosX(node->FirstChild("posx") != nullptr);
+ bool hasPosY(node->FirstChild("posy") != nullptr);
+
+ const TiXmlElement &element = (*it).second;
+ const TiXmlElement *tag = element.FirstChildElement();
+ while (tag)
+ {
+ std::string value = tag->ValueStr();
+ bool skip(false);
+ if (hasPosX && (value == "left" || value == "right" || value == "centerleft" || value == "centerright"))
+ skip = true;
+ if (hasPosY && (value == "top" || value == "bottom" || value == "centertop" || value == "centerbottom"))
+ skip = true;
+ // we insert at the end of block
+ if (!skip)
+ node->InsertEndChild(*tag);
+ tag = tag->NextSiblingElement();
+ }
+ }
+}
+
+void CGUIIncludes::ResolveConstants(TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ TiXmlNode *child = node->FirstChild();
+ if (child && child->Type() == TiXmlNode::TINYXML_TEXT && m_constantNodes.count(node->ValueStr()))
+ {
+ child->SetValue(ResolveConstant(child->ValueStr()));
+ }
+ else
+ {
+ TiXmlAttribute *attribute = node->FirstAttribute();
+ while (attribute)
+ {
+ if (m_constantAttributes.count(attribute->Name()))
+ attribute->SetValue(ResolveConstant(attribute->ValueStr()));
+
+ attribute = attribute->Next();
+ }
+ }
+}
+
+void CGUIIncludes::ResolveExpressions(TiXmlElement *node)
+{
+ if (!node)
+ return;
+
+ TiXmlNode *child = node->FirstChild();
+ if (child && child->Type() == TiXmlNode::TINYXML_TEXT && m_expressionNodes.count(node->ValueStr()))
+ {
+ child->SetValue(ResolveExpressions(child->ValueStr()));
+ }
+ else
+ {
+ TiXmlAttribute *attribute = node->FirstAttribute();
+ while (attribute)
+ {
+ if (m_expressionAttributes.count(attribute->Name()))
+ attribute->SetValue(ResolveExpressions(attribute->ValueStr()));
+
+ attribute = attribute->Next();
+ }
+ }
+}
+
+void CGUIIncludes::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
+{
+ if (!node)
+ return;
+
+ TiXmlElement *include = node->FirstChildElement("include");
+ while (include)
+ {
+ // file: load includes from specified XML file
+ const char *file = include->Attribute("file");
+ if (file)
+ Load(g_SkinInfo->GetSkinPath(file));
+
+ // condition: process include if condition evals to true
+ const char *condition = include->Attribute("condition");
+ if (condition)
+ {
+ INFO::InfoPtr conditionID =
+ CServiceBroker::GetGUI()->GetInfoManager().Register(ResolveExpressions(condition));
+ bool value = false;
+
+ if (conditionID)
+ {
+ value = conditionID->Get(INFO::DEFAULT_CONTEXT);
+
+ if (xmlIncludeConditions)
+ xmlIncludeConditions->insert(std::make_pair(conditionID, value));
+ }
+
+ if (!value)
+ {
+ include = include->NextSiblingElement("include");
+ continue;
+ }
+ }
+
+ Params params;
+ std::string tagName;
+
+ // determine which form of include call we have
+ const char *name = include->Attribute("content");
+ if (name)
+ {
+ // <include content="MyControl" />
+ // or
+ // <include content="MyControl">
+ // <param name="posx" value="225" />
+ // <param name="posy">150</param>
+ // ...
+ // </include>
+ tagName = name;
+ GetParameters(include, "value", params);
+ }
+ else
+ {
+ const TiXmlNode *child = include->FirstChild();
+ if (child && child->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ // <include>MyControl</include>
+ // old-style includes for backward compatibility
+ tagName = child->ValueStr();
+ }
+ }
+
+ // check, whether the include exists and therefore should be replaced by its definition
+ auto it = m_includes.find(tagName);
+ if (it != m_includes.end())
+ {
+ const TiXmlElement *includeDefinition = &it->second.first;
+
+ // combine passed include parameters with their default values into a single list (no overwrites)
+ const Params& defaultParams = it->second.second;
+ params.insert(defaultParams.begin(), defaultParams.end());
+
+ // process include definition
+ const TiXmlElement *includeDefinitionChild = includeDefinition->FirstChildElement();
+ while (includeDefinitionChild)
+ {
+ // insert before <include> element to keep order of occurrence in xml file
+ TiXmlElement *insertedNode = static_cast<TiXmlElement*>(node->InsertBeforeChild(include, *includeDefinitionChild));
+
+ // process nested
+ InsertNested(node, include, insertedNode);
+
+ // resolve parameters even if parameter list is empty (to remove param references)
+ ResolveParametersForNode(insertedNode, params);
+
+ includeDefinitionChild = includeDefinitionChild->NextSiblingElement();
+ }
+
+ // remove the include element itself
+ node->RemoveChild(include);
+
+ include = node->FirstChildElement("include");
+ }
+ else
+ { // invalid include
+ CLog::Log(LOGWARNING, "Skin has invalid include: {}", tagName);
+ include = include->NextSiblingElement("include");
+ }
+ }
+}
+
+void CGUIIncludes::InsertNested(TiXmlElement *controls, TiXmlElement *include, TiXmlElement *node)
+{
+ TiXmlElement *target;
+ TiXmlElement *nested;
+
+ if (node->ValueStr() == "nested")
+ {
+ nested = node;
+ target = controls;
+ }
+ else
+ {
+ nested = node->FirstChildElement("nested");
+ target = node;
+ }
+
+ if (nested)
+ {
+ // copy all child elements except param elements
+ const TiXmlElement *child = include->FirstChildElement();
+ while (child)
+ {
+ if (child->ValueStr() != "param")
+ {
+ // insert before <nested> element to keep order of occurrence in xml file
+ target->InsertBeforeChild(nested, *child);
+ }
+ child = child->NextSiblingElement();
+ }
+ target->RemoveChild(nested);
+ }
+
+}
+
+bool CGUIIncludes::GetParameters(const TiXmlElement *include, const char *valueAttribute, Params& params)
+{
+ bool foundAny = false;
+
+ // collect parameters from include tag
+ // <include content="MyControl">
+ // <param name="posx" value="225" /> <!-- comments and other tags are ignored here -->
+ // <param name="posy">150</param>
+ // ...
+ // </include>
+
+ if (include)
+ {
+ const TiXmlElement *param = include->FirstChildElement("param");
+ foundAny = param != NULL; // doesn't matter if param isn't entirely valid
+ while (param)
+ {
+ std::string paramName = XMLUtils::GetAttribute(param, "name");
+ if (!paramName.empty())
+ {
+ std::string paramValue;
+
+ // <param name="posx" value="120" />
+ const char *value = param->Attribute(valueAttribute); // try attribute first
+ if (value)
+ paramValue = value;
+ else
+ {
+ // <param name="posx">120</param>
+ const TiXmlNode *child = param->FirstChild();
+ if (child && child->Type() == TiXmlNode::TINYXML_TEXT)
+ paramValue = child->ValueStr(); // and then tag value
+ }
+
+ params.insert({ paramName, paramValue }); // no overwrites
+ }
+ param = param->NextSiblingElement("param");
+ }
+ }
+
+ return foundAny;
+}
+
+void CGUIIncludes::ResolveParametersForNode(TiXmlElement *node, const Params& params)
+{
+ if (!node)
+ return;
+ std::string newValue;
+ // run through this element's attributes, resolving any parameters
+ TiXmlAttribute *attribute = node->FirstAttribute();
+ while (attribute)
+ {
+ ResolveParamsResult result = ResolveParameters(attribute->ValueStr(), newValue, params);
+ if (result == SINGLE_UNDEFINED_PARAM_RESOLVED && strcmp(node->Value(), "param") == 0 &&
+ strcmp(attribute->Name(), "value") == 0 && node->Parent() && strcmp(node->Parent()->Value(), "include") == 0)
+ {
+ // special case: passing <param name="someName" value="$PARAM[undefinedParam]" /> to the nested include
+ // this usually happens when trying to forward a missing parameter from the enclosing include to the nested include
+ // problem: since 'undefinedParam' is not defined, it expands to <param name="someName" value="" /> and overrides any default value set with <param name="someName" default="someValue" /> in the nested include
+ // to prevent this, we'll completely remove this parameter from the nested include call so that the default value can be correctly picked up later
+ node->Parent()->RemoveChild(node);
+ return;
+ }
+ else if (result != NO_PARAMS_FOUND)
+ attribute->SetValue(newValue);
+ attribute = attribute->Next();
+ }
+ // run through this element's value and children, resolving any parameters
+ TiXmlNode *child = node->FirstChild();
+ if (child)
+ {
+ if (child->Type() == TiXmlNode::TINYXML_TEXT)
+ {
+ ResolveParamsResult result = ResolveParameters(child->ValueStr(), newValue, params);
+ if (result == SINGLE_UNDEFINED_PARAM_RESOLVED && strcmp(node->Value(), "param") == 0 &&
+ node->Parent() && strcmp(node->Parent()->Value(), "include") == 0)
+ {
+ // special case: passing <param name="someName">$PARAM[undefinedParam]</param> to the nested include
+ // we'll remove the offending param tag same as above
+ node->Parent()->RemoveChild(node);
+ }
+ else if (result != NO_PARAMS_FOUND)
+ child->SetValue(newValue);
+ }
+ else if (child->Type() == TiXmlNode::TINYXML_ELEMENT ||
+ child->Type() == TiXmlNode::TINYXML_COMMENT)
+ {
+ do
+ {
+ // save next as current child might be removed from the tree
+ TiXmlElement* next = child->NextSiblingElement();
+
+ if (child->Type() == TiXmlNode::TINYXML_ELEMENT)
+ ResolveParametersForNode(static_cast<TiXmlElement*>(child), params);
+
+ child = next;
+ }
+ while (child);
+ }
+ }
+}
+
+class ParamReplacer
+{
+ const std::map<std::string, std::string>& m_params;
+ // keep some stats so that we know exactly what's been resolved
+ int m_numTotalParams;
+ int m_numUndefinedParams;
+public:
+ explicit ParamReplacer(const std::map<std::string, std::string>& params)
+ : m_params(params), m_numTotalParams(0), m_numUndefinedParams(0) {}
+ int GetNumTotalParams() const { return m_numTotalParams; }
+ int GetNumDefinedParams() const { return m_numTotalParams - m_numUndefinedParams; }
+ int GetNumUndefinedParams() const { return m_numUndefinedParams; }
+
+ std::string operator()(const std::string &paramName)
+ {
+ m_numTotalParams++;
+ std::map<std::string, std::string>::const_iterator it = m_params.find(paramName);
+ if (it != m_params.end())
+ return it->second;
+ m_numUndefinedParams++;
+ return "";
+ }
+};
+
+CGUIIncludes::ResolveParamsResult CGUIIncludes::ResolveParameters(const std::string& strInput, std::string& strOutput, const Params& params)
+{
+ ParamReplacer paramReplacer(params);
+ if (GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(strInput, "PARAM", std::ref(paramReplacer), strOutput))
+ // detect special input values of the form "$PARAM[undefinedParam]" (with no extra characters around)
+ return paramReplacer.GetNumUndefinedParams() == 1 && paramReplacer.GetNumTotalParams() == 1 && strOutput.empty() ? SINGLE_UNDEFINED_PARAM_RESOLVED : PARAMS_RESOLVED;
+ return NO_PARAMS_FOUND;
+}
+
+std::string CGUIIncludes::ResolveConstant(const std::string &constant) const
+{
+ std::vector<std::string> values = StringUtils::Split(constant, ",");
+ for (auto& i : values)
+ {
+ std::map<std::string, std::string>::const_iterator it = m_constants.find(i);
+ if (it != m_constants.end())
+ i = it->second;
+ }
+ return StringUtils::Join(values, ",");
+}
+
+std::string CGUIIncludes::ResolveExpressions(const std::string &expression) const
+{
+ std::string work(expression);
+ GUIINFO::CGUIInfoLabel::ReplaceSpecialKeywordReferences(work, "EXP", [&](const std::string &str) -> std::string {
+ std::map<std::string, std::string>::const_iterator it = m_expressions.find(str);
+ if (it != m_expressions.end())
+ return it->second;
+ return "";
+ });
+
+ return work;
+}
+
+const INFO::CSkinVariableString* CGUIIncludes::CreateSkinVariable(const std::string& name, int context)
+{
+ std::map<std::string, TiXmlElement>::const_iterator it = m_skinvariables.find(name);
+ if (it != m_skinvariables.end())
+ return INFO::CSkinVariable::CreateFromXML(it->second, context);
+ return NULL;
+}
diff --git a/xbmc/guilib/GUIIncludes.h b/xbmc/guilib/GUIIncludes.h
new file mode 100644
index 0000000..cc11aa3
--- /dev/null
+++ b/xbmc/guilib/GUIIncludes.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/info/InfoBool.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+// forward definitions
+class TiXmlElement;
+namespace INFO
+{
+ class CSkinVariableString;
+}
+
+class CGUIIncludes
+{
+public:
+ CGUIIncludes();
+ ~CGUIIncludes();
+
+ /*!
+ \brief Clear all include components (defaults, constants, variables, expressions and includes)
+ */
+ void Clear();
+
+ /*!
+ \brief Load all include components(defaults, constants, variables, expressions and includes)
+ from the main entrypoint \code{file}. Flattens nested expressions and expressions in variable
+ conditions after loading all other included files.
+
+ \param file the file to load
+ */
+ void Load(const std::string &file);
+
+ /*!
+ \brief Resolve all include components (defaults, constants, variables, expressions and includes)
+ for the given \code{node}. Place the conditions specified for <include> elements in \code{includeConditions}.
+
+ \param node the node from where we start to resolve the include components
+ \param includeConditions a map that holds the conditions for resolved includes
+ */
+ void Resolve(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* includeConditions = NULL);
+
+ /*!
+ \brief Create a skin variable for the given \code{name} within the given \code{context}.
+
+ \param name the name of the skin variable
+ \param context the context where the variable is created in
+ \return skin variable
+ */
+ const INFO::CSkinVariableString* CreateSkinVariable(const std::string& name, int context);
+
+private:
+ enum ResolveParamsResult
+ {
+ NO_PARAMS_FOUND,
+ PARAMS_RESOLVED,
+ SINGLE_UNDEFINED_PARAM_RESOLVED
+ };
+
+ /*!
+ \brief Load all include components (defaults, constants, variables, expressions and includes)
+ from the given \code{file}.
+
+ \param file the file to load
+ \return true if the file was loaded otherwise false
+ */
+ bool Load_Internal(const std::string &file);
+
+ bool HasLoaded(const std::string &file) const;
+
+ void LoadDefaults(const TiXmlElement *node);
+ void LoadIncludes(const TiXmlElement *node);
+ void LoadVariables(const TiXmlElement *node);
+ void LoadConstants(const TiXmlElement *node);
+ void LoadExpressions(const TiXmlElement *node);
+
+ /*!
+ \brief Resolve all expressions containing other expressions to a single evaluatable expression.
+ */
+ void FlattenExpressions();
+
+ /*!
+ \brief Expand any expressions nested in this expression.
+
+ \param expression the expression to flatten
+ \param resolved list of already evaluated expression names, to avoid expanding circular references
+ */
+ void FlattenExpression(std::string &expression, const std::vector<std::string> &resolved);
+
+ /*!
+ \brief Resolve all variable conditions containing expressions to a single evaluatable condition.
+ */
+ void FlattenSkinVariableConditions();
+
+ void SetDefaults(TiXmlElement *node);
+ void ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions = NULL);
+ void ResolveConstants(TiXmlElement *node);
+ void ResolveExpressions(TiXmlElement *node);
+
+ typedef std::map<std::string, std::string> Params;
+ static void InsertNested(TiXmlElement* controls, TiXmlElement* include, TiXmlElement* node);
+ static bool GetParameters(const TiXmlElement *include, const char *valueAttribute, Params& params);
+ static void ResolveParametersForNode(TiXmlElement *node, const Params& params);
+ static ResolveParamsResult ResolveParameters(const std::string& strInput, std::string& strOutput, const Params& params);
+
+ std::string ResolveConstant(const std::string &constant) const;
+ std::string ResolveExpressions(const std::string &expression) const;
+
+ std::vector<std::string> m_files;
+ std::map<std::string, std::pair<TiXmlElement, Params>> m_includes;
+ std::map<std::string, TiXmlElement> m_defaults;
+ std::map<std::string, TiXmlElement> m_skinvariables;
+ std::map<std::string, std::string> m_constants;
+ std::map<std::string, std::string> m_expressions;
+
+ std::set<std::string> m_constantAttributes;
+ std::set<std::string> m_constantNodes;
+
+ std::set<std::string> m_expressionAttributes;
+ std::set<std::string> m_expressionNodes;
+};
diff --git a/xbmc/guilib/GUIKeyboard.h b/xbmc/guilib/GUIKeyboard.h
new file mode 100644
index 0000000..a6ca5ad
--- /dev/null
+++ b/xbmc/guilib/GUIKeyboard.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/Timer.h"
+
+#include <string>
+
+class CGUIKeyboard;
+enum FILTERING { FILTERING_NONE = 0, FILTERING_CURRENT, FILTERING_SEARCH };
+typedef void (*char_callback_t) (CGUIKeyboard *ref, const std::string &typedString);
+
+#ifdef TARGET_WINDOWS // disable 4355: 'this' used in base member initializer list
+#pragma warning(push)
+#pragma warning(disable: 4355)
+#endif
+
+class CGUIKeyboard : public ITimerCallback
+{
+ public:
+ CGUIKeyboard() : m_idleTimer(this) {}
+ ~CGUIKeyboard() override = default;
+
+ // entrypoint
+ /*!
+ * \brief each native keyboard needs to implement this function with the following behaviour:
+ *
+ * \param pCallback implementation should call this on each keypress with the current whole string
+ * \param initialString implementation should show that initialstring
+ * \param typedstring returns the typed string after close if return is true
+ * \param heading implementation should show a heading (e.x. "search for a movie")
+ * \param bHiddenInput if true the implementation should obfuscate the user input (e.x. by printing "*" for each char)
+ *
+ * \return - true if typedstring is valid and user has confirmed input - false if typedstring is undefined and user canceled the input
+ *
+ */
+ virtual bool ShowAndGetInput(char_callback_t pCallback,
+ const std::string &initialString,
+ std::string &typedString,
+ const std::string &heading,
+ bool bHiddenInput = false) = 0;
+
+ /*!
+ *\brief This call should cancel a currently shown keyboard dialog. The implementation should
+ * return false from the modal ShowAndGetInput once anyone calls this method.
+ */
+ virtual void Cancel() = 0;
+
+ virtual int GetWindowId() const {return 0;}
+
+ // CTimer Interface for autoclose
+ void OnTimeout() override
+ {
+ Cancel();
+ }
+
+ // helpers for autoclose function
+ void startAutoCloseTimer(unsigned int autoCloseMs)
+ {
+ if ( autoCloseMs > 0 )
+ m_idleTimer.Start(std::chrono::milliseconds(autoCloseMs), false);
+ }
+
+ void resetAutoCloseTimer()
+ {
+ if (m_idleTimer.IsRunning())
+ m_idleTimer.Restart();
+ }
+
+ virtual bool SetTextToKeyboard(const std::string &text, bool closeKeyboard = false) { return false; }
+
+ private:
+ CTimer m_idleTimer;
+};
+
+#ifdef TARGET_WINDOWS
+#pragma warning(pop)
+#endif
diff --git a/xbmc/guilib/GUIKeyboardFactory.cpp b/xbmc/guilib/GUIKeyboardFactory.cpp
new file mode 100644
index 0000000..fbf7a22
--- /dev/null
+++ b/xbmc/guilib/GUIKeyboardFactory.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ServiceBroker.h"
+#include "GUIComponent.h"
+#include "messaging/ApplicationMessenger.h"
+#include "LocalizeStrings.h"
+#include "GUIKeyboardFactory.h"
+#include "GUIUserMessages.h"
+#include "GUIWindowManager.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Digest.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+#include "dialogs/GUIDialogKeyboardGeneric.h"
+#if defined(TARGET_DARWIN_EMBEDDED)
+#include "dialogs/GUIDialogKeyboardTouch.h"
+
+#include "platform/darwin/ios-common/DarwinEmbedKeyboard.h"
+#endif
+
+using namespace KODI::MESSAGING;
+using KODI::UTILITY::CDigest;
+
+CGUIKeyboard *CGUIKeyboardFactory::g_activeKeyboard = NULL;
+FILTERING CGUIKeyboardFactory::m_filtering = FILTERING_NONE;
+
+CGUIKeyboardFactory::CGUIKeyboardFactory(void) = default;
+
+CGUIKeyboardFactory::~CGUIKeyboardFactory(void) = default;
+
+void CGUIKeyboardFactory::keyTypedCB(CGUIKeyboard *ref, const std::string &typedString)
+{
+ if(ref)
+ {
+ // send our search message in safe way (only the active window needs it)
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, ref->GetWindowId(), 0);
+ switch(m_filtering)
+ {
+ case FILTERING_SEARCH:
+ message.SetParam1(GUI_MSG_SEARCH_UPDATE);
+ message.SetStringParam(typedString);
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(
+ message, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
+ break;
+ case FILTERING_CURRENT:
+ message.SetParam1(GUI_MSG_FILTER_ITEMS);
+ message.SetStringParam(typedString);
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(message);
+ break;
+ case FILTERING_NONE:
+ break;
+ }
+ ref->resetAutoCloseTimer();
+ }
+}
+
+bool CGUIKeyboardFactory::SendTextToActiveKeyboard(const std::string &aTextString, bool closeKeyboard /* = false */)
+{
+ if (!g_activeKeyboard)
+ return false;
+ return g_activeKeyboard->SetTextToKeyboard(aTextString, closeKeyboard);
+}
+
+
+// Show keyboard with initial value (aTextString) and replace with result string.
+// Returns: true - successful display and input (empty result may return true or false depending on parameter)
+// false - unsuccessful display of the keyboard or cancelled editing
+bool CGUIKeyboardFactory::ShowAndGetInput(std::string& aTextString,
+ const CVariant& heading,
+ bool allowEmptyResult,
+ bool hiddenInput /* = false */,
+ unsigned int autoCloseMs /* = 0 */)
+{
+ bool confirmed = false;
+ //heading can be a string or a localization id
+ std::string headingStr;
+ if (heading.isString())
+ headingStr = heading.asString();
+ else if (heading.isInteger() && heading.asInteger())
+ headingStr = g_localizeStrings.Get((uint32_t)heading.asInteger());
+
+ bool useKodiKeyboard = true;
+#if defined(TARGET_DARWIN_EMBEDDED)
+#if defined(TARGET_DARWIN_TVOS)
+ useKodiKeyboard = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_INPUT_TVOSUSEKODIKEYBOARD);
+#else
+ useKodiKeyboard = CDarwinEmbedKeyboard::hasExternalKeyboard();
+#endif // defined(TARGET_DARWIN_TVOS)
+#endif
+
+ auto& winManager = CServiceBroker::GetGUI()->GetWindowManager();
+ CGUIKeyboard* kb = nullptr;
+ if (useKodiKeyboard)
+ kb = winManager.GetWindow<CGUIDialogKeyboardGeneric>(WINDOW_DIALOG_KEYBOARD);
+#if defined(TARGET_DARWIN_EMBEDDED)
+ else
+ kb = winManager.GetWindow<CGUIDialogKeyboardTouch>(WINDOW_DIALOG_KEYBOARD_TOUCH);
+#endif // defined(TARGET_DARWIN_EMBEDDED)
+
+ if (kb)
+ {
+ g_activeKeyboard = kb;
+ kb->startAutoCloseTimer(autoCloseMs);
+ confirmed = kb->ShowAndGetInput(keyTypedCB, aTextString, aTextString, headingStr, hiddenInput);
+ g_activeKeyboard = NULL;
+ }
+
+ if (confirmed)
+ {
+ if (!allowEmptyResult && aTextString.empty())
+ confirmed = false;
+ }
+
+ return confirmed;
+}
+
+bool CGUIKeyboardFactory::ShowAndGetInput(std::string& aTextString, bool allowEmptyResult, unsigned int autoCloseMs /* = 0 */)
+{
+ return ShowAndGetInput(aTextString, CVariant{""}, allowEmptyResult, false, autoCloseMs);
+}
+
+// Shows keyboard and prompts for a password.
+// Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
+bool CGUIKeyboardFactory::ShowAndGetNewPassword(std::string& newPassword,
+ const CVariant& heading,
+ bool allowEmpty,
+ unsigned int autoCloseMs /* = 0 */)
+{
+ return ShowAndGetInput(newPassword, heading, allowEmpty, true, autoCloseMs);
+}
+
+// Shows keyboard and prompts for a password.
+// Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
+bool CGUIKeyboardFactory::ShowAndGetNewPassword(std::string& newPassword, unsigned int autoCloseMs /* = 0 */)
+{
+ return ShowAndGetNewPassword(newPassword, 12340, false, autoCloseMs);
+}
+
+bool CGUIKeyboardFactory::ShowAndGetFilter(std::string &filter, bool searching, unsigned int autoCloseMs /* = 0 */)
+{
+ m_filtering = searching ? FILTERING_SEARCH : FILTERING_CURRENT;
+ bool ret = ShowAndGetInput(filter, searching ? 16017 : 16028, true, false, autoCloseMs);
+ m_filtering = FILTERING_NONE;
+ return ret;
+}
+
+
+// \brief Show keyboard twice to get and confirm a user-entered password string.
+// \param newPassword Overwritten with user input if return=true.
+// \param heading Heading to display
+// \param allowEmpty Whether a blank password is valid or not.
+// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing.
+bool CGUIKeyboardFactory::ShowAndVerifyNewPassword(std::string& newPassword,
+ const CVariant& heading,
+ bool allowEmpty,
+ unsigned int autoCloseMs /* = 0 */)
+{
+ // Prompt user for password input
+ std::string userInput;
+ if (!ShowAndGetInput(userInput, heading, allowEmpty, true, autoCloseMs))
+ { // user cancelled, or invalid input
+ return false;
+ }
+ // success - verify the password
+ std::string checkInput;
+ if (!ShowAndGetInput(checkInput, 12341, allowEmpty, true, autoCloseMs))
+ { // user cancelled, or invalid input
+ return false;
+ }
+ // check the password
+ if (checkInput == userInput)
+ {
+ newPassword = CDigest::Calculate(CDigest::Type::MD5, userInput);
+ return true;
+ }
+ HELPERS::ShowOKDialogText(CVariant{12341}, CVariant{12344});
+ return false;
+}
+
+// \brief Show keyboard twice to get and confirm a user-entered password string.
+// \param strNewPassword Overwritten with user input if return=true.
+// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing.
+bool CGUIKeyboardFactory::ShowAndVerifyNewPassword(std::string& newPassword, unsigned int autoCloseMs /* = 0 */)
+{
+ const std::string& heading = g_localizeStrings.Get(12340);
+ return ShowAndVerifyNewPassword(newPassword, heading, false, autoCloseMs);
+}
+
+// \brief Show keyboard and verify user input against strPassword.
+// \param strPassword Value to compare against user input.
+// \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer.
+// \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank.
+// \return 0 if successful display and user input. 1 if unsuccessful input. -1 if no user input or canceled editing.
+int CGUIKeyboardFactory::ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries, unsigned int autoCloseMs /* = 0 */)
+{
+ std::string strHeadingTemp;
+ if (1 > iRetries && strHeading.size())
+ strHeadingTemp = strHeading;
+ else
+ strHeadingTemp =
+ StringUtils::Format("{} - {} {}", g_localizeStrings.Get(12326),
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_MASTERLOCK_MAXRETRIES) -
+ iRetries,
+ g_localizeStrings.Get(12343));
+
+ std::string strUserInput;
+ //! @todo GUI Setting to enable disable this feature y/n?
+ if (!ShowAndGetInput(strUserInput, strHeadingTemp, false, true, autoCloseMs)) //bool hiddenInput = false/true ?
+ return -1; // user canceled out
+
+ if (!strPassword.empty())
+ {
+ std::string md5pword2 = CDigest::Calculate(CDigest::Type::MD5, strUserInput);
+ if (StringUtils::EqualsNoCase(strPassword, md5pword2))
+ return 0; // user entered correct password
+ else return 1; // user must have entered an incorrect password
+ }
+ else
+ {
+ if (!strUserInput.empty())
+ {
+ strPassword = CDigest::Calculate(CDigest::Type::MD5, strUserInput);
+ return 0; // user entered correct password
+ }
+ else return 1;
+ }
+}
+
diff --git a/xbmc/guilib/GUIKeyboardFactory.h b/xbmc/guilib/GUIKeyboardFactory.h
new file mode 100644
index 0000000..04272af
--- /dev/null
+++ b/xbmc/guilib/GUIKeyboardFactory.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIKeyboard.h"
+
+#include <string>
+
+class CVariant;
+
+class CGUIKeyboardFactory
+{
+
+ public:
+ CGUIKeyboardFactory(void);
+ virtual ~CGUIKeyboardFactory(void);
+
+ static bool ShowAndGetInput(std::string& aTextString, bool allowEmptyResult, unsigned int autoCloseMs = 0);
+ static bool ShowAndGetInput(std::string& aTextString,
+ const CVariant& heading,
+ bool allowEmptyResult,
+ bool hiddenInput = false,
+ unsigned int autoCloseMs = 0);
+ static bool ShowAndGetNewPassword(std::string& strNewPassword, unsigned int autoCloseMs = 0);
+ static bool ShowAndGetNewPassword(std::string& newPassword,
+ const CVariant& heading,
+ bool allowEmpty,
+ unsigned int autoCloseMs = 0);
+ static bool ShowAndVerifyNewPassword(std::string& strNewPassword, unsigned int autoCloseMs = 0);
+ static bool ShowAndVerifyNewPassword(std::string& newPassword,
+ const CVariant& heading,
+ bool allowEmpty,
+ unsigned int autoCloseMs = 0);
+ static int ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries, unsigned int autoCloseMs = 0);
+ static bool ShowAndGetFilter(std::string& aTextString, bool searching, unsigned int autoCloseMs = 0);
+
+ static bool SendTextToActiveKeyboard(const std::string &aTextString, bool closeKeyboard = false);
+
+ static bool isKeyboardActivated() { return g_activeKeyboard != NULL; }
+ private:
+ static CGUIKeyboard *g_activeKeyboard;
+ static FILTERING m_filtering;
+ static void keyTypedCB(CGUIKeyboard *ref, const std::string &typedString);
+};
diff --git a/xbmc/guilib/GUILabel.cpp b/xbmc/guilib/GUILabel.cpp
new file mode 100644
index 0000000..2687862
--- /dev/null
+++ b/xbmc/guilib/GUILabel.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUILabel.h"
+
+#include <limits>
+
+CGUILabel::CGUILabel(float posX, float posY, float width, float height, const CLabelInfo& labelInfo, CGUILabel::OVER_FLOW overflow)
+ : m_label(labelInfo)
+ , m_textLayout(labelInfo.font, overflow == OVER_FLOW_WRAP, height)
+ , m_scrolling(overflow == OVER_FLOW_SCROLL)
+ , m_overflowType(overflow)
+ , m_scrollInfo(50, 0, labelInfo.scrollSpeed, labelInfo.scrollSuffix)
+ , m_renderRect()
+ , m_maxRect(posX, posY, posX + width, posY + height)
+ , m_invalid(true)
+ , m_color(COLOR_TEXT)
+{
+}
+
+CGUILabel::CGUILabel(const CGUILabel& label)
+ : m_label(label.m_label),
+ m_textLayout(label.m_textLayout),
+ m_scrolling(label.m_scrolling),
+ m_overflowType(label.m_overflowType),
+ m_scrollInfo(label.m_scrollInfo),
+ m_renderRect(label.m_renderRect),
+ m_maxRect(label.m_maxRect),
+ m_invalid(label.m_invalid),
+ m_color(label.m_color),
+ m_maxScrollLoops(label.m_maxScrollLoops)
+{
+}
+
+bool CGUILabel::SetScrolling(bool scrolling)
+{
+ bool changed = m_scrolling != scrolling;
+
+ m_scrolling = scrolling;
+ if (changed)
+ m_scrollInfo.Reset();
+
+ return changed;
+}
+
+bool CGUILabel::SetOverflow(OVER_FLOW overflow)
+{
+ bool changed = m_overflowType != overflow;
+
+ m_overflowType = overflow;
+
+ return changed;
+}
+
+bool CGUILabel::SetColor(CGUILabel::COLOR color)
+{
+ bool changed = m_color != color;
+
+ m_color = color;
+
+ return changed;
+}
+
+UTILS::COLOR::Color CGUILabel::GetColor() const
+{
+ switch (m_color)
+ {
+ case COLOR_SELECTED:
+ return m_label.selectedColor;
+ case COLOR_DISABLED:
+ return m_label.disabledColor;
+ case COLOR_FOCUSED:
+ return m_label.focusedColor ? m_label.focusedColor : m_label.textColor;
+ case COLOR_INVALID:
+ return m_label.invalidColor ? m_label.invalidColor : m_label.textColor;
+ default:
+ break;
+ }
+ return m_label.textColor;
+}
+
+bool CGUILabel::Process(unsigned int currentTime)
+{
+ //! @todo Add the correct processing
+
+ bool overFlows = (m_renderRect.Width() + 0.5f < m_textLayout.GetTextWidth()); // 0.5f to deal with floating point rounding issues
+ bool renderSolid = (m_color == COLOR_DISABLED);
+
+ if (overFlows && m_scrolling && !renderSolid)
+ {
+ if (m_maxScrollLoops < m_scrollInfo.m_loopCount)
+ SetScrolling(false);
+ else
+ return m_textLayout.UpdateScrollinfo(m_scrollInfo);
+ }
+
+ return false;
+}
+
+void CGUILabel::Render()
+{
+ UTILS::COLOR::Color color = GetColor();
+ bool renderSolid = (m_color == COLOR_DISABLED);
+ bool overFlows = (m_renderRect.Width() + 0.5f < m_textLayout.GetTextWidth()); // 0.5f to deal with floating point rounding issues
+ if (overFlows && m_scrolling && !renderSolid)
+ m_textLayout.RenderScrolling(m_renderRect.x1, m_renderRect.y1, m_label.angle, color, m_label.shadowColor, 0, m_renderRect.Width(), m_scrollInfo);
+ else
+ {
+ float posX = m_renderRect.x1;
+ float posY = m_renderRect.y1;
+ uint32_t align = 0;
+ if (!overFlows)
+ { // hack for right and centered multiline text, as GUITextLayout::Render() treats posX as the right hand
+ // or center edge of the text (see GUIFontTTF::DrawTextInternal), and this has already been taken care of
+ // in UpdateRenderRect(), but we wish to still pass the horizontal alignment info through (so that multiline text
+ // is aligned correctly), so we must undo the UpdateRenderRect() changes for horizontal alignment.
+ if (m_label.align & XBFONT_RIGHT)
+ posX += m_renderRect.Width();
+ else if (m_label.align & XBFONT_CENTER_X)
+ posX += m_renderRect.Width() * 0.5f;
+ if (m_label.align & XBFONT_CENTER_Y) // need to pass a centered Y so that <angle> will rotate around the correct point.
+ posY += m_renderRect.Height() * 0.5f;
+ align = m_label.align;
+ }
+ else
+ align |= XBFONT_TRUNCATED;
+ m_textLayout.Render(posX, posY, m_label.angle, color, m_label.shadowColor, align, m_overflowType == OVER_FLOW_CLIP ? m_textLayout.GetTextWidth() : m_renderRect.Width(), renderSolid);
+ }
+}
+
+void CGUILabel::SetInvalid()
+{
+ m_invalid = true;
+}
+
+bool CGUILabel::UpdateColors()
+{
+ return m_label.UpdateColors();
+}
+
+bool CGUILabel::SetMaxRect(float x, float y, float w, float h)
+{
+ CRect oldRect = m_maxRect;
+
+ m_maxRect.SetRect(x, y, x + w, y + h);
+ UpdateRenderRect();
+
+ return oldRect != m_maxRect;
+}
+
+bool CGUILabel::SetAlign(uint32_t align)
+{
+ bool changed = m_label.align != align;
+
+ m_label.align = align;
+ UpdateRenderRect();
+
+ return changed;
+}
+
+bool CGUILabel::SetStyledText(const vecText& text, const std::vector<UTILS::COLOR::Color>& colors)
+{
+ m_textLayout.UpdateStyled(text, colors, m_maxRect.Width());
+ m_invalid = false;
+ return true;
+}
+
+bool CGUILabel::SetText(const std::string &label)
+{
+ if (m_textLayout.Update(label, m_maxRect.Width(), m_invalid))
+ { // needed an update - reset scrolling and update our text layout
+ m_scrollInfo.Reset();
+ UpdateRenderRect();
+ m_invalid = false;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool CGUILabel::SetTextW(const std::wstring &label)
+{
+ if (m_textLayout.UpdateW(label, m_maxRect.Width(), m_invalid))
+ {
+ m_scrollInfo.Reset();
+ UpdateRenderRect();
+ m_invalid = false;
+ return true;
+ }
+ else
+ return false;
+}
+
+void CGUILabel::UpdateRenderRect()
+{
+ // recalculate our text layout
+ float width, height;
+ m_textLayout.GetTextExtent(width, height);
+ width = std::min(width, GetMaxWidth());
+ if (m_label.align & XBFONT_CENTER_Y)
+ m_renderRect.y1 = m_maxRect.y1 + (m_maxRect.Height() - height) * 0.5f;
+ else
+ m_renderRect.y1 = m_maxRect.y1 + m_label.offsetY;
+ if (m_label.align & XBFONT_RIGHT)
+ m_renderRect.x1 = m_maxRect.x2 - width - m_label.offsetX;
+ else if (m_label.align & XBFONT_CENTER_X)
+ m_renderRect.x1 = m_maxRect.x1 + (m_maxRect.Width() - width) * 0.5f;
+ else
+ m_renderRect.x1 = m_maxRect.x1 + m_label.offsetX;
+ m_renderRect.x2 = m_renderRect.x1 + width;
+ m_renderRect.y2 = m_renderRect.y1 + height;
+}
+
+float CGUILabel::GetMaxWidth() const
+{
+ if (m_label.width) return m_label.width;
+ return m_maxRect.Width() - 2*m_label.offsetX;
+}
+
+bool CGUILabel::CheckAndCorrectOverlap(CGUILabel &label1, CGUILabel &label2)
+{
+ CRect rect(label1.m_renderRect);
+ if (rect.Intersect(label2.m_renderRect).IsEmpty())
+ return false; // nothing to do (though it could potentially encroach on the min_space requirement)
+
+ // overlap vertically and horizontally - check alignment
+ CGUILabel &left = label1.m_renderRect.x1 <= label2.m_renderRect.x1 ? label1 : label2;
+ CGUILabel &right = label1.m_renderRect.x1 <= label2.m_renderRect.x1 ? label2 : label1;
+ if ((left.m_label.align & 3) == 0 && right.m_label.align & XBFONT_RIGHT)
+ {
+ static const float min_space = 10;
+ float chopPoint = (left.m_maxRect.x1 + left.GetMaxWidth() + right.m_maxRect.x2 - right.GetMaxWidth()) * 0.5f;
+ // [1 [2...[2 1].|..........1] 2]
+ // [1 [2.....[2 | 1]..1] 2]
+ // [1 [2..........|.[2 1]..1] 2]
+ if (right.m_renderRect.x1 > chopPoint)
+ chopPoint = right.m_renderRect.x1 - min_space;
+ else if (left.m_renderRect.x2 < chopPoint)
+ chopPoint = left.m_renderRect.x2 + min_space;
+ left.m_renderRect.x2 = chopPoint - min_space;
+ right.m_renderRect.x1 = chopPoint + min_space;
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/guilib/GUILabel.h b/xbmc/guilib/GUILabel.h
new file mode 100644
index 0000000..7344667
--- /dev/null
+++ b/xbmc/guilib/GUILabel.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUILabel.h
+\brief
+*/
+
+#include "GUIFont.h"
+#include "GUITextLayout.h"
+#include "guiinfo/GUIInfoColor.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h"
+
+class CLabelInfo
+{
+public:
+ CLabelInfo():
+ scrollSuffix(" | ")
+ {
+ font = NULL;
+ align = XBFONT_LEFT;
+ offsetX = offsetY = 0;
+ width = 0;
+ angle = 0;
+ scrollSpeed = CScrollInfo::defaultSpeed;
+ };
+ bool UpdateColors()
+ {
+ bool changed = false;
+
+ changed |= textColor.Update();
+ changed |= shadowColor.Update();
+ changed |= selectedColor.Update();
+ changed |= disabledColor.Update();
+ changed |= focusedColor.Update();
+ changed |= invalidColor.Update();
+
+ return changed;
+ };
+
+ KODI::GUILIB::GUIINFO::CGUIInfoColor textColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor shadowColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor selectedColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor disabledColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor focusedColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor invalidColor;
+ uint32_t align;
+ float offsetX;
+ float offsetY;
+ float width;
+ float angle;
+ CGUIFont *font;
+ int scrollSpeed;
+ std::string scrollSuffix;
+};
+
+/*!
+ \ingroup controls, labels
+ \brief Class for rendering text labels. Handles alignment and rendering of text within a control.
+ */
+class CGUILabel
+{
+public:
+ /*! \brief allowed color categories for labels, as defined by the skin
+ */
+ enum COLOR { COLOR_TEXT = 0,
+ COLOR_SELECTED,
+ COLOR_FOCUSED,
+ COLOR_DISABLED,
+ COLOR_INVALID };
+
+ /*! \brief allowed overflow handling techniques for labels, as defined by the skin
+ */
+ enum OVER_FLOW { OVER_FLOW_TRUNCATE = 0,
+ OVER_FLOW_SCROLL,
+ OVER_FLOW_WRAP,
+ OVER_FLOW_CLIP };
+
+ CGUILabel(float posX, float posY, float width, float height, const CLabelInfo& labelInfo, OVER_FLOW overflow = OVER_FLOW_TRUNCATE);
+ CGUILabel(const CGUILabel& label);
+
+ virtual ~CGUILabel() = default;
+
+ /*! \brief Process the label
+ \return bool stating if process caused control to change
+ */
+ bool Process(unsigned int currentTime);
+
+ /*! \brief Render the label on screen
+ */
+ void Render();
+
+ /*! \brief Set the maximal extent of the label
+ Sets the maximal size and positioning that the label may render in. Note that `textwidth>` can override
+ this, and `<textoffsetx>` and `<textoffsety>` may also allow the label to be moved outside this rectangle.
+ */
+ bool SetMaxRect(float x, float y, float w, float h);
+
+ bool SetAlign(uint32_t align);
+
+ /*! \brief Set the text to be displayed in the label
+ Updates the label control and recomputes final position and size
+ \param text std::string to set as this labels text
+ \sa SetTextW, SetStyledText
+ */
+ bool SetText(const std::string &label);
+
+ /*! \brief Set the text to be displayed in the label
+ Updates the label control and recomputes final position and size
+ \param text std::wstring to set as this labels text
+ \sa SetText, SetStyledText
+ */
+ bool SetTextW(const std::wstring &label);
+
+ /*! \brief Set styled text to be displayed in the label
+ Updates the label control and recomputes final position and size
+ \param text styled text to set.
+ \param colors colors referenced in the styled text.
+ \sa SetText, SetTextW
+ */
+ bool SetStyledText(const vecText& text, const std::vector<UTILS::COLOR::Color>& colors);
+
+ /*! \brief Set the color to use for the label
+ Sets the color to be used for this label. Takes effect at the next render
+ \param color color to be used for the label
+ */
+ bool SetColor(COLOR color);
+
+ /*! \brief Set the final layout of the current text
+ Overrides the calculated layout of the current text, forcing a particular size and position
+ \param rect CRect containing the extents of the current text
+ \sa GetRenderRect, UpdateRenderRect
+ */
+ void SetRenderRect(const CRect& rect) { m_renderRect = rect; }
+
+ /*! \brief Set whether or not this label control should scroll
+ \param scrolling true if this label should scroll.
+ */
+ bool SetScrolling(bool scrolling);
+
+ /*! \brief Set max. text scroll count
+ */
+ void SetScrollLoopCount(unsigned int loopCount) { m_maxScrollLoops = loopCount; }
+
+ /*! \brief Set how this label should handle overflowing text.
+ \param overflow the overflow type
+ \sa OVER_FLOW
+ */
+ bool SetOverflow(OVER_FLOW overflow);
+
+ /*! \brief Set this label invalid. Forces an update of the control
+ */
+ void SetInvalid();
+
+ /*! \brief Update this labels colors
+ */
+ bool UpdateColors();
+
+ /*! \brief Returns the precalculated final layout of the current text
+ \return CRect containing the extents of the current text
+ \sa SetRenderRect, UpdateRenderRect
+ */
+ const CRect& GetRenderRect() const { return m_renderRect; }
+
+ /*! \brief Returns the precalculated full width of the current text, regardless of layout
+ \return full width of the current text
+ \sa CalcTextWidth
+ */
+ float GetTextWidth() const { return m_textLayout.GetTextWidth(); }
+
+ /*! \brief Returns the maximal width that this label can render into
+ \return Maximal width that this label can render into. Note that this may differ from the
+ amount given in SetMaxRect as offsets and text width overrides have been taken into account.
+ \sa SetMaxRect
+ */
+ float GetMaxWidth() const;
+
+ /*! \brief Calculates the width of some text
+ \param text std::wstring of text whose width we want
+ \return width of the given text
+ \sa GetTextWidth
+ */
+ float CalcTextWidth(const std::wstring& text) const { return m_textLayout.GetTextWidth(text); }
+
+ const CLabelInfo& GetLabelInfo() const { return m_label; }
+ CLabelInfo& GetLabelInfo() { return m_label; }
+
+ /*! \brief Check a left aligned and right aligned label for overlap and cut the labels off so that no overlap occurs
+
+ If a left-aligned label occupies some of the same space on screen as a right-aligned label, then we may be able to
+ correct for this by restricting the width of one or both of them. This routine checks two labels to see whether they
+ satisfy this assumption and, if so, adjusts the render rect of both labels so that they no longer do so. The order
+ of the two labels is not important, but we do assume that the left-aligned label is also the left-most on screen, and
+ that the right-aligned label is the right most on-screen, so that they overlap due to the fact that one or both of
+ the labels are longer than anticipated. In the following diagram, [R...[R R] refers to the maximal allowed and
+ actual space occupied by the right label. Similarly, [L L]...L] refers to the maximal and actual space occupied
+ by the left label. | refers to the central cutting point, i.e. the point that would divide the maximal allowed
+ overlap perfectly in two. There are 3 scenarios to consider:
+
+ cut
+ [L [R...[R L].|..........L] R] left label ends to the left of the cut -> just crop the left label.
+ [L [R.....[R | L]..L] R] both left and right labels occupy more than the cut allows, so crop both.
+ [L [R..........|.[R L]..L] R] right label ends to the right of the cut -> just crop the right label.
+
+ \param label1 First label to check
+ \param label2 Second label to check
+ */
+ static bool CheckAndCorrectOverlap(CGUILabel &label1, CGUILabel &label2);
+
+protected:
+ UTILS::COLOR::Color GetColor() const;
+
+ /*! \brief Computes the final layout of the text
+ Uses the maximal position and width of the text, as well as the text length
+ and alignment to compute the final render rect of the text.
+ \sa GetRenderRect, SetRenderRect
+ */
+ void UpdateRenderRect();
+
+private:
+ CLabelInfo m_label;
+ CGUITextLayout m_textLayout;
+
+ bool m_scrolling;
+ OVER_FLOW m_overflowType;
+ CScrollInfo m_scrollInfo;
+ CRect m_renderRect; ///< actual sizing of text
+ CRect m_maxRect; ///< maximum sizing of text
+ bool m_invalid; ///< if true, the label needs recomputing
+ COLOR m_color; ///< color to render text \sa SetColor, GetColor
+ unsigned int m_maxScrollLoops = ~0U;
+};
diff --git a/xbmc/guilib/GUILabelControl.cpp b/xbmc/guilib/GUILabelControl.cpp
new file mode 100644
index 0000000..9728639
--- /dev/null
+++ b/xbmc/guilib/GUILabelControl.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUILabelControl.h"
+
+#include "GUIFont.h"
+#include "GUIMessage.h"
+#include "utils/CharsetConverter.h"
+#include "utils/ColorUtils.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI::GUILIB;
+
+CGUILabelControl::CGUILabelControl(int parentID, int controlID, float posX, float posY, float width, float height, const CLabelInfo& labelInfo, bool wrapMultiLine, bool bHasPath)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+ , m_label(posX, posY, width, height, labelInfo, wrapMultiLine ? CGUILabel::OVER_FLOW_WRAP : CGUILabel::OVER_FLOW_TRUNCATE)
+{
+ m_bHasPath = bHasPath;
+ m_iCursorPos = 0;
+ m_bShowCursor = false;
+ m_dwCounter = 0;
+ ControlType = GUICONTROL_LABEL;
+ m_startHighlight = m_endHighlight = 0;
+ m_startSelection = m_endSelection = 0;
+ m_minWidth = 0;
+ m_label.SetScrollLoopCount(2);
+}
+
+CGUILabelControl::~CGUILabelControl(void) = default;
+
+void CGUILabelControl::ShowCursor(bool bShow)
+{
+ m_bShowCursor = bShow;
+}
+
+void CGUILabelControl::SetCursorPos(int iPos)
+{
+ std::string labelUTF8 = m_infoLabel.GetLabel(m_parentID);
+ std::wstring label;
+ g_charsetConverter.utf8ToW(labelUTF8, label);
+ if (iPos > (int)label.length()) iPos = label.length();
+ if (iPos < 0) iPos = 0;
+
+ if (m_iCursorPos != iPos)
+ MarkDirtyRegion();
+
+ m_iCursorPos = iPos;
+}
+
+void CGUILabelControl::SetInfo(const GUIINFO::CGUIInfoLabel &infoLabel)
+{
+ m_infoLabel = infoLabel;
+}
+
+bool CGUILabelControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+
+ return changed;
+}
+
+void CGUILabelControl::UpdateInfo(const CGUIListItem *item)
+{
+ std::string label(m_infoLabel.GetLabel(m_parentID));
+
+ bool changed = false;
+ if (m_startHighlight < m_endHighlight || m_startSelection < m_endSelection || m_bShowCursor)
+ {
+ std::wstring utf16;
+ g_charsetConverter.utf8ToW(label, utf16);
+ vecText text; text.reserve(utf16.size()+1);
+ std::vector<UTILS::COLOR::Color> colors;
+ colors.push_back(m_label.GetLabelInfo().textColor);
+ colors.push_back(m_label.GetLabelInfo().disabledColor);
+ UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor;
+ if (!select)
+ select = 0xFFFF0000;
+ colors.push_back(select);
+ colors.push_back(0xFF000000);
+
+ CGUIFont* font = m_label.GetLabelInfo().font;
+ uint32_t style = (font ? font->GetStyle() : (FONT_STYLE_NORMAL & FONT_STYLE_MASK)) << 24;
+
+ for (unsigned int i = 0; i < utf16.size(); i++)
+ {
+ uint32_t ch = utf16[i] | style;
+ if ((m_startSelection < m_endSelection) && (m_startSelection <= i && i < m_endSelection))
+ ch |= (2 << 16);
+ else if ((m_startHighlight < m_endHighlight) && (i < m_startHighlight || i >= m_endHighlight))
+ ch |= (1 << 16);
+ text.push_back(ch);
+ }
+ if (m_bShowCursor && m_iCursorPos >= 0 && (unsigned int)m_iCursorPos <= utf16.size())
+ {
+ uint32_t ch = L'|' | style;
+ if ((++m_dwCounter % 50) <= 25)
+ ch |= (3 << 16);
+ text.insert(text.begin() + m_iCursorPos, ch);
+ }
+ changed |= m_label.SetMaxRect(m_posX, m_posY, GetMaxWidth(), m_height);
+ changed |= m_label.SetStyledText(text, colors);
+ }
+ else
+ {
+ if (m_bHasPath)
+ label = ShortenPath(label);
+
+ changed |= m_label.SetMaxRect(m_posX, m_posY, GetMaxWidth(), m_height);
+ changed |= m_label.SetText(label);
+ }
+ if (changed)
+ MarkDirtyRegion();
+}
+
+void CGUILabelControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool changed = false;
+
+ changed |= m_label.SetColor(IsDisabled() ? CGUILabel::COLOR_DISABLED : CGUILabel::COLOR_TEXT);
+ changed |= m_label.SetMaxRect(m_posX, m_posY, GetMaxWidth(), m_height);
+ changed |= m_label.Process(currentTime);
+
+ if (changed)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+CRect CGUILabelControl::CalcRenderRegion() const
+{
+ return m_label.GetRenderRect();
+}
+
+void CGUILabelControl::Render()
+{
+ m_label.Render();
+ CGUIControl::Render();
+}
+
+
+bool CGUILabelControl::CanFocus() const
+{
+ return false;
+}
+
+void CGUILabelControl::SetLabel(const std::string &strLabel)
+{
+ // NOTE: this optimization handles fixed labels only (i.e. not info labels).
+ // One way it might be extended to all labels would be for GUIInfoLabel ( or here )
+ // to store the label prior to parsing, and then compare that against what you're setting.
+ if (m_infoLabel.GetLabel(GetParentID(), false) != strLabel)
+ {
+ m_infoLabel.SetLabel(strLabel, "", GetParentID());
+ if (m_iCursorPos > (int)strLabel.size())
+ m_iCursorPos = strLabel.size();
+
+ SetInvalid();
+ }
+}
+
+void CGUILabelControl::SetWidthControl(float minWidth, bool bScroll)
+{
+ if (m_label.SetScrolling(bScroll) || m_minWidth != minWidth)
+ MarkDirtyRegion();
+
+ m_minWidth = minWidth;
+}
+
+void CGUILabelControl::SetAlignment(uint32_t align)
+{
+ if (m_label.GetLabelInfo().align != align)
+ {
+ MarkDirtyRegion();
+ m_label.GetLabelInfo().align = align;
+ }
+}
+
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+float CGUILabelControl::GetWidth() const
+{
+ if (m_minWidth && m_minWidth != m_width)
+ return CLAMP(m_label.GetTextWidth(), m_minWidth, GetMaxWidth());
+ return m_width;
+}
+
+void CGUILabelControl::SetWidth(float width)
+{
+ m_width = width;
+ m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
+ CGUIControl::SetWidth(m_width);
+}
+
+bool CGUILabelControl::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID())
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_SET)
+ {
+ SetLabel(message.GetLabel());
+ return true;
+ }
+ }
+ if (message.GetMessage() == GUI_MSG_REFRESH_TIMER && IsVisible())
+ {
+ UpdateInfo();
+ }
+ else if (message.GetMessage() == GUI_MSG_WINDOW_RESIZE && IsVisible())
+ {
+ m_label.SetInvalid();
+ }
+
+ return CGUIControl::OnMessage(message);
+}
+
+std::string CGUILabelControl::ShortenPath(const std::string &path)
+{
+ if (m_width == 0 || path.empty())
+ return path;
+
+ char cDelim = '\0';
+ size_t nPos;
+
+ nPos = path.find_last_of( '\\' );
+ if ( nPos != std::string::npos )
+ cDelim = '\\';
+ else
+ {
+ nPos = path.find_last_of( '/' );
+ if ( nPos != std::string::npos )
+ cDelim = '/';
+ }
+ if ( cDelim == '\0' )
+ return path;
+
+ std::string workPath(path);
+ // remove trailing slashes
+ if (workPath.size() > 3)
+ if (!StringUtils::EndsWith(workPath, "://") &&
+ !StringUtils::EndsWith(workPath, ":\\"))
+ if (nPos == workPath.size() - 1)
+ {
+ workPath.erase(workPath.size() - 1);
+ nPos = workPath.find_last_of( cDelim );
+ }
+
+ if (m_label.SetText(workPath))
+ MarkDirtyRegion();
+
+ float textWidth = m_label.GetTextWidth();
+
+ while ( textWidth > m_width )
+ {
+ size_t nGreaterDelim = workPath.find_last_of( cDelim, nPos );
+ if (nGreaterDelim == std::string::npos)
+ break;
+
+ nPos = workPath.find_last_of( cDelim, nGreaterDelim - 1 );
+ if ( nPos == std::string::npos )
+ break;
+
+ workPath.replace( nPos + 1, nGreaterDelim - nPos - 1, "..." );
+
+ if (m_label.SetText(workPath))
+ MarkDirtyRegion();
+
+ textWidth = m_label.GetTextWidth();
+ }
+ return workPath;
+}
+
+void CGUILabelControl::SetHighlight(unsigned int start, unsigned int end)
+{
+ m_startHighlight = start;
+ m_endHighlight = end;
+}
+
+void CGUILabelControl::SetSelection(unsigned int start, unsigned int end)
+{
+ m_startSelection = start;
+ m_endSelection = end;
+}
+
+std::string CGUILabelControl::GetDescription() const
+{
+ return m_infoLabel.GetLabel(m_parentID);
+}
diff --git a/xbmc/guilib/GUILabelControl.dox b/xbmc/guilib/GUILabelControl.dox
new file mode 100644
index 0000000..8501c93
--- /dev/null
+++ b/xbmc/guilib/GUILabelControl.dox
@@ -0,0 +1,112 @@
+/*!
+
+\page Label_Control Label Control
+\brief **Used to show some lines of text.**
+
+\tableofcontents
+
+The label control is used for displaying text in Kodi. You can choose the font,
+size, colour, location and contents of the text to be displayed.
+
+
+--------------------------------------------------------------------------------
+\section Label_Control_sect1 Example
+
+~~~~~~~~~~~~~
+ <control type="label" id="1">
+ <description>My First label</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <visible>true</visible>
+ <align>center</align>
+ <aligny>center</aligny>
+ <scroll>false</scroll>
+ <label>6</label>
+ <info>MusicPlayer.Artist</info>
+ <number></number>
+ <angle>30</angle>
+ <haspath>false</haspath>
+ <font>font14</font>
+ <textcolor>FFB2D4F5</textcolor>
+ <shadowcolor>ff000000</shadowcolor>
+ <wrapmultiline>false</wrapmultiline>
+ <scrollspeed>50</scrollspeed>
+ <scrollsuffix> - </scrollsuffix>
+ </control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Label_Control_sect2 Auto size labels
+Wrapping your label in a grouplist with the auto width and appropriate minimum
+and maximum values. Allows the labels width to dynamically change relative to
+how long the label text is. This allows a image or other control to be aligned
+to the right of the actual label text no matter how wide the label is.
+
+~~~~~~~~~~~~~
+ <width min="29" max="200">auto</width>
+~~~~~~~~~~~~~
+
+As of XBMC Gotham, simply specifying <b>`<width>auto</width>`</b> is also supported.
+
+
+--------------------------------------------------------------------------------
+\section Label_Control_sect3 Multi-line labels
+
+If you want your label control to span multiple lines, you can insert a new line
+character in your label. For example:
+
+~~~~~~~~~~~~~
+ <label>This will be on the first line[CR]And this will be on the second line</label>
+~~~~~~~~~~~~~
+
+Also, if you want your label control to conform to the <b>`<width>`</b> parameter, but still
+want to be able to give it more content than will fit on one line, then setting:
+
+~~~~~~~~~~~~~
+ <wrapmultiline>true</wrapmultiline>
+~~~~~~~~~~~~~
+
+will cause the text to be cut up (at the spaces in the text) onto multiple lines.
+Note that if a single word is larger than <b>`<width>`</b> then it will not be cut, and
+will still overflow.
+
+
+--------------------------------------------------------------------------------
+\section Label_Control_sect4 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| align | Can be left, right, or center. Aligns the text within the given label <b>`<width>`</b>. Defaults to left
+| aligny | Can be top or center. Aligns the text within its given label <b>`<height>`</b>. Defaults to top
+| scroll | When true, the text will scroll if longer than the label's <b>`<width>`</b>. If false, the text will be truncated. Defaults to false.
+| label | Specifies the text which should be drawn. You should specify an entry from the strings.po here (either the Kodi strings.po or your skin's strings.po file), however you may also hardcode a piece of text also if you wish, though of course it will not be localisable. You can use the full [label formatting syntax](http://kodi.wiki/view/Label_Formatting) and [you may also specify more than one piece of information here by using the $INFO and $LOCALIZE formats](http://kodi.wiki/view/Label_Parsing).
+| info | Specifies the information that should be presented. Kodi will auto-fill in this info in place of the <b>`<label>`</b>. [See here for more information](http://kodi.wiki/view/InfoLabels).
+| number | Specifies a number that should be presented. This is just here to allow a skinner to use a number rather than a text label (as any number given to <b>`<label>`</b> will be used to lookup in strings.po)
+| angle | The angle the text should be rendered at, in degrees. A value of 0 is horizontal.
+| haspath | Specifies whether or not this label is filled with a path. Long paths are shortened by compressing the file path while keeping the actual filename full length.
+| font | Specifies the font to use from the font.xml file.
+| textcolor | Specifies the color the text should be, in hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| wrapmultiline | If true, any text that doesn't fit on one line will be wrapped onto multiple lines.
+| scrollspeed | Scroll speed of text in pixels per second. Defaults to 60.
+| scrollsuffix | Specifies the suffix used in scrolling labels. Defaults to <b>`"¦"`</b>.
+
+
+
+--------------------------------------------------------------------------------
+\section Label_Control_sect5 See also
+
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+
+*/
diff --git a/xbmc/guilib/GUILabelControl.h b/xbmc/guilib/GUILabelControl.h
new file mode 100644
index 0000000..ce7ba4e
--- /dev/null
+++ b/xbmc/guilib/GUILabelControl.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUILabelControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUILabelControl :
+ public CGUIControl
+{
+public:
+ CGUILabelControl(int parentID, int controlID, float posX, float posY, float width, float height, const CLabelInfo& labelInfo, bool wrapMultiLine, bool bHasPath);
+ ~CGUILabelControl(void) override;
+ CGUILabelControl* Clone() const override { return new CGUILabelControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+ bool CanFocus() const override;
+ bool OnMessage(CGUIMessage& message) override;
+ std::string GetDescription() const override;
+ float GetWidth() const override;
+ void SetWidth(float width) override;
+ CRect CalcRenderRegion() const override;
+
+ const CLabelInfo& GetLabelInfo() const { return m_label.GetLabelInfo(); }
+ void SetLabel(const std::string &strLabel);
+ void ShowCursor(bool bShow = true);
+ void SetCursorPos(int iPos);
+ int GetCursorPos() const { return m_iCursorPos; }
+ void SetInfo(const KODI::GUILIB::GUIINFO::CGUIInfoLabel&labelInfo);
+ void SetWidthControl(float minWidth, bool bScroll);
+ void SetAlignment(uint32_t align);
+ void SetHighlight(unsigned int start, unsigned int end);
+ void SetSelection(unsigned int start, unsigned int end);
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ std::string ShortenPath(const std::string &path);
+
+ /*! \brief Return the maximum width of this label control.
+ \return Return the width of the control if available, else the width of the current text.
+ */
+ float GetMaxWidth() const { return m_width ? m_width : m_label.GetTextWidth(); }
+
+ CGUILabel m_label;
+
+ bool m_bHasPath;
+ bool m_bShowCursor;
+ int m_iCursorPos;
+ unsigned int m_dwCounter;
+
+ // stuff for autowidth
+ float m_minWidth;
+
+ // multi-info stuff
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_infoLabel;
+
+ unsigned int m_startHighlight;
+ unsigned int m_endHighlight;
+ unsigned int m_startSelection;
+ unsigned int m_endSelection;
+};
+
diff --git a/xbmc/guilib/GUIListContainer.cpp b/xbmc/guilib/GUIListContainer.cpp
new file mode 100644
index 0000000..b078263
--- /dev/null
+++ b/xbmc/guilib/GUIListContainer.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIListContainer.h"
+
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "input/Key.h"
+#include "utils/StringUtils.h"
+
+CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
+ : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
+{
+ ControlType = GUICONTAINER_LIST;
+ m_type = VIEW_TYPE_LIST;
+}
+
+CGUIListContainer::CGUIListContainer(const CGUIListContainer& other) : CGUIBaseContainer(other)
+{
+}
+
+CGUIListContainer::~CGUIListContainer(void) = default;
+
+bool CGUIListContainer::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PAGE_UP:
+ {
+ if (GetOffset() == 0)
+ { // already on the first page, so move to the first item
+ SetCursor(0);
+ }
+ else
+ { // scroll up to the previous page
+ Scroll( -m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ case ACTION_PAGE_DOWN:
+ {
+ if (GetOffset() == (int)m_items.size() - m_itemsPerPage || (int)m_items.size() < m_itemsPerPage)
+ { // already at the last page, so move to the last item.
+ SetCursor(m_items.size() - GetOffset() - 1);
+ }
+ else
+ { // scroll down to the next page
+ Scroll(m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ // smooth scrolling (for analog controls)
+ case ACTION_SCROLL_UP:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ if (GetOffset() > 0 && GetCursor() <= m_itemsPerPage / 2)
+ {
+ Scroll(-1);
+ }
+ else if (GetCursor() > 0)
+ {
+ SetCursor(GetCursor() - 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ case ACTION_SCROLL_DOWN:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ if (GetOffset() + m_itemsPerPage < (int)m_items.size() && GetCursor() >= m_itemsPerPage / 2)
+ {
+ Scroll(1);
+ }
+ else if (GetCursor() < m_itemsPerPage - 1 && GetOffset() + GetCursor() < (int)m_items.size() - 1)
+ {
+ SetCursor(GetCursor() + 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ }
+ return CGUIBaseContainer::OnAction(action);
+}
+
+bool CGUIListContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ SetCursor(0);
+ SetOffset(0);
+ m_scroller.SetValue(0);
+ }
+ }
+ return CGUIBaseContainer::OnMessage(message);
+}
+
+bool CGUIListContainer::MoveUp(bool wrapAround)
+{
+ if (GetCursor() > 0)
+ {
+ SetCursor(GetCursor() - 1);
+ }
+ else if (GetCursor() == 0 && GetOffset())
+ {
+ ScrollToOffset(GetOffset() - 1);
+ }
+ else if (wrapAround)
+ {
+ if (!m_items.empty())
+ { // move 2 last item in list, and set our container moving up
+ int offset = m_items.size() - m_itemsPerPage;
+ if (offset < 0) offset = 0;
+ SetCursor(m_items.size() - offset - 1);
+ ScrollToOffset(offset);
+ SetContainerMoving(-1);
+ }
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIListContainer::MoveDown(bool wrapAround)
+{
+ if (GetOffset() + GetCursor() + 1 < (int)m_items.size())
+ {
+ if (GetCursor() + 1 < m_itemsPerPage)
+ {
+ SetCursor(GetCursor() + 1);
+ }
+ else
+ {
+ ScrollToOffset(GetOffset() + 1);
+ }
+ }
+ else if(wrapAround)
+ { // move first item in list, and set our container moving in the "down" direction
+ SetCursor(0);
+ ScrollToOffset(0);
+ SetContainerMoving(1);
+ }
+ else
+ return false;
+ return true;
+}
+
+// scrolls the said amount
+void CGUIListContainer::Scroll(int amount)
+{
+ // increase or decrease the offset
+ int offset = GetOffset() + amount;
+ if (offset > (int)m_items.size() - m_itemsPerPage)
+ {
+ offset = m_items.size() - m_itemsPerPage;
+ }
+ if (offset < 0) offset = 0;
+ ScrollToOffset(offset);
+}
+
+void CGUIListContainer::ValidateOffset()
+{
+ if (!m_layout) return;
+ // first thing is we check the range of our offset
+ // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
+ int minOffset, maxOffset;
+ GetOffsetRange(minOffset, maxOffset);
+ if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
+ {
+ SetOffset(std::max(0, maxOffset));
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+ }
+ if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
+ {
+ SetOffset(0);
+ m_scroller.SetValue(0);
+ }
+}
+
+void CGUIListContainer::SetCursor(int cursor)
+{
+ if (cursor > m_itemsPerPage - 1) cursor = m_itemsPerPage - 1;
+ if (cursor < 0) cursor = 0;
+ if (!m_wasReset)
+ SetContainerMoving(cursor - GetCursor());
+ CGUIBaseContainer::SetCursor(cursor);
+}
+
+void CGUIListContainer::SelectItem(int item)
+{
+ // Check that our offset is valid
+ ValidateOffset();
+ // only select an item if it's in a valid range
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ // Select the item requested
+ if (item >= GetOffset() && item < GetOffset() + m_itemsPerPage)
+ { // the item is on the current page, so don't change it.
+ SetCursor(item - GetOffset());
+ }
+ else if (item < GetOffset())
+ { // item is on a previous page - make it the first item on the page
+ SetCursor(0);
+ ScrollToOffset(item);
+ }
+ else // (item >= GetOffset()+m_itemsPerPage)
+ { // item is on a later page - make it the last item on the page
+ SetCursor(m_itemsPerPage - 1);
+ ScrollToOffset(item - GetCursor());
+ }
+ }
+}
+
+int CGUIListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
+{
+ if (!m_focusedLayout || !m_layout)
+ return -1;
+
+ int row = 0;
+ float pos = (m_orientation == VERTICAL) ? point.y : point.x;
+ while (row < m_itemsPerPage + 1) // 1 more to ensure we get the (possible) half item at the end.
+ {
+ const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
+ if (pos < layout->Size(m_orientation) && row + GetOffset() < (int)m_items.size())
+ { // found correct "row" -> check horizontal
+ if (!InsideLayout(layout, point))
+ return -1;
+
+ if (itemPoint)
+ *itemPoint = m_orientation == VERTICAL ? CPoint(point.x, pos) : CPoint(pos, point.y);
+ return row;
+ }
+ row++;
+ pos -= layout->Size(m_orientation);
+ }
+ return -1;
+}
+
+bool CGUIListContainer::SelectItemFromPoint(const CPoint &point)
+{
+ CPoint itemPoint;
+ int row = GetCursorFromPoint(point, &itemPoint);
+ if (row < 0)
+ return false;
+
+ SetCursor(row);
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout)
+ focusedLayout->SelectItemFromPoint(itemPoint);
+ return true;
+}
+
+//#ifdef GUILIB_PYTHON_COMPATIBILITY
+CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const CLabelInfo& labelInfo2,
+ const CTextureInfo& textureButton, const CTextureInfo& textureButtonFocus,
+ float textureHeight, float itemWidth, float itemHeight, float spaceBetweenItems)
+: CGUIBaseContainer(parentID, controlID, posX, posY, width, height, VERTICAL, 200, 0)
+{
+ m_layouts.emplace_back();
+ m_layouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, false, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, "", "");
+ std::string condition = StringUtils::Format("control.hasfocus({})", controlID);
+ std::string condition2 = "!" + condition;
+ m_focusedLayouts.emplace_back();
+ m_focusedLayouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, true, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, condition2, condition);
+ m_height = floor(m_height / (textureHeight + spaceBetweenItems)) * (textureHeight + spaceBetweenItems);
+ ControlType = GUICONTAINER_LIST;
+}
+//#endif
+
+bool CGUIListContainer::HasNextPage() const
+{
+ return (GetOffset() != (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
+}
+
+bool CGUIListContainer::HasPreviousPage() const
+{
+ return (GetOffset() > 0);
+}
diff --git a/xbmc/guilib/GUIListContainer.dox b/xbmc/guilib/GUIListContainer.dox
new file mode 100644
index 0000000..5e01df4
--- /dev/null
+++ b/xbmc/guilib/GUIListContainer.dox
@@ -0,0 +1,138 @@
+/*!
+
+\page List_Container List Container
+\brief **Used for a scrolling lists of items. Replaces the list control.**
+
+\tableofcontents
+
+The list container is one of several containers used to display items from file
+lists in various ways. The list container is very flexible - it's only
+restriction is that it is a list - i.e. a single column or row of items. The
+layout of the items is very flexible and is up to the skinner.
+
+--------------------------------------------------------------------------------
+\section List_Container_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="list" id="50">
+ <description>My first list container</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ <viewtype label="3D list">list</viewtype>
+ <orientation>vertical</orientation>
+ <pagecontrol>25</pagecontrol>
+ <autoscroll>true</autoscroll>
+ <scrolltime tween="sine" easing="out">200</scrolltime>
+ <itemlayout width="250" height="29">
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </itemlayout>
+ <focusedlayout height="29" width="250">
+ <control type="image">
+ <width>485</width>
+ <height>29</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <visible>Control.HasFocus(50)</visible>
+ <texture>list-focus.png</texture>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </focusedlayout>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section List_Container_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| viewtype | The type of view. Choices are list, icon, wide, wrap, biglist, bigicon, bigwide, bigwrap, info and biginfo. The label attribute indicates the label that will be used in the "View As" control within the GUI. It is localizable via strings.po. viewtype has no effect on the view itself. It is used by kodi when switching skin to automatically select a view with a similar layout. Skinners should try to set viewtype to describe the layout as best as possible.
+| orientation | The orientation of the list. Defaults to vertical.
+| pagecontrol | Used to set the <b>`<id>`</b> of the page control used to control this list.
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is 200ms. The list will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling. The scroll movement can be further adjusted by selecting one of the available [tween](http://kodi.wiki/view/Tweeners) methods.
+| itemlayout | Specifies the layout of items in the list. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<itemlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| focusedlayout | Specifies the layout of items in the list that have focus. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<focusedlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| content | Used to set the item content that this list will contain. Allows the skinner to setup a list anywhere they want with a static set of content, as a useful alternative to the grouplist control. [See here for more information](http://kodi.wiki/view/Static_List_Content).
+| preloaditems | Used in association with the background image loader. [See here for more information](http://kodi.wiki/view/Background_Image_Loader).
+| autoscroll | Used to make the container scroll automatically
+
+
+--------------------------------------------------------------------------------
+\section List_Container_sect3 See also
+
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIListContainer.h b/xbmc/guilib/GUIListContainer.h
new file mode 100644
index 0000000..d16aa65
--- /dev/null
+++ b/xbmc/guilib/GUIListContainer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListContainer.h
+\brief
+*/
+
+#include "GUIBaseContainer.h"
+
+class CLabelInfo;
+class CTextureInfo;
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIListContainer : public CGUIBaseContainer
+{
+public:
+ CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems);
+ explicit CGUIListContainer(const CGUIListContainer& other);
+ //#ifdef GUILIB_PYTHON_COMPATIBILITY
+ CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const CLabelInfo& labelInfo2,
+ const CTextureInfo& textureButton, const CTextureInfo& textureButtonFocus,
+ float textureHeight, float itemWidth, float itemHeight, float spaceBetweenItems);
+ //#endif
+ ~CGUIListContainer(void) override;
+ CGUIListContainer* Clone() const override { return new CGUIListContainer(*this); }
+
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage& message) override;
+
+ bool HasNextPage() const override;
+ bool HasPreviousPage() const override;
+
+protected:
+ void Scroll(int amount) override;
+ void SetCursor(int cursor) override;
+ bool MoveDown(bool wrapAround) override;
+ bool MoveUp(bool wrapAround) override;
+ void ValidateOffset() override;
+ void SelectItem(int item) override;
+ bool SelectItemFromPoint(const CPoint &point) override;
+ int GetCursorFromPoint(const CPoint &point, CPoint *itemPoint = NULL) const override;
+};
+
diff --git a/xbmc/guilib/GUIListGroup.cpp b/xbmc/guilib/GUIListGroup.cpp
new file mode 100644
index 0000000..67645c2
--- /dev/null
+++ b/xbmc/guilib/GUIListGroup.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIListGroup.h"
+
+#include "GUIListLabel.h"
+#include "utils/log.h"
+
+#include <set>
+
+namespace
+{
+// Supported control types. Keep sorted.
+const std::set<CGUIControl::GUICONTROLTYPES> supportedTypes = {
+ // clang-format off
+ CGUIControl::GUICONTROL_BORDEREDIMAGE,
+ CGUIControl::GUICONTROL_GAME,
+ CGUIControl::GUICONTROL_GAMECONTROLLER,
+ CGUIControl::GUICONTROL_IMAGE,
+ CGUIControl::GUICONTROL_LISTGROUP,
+ CGUIControl::GUICONTROL_LISTLABEL,
+ CGUIControl::GUICONTROL_MULTI_IMAGE,
+ CGUIControl::GUICONTROL_PROGRESS,
+ CGUIControl::GUICONTROL_TEXTBOX,
+ // clang-format on
+};
+} // namespace
+
+CGUIListGroup::CGUIListGroup(int parentID, int controlID, float posX, float posY, float width, float height)
+: CGUIControlGroup(parentID, controlID, posX, posY, width, height)
+{
+ m_item = NULL;
+ ControlType = GUICONTROL_LISTGROUP;
+}
+
+CGUIListGroup::CGUIListGroup(const CGUIListGroup &right)
+: CGUIControlGroup(right)
+{
+ m_item = NULL;
+ ControlType = GUICONTROL_LISTGROUP;
+}
+
+CGUIListGroup::~CGUIListGroup(void)
+{
+ FreeResources();
+}
+
+void CGUIListGroup::AddControl(CGUIControl *control, int position /*= -1*/)
+{
+ if (control)
+ {
+ if (supportedTypes.find(control->GetControlType()) == supportedTypes.end())
+ CLog::Log(LOGWARNING, "Trying to add unsupported control type {}", control->GetControlType());
+ }
+ CGUIControlGroup::AddControl(control, position);
+}
+
+void CGUIListGroup::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(m_posX, m_posY);
+
+ CRect rect;
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ control->UpdateVisibility(m_item);
+ unsigned int oldDirty = dirtyregions.size();
+ control->DoProcess(currentTime, dirtyregions);
+ if (control->IsVisible() || (oldDirty != dirtyregions.size())) // visible or dirty (was visible?)
+ rect.Union(control->GetRenderRegion());
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+ CGUIControl::Process(currentTime, dirtyregions);
+ m_renderRegion = rect;
+ m_item = NULL;
+}
+
+void CGUIListGroup::ResetAnimation(ANIMATION_TYPE type)
+{
+ CGUIControl::ResetAnimation(type);
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ (*it)->ResetAnimation(type);
+}
+
+void CGUIListGroup::UpdateVisibility(const CGUIListItem *item)
+{
+ CGUIControlGroup::UpdateVisibility(item);
+ m_item = item;
+}
+
+void CGUIListGroup::UpdateInfo(const CGUIListItem *item)
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ (*it)->UpdateInfo(item);
+ (*it)->UpdateVisibility(item);
+ }
+ // now we have to check our overlapping label pairs
+ for (unsigned int i = 0; i < m_children.size(); i++)
+ {
+ if (m_children[i]->GetControlType() == CGUIControl::GUICONTROL_LISTLABEL && m_children[i]->IsVisible())
+ {
+ for (unsigned int j = i + 1; j < m_children.size(); j++)
+ {
+ if (m_children[j]->GetControlType() == CGUIControl::GUICONTROL_LISTLABEL && m_children[j]->IsVisible())
+ CGUIListLabel::CheckAndCorrectOverlap(*static_cast<CGUIListLabel*>(m_children[i]), *static_cast<CGUIListLabel*>(m_children[j]));
+ }
+ }
+ }
+}
+
+void CGUIListGroup::EnlargeWidth(float difference)
+{
+ // Alters the width of the controls that have an ID of 1 to 14
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ CGUIControl *child = *it;
+ if (child->GetID() >= 1 && child->GetID() <= 14)
+ {
+ if (child->GetID() == 1)
+ {
+ child->SetWidth(child->GetWidth() + difference);
+ child->SetVisible(child->GetWidth() > 10);
+ }
+ else
+ {
+ child->SetWidth(child->GetWidth() + difference);
+ }
+ }
+ }
+ SetInvalid();
+}
+
+void CGUIListGroup::EnlargeHeight(float difference)
+{
+ // Alters the height of the controls that have an ID of 1 to 14
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ CGUIControl *child = *it;
+ if (child->GetID() >= 1 && child->GetID() <= 14)
+ {
+ if (child->GetID() == 1)
+ {
+ child->SetHeight(child->GetHeight() + difference);
+ child->SetVisible(child->GetHeight() > 10);
+ }
+ else
+ {
+ child->SetHeight(child->GetHeight() + difference);
+ }
+ }
+ }
+ SetInvalid();
+}
+
+void CGUIListGroup::SetInvalid()
+{
+ if (!m_bInvalidated)
+ { // this can be triggered by an item change, so all children need invalidating rather than just the group
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ (*it)->SetInvalid();
+ CGUIControlGroup::SetInvalid();
+ }
+}
+
+void CGUIListGroup::SetFocusedItem(unsigned int focus)
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP)
+ ((CGUIListGroup *)(*it))->SetFocusedItem(focus);
+ else
+ (*it)->SetFocus(focus > 0);
+ }
+ SetFocus(focus > 0);
+}
+
+unsigned int CGUIListGroup::GetFocusedItem() const
+{
+ for (ciControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP && ((CGUIListGroup *)(*it))->GetFocusedItem())
+ return ((CGUIListGroup *)(*it))->GetFocusedItem();
+ }
+ return m_bHasFocus ? 1 : 0;
+}
+
+bool CGUIListGroup::MoveLeft()
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP && ((CGUIListGroup *)(*it))->MoveLeft())
+ return true;
+ }
+ return false;
+}
+
+bool CGUIListGroup::MoveRight()
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP && ((CGUIListGroup *)(*it))->MoveRight())
+ return true;
+ }
+ return false;
+}
+
+void CGUIListGroup::SetState(bool selected, bool focused)
+{
+ for (iControls it = m_children.begin(); it != m_children.end(); it++)
+ {
+ if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTLABEL)
+ {
+ CGUIListLabel *label = (CGUIListLabel *)(*it);
+ label->SetSelected(selected);
+ }
+ else if ((*it)->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP)
+ ((CGUIListGroup *)(*it))->SetState(selected, focused);
+ }
+}
+
+void CGUIListGroup::SelectItemFromPoint(const CPoint &point)
+{
+ CPoint controlCoords(point);
+ m_transform.InverseTransformPosition(controlCoords.x, controlCoords.y);
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *child = *it;
+ if (child->GetControlType() == CGUIControl::GUICONTROL_LISTGROUP)
+ static_cast<CGUIListGroup*>(child)->SelectItemFromPoint(point);
+ }
+}
diff --git a/xbmc/guilib/GUIListGroup.dox b/xbmc/guilib/GUIListGroup.dox
new file mode 100644
index 0000000..d57791f
--- /dev/null
+++ b/xbmc/guilib/GUIListGroup.dox
@@ -0,0 +1,67 @@
+/*!
+
+\page Group_List_Control Group List Control
+\brief **Special case of the group control that forms a scrolling list of controls.**
+
+\tableofcontents
+
+The group list control is a special case of the group control. It is used for
+placing a set of controls into a list (either horizontally or vertically) and
+handles all the navigation within the list and placement within the list for
+you. It will be scrollable if the number of items exceeds the size of the
+list, and you can assign a scrollbar to it just like you can to any of the
+containers (list, panel, etc.).
+
+
+--------------------------------------------------------------------------------
+\section Group_List_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="grouplist" id="17">
+ <description>My first group list control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>300</height>
+ <itemgap>10</itemgap>
+ <pagecontrol>25</pagecontrol>
+ <scrolltime tween="sine" easing="out">200</scrolltime>
+ <orientation>vertical</orientation>
+ <usecontrolcoords>false</usecontrolcoords>
+ <visible>true</visible>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ <align>right</align>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Group_List_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|------------------:|:--------------------------------------------------------------|
+| itemgap | Specifies the gap (in pixels) between controls in the list. Defaults to 5px.
+| orientation | Specifies whether the list is horizontal or vertical. Defaults to vertical.
+| pagecontrol | Specifies the page control used to scroll up and down the list. Set the page control's id here.
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is 200ms. The list will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling. The scroll movement can be further adjusted by selecting one of the available [tween](http://kodi.wiki/view/Tweeners) methods.
+| usecontrolcoords | Specifies whether the list should use the control's coordinates as an offset location from the coordinates it would normally use to draw the control. Defaults to false. Useful for staggering a control in from the edge of the grouplist, or for having more space around a control than <c>`<itemgap>`</c> gives.
+| align | Specifies how to align the grouplist. possible values: left, right, center, justify. defaults to left.
+
+
+--------------------------------------------------------------------------------
+\section Group_List_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+
+*/
diff --git a/xbmc/guilib/GUIListGroup.h b/xbmc/guilib/GUIListGroup.h
new file mode 100644
index 0000000..548cdec
--- /dev/null
+++ b/xbmc/guilib/GUIListGroup.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListGroup.h
+\brief
+*/
+
+#include "GUIControlGroup.h"
+
+/*!
+ \ingroup controls
+ \brief a group of controls within a list/panel container
+ */
+class CGUIListGroup final : public CGUIControlGroup
+{
+public:
+ CGUIListGroup(int parentID, int controlID, float posX, float posY, float width, float height);
+ explicit CGUIListGroup(const CGUIListGroup& right);
+ ~CGUIListGroup(void) override;
+ CGUIListGroup* Clone() const override { return new CGUIListGroup(*this); }
+
+ void AddControl(CGUIControl *control, int position = -1) override;
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void ResetAnimation(ANIMATION_TYPE type) override;
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+ void UpdateInfo(const CGUIListItem *item) override;
+ void SetInvalid() override;
+
+ void EnlargeWidth(float difference);
+ void EnlargeHeight(float difference);
+ void SetFocusedItem(unsigned int subfocus);
+ unsigned int GetFocusedItem() const;
+ bool MoveLeft();
+ bool MoveRight();
+ void SetState(bool selected, bool focused);
+ void SelectItemFromPoint(const CPoint &point);
+
+protected:
+ const CGUIListItem *m_item;
+};
+
diff --git a/xbmc/guilib/GUIListItem.cpp b/xbmc/guilib/GUIListItem.cpp
new file mode 100644
index 0000000..e815e4d
--- /dev/null
+++ b/xbmc/guilib/GUIListItem.cpp
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIListItem.h"
+
+#include "GUIListItemLayout.h"
+#include "utils/Archive.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+
+#include <utility>
+
+bool CGUIListItem::icompare::operator()(const std::string &s1, const std::string &s2) const
+{
+ return StringUtils::CompareNoCase(s1, s2) < 0;
+}
+
+CGUIListItem::CGUIListItem(const CGUIListItem& item)
+{
+ *this = item;
+ SetInvalid();
+}
+
+CGUIListItem::CGUIListItem(void)
+{
+ m_bIsFolder = false;
+ m_bSelected = false;
+ m_overlayIcon = ICON_OVERLAY_NONE;
+ m_currentItem = 1;
+}
+
+CGUIListItem::CGUIListItem(const std::string& strLabel):
+ m_strLabel(strLabel)
+{
+ m_bIsFolder = false;
+ SetSortLabel(strLabel);
+ m_bSelected = false;
+ m_overlayIcon = ICON_OVERLAY_NONE;
+ m_currentItem = 1;
+}
+
+CGUIListItem::~CGUIListItem(void)
+{
+ FreeMemory();
+}
+
+void CGUIListItem::SetLabel(const std::string& strLabel)
+{
+ if (m_strLabel == strLabel)
+ return;
+ m_strLabel = strLabel;
+ if (m_sortLabel.empty())
+ SetSortLabel(strLabel);
+ SetInvalid();
+}
+
+const std::string& CGUIListItem::GetLabel() const
+{
+ return m_strLabel;
+}
+
+
+void CGUIListItem::SetLabel2(const std::string& strLabel2)
+{
+ if (m_strLabel2 == strLabel2)
+ return;
+ m_strLabel2 = strLabel2;
+ SetInvalid();
+}
+
+const std::string& CGUIListItem::GetLabel2() const
+{
+ return m_strLabel2;
+}
+
+void CGUIListItem::SetSortLabel(const std::string &label)
+{
+ g_charsetConverter.utf8ToW(label, m_sortLabel, false);
+ // no need to invalidate - this is never shown in the UI
+}
+
+void CGUIListItem::SetSortLabel(const std::wstring &label)
+{
+ m_sortLabel = label;
+}
+
+const std::wstring& CGUIListItem::GetSortLabel() const
+{
+ return m_sortLabel;
+}
+
+void CGUIListItem::SetArt(const std::string &type, const std::string &url)
+{
+ ArtMap::iterator i = m_art.find(type);
+ if (i == m_art.end() || i->second != url)
+ {
+ m_art[type] = url;
+ SetInvalid();
+ }
+}
+
+void CGUIListItem::SetArt(const ArtMap &art)
+{
+ m_art = art;
+ SetInvalid();
+}
+
+void CGUIListItem::SetArtFallback(const std::string &from, const std::string &to)
+{
+ m_artFallbacks[from] = to;
+}
+
+void CGUIListItem::ClearArt()
+{
+ m_art.clear();
+ m_artFallbacks.clear();
+ SetProperty("libraryartfilled", false);
+}
+
+void CGUIListItem::AppendArt(const ArtMap &art, const std::string &prefix)
+{
+ for (const auto& i : art)
+ SetArt(prefix.empty() ? i.first : prefix + '.' + i.first, i.second);
+}
+
+std::string CGUIListItem::GetArt(const std::string &type) const
+{
+ ArtMap::const_iterator i = m_art.find(type);
+ if (i != m_art.end())
+ return i->second;
+ i = m_artFallbacks.find(type);
+ if (i != m_artFallbacks.end())
+ {
+ ArtMap::const_iterator j = m_art.find(i->second);
+ if (j != m_art.end())
+ return j->second;
+ }
+ return "";
+}
+
+const CGUIListItem::ArtMap &CGUIListItem::GetArt() const
+{
+ return m_art;
+}
+
+bool CGUIListItem::HasArt(const std::string &type) const
+{
+ return !GetArt(type).empty();
+}
+
+void CGUIListItem::SetOverlayImage(GUIIconOverlay icon, bool bOnOff)
+{
+ GUIIconOverlay newIcon = (bOnOff) ? GUIIconOverlay((int)(icon)+1) : icon;
+ if (m_overlayIcon == newIcon)
+ return;
+ m_overlayIcon = newIcon;
+ SetInvalid();
+}
+
+std::string CGUIListItem::GetOverlayImage() const
+{
+ switch (m_overlayIcon)
+ {
+ case ICON_OVERLAY_RAR:
+ return "OverlayRAR.png";
+ case ICON_OVERLAY_ZIP:
+ return "OverlayZIP.png";
+ case ICON_OVERLAY_LOCKED:
+ return "OverlayLocked.png";
+ case ICON_OVERLAY_UNWATCHED:
+ return "OverlayUnwatched.png";
+ case ICON_OVERLAY_WATCHED:
+ return "OverlayWatched.png";
+ case ICON_OVERLAY_HD:
+ return "OverlayHD.png";
+ default:
+ return "";
+ }
+}
+
+void CGUIListItem::Select(bool bOnOff)
+{
+ m_bSelected = bOnOff;
+}
+
+bool CGUIListItem::HasOverlay() const
+{
+ return (m_overlayIcon != CGUIListItem::ICON_OVERLAY_NONE);
+}
+
+bool CGUIListItem::IsSelected() const
+{
+ return m_bSelected;
+}
+
+CGUIListItem& CGUIListItem::operator =(const CGUIListItem& item)
+{
+ if (&item == this) return * this;
+ m_strLabel2 = item.m_strLabel2;
+ m_strLabel = item.m_strLabel;
+ m_sortLabel = item.m_sortLabel;
+ FreeMemory();
+ m_bSelected = item.m_bSelected;
+ m_overlayIcon = item.m_overlayIcon;
+ m_bIsFolder = item.m_bIsFolder;
+ m_mapProperties = item.m_mapProperties;
+ m_art = item.m_art;
+ m_artFallbacks = item.m_artFallbacks;
+ SetInvalid();
+ return *this;
+}
+
+void CGUIListItem::Archive(CArchive &ar)
+{
+ if (ar.IsStoring())
+ {
+ ar << m_bIsFolder;
+ ar << m_strLabel;
+ ar << m_strLabel2;
+ ar << m_sortLabel;
+ ar << m_bSelected;
+ ar << m_overlayIcon;
+ ar << (int)m_mapProperties.size();
+ for (const auto& it : m_mapProperties)
+ {
+ ar << it.first;
+ ar << it.second;
+ }
+ ar << (int)m_art.size();
+ for (const auto& i : m_art)
+ {
+ ar << i.first;
+ ar << i.second;
+ }
+ ar << (int)m_artFallbacks.size();
+ for (const auto& i : m_artFallbacks)
+ {
+ ar << i.first;
+ ar << i.second;
+ }
+ }
+ else
+ {
+ ar >> m_bIsFolder;
+ ar >> m_strLabel;
+ ar >> m_strLabel2;
+ ar >> m_sortLabel;
+ ar >> m_bSelected;
+
+ int overlayIcon;
+ ar >> overlayIcon;
+ m_overlayIcon = GUIIconOverlay(overlayIcon);
+
+ int mapSize;
+ ar >> mapSize;
+ for (int i = 0; i < mapSize; i++)
+ {
+ std::string key;
+ CVariant value;
+ ar >> key;
+ ar >> value;
+ SetProperty(key, value);
+ }
+ ar >> mapSize;
+ for (int i = 0; i < mapSize; i++)
+ {
+ std::string key, value;
+ ar >> key;
+ ar >> value;
+ m_art.insert(make_pair(key, value));
+ }
+ ar >> mapSize;
+ for (int i = 0; i < mapSize; i++)
+ {
+ std::string key, value;
+ ar >> key;
+ ar >> value;
+ m_artFallbacks.insert(make_pair(key, value));
+ }
+ SetInvalid();
+ }
+}
+void CGUIListItem::Serialize(CVariant &value)
+{
+ value["isFolder"] = m_bIsFolder;
+ value["strLabel"] = m_strLabel;
+ value["strLabel2"] = m_strLabel2;
+ value["sortLabel"] = m_sortLabel;
+ value["selected"] = m_bSelected;
+
+ for (const auto& it : m_mapProperties)
+ {
+ value["properties"][it.first] = it.second;
+ }
+ for (const auto& it : m_art)
+ value["art"][it.first] = it.second;
+}
+
+void CGUIListItem::FreeIcons()
+{
+ FreeMemory();
+ ClearArt();
+ SetInvalid();
+}
+
+void CGUIListItem::FreeMemory(bool immediately)
+{
+ if (m_layout)
+ {
+ m_layout->FreeResources(immediately);
+ m_layout.reset();
+ }
+ if (m_focusedLayout)
+ {
+ m_focusedLayout->FreeResources(immediately);
+ m_focusedLayout.reset();
+ }
+}
+
+void CGUIListItem::SetLayout(CGUIListItemLayoutPtr layout)
+{
+ m_layout = std::move(layout);
+}
+
+CGUIListItemLayout *CGUIListItem::GetLayout()
+{
+ return m_layout.get();
+}
+
+void CGUIListItem::SetFocusedLayout(CGUIListItemLayoutPtr layout)
+{
+ m_focusedLayout = std::move(layout);
+}
+
+CGUIListItemLayout *CGUIListItem::GetFocusedLayout()
+{
+ return m_focusedLayout.get();
+}
+
+void CGUIListItem::SetInvalid()
+{
+ if (m_layout) m_layout->SetInvalid();
+ if (m_focusedLayout) m_focusedLayout->SetInvalid();
+}
+
+void CGUIListItem::SetProperty(const std::string &strKey, const CVariant &value)
+{
+ PropertyMap::iterator iter = m_mapProperties.find(strKey);
+ if (iter == m_mapProperties.end())
+ {
+ m_mapProperties.insert(make_pair(strKey, value));
+ SetInvalid();
+ }
+ else if (iter->second != value)
+ {
+ iter->second = value;
+ SetInvalid();
+ }
+}
+
+const CVariant &CGUIListItem::GetProperty(const std::string &strKey) const
+{
+ PropertyMap::const_iterator iter = m_mapProperties.find(strKey);
+ static CVariant nullVariant = CVariant(CVariant::VariantTypeNull);
+
+ if (iter == m_mapProperties.end())
+ return nullVariant;
+
+ return iter->second;
+}
+
+bool CGUIListItem::HasProperty(const std::string &strKey) const
+{
+ PropertyMap::const_iterator iter = m_mapProperties.find(strKey);
+ if (iter == m_mapProperties.end())
+ return false;
+
+ return true;
+}
+
+void CGUIListItem::ClearProperty(const std::string &strKey)
+{
+ PropertyMap::iterator iter = m_mapProperties.find(strKey);
+ if (iter != m_mapProperties.end())
+ {
+ m_mapProperties.erase(iter);
+ SetInvalid();
+ }
+}
+
+void CGUIListItem::ClearProperties()
+{
+ if (!m_mapProperties.empty())
+ {
+ m_mapProperties.clear();
+ SetInvalid();
+ }
+}
+
+void CGUIListItem::IncrementProperty(const std::string &strKey, int nVal)
+{
+ int64_t i = GetProperty(strKey).asInteger();
+ i += nVal;
+ SetProperty(strKey, i);
+}
+
+void CGUIListItem::IncrementProperty(const std::string& strKey, int64_t nVal)
+{
+ int64_t i = GetProperty(strKey).asInteger();
+ i += nVal;
+ SetProperty(strKey, i);
+}
+
+void CGUIListItem::IncrementProperty(const std::string &strKey, double dVal)
+{
+ double d = GetProperty(strKey).asDouble();
+ d += dVal;
+ SetProperty(strKey, d);
+}
+
+void CGUIListItem::AppendProperties(const CGUIListItem &item)
+{
+ for (const auto& i : item.m_mapProperties)
+ SetProperty(i.first, i.second);
+}
+
+void CGUIListItem::SetCurrentItem(unsigned int position)
+{
+ m_currentItem = position;
+}
+
+unsigned int CGUIListItem::GetCurrentItem() const
+{
+ return m_currentItem;
+}
diff --git a/xbmc/guilib/GUIListItem.h b/xbmc/guilib/GUIListItem.h
new file mode 100644
index 0000000..7c2d0dd
--- /dev/null
+++ b/xbmc/guilib/GUIListItem.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListItem.h
+\brief
+*/
+
+#include <map>
+#include <memory>
+#include <string>
+
+// Forward
+class CGUIListItemLayout;
+using CGUIListItemLayoutPtr = std::unique_ptr<CGUIListItemLayout>;
+class CArchive;
+class CVariant;
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIListItem
+{
+public:
+ typedef std::map<std::string, std::string> ArtMap;
+
+ ///
+ /// @ingroup controls python_xbmcgui_listitem
+ /// @defgroup kodi_guilib_listitem_iconoverlay Overlay icon types
+ /// @brief Overlay icon types used on list item.
+ /// @{
+ enum GUIIconOverlay { ICON_OVERLAY_NONE = 0, //!< Value **0** - No overlay icon
+ ICON_OVERLAY_RAR, //!< Value **1** - Compressed *.rar files
+ ICON_OVERLAY_ZIP, //!< Value **2** - Compressed *.zip files
+ ICON_OVERLAY_LOCKED, //!< Value **3** - Locked files
+ ICON_OVERLAY_UNWATCHED, //!< Value **4** - For not watched files
+ ICON_OVERLAY_WATCHED, //!< Value **5** - For seen files
+ ICON_OVERLAY_HD //!< Value **6** - Is on hard disk stored
+ };
+ /// @}
+
+ CGUIListItem(void);
+ explicit CGUIListItem(const CGUIListItem& item);
+ explicit CGUIListItem(const std::string& strLabel);
+ virtual ~CGUIListItem(void);
+ virtual CGUIListItem* Clone() const { return new CGUIListItem(*this); }
+
+ CGUIListItem& operator =(const CGUIListItem& item);
+
+ virtual void SetLabel(const std::string& strLabel);
+ const std::string& GetLabel() const;
+
+ void SetLabel2(const std::string& strLabel);
+ const std::string& GetLabel2() const;
+
+ void SetOverlayImage(GUIIconOverlay icon, bool bOnOff=false);
+ std::string GetOverlayImage() const;
+
+ /*! \brief Set a particular art type for an item
+ \param type type of art to set.
+ \param url the url of the art.
+ */
+ void SetArt(const std::string &type, const std::string &url);
+
+ /*! \brief set artwork for an item
+ \param art a type:url map for artwork
+ \sa GetArt
+ */
+ void SetArt(const ArtMap &art);
+
+ /*! \brief append artwork to an item
+ \param art a type:url map for artwork
+ \param prefix a prefix for the art, if applicable.
+ \sa GetArt
+ */
+ void AppendArt(const ArtMap &art, const std::string &prefix = "");
+
+ /*! \brief set a fallback image for art
+ \param from the type to fallback from
+ \param to the type to fallback to
+ \sa SetArt
+ */
+ void SetArtFallback(const std::string &from, const std::string &to);
+
+ /*! \brief clear art on an item
+ \sa SetArt
+ */
+ void ClearArt();
+
+ /*! \brief Get a particular art type for an item
+ \param type type of art to fetch.
+ \return the art URL, if available, else empty.
+ */
+ std::string GetArt(const std::string &type) const;
+
+ /*! \brief get artwork for an item
+ Retrieves artwork in a type:url map
+ \return a type:url map for artwork
+ \sa SetArt
+ */
+ const ArtMap &GetArt() const;
+
+ /*! \brief Check whether an item has a particular piece of art
+ Equivalent to !GetArt(type).empty()
+ \param type type of art to set.
+ \return true if the item has that art set, false otherwise.
+ */
+ bool HasArt(const std::string &type) const;
+
+ void SetSortLabel(const std::string &label);
+ void SetSortLabel(const std::wstring &label);
+ const std::wstring &GetSortLabel() const;
+
+ void Select(bool bOnOff);
+ bool IsSelected() const;
+
+ bool HasOverlay() const;
+ virtual bool IsFileItem() const { return false; }
+
+ void SetLayout(CGUIListItemLayoutPtr layout);
+ CGUIListItemLayout *GetLayout();
+
+ void SetFocusedLayout(CGUIListItemLayoutPtr layout);
+ CGUIListItemLayout *GetFocusedLayout();
+
+ void FreeIcons();
+ void FreeMemory(bool immediately = false);
+ void SetInvalid();
+
+ bool m_bIsFolder; ///< is item a folder or a file
+
+ void SetProperty(const std::string &strKey, const CVariant &value);
+
+ void IncrementProperty(const std::string &strKey, int nVal);
+ void IncrementProperty(const std::string& strKey, int64_t nVal);
+ void IncrementProperty(const std::string &strKey, double dVal);
+
+ void ClearProperties();
+
+ /*! \brief Append the properties of one CGUIListItem to another.
+ Any existing properties in the current item will be overridden if they
+ are set in the passed in item.
+ \param item the item containing the properties to append.
+ */
+ void AppendProperties(const CGUIListItem &item);
+
+ void Archive(CArchive& ar);
+ void Serialize(CVariant& value);
+
+ bool HasProperty(const std::string &strKey) const;
+ bool HasProperties() const { return !m_mapProperties.empty(); }
+ void ClearProperty(const std::string &strKey);
+
+ const CVariant &GetProperty(const std::string &strKey) const;
+
+ /*! \brief Set the current item number within it's container
+ Our container classes will set this member with the items position
+ in the container starting at 1.
+ \param position Position of the item in the container starting at 1.
+ */
+ void SetCurrentItem(unsigned int position);
+
+ /*! \brief Get the current item number within it's container
+ Retrieve the items position in a container, this is useful to show
+ for example numbering in front of entities in an arbitrary list of entities,
+ like songs of a playlist.
+ */
+ unsigned int GetCurrentItem() const;
+
+protected:
+ std::string m_strLabel2; // text of column2
+ GUIIconOverlay m_overlayIcon; // type of overlay icon
+
+ CGUIListItemLayoutPtr m_layout;
+ CGUIListItemLayoutPtr m_focusedLayout;
+ bool m_bSelected; // item is selected or not
+ unsigned int m_currentItem; // current item number within container (starting at 1)
+
+ struct icompare
+ {
+ bool operator()(const std::string &s1, const std::string &s2) const;
+ };
+
+ typedef std::map<std::string, CVariant, icompare> PropertyMap;
+ PropertyMap m_mapProperties;
+private:
+ std::wstring m_sortLabel; // text for sorting. Need to be UTF16 for proper sorting
+ std::string m_strLabel; // text of column1
+
+ ArtMap m_art;
+ ArtMap m_artFallbacks;
+};
+
diff --git a/xbmc/guilib/GUIListItemLayout.cpp b/xbmc/guilib/GUIListItemLayout.cpp
new file mode 100644
index 0000000..3b0f774
--- /dev/null
+++ b/xbmc/guilib/GUIListItemLayout.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIListItemLayout.h"
+
+#include "FileItem.h"
+#include "GUIControlFactory.h"
+#include "GUIImage.h"
+#include "GUIInfoManager.h"
+#include "GUIListLabel.h"
+#include "utils/XBMCTinyXML.h"
+
+using namespace KODI::GUILIB;
+
+CGUIListItemLayout::CGUIListItemLayout()
+: m_group(0, 0, 0, 0, 0, 0)
+{
+ m_group.SetPushUpdates(true);
+}
+
+CGUIListItemLayout::CGUIListItemLayout(const CGUIListItemLayout& from)
+ : CGUIListItemLayout(from, nullptr)
+{
+}
+
+CGUIListItemLayout::CGUIListItemLayout(const CGUIListItemLayout& from, CGUIControl* control)
+ : m_group(from.m_group),
+ m_width(from.m_width),
+ m_height(from.m_height),
+ m_focused(from.m_focused),
+ m_condition(from.m_condition),
+ m_isPlaying(from.m_isPlaying),
+ m_infoUpdateMillis(from.m_infoUpdateMillis)
+{
+ m_group.SetParentControl(control);
+ m_infoUpdateTimeout.Set(m_infoUpdateMillis);
+
+ // m_group was just created, cloned controls with resources must be allocated
+ // before use
+ m_group.AllocResources();
+}
+
+bool CGUIListItemLayout::IsAnimating(ANIMATION_TYPE animType)
+{
+ return m_group.IsAnimating(animType);
+}
+
+void CGUIListItemLayout::ResetAnimation(ANIMATION_TYPE animType)
+{
+ return m_group.ResetAnimation(animType);
+}
+
+float CGUIListItemLayout::Size(ORIENTATION orientation) const
+{
+ return (orientation == HORIZONTAL) ? m_width : m_height;
+}
+
+void CGUIListItemLayout::Process(CGUIListItem *item, int parentID, unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_invalidated)
+ { // need to update our item
+ m_invalidated = false;
+ // could use a dynamic cast here if RTTI was enabled. As it's not,
+ // let's use a static cast with a virtual base function
+ CFileItem *fileItem = item->IsFileItem() ? static_cast<CFileItem*>(item) : new CFileItem(*item);
+ m_isPlaying.Update(INFO::DEFAULT_CONTEXT, item);
+ m_group.SetInvalid();
+ m_group.UpdateInfo(fileItem);
+ // delete our temporary fileitem
+ if (!item->IsFileItem())
+ delete fileItem;
+
+ m_infoUpdateTimeout.Set(m_infoUpdateMillis);
+ }
+ else if (m_infoUpdateTimeout.IsTimePast())
+ {
+ m_isPlaying.Update(INFO::DEFAULT_CONTEXT, item);
+ m_group.UpdateInfo(item);
+
+ m_infoUpdateTimeout.Set(m_infoUpdateMillis);
+ }
+
+ // update visibility, and render
+ m_group.SetState(item->IsSelected() || m_isPlaying, m_focused);
+ m_group.UpdateVisibility(item);
+ m_group.DoProcess(currentTime, dirtyregions);
+}
+
+void CGUIListItemLayout::Render(CGUIListItem *item, int parentID)
+{
+ m_group.DoRender();
+}
+
+void CGUIListItemLayout::SetFocusedItem(unsigned int focus)
+{
+ m_group.SetFocusedItem(focus);
+}
+
+unsigned int CGUIListItemLayout::GetFocusedItem() const
+{
+ return m_group.GetFocusedItem();
+}
+
+void CGUIListItemLayout::SetWidth(float width)
+{
+ if (m_width != width)
+ {
+ m_group.EnlargeWidth(width - m_width);
+ m_width = width;
+ SetInvalid();
+ }
+}
+
+void CGUIListItemLayout::SetHeight(float height)
+{
+ if (m_height != height)
+ {
+ m_group.EnlargeHeight(height - m_height);
+ m_height = height;
+ SetInvalid();
+ }
+}
+
+void CGUIListItemLayout::SelectItemFromPoint(const CPoint &point)
+{
+ m_group.SelectItemFromPoint(point);
+}
+
+bool CGUIListItemLayout::MoveLeft()
+{
+ return m_group.MoveLeft();
+}
+
+bool CGUIListItemLayout::MoveRight()
+{
+ return m_group.MoveRight();
+}
+
+bool CGUIListItemLayout::CheckCondition()
+{
+ return !m_condition || m_condition->Get(INFO::DEFAULT_CONTEXT);
+}
+
+void CGUIListItemLayout::LoadControl(TiXmlElement *child, CGUIControlGroup *group)
+{
+ if (!group) return;
+
+ CRect rect(group->GetXPosition(), group->GetYPosition(), group->GetXPosition() + group->GetWidth(), group->GetYPosition() + group->GetHeight());
+
+ CGUIControlFactory factory;
+ CGUIControl *control = factory.Create(0, rect, child, true); // true indicating we're inside a list for the
+ // different label control + defaults.
+ if (control)
+ {
+ group->AddControl(control);
+ if (control->IsGroup())
+ {
+ TiXmlElement *grandChild = child->FirstChildElement("control");
+ while (grandChild)
+ {
+ LoadControl(grandChild, static_cast<CGUIControlGroup*>(control));
+ grandChild = grandChild->NextSiblingElement("control");
+ }
+ }
+ }
+}
+
+void CGUIListItemLayout::LoadLayout(TiXmlElement *layout, int context, bool focused, float maxWidth, float maxHeight)
+{
+ m_focused = focused;
+ layout->QueryFloatAttribute("width", &m_width);
+ layout->QueryFloatAttribute("height", &m_height);
+ const char *condition = layout->Attribute("condition");
+ if (condition)
+ m_condition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context);
+ unsigned int infoupdatemillis = 0;
+ layout->QueryUnsignedAttribute("infoupdate", &infoupdatemillis);
+ if (infoupdatemillis > 0)
+ m_infoUpdateMillis = std::chrono::milliseconds(infoupdatemillis);
+
+ m_infoUpdateTimeout.Set(m_infoUpdateMillis);
+ m_isPlaying.Parse("listitem.isplaying", context);
+ // ensure width and height are valid
+ if (!m_width)
+ m_width = maxWidth;
+ if (!m_height)
+ m_height = maxHeight;
+ m_width = std::max(1.0f, m_width);
+ m_height = std::max(1.0f, m_height);
+ m_group.SetWidth(m_width);
+ m_group.SetHeight(m_height);
+
+ TiXmlElement *child = layout->FirstChildElement("control");
+ while (child)
+ {
+ LoadControl(child, &m_group);
+ child = child->NextSiblingElement("control");
+ }
+}
+
+//#ifdef GUILIB_PYTHON_COMPATIBILITY
+void CGUIListItemLayout::CreateListControlLayouts(float width, float height, bool focused, const CLabelInfo &labelInfo, const CLabelInfo &labelInfo2, const CTextureInfo &texture, const CTextureInfo &textureFocus, float texHeight, float iconWidth, float iconHeight, const std::string &nofocusCondition, const std::string &focusCondition)
+{
+ m_width = width;
+ m_height = height;
+ m_focused = focused;
+ m_isPlaying.Parse("listitem.isplaying", 0);
+ CGUIImage *tex = new CGUIImage(0, 0, 0, 0, width, texHeight, texture);
+ tex->SetVisibleCondition(nofocusCondition);
+ m_group.AddControl(tex);
+ if (focused)
+ {
+ CGUIImage *tex = new CGUIImage(0, 0, 0, 0, width, texHeight, textureFocus);
+ tex->SetVisibleCondition(focusCondition);
+ m_group.AddControl(tex);
+ }
+ CGUIImage *image = new CGUIImage(0, 0, 8, 0, iconWidth, texHeight, CTextureInfo(""));
+ image->SetInfo(GUIINFO::CGUIInfoLabel("$INFO[ListItem.Icon]", "", m_group.GetParentID()));
+ image->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_group.AddControl(image);
+ float x = iconWidth + labelInfo.offsetX + 10;
+ CGUIListLabel *label = new CGUIListLabel(0, 0, x, labelInfo.offsetY, width - x - 18, height, labelInfo, GUIINFO::CGUIInfoLabel("$INFO[ListItem.Label]", "", m_group.GetParentID()), CGUIControl::FOCUS);
+ m_group.AddControl(label);
+ x = labelInfo2.offsetX ? labelInfo2.offsetX : m_width - 16;
+ label = new CGUIListLabel(0, 0, x, labelInfo2.offsetY, x - iconWidth - 20, height, labelInfo2, GUIINFO::CGUIInfoLabel("$INFO[ListItem.Label2]", "", m_group.GetParentID()), CGUIControl::FOCUS);
+ m_group.AddControl(label);
+}
+//#endif
+
+void CGUIListItemLayout::FreeResources(bool immediately)
+{
+ m_group.FreeResources(immediately);
+}
+
+#ifdef _DEBUG
+void CGUIListItemLayout::DumpTextureUse()
+{
+ m_group.DumpTextureUse();
+}
+#endif
diff --git a/xbmc/guilib/GUIListItemLayout.h b/xbmc/guilib/GUIListItemLayout.h
new file mode 100644
index 0000000..69d4c4f
--- /dev/null
+++ b/xbmc/guilib/GUIListItemLayout.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIListGroup.h"
+#include "GUITexture.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+#include "threads/SystemClock.h"
+
+class CGUIListItem;
+class CFileItem;
+class CLabelInfo;
+
+class CGUIListItemLayout final
+{
+public:
+ CGUIListItemLayout();
+ explicit CGUIListItemLayout(const CGUIListItemLayout& from);
+ explicit CGUIListItemLayout(const CGUIListItemLayout& from, CGUIControl* control);
+ void LoadLayout(TiXmlElement *layout, int context, bool focused, float maxWidth, float maxHeight);
+ void Process(CGUIListItem *item, int parentID, unsigned int currentTime, CDirtyRegionList &dirtyregions);
+ void Render(CGUIListItem *item, int parentID);
+ float Size(ORIENTATION orientation) const;
+ unsigned int GetFocusedItem() const;
+ void SetFocusedItem(unsigned int focus);
+ bool IsAnimating(ANIMATION_TYPE animType);
+ void ResetAnimation(ANIMATION_TYPE animType);
+ void SetInvalid() { m_invalidated = true; }
+ void FreeResources(bool immediately = false);
+ void SetParentControl(CGUIControl* control) { m_group.SetParentControl(control); }
+
+ //#ifdef GUILIB_PYTHON_COMPATIBILITY
+ void CreateListControlLayouts(float width, float height, bool focused, const CLabelInfo &labelInfo, const CLabelInfo &labelInfo2, const CTextureInfo &texture, const CTextureInfo &textureFocus, float texHeight, float iconWidth, float iconHeight, const std::string &nofocusCondition, const std::string &focusCondition);
+//#endif
+
+ void SetWidth(float width);
+ void SetHeight(float height);
+ void SelectItemFromPoint(const CPoint &point);
+ bool MoveLeft();
+ bool MoveRight();
+
+#ifdef _DEBUG
+ void DumpTextureUse();
+#endif
+ bool CheckCondition();
+protected:
+ void LoadControl(TiXmlElement *child, CGUIControlGroup *group);
+
+ CGUIListGroup m_group;
+
+ float m_width{0};
+ float m_height{0};
+ bool m_focused{false};
+ bool m_invalidated{true};
+
+ INFO::InfoPtr m_condition;
+ KODI::GUILIB::GUIINFO::CGUIInfoBool m_isPlaying;
+ std::chrono::milliseconds m_infoUpdateMillis =
+ XbmcThreads::EndTime<decltype(m_infoUpdateMillis)>::Max();
+ XbmcThreads::EndTime<> m_infoUpdateTimeout;
+};
+
diff --git a/xbmc/guilib/GUIListLabel.cpp b/xbmc/guilib/GUIListLabel.cpp
new file mode 100644
index 0000000..b3138eb
--- /dev/null
+++ b/xbmc/guilib/GUIListLabel.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIListLabel.h"
+
+#include "addons/Skin.h"
+
+#include <limits>
+
+using namespace KODI::GUILIB;
+
+CGUIListLabel::CGUIListLabel(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const GUIINFO::CGUIInfoLabel &info, CGUIControl::GUISCROLLVALUE scroll)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+ , m_label(posX, posY, width, height, labelInfo, (scroll == CGUIControl::ALWAYS) ? CGUILabel::OVER_FLOW_SCROLL : CGUILabel::OVER_FLOW_TRUNCATE)
+ , m_info(info)
+{
+ m_scroll = scroll;
+ if (m_info.IsConstant())
+ SetLabel(m_info.GetLabel(m_parentID, true));
+ m_label.SetScrollLoopCount(2);
+ ControlType = GUICONTROL_LISTLABEL;
+}
+
+CGUIListLabel::~CGUIListLabel(void) = default;
+
+void CGUIListLabel::SetSelected(bool selected)
+{
+ if(m_label.SetColor(selected ? CGUILabel::COLOR_SELECTED : CGUILabel::COLOR_TEXT))
+ SetInvalid();
+}
+
+void CGUIListLabel::SetFocus(bool focus)
+{
+ CGUIControl::SetFocus(focus);
+ if (m_scroll == CGUIControl::FOCUS)
+ {
+ m_label.SetScrolling(focus);
+ }
+}
+
+CRect CGUIListLabel::CalcRenderRegion() const
+{
+ return m_label.GetRenderRect();
+}
+
+bool CGUIListLabel::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+
+ return changed;
+}
+
+void CGUIListLabel::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_label.Process(currentTime))
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIListLabel::Render()
+{
+ m_label.Render();
+ CGUIControl::Render();
+}
+
+void CGUIListLabel::UpdateInfo(const CGUIListItem *item)
+{
+ if (m_info.IsConstant() && !m_bInvalidated)
+ return; // nothing to do
+
+ if (item)
+ SetLabel(m_info.GetItemLabel(item));
+ else
+ SetLabel(m_info.GetLabel(m_parentID, true));
+}
+
+void CGUIListLabel::SetInvalid()
+{
+ m_label.SetInvalid();
+ CGUIControl::SetInvalid();
+}
+
+void CGUIListLabel::SetWidth(float width)
+{
+ m_width = width;
+ if (m_label.GetLabelInfo().align & XBFONT_RIGHT)
+ m_label.SetMaxRect(m_posX - m_width, m_posY, m_width, m_height);
+ else if (m_label.GetLabelInfo().align & XBFONT_CENTER_X)
+ m_label.SetMaxRect(m_posX - m_width*0.5f, m_posY, m_width, m_height);
+ else
+ m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
+ CGUIControl::SetWidth(m_width);
+}
+
+void CGUIListLabel::SetLabel(const std::string &label)
+{
+ m_label.SetText(label);
+}
diff --git a/xbmc/guilib/GUIListLabel.h b/xbmc/guilib/GUIListLabel.h
new file mode 100644
index 0000000..64db806
--- /dev/null
+++ b/xbmc/guilib/GUIListLabel.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListLabel.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIListLabel :
+ public CGUIControl
+{
+public:
+ CGUIListLabel(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const KODI::GUILIB::GUIINFO::CGUIInfoLabel &label, CGUIControl::GUISCROLLVALUE scroll);
+ ~CGUIListLabel(void) override;
+ CGUIListLabel* Clone() const override { return new CGUIListLabel(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool CanFocus() const override { return false; }
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+ void SetFocus(bool focus) override;
+ void SetInvalid() override;
+ void SetWidth(float width) override;
+
+ void SetLabel(const std::string &label);
+ void SetSelected(bool selected);
+
+ static void CheckAndCorrectOverlap(CGUIListLabel &label1, CGUIListLabel &label2)
+ {
+ CGUILabel::CheckAndCorrectOverlap(label1.m_label, label2.m_label);
+ }
+
+ CRect CalcRenderRegion() const override;
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+
+ CGUILabel m_label;
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info;
+ CGUIControl::GUISCROLLVALUE m_scroll;
+};
diff --git a/xbmc/guilib/GUIMessage.cpp b/xbmc/guilib/GUIMessage.cpp
new file mode 100644
index 0000000..fb6513f
--- /dev/null
+++ b/xbmc/guilib/GUIMessage.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIMessage.h"
+
+#include "LocalizeStrings.h"
+
+std::string CGUIMessage::empty_string;
+
+CGUIMessage::CGUIMessage(int msg, int senderID, int controlID, int param1, int param2)
+{
+ m_message = msg;
+ m_senderID = senderID;
+ m_controlID = controlID;
+ m_param1 = param1;
+ m_param2 = param2;
+ m_pointer = NULL;
+}
+
+CGUIMessage::CGUIMessage(int msg, int senderID, int controlID, int param1, int param2, CFileItemList *item)
+{
+ m_message = msg;
+ m_senderID = senderID;
+ m_controlID = controlID;
+ m_param1 = param1;
+ m_param2 = param2;
+ m_pointer = item;
+}
+
+CGUIMessage::CGUIMessage(
+ int msg, int senderID, int controlID, int param1, int param2, const CGUIListItemPtr& item)
+ : m_item(item)
+{
+ m_message = msg;
+ m_senderID = senderID;
+ m_controlID = controlID;
+ m_param1 = param1;
+ m_param2 = param2;
+ m_pointer = NULL;
+}
+
+CGUIMessage::CGUIMessage(const CGUIMessage& msg) = default;
+
+CGUIMessage::~CGUIMessage(void) = default;
+
+
+int CGUIMessage::GetControlId() const
+{
+ return m_controlID;
+}
+
+int CGUIMessage::GetMessage() const
+{
+ return m_message;
+}
+
+void* CGUIMessage::GetPointer() const
+{
+ return m_pointer;
+}
+
+CGUIListItemPtr CGUIMessage::GetItem() const
+{
+ return m_item;
+}
+
+int CGUIMessage::GetParam1() const
+{
+ return m_param1;
+}
+
+int CGUIMessage::GetParam2() const
+{
+ return m_param2;
+}
+
+int CGUIMessage::GetSenderId() const
+{
+ return m_senderID;
+}
+
+CGUIMessage& CGUIMessage::operator = (const CGUIMessage& msg) = default;
+
+void CGUIMessage::SetParam1(int param1)
+{
+ m_param1 = param1;
+}
+
+void CGUIMessage::SetParam2(int param2)
+{
+ m_param2 = param2;
+}
+
+void CGUIMessage::SetPointer(void* lpVoid)
+{
+ m_pointer = lpVoid;
+}
+
+void CGUIMessage::SetLabel(const std::string& strLabel)
+{
+ m_strLabel = strLabel;
+}
+
+const std::string& CGUIMessage::GetLabel() const
+{
+ return m_strLabel;
+}
+
+void CGUIMessage::SetLabel(int iString)
+{
+ m_strLabel = g_localizeStrings.Get(iString);
+}
+
+void CGUIMessage::SetStringParam(const std::string& strParam)
+{
+ m_params.clear();
+ if (strParam.size())
+ m_params.push_back(strParam);
+}
+
+void CGUIMessage::SetStringParams(const std::vector<std::string> &params)
+{
+ m_params = params;
+}
+
+const std::string& CGUIMessage::GetStringParam(size_t param) const
+{
+ if (param >= m_params.size())
+ return empty_string;
+ return m_params[param];
+}
+
+size_t CGUIMessage::GetNumStringParams() const
+{
+ return m_params.size();
+}
+
+void CGUIMessage::SetItem(CGUIListItemPtr item)
+{
+ m_item = std::move(item);
+}
diff --git a/xbmc/guilib/GUIMessage.h b/xbmc/guilib/GUIMessage.h
new file mode 100644
index 0000000..2fc6702
--- /dev/null
+++ b/xbmc/guilib/GUIMessage.h
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIMessage.h
+\brief
+*/
+
+constexpr const int GUI_MSG_WINDOW_INIT = 1; // initialize window
+constexpr const int GUI_MSG_WINDOW_DEINIT = 2; // deinit window
+constexpr const int GUI_MSG_WINDOW_RESET = 27; // reset window to initial state
+
+constexpr const int GUI_MSG_SETFOCUS = 3; // set focus to control param1=up/down/left/right
+constexpr const int GUI_MSG_LOSTFOCUS = 4; // control lost focus
+
+constexpr const int GUI_MSG_CLICKED = 5; // control has been clicked
+
+constexpr const int GUI_MSG_VISIBLE = 6; // set control visible
+constexpr const int GUI_MSG_HIDDEN = 7; // set control hidden
+
+constexpr const int GUI_MSG_ENABLED = 8; // enable control
+constexpr const int GUI_MSG_DISABLED = 9; // disable control
+
+constexpr const int GUI_MSG_SET_SELECTED = 10; // control = selected
+constexpr const int GUI_MSG_SET_DESELECTED = 11; // control = not selected
+
+constexpr const int GUI_MSG_LABEL_ADD = 12; // add label control (for controls supporting more then 1 label)
+
+constexpr const int GUI_MSG_LABEL_SET = 13; // set the label of a control
+
+constexpr const int GUI_MSG_LABEL_RESET = 14; // clear all labels of a control // add label control (for controls supporting more then 1 label)
+
+constexpr const int GUI_MSG_ITEM_SELECTED = 15; // ask control 2 return the selected item
+constexpr const int GUI_MSG_ITEM_SELECT = 16; // ask control 2 select a specific item
+constexpr const int GUI_MSG_LABEL2_SET = 17;
+constexpr const int GUI_MSG_SHOWRANGE = 18;
+
+constexpr const int GUI_MSG_FULLSCREEN = 19; // should go to fullscreen window (vis or video)
+constexpr const int GUI_MSG_EXECUTE = 20; // user has clicked on a button with <execute> tag
+
+constexpr const int GUI_MSG_NOTIFY_ALL = 21; // message will be send to all active and inactive(!) windows, all active modal and modeless dialogs
+ // dwParam1 must contain an additional message the windows should react on
+
+constexpr const int GUI_MSG_REFRESH_THUMBS = 22; // message is sent to all windows to refresh all thumbs
+
+constexpr const int GUI_MSG_MOVE = 23; // message is sent to the window from the base control class when it's
+ // been asked to move. dwParam1 contains direction.
+
+constexpr const int GUI_MSG_LABEL_BIND = 24; // bind label control (for controls supporting more then 1 label)
+
+constexpr const int GUI_MSG_FOCUSED = 26; // a control has become focused
+
+constexpr const int GUI_MSG_PAGE_CHANGE = 28; // a page control has changed the page number
+
+constexpr const int GUI_MSG_REFRESH_LIST = 29; // message sent to all listing controls telling them to refresh their item layouts
+
+constexpr const int GUI_MSG_PAGE_UP = 30; // page up
+constexpr const int GUI_MSG_PAGE_DOWN = 31; // page down
+constexpr const int GUI_MSG_MOVE_OFFSET = 32; // Instruct the control to MoveUp or MoveDown by offset amount
+
+constexpr const int GUI_MSG_SET_TYPE = 33; ///< Instruct a control to set it's type appropriately
+
+/*!
+ \brief Message indicating the window has been resized
+ Any controls that keep stored sizing information based on aspect ratio or window size should
+ recalculate sizing information
+ */
+constexpr const int GUI_MSG_WINDOW_RESIZE = 34;
+
+/*!
+ \brief Message indicating loss of renderer, prior to reset
+ Any controls that keep shared resources should free them on receipt of this message, as the renderer
+ is about to be reset.
+ */
+constexpr const int GUI_MSG_RENDERER_LOST = 35;
+
+/*!
+ \brief Message indicating regain of renderer, after reset
+ Any controls that keep shared resources may reallocate them now that the renderer is back
+ */
+constexpr const int GUI_MSG_RENDERER_RESET = 36;
+
+/*!
+ \brief A control wishes to have (or release) exclusive access to mouse actions
+ */
+constexpr const int GUI_MSG_EXCLUSIVE_MOUSE = 37;
+
+/*!
+ \brief A request for supported gestures is made
+ */
+constexpr const int GUI_MSG_GESTURE_NOTIFY = 38;
+
+/*!
+ \brief A request to add a control
+ */
+constexpr const int GUI_MSG_ADD_CONTROL = 39;
+
+/*!
+ \brief A request to remove a control
+ */
+constexpr const int GUI_MSG_REMOVE_CONTROL = 40;
+
+/*!
+ \brief A request to unfocus all currently focused controls
+ */
+constexpr const int GUI_MSG_UNFOCUS_ALL = 41;
+
+constexpr const int GUI_MSG_SET_TEXT = 42;
+
+constexpr const int GUI_MSG_WINDOW_LOAD = 43;
+
+constexpr const int GUI_MSG_VALIDITY_CHANGED = 44;
+
+/*!
+ \brief Check whether a button is selected
+ */
+constexpr const int GUI_MSG_IS_SELECTED = 45;
+
+/*!
+ \brief Bind a set of labels to a spin (or similar) control
+ */
+constexpr const int GUI_MSG_SET_LABELS = 46;
+
+/*!
+ \brief Set the filename for an image control
+ */
+constexpr const int GUI_MSG_SET_FILENAME = 47;
+
+/*!
+ \brief Get the filename of an image control
+ */
+
+constexpr const int GUI_MSG_GET_FILENAME = 48;
+
+/*!
+ \brief The user interface is ready for usage
+ */
+constexpr const int GUI_MSG_UI_READY = 49;
+
+ /*!
+ \brief Called every 500ms to allow time dependent updates
+ */
+constexpr const int GUI_MSG_REFRESH_TIMER = 50;
+
+ /*!
+ \brief Called if state has changed which could lead to GUI changes
+ */
+constexpr const int GUI_MSG_STATE_CHANGED = 51;
+
+/*!
+ \brief Called when a subtitle download has finished
+ */
+constexpr const int GUI_MSG_SUBTITLE_DOWNLOADED = 52;
+
+
+constexpr const int GUI_MSG_USER = 1000;
+
+/*!
+\brief Complete to get codingtable page
+*/
+constexpr const int GUI_MSG_CODINGTABLE_LOOKUP_COMPLETED = 65000;
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_SELECT(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_SET_SELECTED, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_DESELECT(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_SET_DESELECTED, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_ENABLE(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_ENABLED, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_DISABLE(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_DISABLED, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_ENABLE_ON_CONDITION(controlID, bCondition) \
+ do \
+ { \
+ CGUIMessage _msg(bCondition ? GUI_MSG_ENABLED : GUI_MSG_DISABLED, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define CONTROL_SELECT_ITEM(controlID, iItem) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_ITEM_SELECT, GetID(), controlID, iItem); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief Set the label of the current control
+ */
+#define SET_CONTROL_LABEL(controlID, label) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_LABEL_SET, GetID(), controlID); \
+ _msg.SetLabel(label); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief Set the label of the current control
+ */
+#define SET_CONTROL_LABEL_THREAD_SAFE(controlID, label) \
+ { \
+ CGUIMessage _msg(GUI_MSG_LABEL_SET, GetID(), controlID); \
+ _msg.SetLabel(label); \
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread()) \
+ OnMessage(_msg); \
+ else \
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(_msg, GetID()); \
+ }
+
+/*!
+ \ingroup winmsg
+ \brief Set the second label of the current control
+ */
+#define SET_CONTROL_LABEL2(controlID, label) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_LABEL2_SET, GetID(), controlID); \
+ _msg.SetLabel(label); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief Set a bunch of labels on the given control
+ */
+#define SET_CONTROL_LABELS(controlID, defaultValue, labels) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_SET_LABELS, GetID(), controlID, defaultValue); \
+ _msg.SetPointer(labels); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief Set the label of the current control
+ */
+#define SET_CONTROL_FILENAME(controlID, label) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_SET_FILENAME, GetID(), controlID); \
+ _msg.SetLabel(label); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define SET_CONTROL_HIDDEN(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_HIDDEN, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define SET_CONTROL_FOCUS(controlID, dwParam) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_SETFOCUS, GetID(), controlID, dwParam); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+#define SET_CONTROL_VISIBLE(controlID) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_VISIBLE, GetID(), controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+#define SET_CONTROL_SELECTED(dwSenderId, controlID, bSelect) \
+ do \
+ { \
+ CGUIMessage _msg(bSelect ? GUI_MSG_SET_SELECTED : GUI_MSG_SET_DESELECTED, dwSenderId, \
+ controlID); \
+ OnMessage(_msg); \
+ } while (0)
+
+/*!
+\ingroup winmsg
+\brief Click message sent from controls to windows.
+ */
+#define SEND_CLICK_MESSAGE(id, parentID, action) \
+ do \
+ { \
+ CGUIMessage _msg(GUI_MSG_CLICKED, id, parentID, action); \
+ SendWindowMessage(_msg); \
+ } while (0)
+
+#include <string>
+#include <vector>
+#include <memory>
+
+// forwards
+class CGUIListItem; typedef std::shared_ptr<CGUIListItem> CGUIListItemPtr;
+class CFileItemList;
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+class CGUIMessage final
+{
+public:
+ CGUIMessage(int dwMsg, int senderID, int controlID, int param1 = 0, int param2 = 0);
+ CGUIMessage(int msg, int senderID, int controlID, int param1, int param2, CFileItemList* item);
+ CGUIMessage(int msg, int senderID, int controlID, int param1, int param2, const CGUIListItemPtr &item);
+ CGUIMessage(const CGUIMessage& msg);
+ ~CGUIMessage(void);
+ CGUIMessage& operator = (const CGUIMessage& msg);
+
+ int GetControlId() const ;
+ int GetMessage() const;
+ void* GetPointer() const;
+ CGUIListItemPtr GetItem() const;
+ int GetParam1() const;
+ int GetParam2() const;
+ int GetSenderId() const;
+ void SetParam1(int param1);
+ void SetParam2(int param2);
+ void SetPointer(void* pointer);
+ void SetLabel(const std::string& strLabel);
+ void SetLabel(int iString); // for convenience - looks up in strings.po
+ const std::string& GetLabel() const;
+ void SetStringParam(const std::string &strParam);
+ void SetStringParams(const std::vector<std::string> &params);
+ const std::string& GetStringParam(size_t param = 0) const;
+ size_t GetNumStringParams() const;
+ void SetItem(CGUIListItemPtr item);
+
+private:
+ std::string m_strLabel;
+ std::vector<std::string> m_params;
+ int m_senderID;
+ int m_controlID;
+ int m_message;
+ void* m_pointer;
+ int m_param1;
+ int m_param2;
+ CGUIListItemPtr m_item;
+
+ static std::string empty_string;
+};
+
diff --git a/xbmc/guilib/GUIMoverControl.cpp b/xbmc/guilib/GUIMoverControl.cpp
new file mode 100644
index 0000000..bb2c00f
--- /dev/null
+++ b/xbmc/guilib/GUIMoverControl.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIMoverControl.h"
+
+#include "GUIMessage.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/TimeUtils.h"
+
+using namespace UTILS;
+
+CGUIMoverControl::CGUIMoverControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_imgFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureFocus)),
+ m_imgNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureNoFocus))
+{
+ m_frameCounter = 0;
+ m_movingSpeed.AddEventMapConfig(movingSpeedCfg);
+ m_fAnalogSpeed = 2.0f; //! @todo implement correct analog speed
+ ControlType = GUICONTROL_MOVER;
+ SetLimits(0, 0, 720, 576); // defaults
+ SetLocation(0, 0, false); // defaults
+}
+
+CGUIMoverControl::CGUIMoverControl(const CGUIMoverControl& control)
+ : CGUIControl(control),
+ m_imgFocus(control.m_imgFocus->Clone()),
+ m_imgNoFocus(control.m_imgNoFocus->Clone()),
+ m_frameCounter(control.m_frameCounter),
+ m_movingSpeed(control.m_movingSpeed),
+ m_fAnalogSpeed(control.m_fAnalogSpeed),
+ m_iX1(control.m_iX1),
+ m_iX2(control.m_iX2),
+ m_iY1(control.m_iY1),
+ m_iY2(control.m_iY2),
+ m_iLocationX(control.m_iLocationX),
+ m_iLocationY(control.m_iLocationY)
+{
+}
+
+void CGUIMoverControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_bInvalidated)
+ {
+ m_imgFocus->SetWidth(m_width);
+ m_imgFocus->SetHeight(m_height);
+
+ m_imgNoFocus->SetWidth(m_width);
+ m_imgNoFocus->SetHeight(m_height);
+ }
+ if (HasFocus())
+ {
+ unsigned int alphaCounter = m_frameCounter + 2;
+ unsigned int alphaChannel;
+ if ((alphaCounter % 128) >= 64)
+ alphaChannel = alphaCounter % 64;
+ else
+ alphaChannel = 63 - (alphaCounter % 64);
+
+ alphaChannel += 192;
+ if (SetAlpha( (unsigned char)alphaChannel ))
+ MarkDirtyRegion();
+ m_imgFocus->SetVisible(true);
+ m_imgNoFocus->SetVisible(false);
+ m_frameCounter++;
+ }
+ else
+ {
+ if (SetAlpha(0xff))
+ MarkDirtyRegion();
+ m_imgFocus->SetVisible(false);
+ m_imgNoFocus->SetVisible(true);
+ }
+ m_imgFocus->Process(currentTime);
+ m_imgNoFocus->Process(currentTime);
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIMoverControl::Render()
+{
+ // render both so the visibility settings cause the frame counter to resetcorrectly
+ m_imgFocus->Render();
+ m_imgNoFocus->Render();
+ CGUIControl::Render();
+}
+
+bool CGUIMoverControl::OnAction(const CAction &action)
+{
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ // button selected - send message to parent
+ CGUIMessage message(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(message);
+ return true;
+ }
+ if (action.GetID() == ACTION_ANALOG_MOVE)
+ {
+ // if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_UPDOWN)
+ // Move(0, (int)(-m_fAnalogSpeed*action.GetAmount(1)));
+ // else if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_LEFTRIGHT)
+ // Move((int)(m_fAnalogSpeed*action.GetAmount()), 0);
+ // else // ALLOWED_DIRECTIONS_ALL
+ Move((int)(m_fAnalogSpeed*action.GetAmount()), (int)( -m_fAnalogSpeed*action.GetAmount(1)));
+ return true;
+ }
+ // base class
+ return CGUIControl::OnAction(action);
+}
+
+void CGUIMoverControl::OnUp()
+{
+ // if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_LEFTRIGHT) return;
+ Move(0, -static_cast<int>(m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::UP)));
+}
+
+void CGUIMoverControl::OnDown()
+{
+ // if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_LEFTRIGHT) return;
+ Move(0, static_cast<int>(m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::DOWN)));
+}
+
+void CGUIMoverControl::OnLeft()
+{
+ // if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_UPDOWN) return;
+ Move(-static_cast<int>(m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::LEFT)), 0);
+}
+
+void CGUIMoverControl::OnRight()
+{
+ // if (m_dwAllowedDirections == ALLOWED_DIRECTIONS_UPDOWN) return;
+ Move(static_cast<int>(m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::RIGHT)), 0);
+}
+
+EVENT_RESULT CGUIMoverControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_DRAG || event.m_id == ACTION_MOUSE_DRAG_END)
+ {
+ if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ else if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG_END)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ }
+ Move((int)event.m_offsetX, (int)event.m_offsetY);
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+void CGUIMoverControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_frameCounter = 0;
+ m_imgFocus->AllocResources();
+ m_imgNoFocus->AllocResources();
+ float width = m_width ? m_width : m_imgFocus->GetWidth();
+ float height = m_height ? m_height : m_imgFocus->GetHeight();
+ SetWidth(width);
+ SetHeight(height);
+}
+
+void CGUIMoverControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_imgFocus->FreeResources(immediately);
+ m_imgNoFocus->FreeResources(immediately);
+}
+
+void CGUIMoverControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgFocus->DynamicResourceAlloc(bOnOff);
+ m_imgNoFocus->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIMoverControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_imgFocus->SetInvalid();
+ m_imgNoFocus->SetInvalid();
+}
+
+void CGUIMoverControl::Move(int iX, int iY)
+{
+ if (!m_enabled)
+ return;
+
+ int iLocX = m_iLocationX + iX;
+ int iLocY = m_iLocationY + iY;
+ // check if we are within the bounds
+ if (iLocX < m_iX1) iLocX = m_iX1;
+ if (iLocY < m_iY1) iLocY = m_iY1;
+ if (iLocX > m_iX2) iLocX = m_iX2;
+ if (iLocY > m_iY2) iLocY = m_iY2;
+ // ok, now set the location of the mover
+ SetLocation(iLocX, iLocY);
+}
+
+void CGUIMoverControl::SetLocation(int iLocX, int iLocY, bool bSetPosition)
+{
+ if (bSetPosition) SetPosition(GetXPosition() + iLocX - m_iLocationX, GetYPosition() + iLocY - m_iLocationY);
+ m_iLocationX = iLocX;
+ m_iLocationY = iLocY;
+}
+
+void CGUIMoverControl::SetPosition(float posX, float posY)
+{
+ CGUIControl::SetPosition(posX, posY);
+ m_imgFocus->SetPosition(posX, posY);
+ m_imgNoFocus->SetPosition(posX, posY);
+}
+
+bool CGUIMoverControl::SetAlpha(unsigned char alpha)
+{
+ bool changed = m_imgFocus->SetAlpha(alpha);
+ changed |= m_imgNoFocus->SetAlpha(alpha);
+ return changed;
+}
+
+bool CGUIMoverControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_imgFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgNoFocus->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+void CGUIMoverControl::SetLimits(int iX1, int iY1, int iX2, int iY2)
+{
+ m_iX1 = iX1;
+ m_iY1 = iY1;
+ m_iX2 = iX2;
+ m_iY2 = iY2;
+}
diff --git a/xbmc/guilib/GUIMoverControl.dox b/xbmc/guilib/GUIMoverControl.dox
new file mode 100644
index 0000000..191231f
--- /dev/null
+++ b/xbmc/guilib/GUIMoverControl.dox
@@ -0,0 +1,92 @@
+/*!
+
+\page Mover_Control Mover Control
+\brief **Used in the calibration screens.**
+
+\tableofcontents
+
+The mover control is used for the screen calibration portions of Kodi. You can
+choose the size and look of the mover control.
+
+
+--------------------------------------------------------------------------------
+\section Mover_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="mover" id="3">
+ <description>My first mover control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <texturefocus>mytexture.png</texturefocus>
+ <texturenofocus>mytexture.png</texturenofocus>
+ <pulseonselect>true</pulseonselect>
+ <movingspeed acceleration="180" maxvelocity="300" resettimeout="200" delta="1">
+ <eventconfig type="up">
+ <eventconfig type="down">
+ <eventconfig type="left" acceleration="100" maxvelocity="100" resettimeout="160" delta="1">
+ <eventconfig type="right" acceleration="100" maxvelocity="100" resettimeout="160" delta="1">
+ </movingspeed>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Mover_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|----------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the mover has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus | Specifies the image file which should be displayed when the mover does not have focus.
+| movingspeed | Specifies the allowed directional movements and respective configurations. The <c>`<movingspeed>`</c> tag section can contain as many <c>`<eventconfig>`</c> tags as required.
+
+
+--------------------------------------------------------------------------------
+\section Mover_Control_sect3 Note on use of movingspeed tag
+
+The <c>`movingspeed`</c> tag must be specified to allow the control to be moved via
+an input device such as keyboard. Each direction can be specified and configured
+to simulate the motion effect with the <c>`eventconfig`</c> tag.
+
+The following attributes allow the motion effect of the control to be configured:
+
+| Attribute | Description |
+|----------------:|:--------------------------------------------------------------|
+| type | Specifies the direction of movement. Accepted values are: <c>`up`</c>, <c>`down`</c>, <c>`left`</c>, <c>`right`</c>. All directions are optional and do not have to be included.
+| acceleration | Specifies the acceleration in pixels per second (integer value).
+| maxvelocity | Specifies the maximum movement speed. This depends on the acceleration value, e.g. a suitable value could be (acceleration value)*3. Set to 0 to disable. (integer value).
+| resettimeout | Specifies the idle timeout before resetting the acceleration speed (in milliseconds, integer value).
+| delta | Specifies the minimal pixel increment step (integer value).
+
+The attributes <c>`acceleration`</c>, <c>`maxvelocity`</c>, <c>`resettimeout`</c>,
+<c>`delta`</c> can be specified individually for each <c>`eventconfig`</c> tag,
+or globally in the <c>`movingspeed`</c> tag, or a mixture, as the following example:
+
+~~~~~~~~~~~~~
+<movingspeed acceleration="180" maxvelocity="300" resettimeout="200" delta="1">
+ <eventconfig type="up">
+ <eventconfig type="down">
+ <eventconfig type="left" acceleration="100" maxvelocity="100" resettimeout="160">
+ <eventconfig type="right" acceleration="100" maxvelocity="100" resettimeout="160">
+</movingspeed>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Mover_Control_sect4 Revision History
+
+@skinning_v20 <b>[movingspeed]</b> New tag added.
+
+--------------------------------------------------------------------------------
+\section Mover_Control_sect5 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIMoverControl.h b/xbmc/guilib/GUIMoverControl.h
new file mode 100644
index 0000000..d958df1
--- /dev/null
+++ b/xbmc/guilib/GUIMoverControl.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIMoverControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+#include "utils/MovingSpeed.h"
+
+#define ALLOWED_DIRECTIONS_ALL 0
+#define ALLOWED_DIRECTIONS_UPDOWN 1
+#define ALLOWED_DIRECTIONS_LEFTRIGHT 2
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIMoverControl : public CGUIControl
+{
+public:
+ CGUIMoverControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg);
+
+ ~CGUIMoverControl(void) override = default;
+ CGUIMoverControl* Clone() const override { return new CGUIMoverControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void OnUp() override;
+ void OnDown() override;
+ void OnLeft() override;
+ void OnRight() override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ void SetLimits(int iX1, int iY1, int iX2, int iY2);
+ void SetLocation(int iLocX, int iLocY, bool bSetPosition = true);
+ int GetXLocation() const { return m_iLocationX; }
+ int GetYLocation() const { return m_iLocationY; }
+ bool CanFocus() const override { return true; }
+
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ bool SetAlpha(unsigned char alpha);
+ void Move(int iX, int iY);
+ std::unique_ptr<CGUITexture> m_imgFocus;
+ std::unique_ptr<CGUITexture> m_imgNoFocus;
+ unsigned int m_frameCounter;
+ UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed;
+ float m_fAnalogSpeed;
+ int m_iX1, m_iX2, m_iY1, m_iY2;
+ int m_iLocationX, m_iLocationY;
+
+private:
+ CGUIMoverControl(const CGUIMoverControl& control);
+};
+
diff --git a/xbmc/guilib/GUIMultiImage.cpp b/xbmc/guilib/GUIMultiImage.cpp
new file mode 100644
index 0000000..39593e6
--- /dev/null
+++ b/xbmc/guilib/GUIMultiImage.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIMultiImage.h"
+
+#include "FileItem.h"
+#include "GUIMessage.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "TextureManager.h"
+#include "WindowIDs.h"
+#include "filesystem/Directory.h"
+#include "input/Key.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/JobManager.h"
+#include "utils/Random.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <mutex>
+
+using namespace KODI::GUILIB;
+using namespace XFILE;
+
+CGUIMultiImage::CGUIMultiImage(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& texture, unsigned int timePerImage, unsigned int fadeTime, bool randomized, bool loop, unsigned int timeToPauseAtEnd)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_image(0, 0, posX, posY, width, height, texture)
+{
+ m_currentImage = 0;
+ m_timePerImage = timePerImage + fadeTime;
+ m_timeToPauseAtEnd = timeToPauseAtEnd;
+ m_image.SetCrossFade(fadeTime);
+ m_randomized = randomized;
+ m_loop = loop;
+ ControlType = GUICONTROL_MULTI_IMAGE;
+ m_bDynamicResourceAlloc=false;
+ m_directoryStatus = UNLOADED;
+ m_jobID = 0;
+}
+
+CGUIMultiImage::CGUIMultiImage(const CGUIMultiImage &from)
+ : CGUIControl(from), m_texturePath(from.m_texturePath), m_imageTimer(), m_files(), m_image(from.m_image)
+{
+ m_timePerImage = from.m_timePerImage;
+ m_timeToPauseAtEnd = from.m_timeToPauseAtEnd;
+ m_randomized = from.m_randomized;
+ m_loop = from.m_loop;
+ m_bDynamicResourceAlloc=false;
+ m_directoryStatus = UNLOADED;
+ if (m_texturePath.IsConstant())
+ m_currentPath = m_texturePath.GetLabel(WINDOW_INVALID);
+ m_currentImage = 0;
+ ControlType = GUICONTROL_MULTI_IMAGE;
+ m_jobID = 0;
+}
+
+CGUIMultiImage::~CGUIMultiImage(void)
+{
+ CancelLoading();
+}
+
+void CGUIMultiImage::UpdateVisibility(const CGUIListItem *item)
+{
+ CGUIControl::UpdateVisibility(item);
+
+ // check if we're hidden, and deallocate if so
+ if (!IsVisible() && m_visible != DELAYED)
+ {
+ if (m_bDynamicResourceAlloc && m_bAllocated)
+ FreeResources();
+ return;
+ }
+
+ // we are either delayed or visible, so we can allocate our resources
+ if (m_directoryStatus == UNLOADED)
+ LoadDirectory();
+
+ if (!m_bAllocated)
+ AllocResources();
+
+ if (m_directoryStatus == LOADED)
+ OnDirectoryLoaded();
+}
+
+void CGUIMultiImage::UpdateInfo(const CGUIListItem *item)
+{
+ // check for conditional information before we
+ // alloc as this can free our resources
+ if (!m_texturePath.IsConstant())
+ {
+ std::string texturePath;
+ if (item)
+ texturePath = m_texturePath.GetItemLabel(item, true);
+ else
+ texturePath = m_texturePath.GetLabel(m_parentID);
+ if (texturePath != m_currentPath)
+ {
+ // a new path - set our current path and tell ourselves to load our directory
+ m_currentPath = texturePath;
+ CancelLoading();
+ }
+ }
+}
+
+void CGUIMultiImage::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // Set a viewport so that we don't render outside the defined area
+ if (m_directoryStatus == READY && !m_files.empty())
+ {
+ unsigned int nextImage = m_currentImage + 1;
+ if (nextImage >= m_files.size())
+ nextImage = m_loop ? 0 : m_currentImage; // stay on the last image if <loop>no</loop>
+
+ if (nextImage != m_currentImage)
+ {
+ // check if we should be loading a new image yet
+ unsigned int timeToShow = m_timePerImage;
+ if (0 == nextImage) // last image should be paused for a bit longer if that's what the skinner wishes.
+ timeToShow += m_timeToPauseAtEnd;
+ if (m_imageTimer.IsRunning() && m_imageTimer.GetElapsedMilliseconds() > timeToShow)
+ {
+ // grab a new image
+ m_currentImage = nextImage;
+ m_image.SetFileName(m_files[m_currentImage]);
+ MarkDirtyRegion();
+
+ m_imageTimer.StartZero();
+ }
+ }
+ }
+ else if (m_directoryStatus != LOADING)
+ m_image.SetFileName("");
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ {
+ if (m_image.SetColorDiffuse(m_diffuseColor))
+ MarkDirtyRegion();
+
+ m_image.DoProcess(currentTime, dirtyregions);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIMultiImage::Render()
+{
+ m_image.Render();
+ CGUIControl::Render();
+}
+
+bool CGUIMultiImage::OnAction(const CAction &action)
+{
+ return false;
+}
+
+bool CGUIMultiImage::OnMessage(CGUIMessage &message)
+{
+ if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
+ {
+ if (!m_texturePath.IsConstant())
+ FreeResources();
+ return true;
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIMultiImage::AllocResources()
+{
+ FreeResources();
+ CGUIControl::AllocResources();
+
+ if (m_directoryStatus == UNLOADED)
+ LoadDirectory();
+}
+
+void CGUIMultiImage::FreeResources(bool immediately)
+{
+ m_image.FreeResources(immediately);
+ m_currentImage = 0;
+ CancelLoading();
+ m_files.clear();
+ CGUIControl::FreeResources(immediately);
+}
+
+void CGUIMultiImage::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_bDynamicResourceAlloc=bOnOff;
+}
+
+void CGUIMultiImage::SetInvalid()
+{
+ m_image.SetInvalid();
+ CGUIControl::SetInvalid();
+}
+
+bool CGUIMultiImage::CanFocus() const
+{
+ return false;
+}
+
+void CGUIMultiImage::SetAspectRatio(const CAspectRatio &ratio)
+{
+ m_image.SetAspectRatio(ratio);
+}
+
+void CGUIMultiImage::LoadDirectory()
+{
+ // clear current stuff out
+ m_files.clear();
+
+ // don't load any images if our path is empty
+ if (m_currentPath.empty()) return;
+
+ /* Check the fast cases:
+ 1. Picture extension
+ 2. Cached picture (in case an extension is not present)
+ 3. Bundled folder
+ */
+ CFileItem item(m_currentPath, false);
+ if (item.IsPicture() || CServiceBroker::GetTextureCache()->HasCachedImage(m_currentPath))
+ m_files.push_back(m_currentPath);
+ else // bundled folder?
+ m_files =
+ CServiceBroker::GetGUI()->GetTextureManager().GetBundledTexturesFromPath(m_currentPath);
+ if (!m_files.empty())
+ { // found - nothing more to do
+ OnDirectoryLoaded();
+ return;
+ }
+ // slow(er) checks necessary - do them in the background
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_directoryStatus = LOADING;
+ m_jobID = CServiceBroker::GetJobManager()->AddJob(new CMultiImageJob(m_currentPath), this,
+ CJob::PRIORITY_NORMAL);
+}
+
+void CGUIMultiImage::OnDirectoryLoaded()
+{
+ // Randomize or sort our images if necessary
+ if (m_randomized)
+ KODI::UTILS::RandomShuffle(m_files.begin(), m_files.end());
+ else
+ sort(m_files.begin(), m_files.end());
+
+ // flag as loaded - no point in constantly reloading them
+ m_directoryStatus = READY;
+ m_imageTimer.StartZero();
+ m_currentImage = 0;
+ m_image.SetFileName(m_files.empty() ? "" : m_files[0]);
+}
+
+void CGUIMultiImage::CancelLoading()
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_directoryStatus == LOADING)
+ CServiceBroker::GetJobManager()->CancelJob(m_jobID);
+ m_directoryStatus = UNLOADED;
+}
+
+void CGUIMultiImage::OnJobComplete(unsigned int jobID, bool success, CJob *job)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (m_directoryStatus == LOADING && strncmp(job->GetType(), "multiimage", 10) == 0)
+ {
+ m_files = ((CMultiImageJob *)job)->m_files;
+ m_directoryStatus = LOADED;
+ }
+}
+
+void CGUIMultiImage::SetInfo(const GUIINFO::CGUIInfoLabel &info)
+{
+ m_texturePath = info;
+ if (m_texturePath.IsConstant())
+ m_currentPath = m_texturePath.GetLabel(WINDOW_INVALID);
+}
+
+std::string CGUIMultiImage::GetDescription() const
+{
+ return m_image.GetDescription();
+}
+
+CGUIMultiImage::CMultiImageJob::CMultiImageJob(const std::string &path)
+ : m_path(path)
+{
+}
+
+bool CGUIMultiImage::CMultiImageJob::DoWork()
+{
+ // check to see if we have a single image or a folder of images
+ CFileItem item(m_path, false);
+ item.FillInMimeType();
+ if (item.IsPicture() || StringUtils::StartsWithNoCase(item.GetMimeType(), "image/"))
+ {
+ m_files.push_back(m_path);
+ }
+ else
+ {
+ // Load in images from the directory specified
+ // m_path is relative (as are all skin paths)
+ std::string realPath = CServiceBroker::GetGUI()->GetTextureManager().GetTexturePath(m_path, true);
+ if (realPath.empty())
+ return true;
+
+ URIUtils::AddSlashAtEnd(realPath);
+ CFileItemList items;
+ CDirectory::GetDirectory(realPath, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()+ "|.tbn|.dds", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
+ for (int i=0; i < items.Size(); i++)
+ {
+ CFileItem* pItem = items[i].get();
+ if (pItem && (pItem->IsPicture() || StringUtils::StartsWithNoCase(pItem->GetMimeType(), "image/")))
+ m_files.push_back(pItem->GetPath());
+ }
+ }
+ return true;
+}
diff --git a/xbmc/guilib/GUIMultiImage.dox b/xbmc/guilib/GUIMultiImage.dox
new file mode 100644
index 0000000..c741b43
--- /dev/null
+++ b/xbmc/guilib/GUIMultiImage.dox
@@ -0,0 +1,78 @@
+/*!
+
+\page MultiImage_Control Group MultiImage Control
+\brief **Used to show a slideshow of images.**
+
+\tableofcontents
+
+The MultiImage control is used for displaying a slideshow of images from a folder
+in Kodi. You can choose the position and size of the slideshow, as well as
+timing information.
+
+
+--------------------------------------------------------------------------------
+\section MultiImage_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="multiimage" id="1">
+ <description>My first slideshow control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <imagepath>myimagepath</imagepath>
+ <info></info>
+ <timeperimage>5000</timeperimage>
+ <fadetime>2000</fadetime>
+ <pauseatend>10000</pauseatend>
+ <randomize>true</randomize>
+ <loop>no</loop>
+ <aspectratio>stretch</aspectratio>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section MultiImage_Control_sect2 Image Size and Type Restrictions
+For the <b>`<texture>`</b> tags, and for all <b>`<texture>`</b> tags in other
+controls, there is a small set of rules that you should follow if at all possible:
+
+\subsection MultiImage_Control_sect2_1 Formats
+If you wish to use full 8 bit transparency, then use PNG. If you only need a
+single transparent colour, then you can specify this in the <b>`<colorkey>`</b>
+tag, so any image will be fine. It is suggested that you use PNG and JPG as much
+as possible. The size of the images (in kb) is therefore not as important as the
+size of the images in pixels – so feel free to store them in a lossless (eg PNG)
+manner if you wish.
+
+The only exception to this is if you require an animated texture. In this case, we
+only support animated GIF. There are also SOME animated gifs that may not work.
+Use ImageReady CS and make sure you set the gif-anim to “restore to background”
+and they should work fine.
+
+\section MultiImage_Control_sect3 Available tags and attributes
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| imagepath | Specifies the path containing the images to use for the slideshow. Kodi will first look inside the compressed Textures.xbt file for images, and then will look in the actual folder. The path is relative to the media/ folder if it is not specified completely.
+| info | Specifies the information that this image control is presenting. Kodi will select the texture to use based on this tag. [See here for more information](http://kodi.wiki/view/InfoLabels).
+| timeperimage | Time in milliseconds that an image is shown for.
+| fadetime | Time in milliseconds to fade between images.
+| pauseatend | Time in milliseconds to pause (in addition to <b>`<timeperimage>`</b>) on the last image at the end of a complete cycle through the images. Only useful if <b>`<loop>`</b> is set to yes.
+| loop | If set to no, the last image will display indefinitely. Setting it to yes will loop around once they reach the last image. Defaults to yes.
+| aspectratio | This specifies how the image will be drawn inside the box defined by <b>`<width>`</b> and <b>`<height>`</b>. [See here for more info](http://kodi.wiki/view/Aspect_Ratio).
+
+--------------------------------------------------------------------------------
+\section MultiImage_Control_sect4 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIMultiImage.h b/xbmc/guilib/GUIMultiImage.h
new file mode 100644
index 0000000..c7a4a6a
--- /dev/null
+++ b/xbmc/guilib/GUIMultiImage.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIMultiImage.h
+\brief
+*/
+
+#include "GUIImage.h"
+#include "threads/CriticalSection.h"
+#include "utils/Job.h"
+#include "utils/Stopwatch.h"
+
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIMultiImage : public CGUIControl, public IJobCallback
+{
+public:
+ CGUIMultiImage(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& texture, unsigned int timePerImage, unsigned int fadeTime, bool randomized, bool loop, unsigned int timeToPauseAtEnd);
+ CGUIMultiImage(const CGUIMultiImage &from);
+ ~CGUIMultiImage(void) override;
+ CGUIMultiImage* Clone() const override { return new CGUIMultiImage(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage &message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ bool IsDynamicallyAllocated() override { return m_bDynamicResourceAlloc; }
+ void SetInvalid() override;
+ bool CanFocus() const override;
+ std::string GetDescription() const override;
+
+ void SetInfo(const KODI::GUILIB::GUIINFO::CGUIInfoLabel &info);
+ void SetAspectRatio(const CAspectRatio &ratio);
+
+protected:
+ void LoadDirectory();
+ void OnDirectoryLoaded();
+ void CancelLoading();
+
+ enum DIRECTORY_STATUS { UNLOADED = 0, LOADING, LOADED, READY };
+ void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
+
+ class CMultiImageJob : public CJob
+ {
+ public:
+ explicit CMultiImageJob(const std::string &path);
+ bool DoWork() override;
+ const char* GetType() const override { return "multiimage"; }
+
+ std::vector<std::string> m_files;
+ std::string m_path;
+ };
+
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_texturePath;
+ std::string m_currentPath;
+ unsigned int m_currentImage;
+ CStopWatch m_imageTimer;
+ unsigned int m_timePerImage;
+ unsigned int m_timeToPauseAtEnd;
+ bool m_randomized;
+ bool m_loop;
+
+ bool m_bDynamicResourceAlloc;
+ std::vector<std::string> m_files;
+
+ CGUIImage m_image;
+
+ CCriticalSection m_section;
+ DIRECTORY_STATUS m_directoryStatus;
+ unsigned int m_jobID;
+};
+
diff --git a/xbmc/guilib/GUIPanelContainer.cpp b/xbmc/guilib/GUIPanelContainer.cpp
new file mode 100644
index 0000000..0195d57
--- /dev/null
+++ b/xbmc/guilib/GUIPanelContainer.cpp
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIPanelContainer.h"
+
+#include "FileItem.h"
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "utils/StringUtils.h"
+
+#include <cassert>
+
+CGUIPanelContainer::CGUIPanelContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
+ : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
+{
+ ControlType = GUICONTAINER_PANEL;
+ m_type = VIEW_TYPE_ICON;
+ m_itemsPerRow = 1;
+}
+
+CGUIPanelContainer::~CGUIPanelContainer(void) = default;
+
+void CGUIPanelContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ ValidateOffset();
+
+ if (m_bInvalidated)
+ UpdateLayout();
+
+ if (!m_layout || !m_focusedLayout)
+ return;
+
+ UpdateScrollOffset(currentTime);
+
+ int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ // Free memory not used on screen
+ if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
+ FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
+
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+ pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ int current = (offset - cacheBefore) * m_itemsPerRow;
+ int col = 0;
+ while (pos < end && m_items.size())
+ {
+ if (current >= (int)m_items.size())
+ break;
+ if (current >= 0)
+ {
+ CGUIListItemPtr item = m_items[current];
+ item->SetCurrentItem(current + 1);
+ bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
+
+ if (m_orientation == VERTICAL)
+ ProcessItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item, focused, currentTime, dirtyregions);
+ else
+ ProcessItem(pos, origin.y + col * m_layout->Size(VERTICAL), item, focused, currentTime, dirtyregions);
+ }
+ // increment our position
+ if (col < m_itemsPerRow - 1)
+ col++;
+ else
+ {
+ pos += m_layout->Size(m_orientation);
+ col = 0;
+ }
+ current++;
+ }
+
+ // when we are scrolling up, offset will become lower (integer division, see offset calc)
+ // to have same behaviour when scrolling down, we need to set page control to offset+1
+ UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+
+void CGUIPanelContainer::Render()
+{
+ if (!m_layout || !m_focusedLayout)
+ return;
+
+ int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ {
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+ pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ float focusedPos = 0;
+ int focusedCol = 0;
+ CGUIListItemPtr focusedItem;
+ int current = (offset - cacheBefore) * m_itemsPerRow;
+ int col = 0;
+ while (pos < end && m_items.size())
+ {
+ if (current >= (int)m_items.size())
+ break;
+ if (current >= 0)
+ {
+ CGUIListItemPtr item = m_items[current];
+ bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
+ // render our item
+ if (focused)
+ {
+ focusedPos = pos;
+ focusedCol = col;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item.get(), false);
+ else
+ RenderItem(pos, origin.y + col * m_layout->Size(VERTICAL), item.get(), false);
+ }
+ }
+ // increment our position
+ if (col < m_itemsPerRow - 1)
+ col++;
+ else
+ {
+ pos += m_layout->Size(m_orientation);
+ col = 0;
+ }
+ current++;
+ }
+ // and render the focused item last (for overlapping purposes)
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x + focusedCol * m_layout->Size(HORIZONTAL), focusedPos, focusedItem.get(), true);
+ else
+ RenderItem(focusedPos, origin.y + focusedCol * m_layout->Size(VERTICAL), focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ CGUIControl::Render();
+}
+
+bool CGUIPanelContainer::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PAGE_UP:
+ {
+ if (GetOffset() == 0)
+ { // already on the first page, so move to the first item
+ SetCursor(0);
+ }
+ else
+ { // scroll up to the previous page
+ Scroll( -m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ case ACTION_PAGE_DOWN:
+ {
+ if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow >= (int)m_items.size() || (int)m_items.size() < m_itemsPerPage)
+ { // already at the last page, so move to the last item.
+ SetCursor(m_items.size() - GetOffset() * m_itemsPerRow - 1);
+ }
+ else
+ { // scroll down to the next page
+ Scroll(m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ // smooth scrolling (for analog controls)
+ case ACTION_SCROLL_UP:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > AnalogScrollSpeed())
+ {
+ handled = true;
+ m_analogScrollCount -= AnalogScrollSpeed();
+ if (GetOffset() > 0)// && GetCursor() <= m_itemsPerPage * m_itemsPerRow / 2)
+ {
+ Scroll(-1);
+ }
+ else if (GetCursor() > 0)
+ {
+ SetCursor(GetCursor() - 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ case ACTION_SCROLL_DOWN:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > AnalogScrollSpeed())
+ {
+ handled = true;
+ m_analogScrollCount -= AnalogScrollSpeed();
+ if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow < (int)m_items.size())// && GetCursor() >= m_itemsPerPage * m_itemsPerRow / 2)
+ {
+ Scroll(1);
+ }
+ else if (GetCursor() < m_itemsPerPage * m_itemsPerRow - 1 && GetOffset() * m_itemsPerRow + GetCursor() < (int)m_items.size() - 1)
+ {
+ SetCursor(GetCursor() + 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ }
+ return CGUIBaseContainer::OnAction(action);
+}
+
+bool CGUIPanelContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ SetCursor(0);
+ // fall through to base class
+ }
+ }
+ return CGUIBaseContainer::OnMessage(message);
+}
+
+void CGUIPanelContainer::OnLeft()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_LEFT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveLeft(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
+ return;
+ CGUIControl::OnLeft();
+}
+
+void CGUIPanelContainer::OnRight()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveRight(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
+ return;
+ return CGUIControl::OnRight();
+}
+
+void CGUIPanelContainer::OnUp()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_UP);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveUp(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveLeft(wrapAround))
+ return;
+ CGUIControl::OnUp();
+}
+
+void CGUIPanelContainer::OnDown()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_DOWN);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveDown(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveRight(wrapAround))
+ return;
+ return CGUIControl::OnDown();
+}
+
+bool CGUIPanelContainer::MoveDown(bool wrapAround)
+{
+ if (GetCursor() + m_itemsPerRow < m_itemsPerPage * m_itemsPerRow && (GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
+ { // move to last item if necessary
+ if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - 1 - GetOffset()*m_itemsPerRow);
+ else
+ SetCursor(GetCursor() + m_itemsPerRow);
+ }
+ else if ((GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
+ { // we scroll to the next row, and move to last item if necessary
+ if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - 1 - (GetOffset() + 1)*m_itemsPerRow);
+ ScrollToOffset(GetOffset() + 1);
+ }
+ else if (wrapAround)
+ { // move first item in list
+ SetCursor(GetCursor() % m_itemsPerRow);
+ ScrollToOffset(0);
+ SetContainerMoving(1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveUp(bool wrapAround)
+{
+ if (GetCursor() >= m_itemsPerRow)
+ SetCursor(GetCursor() - m_itemsPerRow);
+ else if (GetOffset() > 0)
+ ScrollToOffset(GetOffset() - 1);
+ else if (wrapAround)
+ { // move last item in list in this column
+ SetCursor((GetCursor() % m_itemsPerRow) + (m_itemsPerPage - 1) * m_itemsPerRow);
+ int offset = std::max((int)GetRows() - m_itemsPerPage, 0);
+ // should check here whether cursor is actually allowed here, and reduce accordingly
+ if (offset * m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - offset * m_itemsPerRow - 1);
+ ScrollToOffset(offset);
+ SetContainerMoving(-1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveLeft(bool wrapAround)
+{
+ int col = GetCursor() % m_itemsPerRow;
+ if (col > 0)
+ SetCursor(GetCursor() - 1);
+ else if (wrapAround)
+ { // wrap around
+ SetCursor(GetCursor() + m_itemsPerRow - 1);
+ if (GetOffset() * m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - GetOffset() * m_itemsPerRow - 1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveRight(bool wrapAround)
+{
+ int col = GetCursor() % m_itemsPerRow;
+ if (col + 1 < m_itemsPerRow && GetOffset() * m_itemsPerRow + GetCursor() + 1 < (int)m_items.size())
+ SetCursor(GetCursor() + 1);
+ else if (wrapAround) // move first item in row
+ SetCursor(GetCursor() - col);
+ else
+ return false;
+ return true;
+}
+
+// scrolls the said amount
+void CGUIPanelContainer::Scroll(int amount)
+{
+ // increase or decrease the offset
+ int offset = GetOffset() + amount;
+ if (offset > ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow)
+ {
+ offset = ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow;
+ }
+ if (offset < 0) offset = 0;
+ ScrollToOffset(offset);
+}
+
+void CGUIPanelContainer::ValidateOffset()
+{
+ if (!m_layout) return;
+ // first thing is we check the range of our offset
+ // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
+ if (GetOffset() > (int)GetRows() - m_itemsPerPage || (!m_scroller.IsScrolling() && m_scroller.GetValue() > ((int)GetRows() - m_itemsPerPage) * m_layout->Size(m_orientation)))
+ {
+ SetOffset(std::max(0, (int)GetRows() - m_itemsPerPage));
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+ }
+ if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
+ {
+ SetOffset(0);
+ m_scroller.SetValue(0);
+ }
+}
+
+void CGUIPanelContainer::SetCursor(int cursor)
+{
+ if (cursor > m_itemsPerPage * m_itemsPerRow - 1)
+ cursor = m_itemsPerPage * m_itemsPerRow - 1;
+ if (cursor < 0) cursor = 0;
+ if (!m_wasReset)
+ SetContainerMoving(cursor - GetCursor());
+ CGUIBaseContainer::SetCursor(cursor);
+}
+
+void CGUIPanelContainer::CalculateLayout()
+{
+ GetCurrentLayouts();
+
+ if (!m_layout || !m_focusedLayout) return;
+ // calculate the number of items to display
+ if (m_orientation == HORIZONTAL)
+ {
+ m_itemsPerRow = (int)(m_height / m_layout->Size(VERTICAL));
+ m_itemsPerPage = (int)(m_width / m_layout->Size(HORIZONTAL));
+ }
+ else
+ {
+ m_itemsPerRow = (int)(m_width / m_layout->Size(HORIZONTAL));
+ m_itemsPerPage = (int)(m_height / m_layout->Size(VERTICAL));
+ }
+ if (m_itemsPerRow < 1) m_itemsPerRow = 1;
+ if (m_itemsPerPage < 1) m_itemsPerPage = 1;
+
+ // ensure that the scroll offset is a multiple of our size
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+}
+
+unsigned int CGUIPanelContainer::GetRows() const
+{
+ assert(m_itemsPerRow > 0);
+ return (m_items.size() + m_itemsPerRow - 1) / m_itemsPerRow;
+}
+
+float CGUIPanelContainer::AnalogScrollSpeed() const
+{
+ return 10.0f / m_itemsPerPage;
+}
+
+int CGUIPanelContainer::CorrectOffset(int offset, int cursor) const
+{
+ return offset * m_itemsPerRow + cursor;
+}
+
+int CGUIPanelContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
+{
+ if (!m_layout)
+ return -1;
+
+ float sizeX = m_orientation == VERTICAL ? m_layout->Size(HORIZONTAL) : m_layout->Size(VERTICAL);
+ float sizeY = m_orientation == VERTICAL ? m_layout->Size(VERTICAL) : m_layout->Size(HORIZONTAL);
+
+ float posY = m_orientation == VERTICAL ? point.y : point.x;
+ for (int y = 0; y < m_itemsPerPage + 1; y++) // +1 to ensure if we have a half item we can select it
+ {
+ float posX = m_orientation == VERTICAL ? point.x : point.y;
+ for (int x = 0; x < m_itemsPerRow; x++)
+ {
+ int item = x + y * m_itemsPerRow;
+ if (posX < sizeX && posY < sizeY && item + GetOffset() < (int)m_items.size())
+ { // found
+ return item;
+ }
+ posX -= sizeX;
+ }
+ posY -= sizeY;
+ }
+ return -1;
+}
+
+bool CGUIPanelContainer::SelectItemFromPoint(const CPoint &point)
+{
+ int cursor = GetCursorFromPoint(point);
+ if (cursor < 0)
+ return false;
+ SetCursor(cursor);
+ return true;
+}
+
+int CGUIPanelContainer::GetCurrentRow() const
+{
+ return m_itemsPerRow > 0 ? GetCursor() / m_itemsPerRow : 0;
+}
+
+int CGUIPanelContainer::GetCurrentColumn() const
+{
+ return GetCursor() % m_itemsPerRow;
+}
+
+bool CGUIPanelContainer::GetCondition(int condition, int data) const
+{
+ int row = GetCurrentRow();
+ int col = GetCurrentColumn();
+
+ if (m_orientation == HORIZONTAL)
+ std::swap(row, col);
+
+ switch (condition)
+ {
+ case CONTAINER_ROW:
+ return (row == data);
+ case CONTAINER_COLUMN:
+ return (col == data);
+ default:
+ return CGUIBaseContainer::GetCondition(condition, data);
+ }
+}
+
+std::string CGUIPanelContainer::GetLabel(int info) const
+{
+ int row = GetCurrentRow();
+ int col = GetCurrentColumn();
+
+ if (m_orientation == HORIZONTAL)
+ std::swap(row, col);
+
+ switch (info)
+ {
+ case CONTAINER_ROW:
+ return std::to_string(row);
+ case CONTAINER_COLUMN:
+ return std::to_string(col);
+ default:
+ return CGUIBaseContainer::GetLabel(info);
+ }
+ return StringUtils::Empty;
+}
+
+void CGUIPanelContainer::SelectItem(int item)
+{
+ // Check that our offset is valid
+ ValidateOffset();
+ // only select an item if it's in a valid range
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ // Select the item requested
+ if (item >= GetOffset() * m_itemsPerRow && item < (GetOffset() + m_itemsPerPage) * m_itemsPerRow)
+ { // the item is on the current page, so don't change it.
+ SetCursor(item - GetOffset() * m_itemsPerRow);
+ }
+ else if (item < GetOffset() * m_itemsPerRow)
+ { // item is on a previous page - make it the first item on the page
+ SetCursor(item % m_itemsPerRow);
+ ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
+ }
+ else // (item >= GetOffset()+m_itemsPerPage)
+ { // item is on a later page - make it the last row on the page
+ SetCursor(item % m_itemsPerRow + m_itemsPerRow * (m_itemsPerPage - 1));
+ ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
+ }
+ }
+}
+
+bool CGUIPanelContainer::HasPreviousPage() const
+{
+ return (GetOffset() > 0);
+}
+
+bool CGUIPanelContainer::HasNextPage() const
+{
+ return (GetOffset() != (int)GetRows() - m_itemsPerPage && (int)GetRows() > m_itemsPerPage);
+}
+
diff --git a/xbmc/guilib/GUIPanelContainer.dox b/xbmc/guilib/GUIPanelContainer.dox
new file mode 100644
index 0000000..1b36fd5
--- /dev/null
+++ b/xbmc/guilib/GUIPanelContainer.dox
@@ -0,0 +1,126 @@
+/*!
+
+\page Panel_Container Panel Container
+\brief **Used for a scrolling panel of items. Replaces the thumbnail panel.**
+
+\tableofcontents
+
+The panel container is one of several containers used to display items from file
+lists in various ways. The panel container is very flexible - it's essentially a
+multi-column list. The layout of the items is very flexible and is up to the
+skinner.
+
+
+--------------------------------------------------------------------------------
+\section Panel_Container_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="panel" id="52">
+ <posx>190</posx>
+ <posy>100</posy>
+ <width>485</width>
+ <height>425</height>
+ <onleft>9000</onleft>
+ <onright>60</onright>
+ <onup>52</onup>
+ <ondown>52</ondown>
+ <scrolltime tween="sine" easing="out">200</scrolltime>
+ <autoscroll>true</autoscroll>
+ <viewtype label="536">icon</viewtype>
+ <pagecontrol>60</pagecontrol>
+ <include>contentpanelslide</include>
+ <itemlayout height="141" width="120">
+ <control type="image">
+ <posx>10</posx>
+ <posy>10</posy>
+ <width>100</width>
+ <height>100</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="image">
+ <posx>80</posx>
+ <posy>75</posy>
+ <width>32</width>
+ <height>32</height>
+ <info>ListItem.Overlay</info>
+ </control>
+ <control type="label">
+ <posx>60</posx>
+ <posy>115</posy>
+ <width>110</width>
+ <height>22</height>
+ <font>font13</font>
+ <selectedcolor>green</selectedcolor>
+ <align>center</align>
+ <info>ListItem.Label</info>
+ </control>
+ </itemlayout>
+ <focusedlayout height="141" width="120">
+ <control type="image">
+ <width>110</width>
+ <height>110</height>
+ <posx>5</posx>
+ <posy>5</posy>
+ <texture>folder-focus.png</texture>
+ <animation effect="zoom" end="0,0,120,120" time="100">focus</animation>
+ </control>
+ <control type="image">
+ <posx>10</posx>
+ <posy>10</posy>
+ <width>100</width>
+ <height>100</height>
+ <info>ListItem.Icon</info>
+ <animation effect="zoom" end="5,5,110,110" time="100">focus</animation>
+ </control>
+ <control type="image">
+ <posx>80</posx>
+ <posy>75</posy>
+ <width>32</width>
+ <height>32</height>
+ <info>ListItem.Overlay</info>
+ <animation effect="slide" end="5,5" time="100">focus</animation>
+ </control>
+ <control type="label">
+ <posx>60</posx>
+ <posy>120</posy>
+ <width>110</width>
+ <height>22</height>
+ <font>font13</font>
+ <selectedcolor>green</selectedcolor>
+ <align>center</align>
+ <info>ListItem.Label</info>
+ </control>
+ </focusedlayout>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Panel_Container_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| viewtype | The type of view. Choices are list, icon, wide, wrap, biglist, bigicon, bigwide, bigwrap, info and biginfo. The label attribute indicates the label that will be used in the "View As" control within the GUI. It is localizable via strings.po. viewtype has no effect on the view itself. It is used by kodi when switching skin to automatically select a view with a similar layout. Skinners should try to set viewtype to describe the layout as best as possible.
+| orientation | The orientation of the panel. Defaults to vertical.
+| pagecontrol | Used to set the <b>`<id>`</b> of the page control used to control this panel.
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is 200ms. The panel will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling. The scroll movement can be further adjusted by selecting one of the available [tween](http://kodi.wiki/view/Tweeners) methods.
+| itemlayout | Specifies the layout of items in the list. Requires both width and height attributes set. The <b>`<itemlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| focusedlayout | Specifies the layout of items in the list that have focus. Requires both width and height attributes set. The <b>`<focusedlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| content | Used to set the item content that this panel will contain. Allows the skinner to setup a panel anywhere they want with a static set of content, as a useful alternative to the grouplist control. [See here for more information](http://kodi.wiki/view/Static_List_Content)
+| preloaditems | Used in association with the background image loader. [See here for more information](http://kodi.wiki/view/Background_Image_Loader)
+| autoscroll | Used to make the container scroll automatically
+
+
+--------------------------------------------------------------------------------
+\section Panel_Container_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIPanelContainer.h b/xbmc/guilib/GUIPanelContainer.h
new file mode 100644
index 0000000..dff52af
--- /dev/null
+++ b/xbmc/guilib/GUIPanelContainer.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIPanelContainer.h
+\brief
+*/
+
+#include "GUIBaseContainer.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIPanelContainer : public CGUIBaseContainer
+{
+public:
+ CGUIPanelContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems);
+ ~CGUIPanelContainer(void) override;
+ CGUIPanelContainer* Clone() const override { return new CGUIPanelContainer(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void OnLeft() override;
+ void OnRight() override;
+ void OnUp() override;
+ void OnDown() override;
+ bool GetCondition(int condition, int data) const override;
+ std::string GetLabel(int info) const override;
+protected:
+ bool MoveUp(bool wrapAround) override;
+ bool MoveDown(bool wrapAround) override;
+ virtual bool MoveLeft(bool wrapAround);
+ virtual bool MoveRight(bool wrapAround);
+ void Scroll(int amount) override;
+ float AnalogScrollSpeed() const;
+ void ValidateOffset() override;
+ void CalculateLayout() override;
+ unsigned int GetRows() const override;
+ int CorrectOffset(int offset, int cursor) const override;
+ bool SelectItemFromPoint(const CPoint &point) override;
+ int GetCursorFromPoint(const CPoint &point, CPoint *itemPoint = NULL) const override;
+ void SetCursor(int cursor) override;
+ void SelectItem(int item) override;
+ bool HasPreviousPage() const override;
+ bool HasNextPage() const override;
+
+ int GetCurrentRow() const;
+ int GetCurrentColumn() const;
+
+ int m_itemsPerRow;
+};
+
diff --git a/xbmc/guilib/GUIProgressControl.cpp b/xbmc/guilib/GUIProgressControl.cpp
new file mode 100644
index 0000000..1167d64
--- /dev/null
+++ b/xbmc/guilib/GUIProgressControl.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIProgressControl.h"
+
+#include "GUIInfoManager.h"
+#include "GUIListItem.h"
+#include "GUIMessage.h"
+#include "utils/StringUtils.h"
+
+CGUIProgressControl::CGUIProgressControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& backGroundTexture,
+ const CTextureInfo& leftTexture,
+ const CTextureInfo& midTexture,
+ const CTextureInfo& rightTexture,
+ const CTextureInfo& overlayTexture,
+ bool reveal)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_guiBackground(CGUITexture::CreateTexture(posX, posY, width, height, backGroundTexture)),
+ m_guiLeft(CGUITexture::CreateTexture(posX, posY, width, height, leftTexture)),
+ m_guiMid(CGUITexture::CreateTexture(posX, posY, width, height, midTexture)),
+ m_guiRight(CGUITexture::CreateTexture(posX, posY, width, height, rightTexture)),
+ m_guiOverlay(CGUITexture::CreateTexture(posX, posY, width, height, overlayTexture))
+{
+ m_fPercent = 0;
+ m_iInfoCode = 0;
+ ControlType = GUICONTROL_PROGRESS;
+ m_bReveal = reveal;
+ m_bChanged = false;
+}
+
+CGUIProgressControl::CGUIProgressControl(const CGUIProgressControl& control)
+ : CGUIControl(control),
+ m_guiBackground(control.m_guiBackground->Clone()),
+ m_guiLeft(control.m_guiLeft->Clone()),
+ m_guiMid(control.m_guiMid->Clone()),
+ m_guiRight(control.m_guiRight->Clone()),
+ m_guiOverlay(control.m_guiOverlay->Clone()),
+ m_guiMidClipRect(control.m_guiMidClipRect),
+ m_iInfoCode(control.m_iInfoCode),
+ m_iInfoCode2(control.m_iInfoCode2),
+ m_fPercent(control.m_fPercent),
+ m_fPercent2(control.m_fPercent2),
+ m_bReveal(control.m_bReveal),
+ m_bChanged(control.m_bChanged)
+{
+}
+
+void CGUIProgressControl::SetPosition(float posX, float posY)
+{
+ // everything is positioned based on the background image position
+ CGUIControl::SetPosition(posX, posY);
+ m_guiBackground->SetPosition(posX, posY);
+}
+
+void CGUIProgressControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool changed = false;
+
+ if (!IsDisabled())
+ changed |= UpdateLayout();
+ changed |= m_guiBackground->Process(currentTime);
+ changed |= m_guiMid->Process(currentTime);
+ changed |= m_guiLeft->Process(currentTime);
+ changed |= m_guiRight->Process(currentTime);
+ changed |= m_guiOverlay->Process(currentTime);
+
+ if (changed)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIProgressControl::Render()
+{
+ if (!IsDisabled())
+ {
+ m_guiBackground->Render();
+
+ if (m_guiLeft->GetFileName().empty() && m_guiRight->GetFileName().empty())
+ {
+ if (m_bReveal && !m_guiMidClipRect.IsEmpty())
+ {
+ bool restore = CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_guiMidClipRect.x1, m_guiMidClipRect.y1, m_guiMidClipRect.Width(), m_guiMidClipRect.Height());
+ m_guiMid->Render();
+ if (restore)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ else if (!m_bReveal && m_guiMid->GetWidth() > 0)
+ m_guiMid->Render();
+ }
+ else
+ {
+ m_guiLeft->Render();
+
+ if (m_bReveal && !m_guiMidClipRect.IsEmpty())
+ {
+ bool restore = CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_guiMidClipRect.x1, m_guiMidClipRect.y1, m_guiMidClipRect.Width(), m_guiMidClipRect.Height());
+ m_guiMid->Render();
+ if (restore)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ else if (!m_bReveal && m_guiMid->GetWidth() > 0)
+ m_guiMid->Render();
+
+ m_guiRight->Render();
+ }
+
+ m_guiOverlay->Render();
+ }
+
+ CGUIControl::Render();
+}
+
+
+bool CGUIProgressControl::CanFocus() const
+{
+ return false;
+}
+
+
+bool CGUIProgressControl::OnMessage(CGUIMessage& message)
+{
+ if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
+ {
+ SetPercentage((float)message.GetParam1());
+ return true;
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIProgressControl::SetPercentage(float fPercent)
+{
+ fPercent = std::max(0.0f, std::min(fPercent, 100.0f));
+ if (m_fPercent != fPercent)
+ SetInvalid();
+ m_fPercent = fPercent;
+}
+
+float CGUIProgressControl::GetPercentage() const
+{
+ return m_fPercent;
+}
+void CGUIProgressControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_guiBackground->FreeResources(immediately);
+ m_guiMid->FreeResources(immediately);
+ m_guiRight->FreeResources(immediately);
+ m_guiLeft->FreeResources(immediately);
+ m_guiOverlay->FreeResources(immediately);
+}
+
+void CGUIProgressControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_guiBackground->DynamicResourceAlloc(bOnOff);
+ m_guiMid->DynamicResourceAlloc(bOnOff);
+ m_guiRight->DynamicResourceAlloc(bOnOff);
+ m_guiLeft->DynamicResourceAlloc(bOnOff);
+ m_guiOverlay->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIProgressControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_guiBackground->AllocResources();
+ m_guiMid->AllocResources();
+ m_guiRight->AllocResources();
+ m_guiLeft->AllocResources();
+ m_guiOverlay->AllocResources();
+
+ m_guiBackground->SetHeight(25);
+ m_guiRight->SetHeight(20);
+ m_guiLeft->SetHeight(20);
+ m_guiMid->SetHeight(20);
+ m_guiOverlay->SetHeight(20);
+}
+
+void CGUIProgressControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_guiBackground->SetInvalid();
+ m_guiMid->SetInvalid();
+ m_guiRight->SetInvalid();
+ m_guiLeft->SetInvalid();
+ m_guiOverlay->SetInvalid();
+}
+
+void CGUIProgressControl::SetInfo(int iInfo, int iInfo2)
+{
+ m_iInfoCode = iInfo;
+ m_iInfoCode2 = iInfo2;
+}
+
+bool CGUIProgressControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_guiBackground->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiRight->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiLeft->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiMid->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiOverlay->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+std::string CGUIProgressControl::GetDescription() const
+{
+ return StringUtils::Format("{:1.0f}", m_fPercent);
+}
+
+bool CGUIProgressControl::UpdateLayout(void)
+{
+ bool bChanged(false);
+
+ if (m_width == 0)
+ m_width = m_guiBackground->GetTextureWidth();
+ if (m_height == 0)
+ m_height = m_guiBackground->GetTextureHeight();
+
+ bChanged |= m_guiBackground->SetHeight(m_height);
+ bChanged |= m_guiBackground->SetWidth(m_width);
+
+ float fScaleX, fScaleY;
+ fScaleY =
+ m_guiBackground->GetTextureHeight() ? m_height / m_guiBackground->GetTextureHeight() : 1.0f;
+ fScaleX =
+ m_guiBackground->GetTextureWidth() ? m_width / m_guiBackground->GetTextureWidth() : 1.0f;
+
+ float posX = m_guiBackground->GetXPosition();
+ float posY = m_guiBackground->GetYPosition();
+
+ if (m_guiLeft->GetFileName().empty() && m_guiRight->GetFileName().empty())
+ { // rendering without left and right image - fill the mid image completely
+ float width = (m_fPercent - m_fPercent2) * m_width * 0.01f;
+ float offsetX = m_fPercent2 * m_width * 0.01f;;
+ float offsetY =
+ fabs(fScaleY * 0.5f * (m_guiMid->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ bChanged |= m_guiMid->SetPosition(posX + (offsetX > 0 ? offsetX : 0),
+ posY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiMid->SetHeight(fScaleY * m_guiMid->GetTextureHeight());
+ if (m_bReveal)
+ {
+ bChanged |= m_guiMid->SetWidth(m_width);
+ float x = posX + offsetX, y = posY + offsetY, w = width,
+ h = fScaleY * m_guiMid->GetTextureHeight();
+ CRect rect(x, y, x + w, y + h);
+ if (rect != m_guiMidClipRect)
+ {
+ m_guiMidClipRect = rect;
+ bChanged = true;
+ }
+ }
+ else
+ {
+ bChanged |= m_guiMid->SetWidth(width);
+ m_guiMidClipRect = CRect();
+ }
+ }
+ else
+ {
+ float fWidth = m_fPercent - m_fPercent2;
+ float fFullWidth = m_guiBackground->GetTextureWidth() - m_guiLeft->GetTextureWidth() -
+ m_guiRight->GetTextureWidth();
+ fWidth /= 100.0f;
+ fWidth *= fFullWidth;
+
+ float offsetY = fabs(fScaleY * 0.5f *
+ (m_guiLeft->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ bChanged |= m_guiLeft->SetPosition(posX, posY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiLeft->SetHeight(fScaleY * m_guiLeft->GetTextureHeight());
+ bChanged |= m_guiLeft->SetWidth(fScaleX * m_guiLeft->GetTextureWidth());
+
+ posX += fScaleX * m_guiLeft->GetTextureWidth();
+ float offsetX = m_fPercent2 * fWidth * 0.01f;;
+ offsetY =
+ fabs(fScaleY * 0.5f * (m_guiMid->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ bChanged |= m_guiMid->SetPosition(posX + offsetX, posY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiMid->SetHeight(fScaleY * m_guiMid->GetTextureHeight());
+ if (m_bReveal)
+ {
+ bChanged |= m_guiMid->SetWidth(fScaleX * fFullWidth);
+ float x = posX + offsetX, y = posY + offsetY, w = fScaleX * fWidth,
+ h = fScaleY * m_guiMid->GetTextureHeight();
+ CRect rect(x, y, x + w, y + h);
+ if (rect != m_guiMidClipRect)
+ {
+ m_guiMidClipRect = rect;
+ bChanged = true;
+ }
+ }
+ else
+ {
+ bChanged |= m_guiMid->SetWidth(fScaleX * fWidth);
+ m_guiMidClipRect = CRect();
+ }
+
+ posX += fWidth * fScaleX;
+
+ offsetY = fabs(fScaleY * 0.5f *
+ (m_guiRight->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ bChanged |= m_guiRight->SetPosition(posX, posY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiRight->SetHeight(fScaleY * m_guiRight->GetTextureHeight());
+ bChanged |= m_guiRight->SetWidth(fScaleX * m_guiRight->GetTextureWidth());
+ }
+ float offset = fabs(fScaleY * 0.5f *
+ (m_guiOverlay->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ if (offset > 0) // Center texture to the background if necessary
+ bChanged |= m_guiOverlay->SetPosition(m_guiBackground->GetXPosition(),
+ m_guiBackground->GetYPosition() + offset);
+ else
+ bChanged |=
+ m_guiOverlay->SetPosition(m_guiBackground->GetXPosition(), m_guiBackground->GetYPosition());
+ bChanged |= m_guiOverlay->SetHeight(fScaleY * m_guiOverlay->GetTextureHeight());
+ bChanged |= m_guiOverlay->SetWidth(fScaleX * m_guiOverlay->GetTextureWidth());
+
+ return bChanged;
+}
+
+void CGUIProgressControl::UpdateInfo(const CGUIListItem *item)
+{
+ if (!IsDisabled())
+ {
+ if (m_iInfoCode)
+ {
+ int value;
+ if (CServiceBroker::GetGUI()->GetInfoManager().GetInt(value, m_iInfoCode, m_parentID, item))
+ m_fPercent = std::max(0.0f, std::min(static_cast<float>(value), 100.0f));
+ }
+ if (m_iInfoCode2)
+ {
+ int value;
+ if (CServiceBroker::GetGUI()->GetInfoManager().GetInt(value, m_iInfoCode2, m_parentID, item))
+ m_fPercent2 = std::max(0.0f, std::min(static_cast<float>(value), 100.0f));
+ }
+ }
+}
diff --git a/xbmc/guilib/GUIProgressControl.dox b/xbmc/guilib/GUIProgressControl.dox
new file mode 100644
index 0000000..c3b9ac4
--- /dev/null
+++ b/xbmc/guilib/GUIProgressControl.dox
@@ -0,0 +1,62 @@
+/*!
+
+\page Progress_Control Progress Control
+\brief **Used to show the progress of a particular operation.**
+
+\tableofcontents
+
+The progress control is used to show the progress of an item that may take a
+long time, or to show how far through a movie you are. You can choose the
+position, size, and look of the progress control.
+
+
+--------------------------------------------------------------------------------
+\section Progress_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="progress" id="12">
+ <description>My first progress control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>30</height>
+ <visible>true</visible>
+ <reveal>false</reveal>
+ <texturebg>mybackgroundtexture.png</texturebg>
+ <lefttexture>mydowntexture.png</lefttexture>
+ <midtexture>mymidtexture.png</midtexture>
+ <righttexture>myrighttexture.png</righttexture>
+ <overlaytexture>myoverlaytexture.png</overlaytexture>
+ <info></info>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Progress_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|----------------:|:--------------------------------------------------------------|
+| reveal | If set to true the midtexture will reveal itself instead of stretching to fill the gap (works best when its the same size as **texturebg**)
+| texturebg | Specifies the image file which should be displayed in the background of the progress control. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| lefttexture | Specifies the image file which should be displayed for the left side of the progress bar. This is rendered on the left side of the bar.
+| midtexture | Specifies the image file which should be displayed for the middle portion of the progress bar. This is the `fill` texture used to fill up the bar. It's positioned on the right of the <b>`<lefttexture>`</b> texture, and fills the gap between the <b>`<lefttexture>`</b> and <b>`<righttexture>`</b> textures, depending on how far progressed the item is.
+| righttexture | Specifies the image file which should be displayed for the right side of the progress bar. This is rendered on the right side of the bar.
+| overlaytexture | Specifies the image file which should be displayed over the top of all other images in the progress bar. It is centered vertically and horizontally within the space taken up by the background image.
+| info | Specifies the information that the progress bar is reporting on. [See here for more information](http://kodi.wiki/view/InfoLabels).
+
+
+--------------------------------------------------------------------------------
+\section Progress_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+
+*/
diff --git a/xbmc/guilib/GUIProgressControl.h b/xbmc/guilib/GUIProgressControl.h
new file mode 100644
index 0000000..9509e1c
--- /dev/null
+++ b/xbmc/guilib/GUIProgressControl.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIProgressControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIProgressControl :
+ public CGUIControl
+{
+public:
+ CGUIProgressControl(int parentID, int controlID, float posX, float posY,
+ float width, float height, const CTextureInfo& backGroundTexture,
+ const CTextureInfo& leftTexture, const CTextureInfo& midTexture,
+ const CTextureInfo& rightTexture, const CTextureInfo& overlayTexture,
+ bool reveal=false);
+ ~CGUIProgressControl() override = default;
+ CGUIProgressControl* Clone() const override { return new CGUIProgressControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool CanFocus() const override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ bool OnMessage(CGUIMessage& message) override;
+ void SetPosition(float posX, float posY) override;
+ void SetPercentage(float fPercent);
+ void SetInfo(int iInfo, int iInfo2 = 0);
+ int GetInfo() const { return m_iInfoCode; }
+
+ float GetPercentage() const;
+ std::string GetDescription() const override;
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+ bool UpdateLayout(void);
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ std::unique_ptr<CGUITexture> m_guiBackground;
+ std::unique_ptr<CGUITexture> m_guiLeft;
+ std::unique_ptr<CGUITexture> m_guiMid;
+ std::unique_ptr<CGUITexture> m_guiRight;
+ std::unique_ptr<CGUITexture> m_guiOverlay;
+ CRect m_guiMidClipRect;
+
+ int m_iInfoCode;
+ int m_iInfoCode2 = 0;
+ float m_fPercent;
+ float m_fPercent2 = 0.0f;
+ bool m_bReveal;
+ bool m_bChanged;
+
+private:
+ CGUIProgressControl(const CGUIProgressControl& control);
+};
+
diff --git a/xbmc/guilib/GUIRSSControl.cpp b/xbmc/guilib/GUIRSSControl.cpp
new file mode 100644
index 0000000..f35441e
--- /dev/null
+++ b/xbmc/guilib/GUIRSSControl.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRSSControl.h"
+
+#include "ServiceBroker.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/ColorUtils.h"
+#include "utils/RssManager.h"
+#include "utils/RssReader.h"
+#include "utils/StringUtils.h"
+
+#include <mutex>
+
+using namespace KODI::GUILIB;
+
+CGUIRSSControl::CGUIRSSControl(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const GUIINFO::CGUIInfoColor &channelColor,
+ const GUIINFO::CGUIInfoColor &headlineColor, std::string& strRSSTags)
+: CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_strRSSTags(strRSSTags),
+ m_label(labelInfo),
+ m_channelColor(channelColor),
+ m_headlineColor(headlineColor),
+ m_scrollInfo(0,0,labelInfo.scrollSpeed,""),
+ m_dirty(true)
+{
+ m_pReader = NULL;
+ m_rtl = false;
+ m_stopped = false;
+ m_urlset = 1;
+ ControlType = GUICONTROL_RSS;
+}
+
+CGUIRSSControl::CGUIRSSControl(const CGUIRSSControl &from)
+ : CGUIControl(from),
+ m_feed(),
+ m_strRSSTags(from.m_strRSSTags),
+ m_label(from.m_label),
+ m_channelColor(from.m_channelColor),
+ m_headlineColor(from.m_headlineColor),
+ m_vecUrls(),
+ m_vecIntervals(),
+ m_scrollInfo(from.m_scrollInfo),
+ m_dirty(true)
+{
+ m_pReader = NULL;
+ m_rtl = from.m_rtl;
+ m_stopped = from.m_stopped;
+ m_urlset = 1;
+ ControlType = GUICONTROL_RSS;
+}
+
+CGUIRSSControl::~CGUIRSSControl(void)
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ if (m_pReader)
+ m_pReader->SetObserver(NULL);
+ m_pReader = NULL;
+}
+
+void CGUIRSSControl::OnFocus()
+{
+ m_stopped = true;
+}
+
+void CGUIRSSControl::OnUnFocus()
+{
+ m_stopped = false;
+}
+
+void CGUIRSSControl::SetUrlSet(const int urlset)
+{
+ m_urlset = urlset;
+}
+
+bool CGUIRSSControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+ changed |= m_headlineColor.Update();
+ changed |= m_channelColor.Update();
+ return changed;
+}
+
+void CGUIRSSControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool dirty = false;
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_LOOKANDFEEL_ENABLERSSFEEDS) && CRssManager::GetInstance().IsActive())
+ {
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ // Create RSS background/worker thread if needed
+ if (m_pReader == NULL)
+ {
+
+ RssUrls::const_iterator iter = CRssManager::GetInstance().GetUrls().find(m_urlset);
+ if (iter != CRssManager::GetInstance().GetUrls().end())
+ {
+ m_rtl = iter->second.rtl;
+ m_vecUrls = iter->second.url;
+ m_vecIntervals = iter->second.interval;
+ m_scrollInfo.SetSpeed(m_label.scrollSpeed * (m_rtl ? -1 : 1));
+ }
+
+ dirty = true;
+
+ if (CRssManager::GetInstance().GetReader(GetID(), GetParentID(), this, m_pReader))
+ {
+ m_scrollInfo.m_pixelPos = m_pReader->m_savedScrollPixelPos;
+ }
+ else
+ {
+ if (m_strRSSTags != "")
+ {
+ std::vector<std::string> tags = StringUtils::Split(m_strRSSTags, ",");
+ for (const std::string& i : tags)
+ m_pReader->AddTag(i);
+ }
+ // use half the width of the control as spacing between feeds, and double this between feed sets
+ float spaceWidth = (m_label.font) ? m_label.font->GetCharWidth(L' ') : 15;
+ m_pReader->Create(this, m_vecUrls, m_vecIntervals, (int)(0.5f*GetWidth() / spaceWidth) + 1, m_rtl);
+ }
+ }
+
+ if(m_dirty)
+ dirty = true;
+ m_dirty = false;
+
+ if (m_label.font)
+ {
+ if ( m_stopped )
+ m_scrollInfo.SetSpeed(0);
+ else
+ m_scrollInfo.SetSpeed(m_label.scrollSpeed * (m_rtl ? -1 : 1));
+
+ if(m_label.font->UpdateScrollInfo(m_feed, m_scrollInfo))
+ dirty = true;
+ }
+ }
+
+ if(dirty)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIRSSControl::Render()
+{
+ // only render the control if they are enabled
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_LOOKANDFEEL_ENABLERSSFEEDS) && CRssManager::GetInstance().IsActive())
+ {
+
+ if (m_label.font)
+ {
+ std::vector<UTILS::COLOR::Color> colors;
+ colors.push_back(m_label.textColor);
+ colors.push_back(m_headlineColor);
+ colors.push_back(m_channelColor);
+ m_label.font->DrawScrollingText(m_posX, m_posY, colors, m_label.shadowColor, m_feed, 0, m_width, m_scrollInfo);
+ }
+
+ if (m_pReader)
+ {
+ m_pReader->CheckForUpdates();
+ m_pReader->m_savedScrollPixelPos = m_scrollInfo.m_pixelPos;
+ }
+ }
+ CGUIControl::Render();
+}
+
+CRect CGUIRSSControl::CalcRenderRegion() const
+{
+ if (m_label.font)
+ return CRect(m_posX, m_posY, m_posX + m_width, m_posY + m_label.font->GetTextHeight(1));
+ return CGUIControl::CalcRenderRegion();
+}
+
+void CGUIRSSControl::OnFeedUpdate(const vecText &feed)
+{
+ std::unique_lock<CCriticalSection> lock(m_criticalSection);
+ m_feed = feed;
+ m_dirty = true;
+}
+
+void CGUIRSSControl::OnFeedRelease()
+{
+ m_pReader = NULL;
+}
+
+
diff --git a/xbmc/guilib/GUIRSSControl.dox b/xbmc/guilib/GUIRSSControl.dox
new file mode 100644
index 0000000..e51430c
--- /dev/null
+++ b/xbmc/guilib/GUIRSSControl.dox
@@ -0,0 +1,106 @@
+/*!
+
+\page RSS_feed_Control RSS ticker
+\brief **Used to display scrolling RSS feeds.**
+
+\tableofcontents
+
+Kodi can display an RSS feed on the home screen of the default skin/interface
+(Estuary), as well as any other skin that supports RSS feeds. By default,
+the RSS news feed is taken from [http://kodi.tv](http://kodi.tv/), but the feed
+can be changed to almost any RSS feed.
+
+@note Don't confuse the RSS ticker with RSS media source, which allows access to
+video and/or audio RSS streams.
+
+
+--------------------------------------------------------------------------------
+\section RSS_feed_Control_sect1 RSS ticker settings
+
+The RSS ticker can be toggled on or off by going to
+<b>`Settings -> Appearance -> Skin -> Show RSS news feed`</b>
+
+Below this setting one can also change the RSS news feed address.
+
+
+--------------------------------------------------------------------------------
+\section RSS_feed_Control_sect2 Technical documentation for skinners
+
+ Main page: [Skin development](http://kodi.wiki/view/Skin_development)
+
+\subsection RSS_feed_Control_sect2_1 RSS control
+The rss control is used for displaying scrolling RSS feeds from the internet
+in Kodi. You can choose the font, size, colour, location and the RSS feed to
+be displayed.
+
+__Example:__
+
+~~~~~~~~~~~~~
+<control type="rss" id="1">
+ <description>My First RSS control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>500</width>
+ <visible>true</visible>
+ <font>font14</font>
+ <textcolor>FFB2D4F5</textcolor>
+ <headlinecolor>FFFFFFFF</headlinecolor>
+ <titlecolor>FF655656</titlecolor>
+</control>
+~~~~~~~~~~~~~
+
+
+\subsection RSS_feed_Control_sect2_2 Available tags and attributes
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| id | This refers to the feedset to be displayed. This is the id reference to the <b>`<set>`</b> section in [RssFeeds.xml](http://kodi.wiki/view/RssFeeds.xml) (see below):
+| font | Specifies the font to use from the font.xml file.
+| textcolor | Specified the color the text should be. In hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text. In **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| headlinecolor | Specified the color that any highlighted text should be. In hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| titlecolor | Specified the color the titles of the feeds should be. In hex **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| scrollspeed | Scroll speed of text in pixels per second.
+
+
+\subsection RSS_feed_Control_sect2_3 RssFeeds.xml
+
+- <em>Main page: [RssFeeds.xml](http://kodi.wiki/view/RssFeeds.xml)</em>
+
+The actual content of the RSS feed is defined in the
+[RssFeeds.xml](http://kodi.wiki/view/RssFeeds.xml) file stored in the user's
+profile. Here is an example :
+
+~~~~~~~~~~~~~
+ <rssfeeds>
+ <set id="1">
+ <feed updateinterval="30">http://feeds.feedburner.com/XboxScene</feed>
+ <feed updateinterval="30">http://feeds.wired.com/wired/topheadlines</feed>
+ </set>
+ <set id="2">
+ <feed updateinterval="30">http://www.cnet.co.uk/feeds/public/rss_news_10.htm</feed>
+ </set>
+ </rssfeeds>
+~~~~~~~~~~~~~
+
+As can be seen, each feedset has an id attribute – this is what we are
+referencing in the <b>`<id>`</b> attribute of the control. There can be more
+than one <b>`<set>`</b> defined, and more than one <b>`<feed>`</b> per
+set. The <b>`<feed>`</b>'s must be escaped so that they're xml-safe (ie replace &
+with & etc.). Each feed in the set runs through in the order they are defined.
+
+
+--------------------------------------------------------------------------------
+\section RSS_feed_Control_sect3 See also
+
+- [RssFeeds.xml](http://kodi.wiki/view/RssFeeds.xml)
+
+#### Development:
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIRSSControl.h b/xbmc/guilib/GUIRSSControl.h
new file mode 100644
index 0000000..851f72f
--- /dev/null
+++ b/xbmc/guilib/GUIRSSControl.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIRSSControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "utils/IRssObserver.h"
+
+#include <vector>
+
+class CRssReader;
+
+/*!
+\ingroup controls
+\brief
+*/
+class CGUIRSSControl : public CGUIControl, public IRssObserver
+{
+public:
+ CGUIRSSControl(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, const KODI::GUILIB::GUIINFO::CGUIInfoColor &channelColor,
+ const KODI::GUILIB::GUIINFO::CGUIInfoColor &headlineColor, std::string& strRSSTags);
+ CGUIRSSControl(const CGUIRSSControl &from);
+ ~CGUIRSSControl(void) override;
+ CGUIRSSControl* Clone() const override { return new CGUIRSSControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void OnFeedUpdate(const vecText &feed) override;
+ void OnFeedRelease() override;
+ bool CanFocus() const override { return true; }
+ CRect CalcRenderRegion() const override;
+
+ void OnFocus() override;
+ void OnUnFocus() override;
+
+ void SetUrlSet(const int urlset);
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+
+ CCriticalSection m_criticalSection;
+
+ CRssReader* m_pReader;
+ vecText m_feed;
+
+ std::string m_strRSSTags;
+
+ CLabelInfo m_label;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor m_channelColor;
+ KODI::GUILIB::GUIINFO::CGUIInfoColor m_headlineColor;
+
+ std::vector<std::string> m_vecUrls;
+ std::vector<int> m_vecIntervals;
+ bool m_rtl;
+ CScrollInfo m_scrollInfo;
+ bool m_dirty;
+ bool m_stopped;
+ int m_urlset;
+};
+
diff --git a/xbmc/guilib/GUIRadioButtonControl.cpp b/xbmc/guilib/GUIRadioButtonControl.cpp
new file mode 100644
index 0000000..cc169ec
--- /dev/null
+++ b/xbmc/guilib/GUIRadioButtonControl.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRadioButtonControl.h"
+
+#include "GUIInfoManager.h"
+#include "LocalizeStrings.h"
+#include "input/Key.h"
+
+CGUIRadioButtonControl::CGUIRadioButtonControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ const CLabelInfo& labelInfo,
+ const CTextureInfo& radioOnFocus,
+ const CTextureInfo& radioOnNoFocus,
+ const CTextureInfo& radioOffFocus,
+ const CTextureInfo& radioOffNoFocus,
+ const CTextureInfo& radioOnDisabled,
+ const CTextureInfo& radioOffDisabled)
+ : CGUIButtonControl(
+ parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo),
+ m_imgRadioOnFocus(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOnFocus)),
+ m_imgRadioOnNoFocus(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOnNoFocus)),
+ m_imgRadioOffFocus(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOffFocus)),
+ m_imgRadioOffNoFocus(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOffNoFocus)),
+ m_imgRadioOnDisabled(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOnDisabled)),
+ m_imgRadioOffDisabled(CGUITexture::CreateTexture(posX, posY, 16, 16, radioOffDisabled))
+{
+ m_radioPosX = 0;
+ m_radioPosY = 0;
+ m_imgRadioOnFocus->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgRadioOnNoFocus->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgRadioOffFocus->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgRadioOffNoFocus->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgRadioOnDisabled->SetAspectRatio(CAspectRatio::AR_KEEP);
+ m_imgRadioOffDisabled->SetAspectRatio(CAspectRatio::AR_KEEP);
+ ControlType = GUICONTROL_RADIO;
+ m_useLabel2 = false;
+}
+
+CGUIRadioButtonControl::CGUIRadioButtonControl(const CGUIRadioButtonControl& control)
+ : CGUIButtonControl(control),
+ m_imgRadioOnFocus(control.m_imgRadioOnFocus->Clone()),
+ m_imgRadioOnNoFocus(control.m_imgRadioOnNoFocus->Clone()),
+ m_imgRadioOffFocus(control.m_imgRadioOffFocus->Clone()),
+ m_imgRadioOffNoFocus(control.m_imgRadioOffNoFocus->Clone()),
+ m_imgRadioOnDisabled(control.m_imgRadioOnDisabled->Clone()),
+ m_imgRadioOffDisabled(control.m_imgRadioOffDisabled->Clone()),
+ m_radioPosX(control.m_radioPosX),
+ m_radioPosY(control.m_radioPosY),
+ m_toggleSelect(control.m_toggleSelect),
+ m_useLabel2(control.m_useLabel2)
+{
+}
+
+void CGUIRadioButtonControl::Render()
+{
+ CGUIButtonControl::Render();
+
+ if ( IsSelected() && !IsDisabled() )
+ {
+ if (HasFocus())
+ m_imgRadioOnFocus->Render();
+ else
+ m_imgRadioOnNoFocus->Render();
+ }
+ else if ( !IsSelected() && !IsDisabled() )
+ {
+ if (HasFocus())
+ m_imgRadioOffFocus->Render();
+ else
+ m_imgRadioOffNoFocus->Render();
+ }
+ else if ( IsSelected() && IsDisabled() )
+ m_imgRadioOnDisabled->Render();
+ else
+ m_imgRadioOffDisabled->Render();
+}
+
+void CGUIRadioButtonControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_toggleSelect)
+ {
+ // ask our infoManager whether we are selected or not...
+ bool selected = m_toggleSelect->Get(INFO::DEFAULT_CONTEXT);
+
+ if (selected != m_bSelected)
+ {
+ MarkDirtyRegion();
+ m_bSelected = selected;
+ }
+ }
+
+ m_imgRadioOnFocus->Process(currentTime);
+ m_imgRadioOnNoFocus->Process(currentTime);
+ m_imgRadioOffFocus->Process(currentTime);
+ m_imgRadioOffNoFocus->Process(currentTime);
+ m_imgRadioOnDisabled->Process(currentTime);
+ m_imgRadioOffDisabled->Process(currentTime);
+
+ if (m_useLabel2)
+ SetLabel2(g_localizeStrings.Get(m_bSelected ? 16041 : 351));
+
+ CGUIButtonControl::Process(currentTime, dirtyregions);
+}
+
+bool CGUIRadioButtonControl::OnAction(const CAction &action)
+{
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ m_bSelected = !m_bSelected;
+ MarkDirtyRegion();
+ }
+ return CGUIButtonControl::OnAction(action);
+}
+
+bool CGUIRadioButtonControl::OnMessage(CGUIMessage& message)
+{
+ return CGUIButtonControl::OnMessage(message);
+}
+
+void CGUIRadioButtonControl::AllocResources()
+{
+ CGUIButtonControl::AllocResources();
+ m_imgRadioOnFocus->AllocResources();
+ m_imgRadioOnNoFocus->AllocResources();
+ m_imgRadioOffFocus->AllocResources();
+ m_imgRadioOffNoFocus->AllocResources();
+ m_imgRadioOnDisabled->AllocResources();
+ m_imgRadioOffDisabled->AllocResources();
+ SetPosition(m_posX, m_posY);
+}
+
+void CGUIRadioButtonControl::FreeResources(bool immediately)
+{
+ CGUIButtonControl::FreeResources(immediately);
+ m_imgRadioOnFocus->FreeResources(immediately);
+ m_imgRadioOnNoFocus->FreeResources(immediately);
+ m_imgRadioOffFocus->FreeResources(immediately);
+ m_imgRadioOffNoFocus->FreeResources(immediately);
+ m_imgRadioOnDisabled->FreeResources(immediately);
+ m_imgRadioOffDisabled->FreeResources(immediately);
+}
+
+void CGUIRadioButtonControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgRadioOnFocus->DynamicResourceAlloc(bOnOff);
+ m_imgRadioOnNoFocus->DynamicResourceAlloc(bOnOff);
+ m_imgRadioOffFocus->DynamicResourceAlloc(bOnOff);
+ m_imgRadioOffNoFocus->DynamicResourceAlloc(bOnOff);
+ m_imgRadioOnDisabled->DynamicResourceAlloc(bOnOff);
+ m_imgRadioOffDisabled->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIRadioButtonControl::SetInvalid()
+{
+ CGUIButtonControl::SetInvalid();
+ m_imgRadioOnFocus->SetInvalid();
+ m_imgRadioOnNoFocus->SetInvalid();
+ m_imgRadioOffFocus->SetInvalid();
+ m_imgRadioOffNoFocus->SetInvalid();
+ m_imgRadioOnDisabled->SetInvalid();
+ m_imgRadioOffDisabled->SetInvalid();
+}
+
+void CGUIRadioButtonControl::SetPosition(float posX, float posY)
+{
+ CGUIButtonControl::SetPosition(posX, posY);
+ float radioPosX =
+ m_radioPosX ? m_posX + m_radioPosX : (m_posX + m_width - 8) - m_imgRadioOnFocus->GetWidth();
+ float radioPosY =
+ m_radioPosY ? m_posY + m_radioPosY : m_posY + (m_height - m_imgRadioOnFocus->GetHeight()) / 2;
+ m_imgRadioOnFocus->SetPosition(radioPosX, radioPosY);
+ m_imgRadioOnNoFocus->SetPosition(radioPosX, radioPosY);
+ m_imgRadioOffFocus->SetPosition(radioPosX, radioPosY);
+ m_imgRadioOffNoFocus->SetPosition(radioPosX, radioPosY);
+ m_imgRadioOnDisabled->SetPosition(radioPosX, radioPosY);
+ m_imgRadioOffDisabled->SetPosition(radioPosX, radioPosY);
+}
+
+void CGUIRadioButtonControl::SetRadioDimensions(float posX, float posY, float width, float height)
+{
+ m_radioPosX = posX;
+ m_radioPosY = posY;
+ if (width)
+ {
+ m_imgRadioOnFocus->SetWidth(width);
+ m_imgRadioOnNoFocus->SetWidth(width);
+ m_imgRadioOffFocus->SetWidth(width);
+ m_imgRadioOffNoFocus->SetWidth(width);
+ m_imgRadioOnDisabled->SetWidth(width);
+ m_imgRadioOffDisabled->SetWidth(width);
+ }
+ if (height)
+ {
+ m_imgRadioOnFocus->SetHeight(height);
+ m_imgRadioOnNoFocus->SetHeight(height);
+ m_imgRadioOffFocus->SetHeight(height);
+ m_imgRadioOffNoFocus->SetHeight(height);
+ m_imgRadioOnDisabled->SetHeight(height);
+ m_imgRadioOffDisabled->SetHeight(height);
+ }
+
+ // use label2 to display the button value in case no
+ // dimensions were specified and there's no label2 yet.
+ if (GetLabel2().empty() && !width && !height)
+ m_useLabel2 = true;
+
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+void CGUIRadioButtonControl::SetWidth(float width)
+{
+ CGUIButtonControl::SetWidth(width);
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+void CGUIRadioButtonControl::SetHeight(float height)
+{
+ CGUIButtonControl::SetHeight(height);
+ SetPosition(GetXPosition(), GetYPosition());
+}
+
+std::string CGUIRadioButtonControl::GetDescription() const
+{
+ std::string strLabel = CGUIButtonControl::GetDescription();
+ if (m_bSelected)
+ strLabel += " (*)";
+ else
+ strLabel += " ( )";
+ return strLabel;
+}
+
+bool CGUIRadioButtonControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIButtonControl::UpdateColors(nullptr);
+ changed |= m_imgRadioOnFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgRadioOnNoFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgRadioOffFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgRadioOffNoFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgRadioOnDisabled->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgRadioOffDisabled->SetDiffuseColor(m_diffuseColor);
+ return changed;
+}
+
+void CGUIRadioButtonControl::SetToggleSelect(const std::string &toggleSelect)
+{
+ m_toggleSelect = CServiceBroker::GetGUI()->GetInfoManager().Register(toggleSelect, GetParentID());
+}
diff --git a/xbmc/guilib/GUIRadioButtonControl.dox b/xbmc/guilib/GUIRadioButtonControl.dox
new file mode 100644
index 0000000..d69eed2
--- /dev/null
+++ b/xbmc/guilib/GUIRadioButtonControl.dox
@@ -0,0 +1,102 @@
+/*!
+
+\page Radio_button_control Radio button control
+\brief **A radio button control (as used for on/off settings).**
+
+\tableofcontents
+
+The radio button control is used for creating push button on/off settings in
+Kodi. You can choose the position, size, and look of the button. When the user
+clicks on the radio button, the state will change, toggling the extra textures
+(`textureradioon` and `textureradiooff`). Used for settings controls.
+
+--------------------------------------------------------------------------------
+\section Radio_button_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="radiobutton" id="2">
+ <description>My first radiobutton control</description>
+ <type>radiobutton</type>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <texturefocus>myfocustexture.png</texturefocus>
+ <texturenofocus>mynormaltexture.png</texturenofocus>
+ <textureradioonfocus colordiffuse="FFFFAAFF">myradiobutton.png</textureradioonfocus>
+ <textureradioonnofocus colordiffuse="FFFFAAFF">myradiobutton.png</textureradioonnofocus>
+ <textureradioofffocus colordiffuse="FFFFAAFF">myradiobutton_nf.png</textureradioofffocus>
+ <textureradiooffnofocus colordiffuse="FFFFAAFF">myradiobutton_nf.png</textureradiooffnofocus>
+ <selected>Player.Paused</selected>
+ <onclick>PlayerControls(Pause)</onclick>
+ <label>29</label>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <focusedcolor>FFFFFFFF</focusedcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <align>left</align>
+ <aligny>center</aligny>
+ <textoffsetx>4</textoffsetx>
+ <textoffsety>5</textoffsety>
+ <pulseonselect>false</pulseonselect>
+ <onfocus>-</onfocus>
+ <onunfocus>-</onunfocus>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Radio_button_control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|------------------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the button has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus | Specifies the image file which should be displayed when the button does not have focus.
+| textureradioonfocus | Specifies the image file which should be displayed for the radio button portion when it's the button is on and focused. This texture is positioned on the right of the button – it's positioned 24 pixels from the right edge of the button, and 8 pixels above the center vertically.
+| textureradioonnofocus | Specifies the image file which should be displayed for the radio button portion when it's the button is on and unfocused. This texture is positioned on the right of the button – it's positioned 24 pixels from the right edge of the button, and 8 pixels above the center vertically.
+| textureradioon | A shortcut to set both of the above textures to the same image file.
+| textureradioondisabled | Specifies the image file which should be displayed for the radio button portion when the button is on and disabled.
+| textureradioofffocus | Specifies the image file which should be displayed for the radio button portion when the button is off and focused.
+| textureradiooffnofocus | Specifies the image file which should be displayed for the radio button portion when the button is off and unfocused.
+| textureradiooff | A shortcut to set both of the above textures to the same image file.
+| textureradioondisabled | Specifies the image file which should be displayed for the radio button portion when the button is off and disabled.
+| label | The label used on the button. It can be a link into strings.po, or an actual text label.
+| label2 | Optional. Will display an 'on' or 'off' label. Only available if you specify an empty radiowidth and radioheight.
+| font | Font used for the button label. From fonts.xml.
+| textcolor | Color used for displaying the button label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| focusedcolor | Color used for the button label when the button has in focus. In **AARRGGBB** hex format or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the button label if the button is disabled. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text, in **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| align | Label horizontal alignment on the button. Defaults to left, can also be center or right.
+| aligny | Label vertical alignment on the button. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| selected | The boolean condition that when met will cause the control to become selected. [see here for more information](http://kodi.wiki/view/Conditional_Visibility).
+| onclick | The function to perform when the radio button is clicked. Should be a [built in function](http://kodi.wiki/view/Built-in_functions_available_to_FTP,_Webserver,_skins,_keymap_and_to_python).
+| radioposx | X offset of the dot or radio button itself
+| radioposy | Y offset of the dot or radio button itself
+| radiowidth | Width in Pixels of the dot or radio button itself
+| radioheight | Height in Pixels of the dot or radio button itself
+| onfocus | Specifies the action to perform when the button is focused. Should be a built in function. The action is performed after any focus animations have completed. See here for more information.
+| onunfocus | Specifies the action to perform when the button loses focus. Should be a built in function.
+
+
+--------------------------------------------------------------------------------
+\section Radio_button_control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIRadioButtonControl.h b/xbmc/guilib/GUIRadioButtonControl.h
new file mode 100644
index 0000000..9befd52
--- /dev/null
+++ b/xbmc/guilib/GUIRadioButtonControl.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIRadioButtonControl.h
+\brief
+*/
+
+#include "GUIButtonControl.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIRadioButtonControl :
+ public CGUIButtonControl
+{
+public:
+ CGUIRadioButtonControl(int parentID, int controlID,
+ float posX, float posY, float width, float height,
+ const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus,
+ const CLabelInfo& labelInfo,
+ const CTextureInfo& radioOnFocus, const CTextureInfo& radioOnNoFocus,
+ const CTextureInfo& radioOffFocus, const CTextureInfo& radioOffNoFocus,
+ const CTextureInfo& radioOnDisabled, const CTextureInfo& radioOffDisabled);
+
+ ~CGUIRadioButtonControl() override = default;
+ CGUIRadioButtonControl* Clone() const override { return new CGUIRadioButtonControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override ;
+ bool OnMessage(CGUIMessage& message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ void SetWidth(float width) override;
+ void SetHeight(float height) override;
+ std::string GetDescription() const override;
+ void SetRadioDimensions(float posX, float posY, float width, float height);
+ void SetToggleSelect(const std::string &toggleSelect);
+ bool IsSelected() const { return m_bSelected; }
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ std::unique_ptr<CGUITexture> m_imgRadioOnFocus;
+ std::unique_ptr<CGUITexture> m_imgRadioOnNoFocus;
+ std::unique_ptr<CGUITexture> m_imgRadioOffFocus;
+ std::unique_ptr<CGUITexture> m_imgRadioOffNoFocus;
+ std::unique_ptr<CGUITexture> m_imgRadioOnDisabled;
+ std::unique_ptr<CGUITexture> m_imgRadioOffDisabled;
+ float m_radioPosX;
+ float m_radioPosY;
+ INFO::InfoPtr m_toggleSelect;
+ bool m_useLabel2;
+
+private:
+ CGUIRadioButtonControl(const CGUIRadioButtonControl& control);
+};
diff --git a/xbmc/guilib/GUIRangesControl.cpp b/xbmc/guilib/GUIRangesControl.cpp
new file mode 100644
index 0000000..6abfbd1
--- /dev/null
+++ b/xbmc/guilib/GUIRangesControl.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRangesControl.h"
+
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <cmath>
+
+CGUIRangesControl::CGUIRange::CGUIRange(float fPosX,
+ float fPosY,
+ float fWidth,
+ float fHeight,
+ const CTextureInfo& lowerTextureInfo,
+ const CTextureInfo& fillTextureInfo,
+ const CTextureInfo& upperTextureInfo,
+ const std::pair<float, float>& percentages)
+ : m_guiLowerTexture(CGUITexture::CreateTexture(fPosX, fPosY, fWidth, fHeight, lowerTextureInfo)),
+ m_guiFillTexture(CGUITexture::CreateTexture(fPosX, fPosY, fWidth, fHeight, fillTextureInfo)),
+ m_guiUpperTexture(CGUITexture::CreateTexture(fPosX, fPosY, fWidth, fHeight, upperTextureInfo)),
+ m_percentValues(percentages)
+{
+}
+
+CGUIRangesControl::CGUIRange::CGUIRange(const CGUIRange& range)
+ : m_guiLowerTexture(range.m_guiLowerTexture->Clone()),
+ m_guiFillTexture(range.m_guiFillTexture->Clone()),
+ m_guiUpperTexture(range.m_guiUpperTexture->Clone()),
+ m_percentValues(range.m_percentValues)
+{
+}
+
+void CGUIRangesControl::CGUIRange::AllocResources()
+{
+ m_guiFillTexture->AllocResources();
+ m_guiUpperTexture->AllocResources();
+ m_guiLowerTexture->AllocResources();
+
+ m_guiFillTexture->SetHeight(20);
+ m_guiUpperTexture->SetHeight(20);
+ m_guiLowerTexture->SetHeight(20);
+}
+
+void CGUIRangesControl::CGUIRange::FreeResources(bool bImmediately)
+{
+ m_guiFillTexture->FreeResources(bImmediately);
+ m_guiUpperTexture->FreeResources(bImmediately);
+ m_guiLowerTexture->FreeResources(bImmediately);
+}
+
+void CGUIRangesControl::CGUIRange::DynamicResourceAlloc(bool bOnOff)
+{
+ m_guiFillTexture->DynamicResourceAlloc(bOnOff);
+ m_guiUpperTexture->DynamicResourceAlloc(bOnOff);
+ m_guiLowerTexture->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIRangesControl::CGUIRange::SetInvalid()
+{
+ m_guiFillTexture->SetInvalid();
+ m_guiUpperTexture->SetInvalid();
+ m_guiLowerTexture->SetInvalid();
+}
+
+bool CGUIRangesControl::CGUIRange::SetDiffuseColor(const KODI::GUILIB::GUIINFO::CGUIInfoColor& color)
+{
+ bool bChanged = false;
+ bChanged |= m_guiFillTexture->SetDiffuseColor(color);
+ bChanged |= m_guiUpperTexture->SetDiffuseColor(color);
+ bChanged |= m_guiLowerTexture->SetDiffuseColor(color);
+ return bChanged;
+}
+
+bool CGUIRangesControl::CGUIRange::Process(unsigned int currentTime)
+{
+ bool bChanged = false;
+ bChanged |= m_guiFillTexture->Process(currentTime);
+ bChanged |= m_guiUpperTexture->Process(currentTime);
+ bChanged |= m_guiLowerTexture->Process(currentTime);
+ return bChanged;
+}
+
+void CGUIRangesControl::CGUIRange::Render()
+{
+ if (m_guiLowerTexture->GetFileName().empty() && m_guiUpperTexture->GetFileName().empty())
+ {
+ if (m_guiFillTexture->GetWidth() > 0)
+ m_guiFillTexture->Render();
+ }
+ else
+ {
+ m_guiLowerTexture->Render();
+
+ if (m_guiFillTexture->GetWidth() > 0)
+ m_guiFillTexture->Render();
+
+ m_guiUpperTexture->Render();
+ }
+}
+
+bool CGUIRangesControl::CGUIRange::UpdateLayout(float fBackgroundTextureHeight,
+ float fPosX, float fPosY, float fWidth,
+ float fScaleX, float fScaleY)
+{
+ bool bChanged = false;
+
+ if (m_guiLowerTexture->GetFileName().empty() && m_guiUpperTexture->GetFileName().empty())
+ {
+ // rendering without left and right image - fill the mid image completely
+ float width = (m_percentValues.second - m_percentValues.first) * fWidth * 0.01f;
+ float offsetX = m_percentValues.first * fWidth * 0.01f;
+ float offsetY = std::fabs(fScaleY * 0.5f *
+ (m_guiFillTexture->GetTextureHeight() - fBackgroundTextureHeight));
+ bChanged |= m_guiFillTexture->SetPosition(fPosX + (offsetX > 0 ? offsetX : 0),
+ fPosY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiFillTexture->SetHeight(fScaleY * m_guiFillTexture->GetTextureHeight());
+ bChanged |= m_guiFillTexture->SetWidth(width);
+ }
+ else
+ {
+ float offsetX =
+ m_percentValues.first * fWidth * 0.01f - m_guiLowerTexture->GetTextureWidth() * 0.5f;
+ float offsetY = std::fabs(fScaleY * 0.5f *
+ (m_guiLowerTexture->GetTextureHeight() - fBackgroundTextureHeight));
+ bChanged |= m_guiLowerTexture->SetPosition(fPosX + (offsetX > 0 ? offsetX : 0),
+ fPosY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiLowerTexture->SetHeight(fScaleY * m_guiLowerTexture->GetTextureHeight());
+ bChanged |= m_guiLowerTexture->SetWidth(m_percentValues.first == 0.0f
+ ? m_guiLowerTexture->GetTextureWidth() * 0.5f
+ : m_guiLowerTexture->GetTextureWidth());
+
+ if (m_percentValues.first != m_percentValues.second)
+ {
+ float width = (m_percentValues.second - m_percentValues.first) * fWidth * 0.01f -
+ m_guiLowerTexture->GetTextureWidth() * 0.5f -
+ m_guiUpperTexture->GetTextureWidth() * 0.5f;
+
+ offsetX += m_guiLowerTexture->GetTextureWidth();
+ offsetY = std::fabs(fScaleY * 0.5f *
+ (m_guiFillTexture->GetTextureHeight() - fBackgroundTextureHeight));
+ bChanged |=
+ m_guiFillTexture->SetPosition(fPosX + offsetX, fPosY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiFillTexture->SetHeight(fScaleY * m_guiFillTexture->GetTextureHeight());
+ bChanged |= m_guiFillTexture->SetWidth(width);
+
+ offsetX += width;
+ offsetY = std::fabs(fScaleY * 0.5f *
+ (m_guiUpperTexture->GetTextureHeight() - fBackgroundTextureHeight));
+ bChanged |=
+ m_guiUpperTexture->SetPosition(fPosX + offsetX, fPosY + (offsetY > 0 ? offsetY : 0));
+ bChanged |= m_guiUpperTexture->SetHeight(fScaleY * m_guiUpperTexture->GetTextureHeight());
+ bChanged |= m_guiUpperTexture->SetWidth(m_percentValues.first == 100.0f
+ ? m_guiUpperTexture->GetTextureWidth() * 0.5f
+ : m_guiUpperTexture->GetTextureWidth());
+ }
+ else
+ {
+ // render only lower texture if range is zero
+ bChanged |= m_guiFillTexture->SetVisible(false);
+ bChanged |= m_guiUpperTexture->SetVisible(false);
+ }
+ }
+ return bChanged;
+}
+
+CGUIRangesControl::CGUIRangesControl(int iParentID,
+ int iControlID,
+ float fPosX,
+ float fPosY,
+ float fWidth,
+ float fHeight,
+ const CTextureInfo& backGroundTextureInfo,
+ const CTextureInfo& lowerTextureInfo,
+ const CTextureInfo& fillTextureInfo,
+ const CTextureInfo& upperTextureInfo,
+ const CTextureInfo& overlayTextureInfo,
+ int iInfo)
+ : CGUIControl(iParentID, iControlID, fPosX, fPosY, fWidth, fHeight),
+ m_guiBackground(
+ CGUITexture::CreateTexture(fPosX, fPosY, fWidth, fHeight, backGroundTextureInfo)),
+ m_guiOverlay(CGUITexture::CreateTexture(fPosX, fPosY, fWidth, fHeight, overlayTextureInfo)),
+ m_guiLowerTextureInfo(lowerTextureInfo),
+ m_guiFillTextureInfo(fillTextureInfo),
+ m_guiUpperTextureInfo(upperTextureInfo),
+ m_iInfoCode(iInfo)
+{
+ ControlType = GUICONTROL_RANGES;
+}
+
+CGUIRangesControl::CGUIRangesControl(const CGUIRangesControl& control)
+ : CGUIControl(control),
+ m_guiBackground(control.m_guiBackground->Clone()),
+ m_guiOverlay(control.m_guiOverlay->Clone()),
+ m_guiLowerTextureInfo(control.m_guiLowerTextureInfo),
+ m_guiFillTextureInfo(control.m_guiFillTextureInfo),
+ m_guiUpperTextureInfo(control.m_guiUpperTextureInfo),
+ m_ranges(control.m_ranges),
+ m_iInfoCode(control.m_iInfoCode),
+ m_prevRanges(control.m_prevRanges)
+{
+}
+
+void CGUIRangesControl::SetPosition(float fPosX, float fPosY)
+{
+ // everything is positioned based on the background image position
+ CGUIControl::SetPosition(fPosX, fPosY);
+ m_guiBackground->SetPosition(fPosX, fPosY);
+}
+
+void CGUIRangesControl::Process(unsigned int iCurrentTime, CDirtyRegionList& dirtyregions)
+{
+ bool bChanged = false;
+
+ if (!IsDisabled())
+ bChanged |= UpdateLayout();
+
+ bChanged |= m_guiBackground->Process(iCurrentTime);
+ bChanged |= m_guiOverlay->Process(iCurrentTime);
+
+ for (auto& range : m_ranges)
+ bChanged |= range.Process(iCurrentTime);
+
+ if (bChanged)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(iCurrentTime, dirtyregions);
+}
+
+void CGUIRangesControl::Render()
+{
+ if (!IsDisabled())
+ {
+ m_guiBackground->Render();
+
+ for (auto& range : m_ranges)
+ range.Render();
+
+ m_guiOverlay->Render();
+ }
+
+ CGUIControl::Render();
+}
+
+
+bool CGUIRangesControl::CanFocus() const
+{
+ return false;
+}
+
+
+void CGUIRangesControl::SetRanges(const std::vector<std::pair<float, float>>& ranges)
+{
+ ClearRanges();
+ for (const auto& range : ranges)
+ m_ranges.emplace_back(CGUIRange(m_posX, m_posY, m_width, m_height,
+ m_guiLowerTextureInfo, m_guiFillTextureInfo, m_guiUpperTextureInfo, range));
+
+ for (auto& range : m_ranges)
+ range.AllocResources(); // note: we need to alloc the instance actually inserted into the vector; hence the second loop.
+}
+
+void CGUIRangesControl::ClearRanges()
+{
+ for (auto& range : m_ranges)
+ range.FreeResources(true);
+
+ m_ranges.clear();
+ m_prevRanges.clear();
+}
+
+void CGUIRangesControl::FreeResources(bool bImmediately /* = false */)
+{
+ CGUIControl::FreeResources(bImmediately);
+
+ m_guiBackground->FreeResources(bImmediately);
+ m_guiOverlay->FreeResources(bImmediately);
+
+ for (auto& range : m_ranges)
+ range.FreeResources(bImmediately);
+}
+
+void CGUIRangesControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+
+ m_guiBackground->DynamicResourceAlloc(bOnOff);
+ m_guiOverlay->DynamicResourceAlloc(bOnOff);
+
+ for (auto& range : m_ranges)
+ range.DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIRangesControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+
+ m_guiBackground->AllocResources();
+ m_guiOverlay->AllocResources();
+
+ m_guiBackground->SetHeight(25);
+ m_guiOverlay->SetHeight(20);
+
+ for (auto& range : m_ranges)
+ range.AllocResources();
+}
+
+void CGUIRangesControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+
+ m_guiBackground->SetInvalid();
+ m_guiOverlay->SetInvalid();
+
+ for (auto& range : m_ranges)
+ range.SetInvalid();
+}
+
+bool CGUIRangesControl::UpdateColors(const CGUIListItem* item)
+{
+ bool bChanged = CGUIControl::UpdateColors(nullptr);
+
+ bChanged |= m_guiBackground->SetDiffuseColor(m_diffuseColor);
+ bChanged |= m_guiOverlay->SetDiffuseColor(m_diffuseColor);
+
+ for (auto& range : m_ranges)
+ bChanged |= range.SetDiffuseColor(m_diffuseColor);
+
+ return bChanged;
+}
+
+bool CGUIRangesControl::UpdateLayout()
+{
+ bool bChanged = false;
+
+ if (m_width == 0)
+ m_width = m_guiBackground->GetTextureWidth();
+
+ if (m_height == 0)
+ m_height = m_guiBackground->GetTextureHeight();
+
+ bChanged |= m_guiBackground->SetHeight(m_height);
+ bChanged |= m_guiBackground->SetWidth(m_width);
+
+ float fScaleX =
+ m_guiBackground->GetTextureWidth() ? m_width / m_guiBackground->GetTextureWidth() : 1.0f;
+ float fScaleY =
+ m_guiBackground->GetTextureHeight() ? m_height / m_guiBackground->GetTextureHeight() : 1.0f;
+
+ float posX = m_guiBackground->GetXPosition();
+ float posY = m_guiBackground->GetYPosition();
+
+ for (auto& range : m_ranges)
+ bChanged |= range.UpdateLayout(m_guiBackground->GetTextureHeight(), posX, posY, m_width,
+ fScaleX, fScaleY);
+
+ float offset = std::fabs(
+ fScaleY * 0.5f * (m_guiOverlay->GetTextureHeight() - m_guiBackground->GetTextureHeight()));
+ if (offset > 0) // Center texture to the background if necessary
+ bChanged |= m_guiOverlay->SetPosition(m_guiBackground->GetXPosition(),
+ m_guiBackground->GetYPosition() + offset);
+ else
+ bChanged |=
+ m_guiOverlay->SetPosition(m_guiBackground->GetXPosition(), m_guiBackground->GetYPosition());
+
+ bChanged |= m_guiOverlay->SetHeight(fScaleY * m_guiOverlay->GetTextureHeight());
+ bChanged |= m_guiOverlay->SetWidth(fScaleX * m_guiOverlay->GetTextureWidth());
+
+ return bChanged;
+}
+
+void CGUIRangesControl::UpdateInfo(const CGUIListItem* item /* = nullptr */)
+{
+ if (!IsDisabled() && m_iInfoCode)
+ {
+ const std::string value = CServiceBroker::GetGUI()->GetInfoManager().GetLabel(m_iInfoCode, m_parentID);
+ if (value != m_prevRanges)
+ {
+ std::vector<std::pair<float, float>> ranges;
+
+ // Parse csv string into ranges...
+ const std::vector<std::string> values = StringUtils::Split(value, ',');
+
+ // we must have an even number of values
+ if (values.size() % 2 == 0)
+ {
+ for (auto it = values.begin(); it != values.end();)
+ {
+ float first = std::stof(*it, nullptr);
+ ++it;
+ float second = std::stof(*it, nullptr);
+ ++it;
+
+ if (first <= second)
+ ranges.emplace_back(std::make_pair(first, second));
+ else
+ CLog::Log(LOGERROR, "CGUIRangesControl::UpdateInfo - malformed ranges csv string (end element must be larger or equal than start element)");
+ }
+ }
+ else
+ CLog::Log(LOGERROR, "CGUIRangesControl::UpdateInfo - malformed ranges csv string (string must contain even number of elements)");
+
+ SetRanges(ranges);
+ m_prevRanges = value;
+ }
+ }
+}
diff --git a/xbmc/guilib/GUIRangesControl.dox b/xbmc/guilib/GUIRangesControl.dox
new file mode 100644
index 0000000..637da34
--- /dev/null
+++ b/xbmc/guilib/GUIRangesControl.dox
@@ -0,0 +1,56 @@
+/*!
+
+\page Ranges_Control Ranges Control
+\brief **Used to show multiple range blocks**
+
+\tableofcontents
+
+The ranges control is used for showing multiple range UI elements on the same control. It is used
+in Kodi, for example, to show the intervals of a cutlist (EDL) or chapters in the video seekbar.
+You can choose the position, size and look and feel of the control.
+
+--------------------------------------------------------------------------------
+\section Ranges_Control_sect1 Example
+
+~~~~~~~~~~~~~xml
+<control type="ranges">
+ <left>0</left>
+ <top>70</top>
+ <width>100%</width>
+ <height>8</height>
+ <texturebg border="3" colordiffuse="00FFFFFF">colors/white50.png</texturebg>
+ <lefttexture>colors/white.png</lefttexture>
+ <midtexture colordiffuse="FFFF0000">colors/white.png</midtexture>
+ <righttexture>colors/white.png</righttexture>
+ <info>Player.Editlist</info>
+</control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Ranges_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| texturebg | The background texture for the range control.
+| lefttexture | The texture used in the left hand side of the range
+| midtexture | The texture used for the mid section of the range
+| righttexture | The texture used in the right hand side of the range
+| info | Specifies the information the range control holds. It expects an infolabel that returns a string in CSV format: e.g. `"start1,end1,start2,end2,..."`. Tokens must have values in the range from 0.0 to 100.0. end token must be less or equal than start token. Examples of currently supported infolabels are \link Player_Editlist `Player.Editlist`\endlink, \link Player_Cutlist `Player.Cutlist`\endlink (@deprecated), \link Player_Cuts `Player.Cuts`\endlink, \link Player_SceneMarkers `Player.Cutlist`\endlink and \link Player_Chapters `Player.Chapters`\endlink.
+
+\section Ranges_Control_sect3 Revision History
+
+@skinning_v19 <b>[Ranges Control]</b> New control added.
+
+--------------------------------------------------------------------------------
+\section Ranges_Control_sect4 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIRangesControl.h b/xbmc/guilib/GUIRangesControl.h
new file mode 100644
index 0000000..a043c7d
--- /dev/null
+++ b/xbmc/guilib/GUIRangesControl.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIRangesControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+
+#include <utility>
+#include <vector>
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIRangesControl : public CGUIControl
+{
+ class CGUIRange
+ {
+ public:
+ CGUIRange(float fPosX, float fPosY, float fWidth, float fHeight,
+ const CTextureInfo& lowerTextureInfo, const CTextureInfo& fillTextureInfo,
+ const CTextureInfo& upperTextureInfo, const std::pair<float, float>& percentages);
+
+ CGUIRange(const CGUIRange& range);
+
+ void AllocResources();
+ void FreeResources(bool bImmediately);
+ void DynamicResourceAlloc(bool bOnOff);
+ void SetInvalid();
+ bool SetDiffuseColor(const KODI::GUILIB::GUIINFO::CGUIInfoColor& color);
+
+ bool Process(unsigned int iCurrentTime);
+ void Render();
+ bool UpdateLayout(float fBackgroundTextureHeight, float fPosX, float fPosY, float fWidth, float fScaleX, float fScaleY);
+
+ private:
+ std::unique_ptr<CGUITexture> m_guiLowerTexture;
+ std::unique_ptr<CGUITexture> m_guiFillTexture;
+ std::unique_ptr<CGUITexture> m_guiUpperTexture;
+ std::pair<float,float> m_percentValues;
+ };
+
+public:
+ CGUIRangesControl(int iParentID, int iControlID, float fPosX, float fPosY,
+ float fWidth, float fHeight, const CTextureInfo& backGroundTexture,
+ const CTextureInfo& leftTexture, const CTextureInfo& midTexture,
+ const CTextureInfo& rightTexture, const CTextureInfo& overlayTexture,
+ int iInfo);
+ ~CGUIRangesControl() override = default;
+ CGUIRangesControl* Clone() const override { return new CGUIRangesControl(*this); }
+
+ void Process(unsigned int iCurrentTime, CDirtyRegionList& dirtyregions) override;
+ void Render() override;
+ bool CanFocus() const override;
+ void AllocResources() override;
+ void FreeResources(bool bImmediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float fPosX, float fPosY) override;
+ void UpdateInfo(const CGUIListItem* item = nullptr) override;
+
+protected:
+ void SetRanges(const std::vector<std::pair<float, float>>& ranges);
+ void ClearRanges();
+
+ bool UpdateColors(const CGUIListItem* item) override;
+ bool UpdateLayout();
+
+ std::unique_ptr<CGUITexture> m_guiBackground;
+ std::unique_ptr<CGUITexture> m_guiOverlay;
+ const CTextureInfo m_guiLowerTextureInfo;
+ const CTextureInfo m_guiFillTextureInfo;
+ const CTextureInfo m_guiUpperTextureInfo;
+ std::vector<CGUIRange> m_ranges;
+ int m_iInfoCode = 0;
+ std::string m_prevRanges;
+
+private:
+ CGUIRangesControl(const CGUIRangesControl& control);
+};
diff --git a/xbmc/guilib/GUIRenderingControl.cpp b/xbmc/guilib/GUIRenderingControl.cpp
new file mode 100644
index 0000000..f6e460b
--- /dev/null
+++ b/xbmc/guilib/GUIRenderingControl.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIRenderingControl.h"
+
+#include "guilib/IRenderingCallback.h"
+
+#include <mutex>
+#ifdef TARGET_WINDOWS
+#include "rendering/dx/DeviceResources.h"
+#endif
+
+#define LABEL_ROW1 10
+#define LABEL_ROW2 11
+#define LABEL_ROW3 12
+
+CGUIRenderingControl::CGUIRenderingControl(int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+{
+ ControlType = GUICONTROL_RENDERADDON;
+ m_callback = NULL;
+}
+
+CGUIRenderingControl::CGUIRenderingControl(const CGUIRenderingControl &from)
+: CGUIControl(from)
+{
+ ControlType = GUICONTROL_RENDERADDON;
+ m_callback = NULL;
+}
+
+bool CGUIRenderingControl::InitCallback(IRenderingCallback *callback)
+{
+ if (!callback)
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_rendering);
+ CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
+ float x = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(GetXPosition(), GetYPosition());
+ float y = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(GetXPosition(), GetYPosition());
+ float w = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - x;
+ float h = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - y;
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+ if (x + w > CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()) w = CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() - x;
+ if (y + h > CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()) h = CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() - y;
+
+ void *device = NULL;
+#if TARGET_WINDOWS
+ device = DX::DeviceResources::Get()->GetD3DDevice();
+#endif
+ if (callback->Create((int)(x+0.5f), (int)(y+0.5f), (int)(w+0.5f), (int)(h+0.5f), device))
+ m_callback = callback;
+ else
+ return false;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
+ return true;
+}
+
+void CGUIRenderingControl::UpdateVisibility(const CGUIListItem *item)
+{
+ // if made invisible, start timer, only free addonptr after
+ // some period, configurable by window class
+ CGUIControl::UpdateVisibility(item);
+ if (!IsVisible() && m_callback)
+ FreeResources();
+}
+
+void CGUIRenderingControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ //! @todo Add processing to the addon so it could mark when actually changing
+ std::unique_lock<CCriticalSection> lock(m_rendering);
+ if (m_callback && m_callback->IsDirty())
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIRenderingControl::Render()
+{
+ std::unique_lock<CCriticalSection> lock(m_rendering);
+ if (m_callback)
+ {
+ // set the viewport - note: We currently don't have any control over how
+ // the addon renders, so the best we can do is attempt to define
+ // a viewport??
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetViewPort(m_posX, m_posY, m_width, m_height);
+ CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
+ m_callback->Render();
+ CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreViewPort();
+ }
+
+ CGUIControl::Render();
+}
+
+void CGUIRenderingControl::FreeResources(bool immediately)
+{
+ std::unique_lock<CCriticalSection> lock(m_rendering);
+
+ if (!m_callback) return;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock(); //! @todo locking
+ m_callback->Stop();
+ CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
+ m_callback = NULL;
+}
+
+bool CGUIRenderingControl::CanFocusFromPoint(const CPoint &point) const
+{ // mouse is allowed to focus this control, but it doesn't actually receive focus
+ return IsVisible() && HitTest(point);
+}
diff --git a/xbmc/guilib/GUIRenderingControl.dox b/xbmc/guilib/GUIRenderingControl.dox
new file mode 100644
index 0000000..1f1b44b
--- /dev/null
+++ b/xbmc/guilib/GUIRenderingControl.dox
@@ -0,0 +1,34 @@
+/*!
+
+\page Addon_Rendering_control Add-on Rendering control
+\brief **Control where rendering becomes performed from add-on to show on Kodi**
+
+\tableofcontents
+
+--------------------------------------------------------------------------------
+\section Addon_Rendering_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="renderaddon" id ="9">
+ <left>5</left>
+ <top>50</top>
+ <width>780</width>
+ <height>515</height>
+</control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Addon_Rendering_control_sect2 Available tags
+
+Only the [default control](http://kodi.wiki/view/Default_Control_Tags) tags are
+applicable to this control.
+
+
+--------------------------------------------------------------------------------
+\section Addon_Rendering_control_sect3 See also
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIRenderingControl.h b/xbmc/guilib/GUIRenderingControl.h
new file mode 100644
index 0000000..b901b8c
--- /dev/null
+++ b/xbmc/guilib/GUIRenderingControl.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+
+class IRenderingCallback;
+
+class CGUIRenderingControl : public CGUIControl
+{
+public:
+ CGUIRenderingControl(int parentID, int controlID, float posX, float posY, float width, float height);
+ CGUIRenderingControl(const CGUIRenderingControl &from);
+ CGUIRenderingControl *Clone() const override { return new CGUIRenderingControl(*this); }; //! @todo check for naughties
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+ void FreeResources(bool immediately = false) override;
+ bool CanFocus() const override { return false; }
+ bool CanFocusFromPoint(const CPoint &point) const override;
+ bool InitCallback(IRenderingCallback *callback);
+
+protected:
+ CCriticalSection m_rendering;
+ IRenderingCallback *m_callback;
+};
diff --git a/xbmc/guilib/GUIResizeControl.cpp b/xbmc/guilib/GUIResizeControl.cpp
new file mode 100644
index 0000000..83f79d4
--- /dev/null
+++ b/xbmc/guilib/GUIResizeControl.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIResizeControl.h"
+
+#include "GUIMessage.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+
+using namespace UTILS;
+
+CGUIResizeControl::CGUIResizeControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_imgFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureFocus)),
+ m_imgNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureNoFocus))
+{
+ m_frameCounter = 0;
+ m_movingSpeed.AddEventMapConfig(movingSpeedCfg);
+ m_fAnalogSpeed = 2.0f; //! @todo implement correct analog speed
+ ControlType = GUICONTROL_RESIZE;
+ SetLimits(0, 0, 720, 576); // defaults
+}
+
+CGUIResizeControl::CGUIResizeControl(const CGUIResizeControl& control)
+ : CGUIControl(control),
+ m_imgFocus(control.m_imgFocus->Clone()),
+ m_imgNoFocus(control.m_imgNoFocus->Clone()),
+ m_frameCounter(control.m_frameCounter),
+ m_movingSpeed(control.m_movingSpeed),
+ m_fAnalogSpeed(control.m_fAnalogSpeed),
+ m_x1(control.m_x1),
+ m_x2(control.m_x2),
+ m_y1(control.m_y1),
+ m_y2(control.m_y2)
+{
+}
+
+void CGUIResizeControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_bInvalidated)
+ {
+ m_imgFocus->SetWidth(m_width);
+ m_imgFocus->SetHeight(m_height);
+
+ m_imgNoFocus->SetWidth(m_width);
+ m_imgNoFocus->SetHeight(m_height);
+ }
+ if (HasFocus())
+ {
+ unsigned int alphaCounter = m_frameCounter + 2;
+ unsigned int alphaChannel;
+ if ((alphaCounter % 128) >= 64)
+ alphaChannel = alphaCounter % 64;
+ else
+ alphaChannel = 63 - (alphaCounter % 64);
+
+ alphaChannel += 192;
+ if (SetAlpha( (unsigned char)alphaChannel ))
+ MarkDirtyRegion();
+ m_imgFocus->SetVisible(true);
+ m_imgNoFocus->SetVisible(false);
+ m_frameCounter++;
+ }
+ else
+ {
+ if (SetAlpha(0xff))
+ MarkDirtyRegion();
+ m_imgFocus->SetVisible(false);
+ m_imgNoFocus->SetVisible(true);
+ }
+ m_imgFocus->Process(currentTime);
+ m_imgNoFocus->Process(currentTime);
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIResizeControl::Render()
+{
+ m_imgFocus->Render();
+ m_imgNoFocus->Render();
+ CGUIControl::Render();
+}
+
+bool CGUIResizeControl::OnAction(const CAction &action)
+{
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ // button selected - send message to parent
+ CGUIMessage message(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(message);
+ return true;
+ }
+ if (action.GetID() == ACTION_ANALOG_MOVE)
+ {
+ Resize(m_fAnalogSpeed*action.GetAmount(), -m_fAnalogSpeed*action.GetAmount(1));
+ return true;
+ }
+ return CGUIControl::OnAction(action);
+}
+
+void CGUIResizeControl::OnUp()
+{
+ Resize(0, -m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::UP));
+}
+
+void CGUIResizeControl::OnDown()
+{
+ Resize(0, m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::DOWN));
+}
+
+void CGUIResizeControl::OnLeft()
+{
+ Resize(-m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::LEFT), 0);
+}
+
+void CGUIResizeControl::OnRight()
+{
+ Resize(m_movingSpeed.GetUpdatedDistance(MOVING_SPEED::EventType::RIGHT), 0);
+}
+
+EVENT_RESULT CGUIResizeControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_DRAG || event.m_id == ACTION_MOUSE_DRAG_END)
+ {
+ if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ else if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG_END)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ }
+ Resize(event.m_offsetX, event.m_offsetY);
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+void CGUIResizeControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_frameCounter = 0;
+ m_imgFocus->AllocResources();
+ m_imgNoFocus->AllocResources();
+ m_width = m_imgFocus->GetWidth();
+ m_height = m_imgFocus->GetHeight();
+}
+
+void CGUIResizeControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_imgFocus->FreeResources(immediately);
+ m_imgNoFocus->FreeResources(immediately);
+}
+
+void CGUIResizeControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgFocus->DynamicResourceAlloc(bOnOff);
+ m_imgNoFocus->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIResizeControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_imgFocus->SetInvalid();
+ m_imgNoFocus->SetInvalid();
+}
+
+void CGUIResizeControl::Resize(float x, float y)
+{
+ float width = m_width + x;
+ float height = m_height + y;
+ // check if we are within the bounds
+ if (width < m_x1) width = m_x1;
+ if (height < m_y1) height = m_y1;
+ if (width > m_x2) width = m_x2;
+ if (height > m_y2) height = m_y2;
+ // ok, now set the default size of the resize control
+ SetWidth(width);
+ SetHeight(height);
+}
+
+void CGUIResizeControl::SetPosition(float posX, float posY)
+{
+ CGUIControl::SetPosition(posX, posY);
+ m_imgFocus->SetPosition(posX, posY);
+ m_imgNoFocus->SetPosition(posX, posY);
+}
+
+bool CGUIResizeControl::SetAlpha(unsigned char alpha)
+{
+ bool changed = m_imgFocus->SetAlpha(alpha);
+ changed |= m_imgNoFocus->SetAlpha(alpha);
+ return changed;
+}
+
+bool CGUIResizeControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_imgFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgNoFocus->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+void CGUIResizeControl::SetLimits(float x1, float y1, float x2, float y2)
+{
+ m_x1 = x1;
+ m_y1 = y1;
+ m_x2 = x2;
+ m_y2 = y2;
+}
diff --git a/xbmc/guilib/GUIResizeControl.dox b/xbmc/guilib/GUIResizeControl.dox
new file mode 100644
index 0000000..d3bcd5a
--- /dev/null
+++ b/xbmc/guilib/GUIResizeControl.dox
@@ -0,0 +1,93 @@
+/*!
+
+\page Resize_Control Resize control
+\brief **Used to set the pixel ratio in Video Calibration.**
+
+\tableofcontents
+
+The resize control is used to specify an area of changeable ratio for use in
+the screen calibration portion of Kodi. You can choose the size, and look of
+the resizer.
+
+
+--------------------------------------------------------------------------------
+\section Resize_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="resize" id="3">
+ <description>My first resize control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <texturefocus>mytexture.png</texturefocus>
+ <texturenofocus>mytexture.png</texturenofocus>
+ <pulseonselect>true</pulseonselect>
+ <movingspeed acceleration="180" maxvelocity="300" resettimeout="200" delta="1">
+ <eventconfig type="up">
+ <eventconfig type="down">
+ <eventconfig type="left" acceleration="100" maxvelocity="100" resettimeout="160" delta="1">
+ <eventconfig type="right" acceleration="100" maxvelocity="100" resettimeout="160" delta="1">
+ </movingspeed>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Resize_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|----------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the resizer has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus | Specifies the image file which should be displayed when the resizer does not have focus.
+| movingspeed | Specifies the allowed directional movements and respective configurations. The <c>`<movingspeed>`</c> tag section can contain as many <c>`<eventconfig>`</c> tags as required.
+
+
+--------------------------------------------------------------------------------
+\section Resize_Control_sect3 Note on use of movingspeed tag
+
+The <c>`movingspeed`</c> tag must be specified to allow the control to be moved via
+an input device such as keyboard. Each direction can be specified and configured
+to simulate the motion effect with the <c>`eventconfig`</c> tag.
+
+The following attributes allow the motion effect of the control to be configured:
+
+| Attribute | Description |
+|----------------:|:--------------------------------------------------------------|
+| type | Specifies the direction of movement. Accepted values are: <c>`up`</c>, <c>`down`</c>, <c>`left`</c>, <c>`right`</c>. All directions are optional and do not have to be included.
+| acceleration | Specifies the acceleration in pixels per second (integer value).
+| maxvelocity | Specifies the maximum movement speed. This depends on the acceleration value, e.g. a suitable value could be (acceleration value)*3. Set to 0 to disable. (integer value).
+| resettimeout | Specifies the idle timeout before resetting the acceleration speed (in milliseconds, integer value).
+| delta | Specifies the minimal pixel increment step (integer value).
+
+The attributes <c>`acceleration`</c>, <c>`maxvelocity`</c>, <c>`resettimeout`</c>,
+<c>`delta`</c> can be specified individually for each <c>`eventconfig`</c> tag,
+or globally in the <c>`movingspeed`</c> tag, or a mixture, as the following example:
+
+~~~~~~~~~~~~~
+<movingspeed acceleration="180" maxvelocity="300" resettimeout="200" delta="1">
+ <eventconfig type="up">
+ <eventconfig type="down">
+ <eventconfig type="left" acceleration="100" maxvelocity="100" resettimeout="160">
+ <eventconfig type="right" acceleration="100" maxvelocity="100" resettimeout="160">
+</movingspeed>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Resize_Control_sect4 Revision History
+
+@skinning_v20 <b>[movingspeed]</b> New tag added.
+
+--------------------------------------------------------------------------------
+\section Resize_Control_sect5 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIResizeControl.h b/xbmc/guilib/GUIResizeControl.h
new file mode 100644
index 0000000..998cc1b
--- /dev/null
+++ b/xbmc/guilib/GUIResizeControl.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIRESIZEControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+#include "utils/MovingSpeed.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIResizeControl : public CGUIControl
+{
+public:
+ CGUIResizeControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureFocus,
+ const CTextureInfo& textureNoFocus,
+ UTILS::MOVING_SPEED::MapEventConfig& movingSpeedCfg);
+
+ ~CGUIResizeControl() override = default;
+ CGUIResizeControl* Clone() const override { return new CGUIResizeControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void OnUp() override;
+ void OnDown() override;
+ void OnLeft() override;
+ void OnRight() override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ void SetLimits(float x1, float y1, float x2, float y2);
+ bool CanFocus() const override { return true; }
+
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ bool SetAlpha(unsigned char alpha);
+ void Resize(float x, float y);
+ std::unique_ptr<CGUITexture> m_imgFocus;
+ std::unique_ptr<CGUITexture> m_imgNoFocus;
+ unsigned int m_frameCounter;
+ UTILS::MOVING_SPEED::CMovingSpeed m_movingSpeed;
+ float m_fAnalogSpeed;
+ float m_x1, m_x2, m_y1, m_y2;
+
+private:
+ CGUIResizeControl(const CGUIResizeControl& control);
+};
+
diff --git a/xbmc/guilib/GUIScrollBarControl.cpp b/xbmc/guilib/GUIScrollBarControl.cpp
new file mode 100644
index 0000000..bcb2101
--- /dev/null
+++ b/xbmc/guilib/GUIScrollBarControl.cpp
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIScrollBarControl.h"
+
+#include "GUIMessage.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/StringUtils.h"
+
+#define MIN_NIB_SIZE 4.0f
+
+GUIScrollBarControl::GUIScrollBarControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& backGroundTexture,
+ const CTextureInfo& barTexture,
+ const CTextureInfo& barTextureFocus,
+ const CTextureInfo& nibTexture,
+ const CTextureInfo& nibTextureFocus,
+ ORIENTATION orientation,
+ bool showOnePage)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_guiBackground(CGUITexture::CreateTexture(posX, posY, width, height, backGroundTexture)),
+ m_guiBarNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, barTexture)),
+ m_guiBarFocus(CGUITexture::CreateTexture(posX, posY, width, height, barTextureFocus)),
+ m_guiNibNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTexture)),
+ m_guiNibFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTextureFocus))
+{
+ m_guiNibNoFocus->SetAspectRatio(CAspectRatio::AR_CENTER);
+ m_guiNibFocus->SetAspectRatio(CAspectRatio::AR_CENTER);
+ m_numItems = 100;
+ m_offset = 0;
+ m_pageSize = 10;
+ ControlType = GUICONTROL_SCROLLBAR;
+ m_orientation = orientation;
+ m_showOnePage = showOnePage;
+}
+
+GUIScrollBarControl::GUIScrollBarControl(const GUIScrollBarControl& control)
+ : CGUIControl(control),
+ m_guiBackground(control.m_guiBackground->Clone()),
+ m_guiBarNoFocus(control.m_guiBarNoFocus->Clone()),
+ m_guiBarFocus(control.m_guiBarFocus->Clone()),
+ m_guiNibNoFocus(control.m_guiNibNoFocus->Clone()),
+ m_guiNibFocus(control.m_guiNibFocus->Clone()),
+ m_numItems(control.m_numItems),
+ m_pageSize(control.m_pageSize),
+ m_offset(control.m_offset),
+ m_showOnePage(control.m_showOnePage),
+ m_orientation(control.m_orientation)
+{
+}
+
+void GUIScrollBarControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool changed = false;
+
+ if (m_bInvalidated)
+ changed |= UpdateBarSize();
+
+ changed |= m_guiBackground->Process(currentTime);
+ changed |= m_guiBarNoFocus->Process(currentTime);
+ changed |= m_guiBarFocus->Process(currentTime);
+ changed |= m_guiNibNoFocus->Process(currentTime);
+ changed |= m_guiNibFocus->Process(currentTime);
+
+ if (changed)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void GUIScrollBarControl::Render()
+{
+ m_guiBackground->Render();
+ if (m_bHasFocus)
+ {
+ m_guiBarFocus->Render();
+ m_guiNibFocus->Render();
+ }
+ else
+ {
+ m_guiBarNoFocus->Render();
+ m_guiNibNoFocus->Render();
+ }
+
+ CGUIControl::Render();
+}
+
+bool GUIScrollBarControl::OnMessage(CGUIMessage& message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_ITEM_SELECT:
+ SetValue(message.GetParam1());
+ return true;
+ case GUI_MSG_LABEL_RESET:
+ SetRange(message.GetParam1(), message.GetParam2());
+ return true;
+ case GUI_MSG_PAGE_UP:
+ Move(-1);
+ return true;
+ case GUI_MSG_PAGE_DOWN:
+ Move(1);
+ return true;
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+bool GUIScrollBarControl::OnAction(const CAction &action)
+{
+ switch ( action.GetID() )
+ {
+ case ACTION_MOVE_LEFT:
+ if (m_orientation == HORIZONTAL)
+ {
+ if(Move( -1))
+ return true;
+ }
+ break;
+
+ case ACTION_MOVE_RIGHT:
+ if (m_orientation == HORIZONTAL)
+ {
+ if(Move(1))
+ return true;
+ }
+ break;
+ case ACTION_MOVE_UP:
+ if (m_orientation == VERTICAL)
+ {
+ if(Move(-1))
+ return true;
+ }
+ break;
+
+ case ACTION_MOVE_DOWN:
+ if (m_orientation == VERTICAL)
+ {
+ if(Move(1))
+ return true;
+ }
+ break;
+ }
+ return CGUIControl::OnAction(action);
+}
+
+bool GUIScrollBarControl::Move(int numSteps)
+{
+ if (numSteps < 0 && m_offset == 0) // we are at the beginning - can't scroll up/left anymore
+ return false;
+ if (numSteps > 0 && m_offset == std::max(m_numItems - m_pageSize, 0)) // we are at the end - we can't scroll down/right anymore
+ return false;
+
+ m_offset += numSteps * m_pageSize;
+ if (m_offset > m_numItems - m_pageSize) m_offset = m_numItems - m_pageSize;
+ if (m_offset < 0) m_offset = 0;
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetParentID(), GetID(), GUI_MSG_PAGE_CHANGE, m_offset);
+ SendWindowMessage(message);
+ SetInvalid();
+ return true;
+}
+
+void GUIScrollBarControl::SetRange(int pageSize, int numItems)
+{
+ if (m_pageSize != pageSize || m_numItems != numItems)
+ {
+ m_pageSize = pageSize;
+ m_numItems = numItems;
+ m_offset = 0;
+ SetInvalid();
+ }
+}
+
+void GUIScrollBarControl::SetValue(int value)
+{
+ if (m_offset != value)
+ {
+ m_offset = value;
+ SetInvalid();
+ }
+}
+
+void GUIScrollBarControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_guiBackground->FreeResources(immediately);
+ m_guiBarNoFocus->FreeResources(immediately);
+ m_guiBarFocus->FreeResources(immediately);
+ m_guiNibNoFocus->FreeResources(immediately);
+ m_guiNibFocus->FreeResources(immediately);
+}
+
+void GUIScrollBarControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_guiBackground->DynamicResourceAlloc(bOnOff);
+ m_guiBarNoFocus->DynamicResourceAlloc(bOnOff);
+ m_guiBarFocus->DynamicResourceAlloc(bOnOff);
+ m_guiNibNoFocus->DynamicResourceAlloc(bOnOff);
+ m_guiNibFocus->DynamicResourceAlloc(bOnOff);
+}
+
+void GUIScrollBarControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_guiBackground->AllocResources();
+ m_guiBarNoFocus->AllocResources();
+ m_guiBarFocus->AllocResources();
+ m_guiNibNoFocus->AllocResources();
+ m_guiNibFocus->AllocResources();
+}
+
+void GUIScrollBarControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_guiBackground->SetInvalid();
+ m_guiBarFocus->SetInvalid();
+ m_guiBarFocus->SetInvalid();
+ m_guiNibNoFocus->SetInvalid();
+ m_guiNibFocus->SetInvalid();
+}
+
+bool GUIScrollBarControl::UpdateBarSize()
+{
+ bool changed = false;
+
+ // scale our textures to suit
+ if (m_orientation == VERTICAL)
+ {
+ // calculate the height to display the nib at
+ float percent = (m_numItems == 0) ? 0 : (float)m_pageSize / m_numItems;
+ float nibSize = GetHeight() * percent;
+ if (nibSize < m_guiNibFocus->GetTextureHeight() + 2 * MIN_NIB_SIZE)
+ nibSize = m_guiNibFocus->GetTextureHeight() + 2 * MIN_NIB_SIZE;
+ if (nibSize > GetHeight()) nibSize = GetHeight();
+
+ changed |= m_guiBarNoFocus->SetHeight(nibSize);
+ changed |= m_guiBarFocus->SetHeight(nibSize);
+ changed |= m_guiNibNoFocus->SetHeight(nibSize);
+ changed |= m_guiNibFocus->SetHeight(nibSize);
+ // nibSize may be altered by the border size of the nib (and bar).
+ nibSize = std::max(m_guiBarFocus->GetHeight(), m_guiNibFocus->GetHeight());
+
+ // and the position
+ percent = (m_numItems == m_pageSize) ? 0 : (float)m_offset / (m_numItems - m_pageSize);
+ float nibPos = (GetHeight() - nibSize) * percent;
+ if (nibPos < 0) nibPos = 0;
+ if (nibPos > GetHeight() - nibSize) nibPos = GetHeight() - nibSize;
+
+ changed |= m_guiBarNoFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos);
+ changed |= m_guiBarFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos);
+ changed |= m_guiNibNoFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos);
+ changed |= m_guiNibFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos);
+ }
+ else
+ {
+ // calculate the height to display the nib at
+ float percent = (m_numItems == 0) ? 0 : (float)m_pageSize / m_numItems;
+ float nibSize = GetWidth() * percent + 0.5f;
+ if (nibSize < m_guiNibFocus->GetTextureWidth() + 2 * MIN_NIB_SIZE)
+ nibSize = m_guiNibFocus->GetTextureWidth() + 2 * MIN_NIB_SIZE;
+ if (nibSize > GetWidth()) nibSize = GetWidth();
+
+ changed |= m_guiBarNoFocus->SetWidth(nibSize);
+ changed |= m_guiBarFocus->SetWidth(nibSize);
+ changed |= m_guiNibNoFocus->SetWidth(nibSize);
+ changed |= m_guiNibFocus->SetWidth(nibSize);
+
+ // and the position
+ percent = (m_numItems == m_pageSize) ? 0 : (float)m_offset / (m_numItems - m_pageSize);
+ float nibPos = (GetWidth() - nibSize) * percent;
+ if (nibPos < 0) nibPos = 0;
+ if (nibPos > GetWidth() - nibSize) nibPos = GetWidth() - nibSize;
+
+ changed |= m_guiBarNoFocus->SetPosition(GetXPosition() + nibPos, GetYPosition());
+ changed |= m_guiBarFocus->SetPosition(GetXPosition() + nibPos, GetYPosition());
+ changed |= m_guiNibNoFocus->SetPosition(GetXPosition() + nibPos, GetYPosition());
+ changed |= m_guiNibFocus->SetPosition(GetXPosition() + nibPos, GetYPosition());
+ }
+
+ return changed;
+}
+
+void GUIScrollBarControl::SetFromPosition(const CPoint &point)
+{
+ float fPercent;
+ if (m_orientation == VERTICAL)
+ fPercent = (point.y - m_guiBackground->GetYPosition() - 0.5f * m_guiBarFocus->GetHeight()) /
+ (m_guiBackground->GetHeight() - m_guiBarFocus->GetHeight());
+ else
+ fPercent = (point.x - m_guiBackground->GetXPosition() - 0.5f * m_guiBarFocus->GetWidth()) /
+ (m_guiBackground->GetWidth() - m_guiBarFocus->GetWidth());
+ if (fPercent < 0) fPercent = 0;
+ if (fPercent > 1) fPercent = 1;
+
+ int offset = (int)(floor(fPercent * (m_numItems - m_pageSize) + 0.5f));
+
+ if (m_offset != offset)
+ {
+ m_offset = offset;
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetParentID(), GetID(), GUI_MSG_PAGE_CHANGE, m_offset);
+ SendWindowMessage(message);
+ SetInvalid();
+ }
+}
+
+EVENT_RESULT GUIScrollBarControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_DRAG || event.m_id == ACTION_MOUSE_DRAG_END)
+ {
+ if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG)
+ { // we want exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ else if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG_END)
+ { // we're done with exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ }
+ SetFromPosition(point);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_LEFT_CLICK && m_guiBackground->HitTest(point))
+ {
+ SetFromPosition(point);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
+ {
+ Move(-1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ Move(1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_NOTIFY)
+ {
+ return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA : EVENT_RESULT_PAN_VERTICAL_WITHOUT_INERTIA;
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ { // do the drag
+ SetFromPosition(point);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+
+ return EVENT_RESULT_UNHANDLED;
+}
+
+std::string GUIScrollBarControl::GetDescription() const
+{
+ return StringUtils::Format("{}/{}", m_offset, m_numItems);
+}
+
+bool GUIScrollBarControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_guiBackground->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiBarNoFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiBarFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiNibNoFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiNibFocus->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+bool GUIScrollBarControl::IsVisible() const
+{
+ // page controls can be optionally disabled if the number of pages is 1
+ if (m_numItems <= m_pageSize && !m_showOnePage)
+ return false;
+ return CGUIControl::IsVisible();
+}
diff --git a/xbmc/guilib/GUIScrollBarControl.dox b/xbmc/guilib/GUIScrollBarControl.dox
new file mode 100644
index 0000000..4ee67a7
--- /dev/null
+++ b/xbmc/guilib/GUIScrollBarControl.dox
@@ -0,0 +1,79 @@
+/*!
+
+\page Scroll_Bar_Control Scroll Bar Control
+\brief **Used for a implementing a scroll bar.**
+
+\tableofcontents
+
+The scroll bar control is used as a page control for lists, panels, wraplists,
+fixedlists, textboxes, and grouplists. You can choose the position, size, and
+look of the scroll bar.
+
+
+--------------------------------------------------------------------------------
+\section Scroll_Bar_Control_sect Example
+
+~~~~~~~~~~~~~
+<control type="scrollbar" id="17">
+ <description>My first scroll bar control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>30</height>
+ <visible>true</visible>
+ <texturesliderbackground>scroll-background.png</texturesliderbackground>
+ <texturesliderbar>bar.png</texturesliderbar>
+ <texturesliderbarfocus>bar-focus.png</texturesliderbarfocus>
+ <textureslidernib>nib.png</textureslidernib>
+ <textureslidernibfocus>nib-focus.png</textureslidernibfocus>
+ <pulseonselect></pulseonselect>
+ <orientation>vertical</orientation>
+ <showonepage>false</showonepage>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Scroll_Bar_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|------------------------:|:--------------------------------------------------------------|
+| texturesliderbackground | Specifies the image file which should be displayed in the background of the scroll bar control. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturesliderbar | Specifies the image file which should be displayed for the scroll bar. As the size of the scroll bar is dynamic, it is often useful to [use the border attribute of this texture](http://kodi.wiki/view/Texture_Attributes).
+| texturesliderbarfocus | Specifies the image file which should be displayed for the scroll bar when it has focus.
+| textureslidernib | Specifies the image file which should be displayed for the scroll bar nib. The nib is always centered within the scroll bar.
+| textureslidernibfocus | Specifies the image file which should be displayed for the scroll bar nib when it has focus. The nib is always centered within the scroll bar.
+| orientation | Specifies whether this scrollbar is horizontal or vertical. Defaults to vertical.
+| showonepage | Specifies whether the scrollbar will show if the container it's controlling has just one page. Defaults to true.
+
+\section Scroll_Bar_Control_sect3 Adding Up and Down buttons above and below a scrollbar
+To add arrow buttons above and below the scrollbar, you need to add 2 additional button controls to the window, and set their <b>`<onclick>`</b> tag to
+
+~~~~~~~~~~~~~
+<onclick>pageup(id)</onclick>
+~~~~~~~~~~~~~
+or
+~~~~~~~~~~~~~
+<onclick>pagedown(id)</onclick>
+~~~~~~~~~~~~~
+
+where id is the id of this scroll bar.
+
+
+--------------------------------------------------------------------------------
+\section Scroll_Bar_Control_sect4 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIScrollBarControl.h b/xbmc/guilib/GUIScrollBarControl.h
new file mode 100644
index 0000000..0000386
--- /dev/null
+++ b/xbmc/guilib/GUIScrollBarControl.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class GUIScrollBarControl :
+ public CGUIControl
+{
+public:
+ GUIScrollBarControl(int parentID, int controlID, float posX, float posY,
+ float width, float height,
+ const CTextureInfo& backGroundTexture,
+ const CTextureInfo& barTexture, const CTextureInfo& barTextureFocus,
+ const CTextureInfo& nibTexture, const CTextureInfo& nibTextureFocus,
+ ORIENTATION orientation, bool showOnePage);
+ ~GUIScrollBarControl() override = default;
+ GUIScrollBarControl* Clone() const override { return new GUIScrollBarControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ virtual void SetRange(int pageSize, int numItems);
+ bool OnMessage(CGUIMessage& message) override;
+ void SetValue(int value);
+ int GetValue() const;
+ std::string GetDescription() const override;
+ bool IsVisible() const override;
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ bool UpdateBarSize();
+ bool Move(int iNumSteps);
+ virtual void SetFromPosition(const CPoint &point);
+
+ std::unique_ptr<CGUITexture> m_guiBackground;
+ std::unique_ptr<CGUITexture> m_guiBarNoFocus;
+ std::unique_ptr<CGUITexture> m_guiBarFocus;
+ std::unique_ptr<CGUITexture> m_guiNibNoFocus;
+ std::unique_ptr<CGUITexture> m_guiNibFocus;
+
+ int m_numItems;
+ int m_pageSize;
+ int m_offset;
+
+ bool m_showOnePage;
+ ORIENTATION m_orientation;
+
+private:
+ GUIScrollBarControl(const GUIScrollBarControl& control);
+};
+
diff --git a/xbmc/guilib/GUISettingsSliderControl.cpp b/xbmc/guilib/GUISettingsSliderControl.cpp
new file mode 100644
index 0000000..7c275ab
--- /dev/null
+++ b/xbmc/guilib/GUISettingsSliderControl.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUISettingsSliderControl.h"
+
+#include "input/Key.h"
+
+CGUISettingsSliderControl::CGUISettingsSliderControl(int parentID, int controlID, float posX, float posY, float width, float height, float sliderWidth, float sliderHeight, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus, const CTextureInfo& backGroundTexture, const CTextureInfo& nibTexture, const CTextureInfo& nibTextureFocus, const CLabelInfo &labelInfo, int iType)
+ : CGUISliderControl(parentID, controlID, posX, posY, sliderWidth, sliderHeight, backGroundTexture, nibTexture,nibTextureFocus, iType, HORIZONTAL)
+ , m_buttonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
+ , m_label(posX, posY, width, height, labelInfo)
+{
+ m_label.SetAlign((labelInfo.align & XBFONT_CENTER_Y) | XBFONT_RIGHT);
+ ControlType = GUICONTROL_SETTINGS_SLIDER;
+ m_active = false;
+}
+
+CGUISettingsSliderControl::CGUISettingsSliderControl(const CGUISettingsSliderControl& control)
+ : CGUISliderControl(control),
+ m_buttonControl(control.m_buttonControl),
+ m_label(control.m_label),
+ m_active(control.m_active)
+{
+}
+
+void CGUISettingsSliderControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (m_bInvalidated)
+ {
+ float sliderPosX = m_buttonControl.GetXPosition() + m_buttonControl.GetWidth() - m_width - m_buttonControl.GetLabelInfo().offsetX;
+ float sliderPosY = m_buttonControl.GetYPosition() + (m_buttonControl.GetHeight() - m_height) * 0.5f;
+ CGUISliderControl::SetPosition(sliderPosX, sliderPosY);
+ }
+ m_buttonControl.SetFocus(HasFocus());
+ m_buttonControl.SetPulseOnSelect(m_pulseOnSelect);
+ m_buttonControl.SetEnabled(m_enabled);
+ m_buttonControl.DoProcess(currentTime, dirtyregions);
+ ProcessText();
+ CGUISliderControl::Process(currentTime, dirtyregions);
+}
+
+void CGUISettingsSliderControl::Render()
+{
+ m_buttonControl.Render();
+ CGUISliderControl::Render();
+ m_label.Render();
+}
+
+void CGUISettingsSliderControl::ProcessText()
+{
+ bool changed = false;
+
+ changed |= m_label.SetMaxRect(m_buttonControl.GetXPosition(), m_buttonControl.GetYPosition(), m_posX - m_buttonControl.GetXPosition(), m_buttonControl.GetHeight());
+ changed |= m_label.SetText(CGUISliderControl::GetDescription());
+ if (IsDisabled())
+ changed |= m_label.SetColor(CGUILabel::COLOR_DISABLED);
+ else if (HasFocus())
+ changed |= m_label.SetColor(CGUILabel::COLOR_FOCUSED);
+ else
+ changed |= m_label.SetColor(CGUILabel::COLOR_TEXT);
+
+ if (changed)
+ MarkDirtyRegion();
+}
+
+bool CGUISettingsSliderControl::OnAction(const CAction &action)
+{
+ // intercept ACTION_SELECT_ITEM because onclick functionality is different from base class
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ if (!IsActive())
+ m_active = true;
+ // switch between the two sliders
+ else if (m_rangeSelection && m_currentSelector == RangeSelectorLower)
+ SwitchRangeSelector();
+ else
+ {
+ m_active = false;
+ if (m_rangeSelection)
+ SwitchRangeSelector();
+ }
+ return true;
+ }
+ return CGUISliderControl::OnAction(action);
+}
+
+void CGUISettingsSliderControl::OnUnFocus()
+{
+ m_active = false;
+}
+
+EVENT_RESULT CGUISettingsSliderControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ SetActive();
+ return CGUISliderControl::OnMouseEvent(point, event);
+}
+
+void CGUISettingsSliderControl::SetActive()
+{
+ m_active = true;
+}
+
+void CGUISettingsSliderControl::FreeResources(bool immediately)
+{
+ CGUISliderControl::FreeResources(immediately);
+ m_buttonControl.FreeResources(immediately);
+}
+
+void CGUISettingsSliderControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUISliderControl::DynamicResourceAlloc(bOnOff);
+ m_buttonControl.DynamicResourceAlloc(bOnOff);
+}
+
+void CGUISettingsSliderControl::AllocResources()
+{
+ CGUISliderControl::AllocResources();
+ m_buttonControl.AllocResources();
+}
+
+void CGUISettingsSliderControl::SetInvalid()
+{
+ CGUISliderControl::SetInvalid();
+ m_buttonControl.SetInvalid();
+}
+
+void CGUISettingsSliderControl::SetPosition(float posX, float posY)
+{
+ m_buttonControl.SetPosition(posX, posY);
+ CGUISliderControl::SetInvalid();
+}
+
+void CGUISettingsSliderControl::SetWidth(float width)
+{
+ m_buttonControl.SetWidth(width);
+ CGUISliderControl::SetInvalid();
+}
+
+void CGUISettingsSliderControl::SetHeight(float height)
+{
+ m_buttonControl.SetHeight(height);
+ CGUISliderControl::SetInvalid();
+}
+
+void CGUISettingsSliderControl::SetEnabled(bool bEnable)
+{
+ CGUISliderControl::SetEnabled(bEnable);
+ m_buttonControl.SetEnabled(bEnable);
+}
+
+std::string CGUISettingsSliderControl::GetDescription() const
+{
+ return m_buttonControl.GetDescription() + " " + CGUISliderControl::GetDescription();
+}
+
+bool CGUISettingsSliderControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUISliderControl::UpdateColors(nullptr);
+ changed |= m_buttonControl.SetColorDiffuse(m_diffuseColor);
+ changed |= m_buttonControl.UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+
+ return changed;
+}
diff --git a/xbmc/guilib/GUISettingsSliderControl.dox b/xbmc/guilib/GUISettingsSliderControl.dox
new file mode 100644
index 0000000..ea85da8
--- /dev/null
+++ b/xbmc/guilib/GUISettingsSliderControl.dox
@@ -0,0 +1,80 @@
+/*!
+
+\page Settings_Slider_Control Settings Slider Control
+\brief **Used for a slider control in the settings menus.**
+
+\tableofcontents
+
+The settings slider control is used in the settings screens for when an option
+is best specified on a sliding scale. You can choose the position, size, and
+look of the slider control. It is basically a cross between the button control
+and a slider control. It has a label and focus and non focus textures, as well
+as a slider control on the right.
+
+--------------------------------------------------------------------------------
+\section Settings_Slider_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="sliderex" id="12">
+ <description>My first settings slider control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <sliderwidth>100</sliderwidth>
+ <sliderheight>20</sliderheight>
+ <visible>true</visible>
+ <texturefocus>myfocustexture.png</texturefocus>
+ <texturenofocus>mynofocustexture.png</texturenofocus>
+ <texturebg>mybackgroundtexture.png</texturebg>
+ <textureslidernib>mydowntexture.png</textureslidernib>
+ <textureslidernibfocus>mydownfocustexture.png</textureslidernibfocus>
+ <info></info>
+ <label>46</label>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <textoffsetx></textoffsetx>
+ <pulseonselect></pulseonselect>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Settings_Slider_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|----------------------:|:--------------------------------------------------------------|
+| sliderwidth | Specifies the width of the slider portion of the slider control (ie without the text value, if present). The texture image for the slider background will be resized to fit into this width, and the nib textures will be resized by the same amount.
+| sliderheight | Specifies the height of the slider portion of the slider control (ie without the text value, if present). The texture image for the slider background will be resized to fit into this height, and the nib textures will be resized by the same amount.
+| texturefocus | Specifies the image file which should be displayed for the control when it has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus | Specifies the image file which should be displayed for the control when it doesn't focus.
+| texturebg | Specifies the image file which should be displayed in the background of the slider portion of the control. Will be positioned so that the right edge is <b>`<textoffsetx>`</b> away from the right edge of the <b>`<texturefocus>`</b> image, and centered vertically.
+| textureslidernib | Specifies the image file which should be displayed for the slider nib.
+| textureslidernibfocus | Specifies the image file which should be displayed for the slider nib when it has focus.
+| label | Either a numeric reference into strings.po (for localization), or a string that will be shown on the left of the control.
+| font | Font used for the controls label. From fonts.xml.
+| textcolor | Color used for displaying the label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the label if the control is disabled. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| textoffsetx | Amount to offset the label from the left edge of the control.
+| info | Specifies the information that the slider controls. [See here for more information](http://kodi.wiki/view/InfoLabels).
+
+
+--------------------------------------------------------------------------------
+\section Settings_Slider_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUISettingsSliderControl.h b/xbmc/guilib/GUISettingsSliderControl.h
new file mode 100644
index 0000000..98b0411
--- /dev/null
+++ b/xbmc/guilib/GUISettingsSliderControl.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUISliderControl.h
+\brief
+*/
+
+#include "GUIButtonControl.h"
+#include "GUISliderControl.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUISettingsSliderControl :
+ public CGUISliderControl
+{
+public:
+ CGUISettingsSliderControl(int parentID, int controlID, float posX, float posY, float width, float height, float sliderWidth, float sliderHeight, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus, const CTextureInfo& backGroundTexture, const CTextureInfo& nibTexture, const CTextureInfo& nibTextureFocus, const CLabelInfo &labelInfo, int iType);
+ ~CGUISettingsSliderControl() override = default;
+ CGUISettingsSliderControl *Clone() const override { return new CGUISettingsSliderControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void OnUnFocus() override;
+ EVENT_RESULT OnMouseEvent(const CPoint& point, const CMouseEvent& event) override;
+ void SetActive();
+ bool IsActive() const override { return m_active; }
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ float GetWidth() const override { return m_buttonControl.GetWidth(); }
+ void SetWidth(float width) override;
+ float GetHeight() const override { return m_buttonControl.GetHeight(); }
+ void SetHeight(float height) override;
+ void SetEnabled(bool bEnable) override;
+
+ void SetText(const std::string& label) { m_buttonControl.SetLabel(label); }
+ float GetXPosition() const override { return m_buttonControl.GetXPosition(); }
+ float GetYPosition() const override { return m_buttonControl.GetYPosition(); }
+ std::string GetDescription() const override;
+ bool HitTest(const CPoint& point) const override { return m_buttonControl.HitTest(point); }
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ virtual void ProcessText();
+
+private:
+ CGUISettingsSliderControl(const CGUISettingsSliderControl& control);
+
+ CGUIButtonControl m_buttonControl;
+ CGUILabel m_label;
+ bool m_active; ///< Whether the slider has been activated by a click.
+};
+
diff --git a/xbmc/guilib/GUIShaderDX.cpp b/xbmc/guilib/GUIShaderDX.cpp
new file mode 100644
index 0000000..b9e801e
--- /dev/null
+++ b/xbmc/guilib/GUIShaderDX.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIShaderDX.h"
+#include "windowing/GraphicContext.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+
+// shaders bytecode includes
+#include "guishader_vert.h"
+#include "guishader_checkerboard_right.h"
+#include "guishader_checkerboard_left.h"
+#include "guishader_default.h"
+#include "guishader_fonts.h"
+#include "guishader_interlaced_right.h"
+#include "guishader_interlaced_left.h"
+#include "guishader_multi_texture_blend.h"
+#include "guishader_texture.h"
+#include "guishader_texture_noblend.h"
+
+#include <d3dcompiler.h>
+
+using namespace DirectX;
+using namespace Microsoft::WRL;
+
+// shaders bytecode holder
+static const D3D_SHADER_DATA cbPSShaderCode[SHADER_METHOD_RENDER_COUNT] =
+{
+ { guishader_default, sizeof(guishader_default) }, // SHADER_METHOD_RENDER_DEFAULT
+ { guishader_texture_noblend, sizeof(guishader_texture_noblend) }, // SHADER_METHOD_RENDER_TEXTURE_NOBLEND
+ { guishader_fonts, sizeof(guishader_fonts) }, // SHADER_METHOD_RENDER_FONT
+ { guishader_texture, sizeof(guishader_texture) }, // SHADER_METHOD_RENDER_TEXTURE_BLEND
+ { guishader_multi_texture_blend, sizeof(guishader_multi_texture_blend) }, // SHADER_METHOD_RENDER_MULTI_TEXTURE_BLEND
+ { guishader_interlaced_left, sizeof(guishader_interlaced_left) }, // SHADER_METHOD_RENDER_STEREO_INTERLACED_LEFT
+ { guishader_interlaced_right, sizeof(guishader_interlaced_right) }, // SHADER_METHOD_RENDER_STEREO_INTERLACED_RIGHT
+ { guishader_checkerboard_left, sizeof(guishader_checkerboard_left) }, // SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_LEFT
+ { guishader_checkerboard_right, sizeof(guishader_checkerboard_right) }, // SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_RIGHT
+};
+
+CGUIShaderDX::CGUIShaderDX() :
+ m_pSampLinear(nullptr),
+ m_pVPBuffer(nullptr),
+ m_pWVPBuffer(nullptr),
+ m_pVertexBuffer(nullptr),
+ m_clipXFactor(0.0f),
+ m_clipXOffset(0.0f),
+ m_clipYFactor(0.0f),
+ m_clipYOffset(0.0f),
+ m_bIsWVPDirty(false),
+ m_bIsVPDirty(false),
+ m_bCreated(false),
+ m_currentShader(0),
+ m_clipPossible(false)
+{
+}
+
+CGUIShaderDX::~CGUIShaderDX()
+{
+ Release();
+}
+
+bool CGUIShaderDX::Initialize()
+{
+ // Create input layout
+ D3D11_INPUT_ELEMENT_DESC layout[] =
+ {
+ { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+
+ if (!m_vertexShader.Create(guishader_vert, sizeof(guishader_vert), layout, ARRAYSIZE(layout)))
+ return false;
+
+ size_t i;
+ bool bSuccess = true;
+ for (i = 0; i < SHADER_METHOD_RENDER_COUNT; i++)
+ {
+ if (!m_pixelShader[i].Create(cbPSShaderCode[i].pBytecode, cbPSShaderCode[i].BytecodeLength))
+ {
+ bSuccess = false;
+ break;
+ }
+ }
+
+ if (!bSuccess)
+ {
+ m_vertexShader.Release();
+ for (size_t j = 0; j < i; j++)
+ m_pixelShader[j].Release();
+ }
+
+ if (!bSuccess || !CreateBuffers() || !CreateSamplers())
+ return false;
+
+ m_bCreated = true;
+ return true;
+}
+
+bool CGUIShaderDX::CreateBuffers()
+{
+ ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
+
+ // create vertex buffer
+ CD3D11_BUFFER_DESC bufferDesc(sizeof(Vertex) * 4, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
+ if (FAILED(pDevice->CreateBuffer(&bufferDesc, NULL, m_pVertexBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to create GUI vertex buffer.");
+ return false;
+ }
+
+ // Create the constant buffer for WVP
+ size_t buffSize = (sizeof(cbWorld) + 15) & ~15;
+ CD3D11_BUFFER_DESC cbbd(buffSize, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); // it can change very frequently
+ if (FAILED(pDevice->CreateBuffer(&cbbd, NULL, m_pWVPBuffer.ReleaseAndGetAddressOf())))
+ {
+ CLog::LogF(LOGERROR, "Failed to create the constant buffer.");
+ return false;
+ }
+ m_bIsWVPDirty = true;
+
+ CRect viewPort;
+ DX::Windowing()->GetViewPort(viewPort);
+
+ // initial data for viewport buffer
+ m_cbViewPort.TopLeftX = viewPort.x1;
+ m_cbViewPort.TopLeftY = viewPort.y1;
+ m_cbViewPort.Width = viewPort.Width();
+ m_cbViewPort.Height = viewPort.Height();
+
+ cbbd.ByteWidth = sizeof(cbViewPort);
+ D3D11_SUBRESOURCE_DATA initData = { &m_cbViewPort, 0, 0 };
+ // create viewport buffer
+ if (FAILED(pDevice->CreateBuffer(&cbbd, &initData, m_pVPBuffer.ReleaseAndGetAddressOf())))
+ return false;
+
+ return true;
+}
+
+bool CGUIShaderDX::CreateSamplers()
+{
+ // Describe the Sampler State
+ D3D11_SAMPLER_DESC sampDesc = {};
+ sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+ sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
+ sampDesc.MinLOD = 0;
+ sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
+
+ if (FAILED(DX::DeviceResources::Get()->GetD3DDevice()->CreateSamplerState(&sampDesc, m_pSampLinear.ReleaseAndGetAddressOf())))
+ return false;
+
+ DX::DeviceResources::Get()->GetD3DContext()->PSSetSamplers(0, 1, m_pSampLinear.GetAddressOf());
+
+ return true;
+}
+
+void CGUIShaderDX::ApplyStateBlock(void)
+{
+ if (!m_bCreated)
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+
+ m_vertexShader.BindShader();
+ pContext->VSSetConstantBuffers(0, 1, m_pWVPBuffer.GetAddressOf());
+
+ m_pixelShader[m_currentShader].BindShader();
+ pContext->PSSetConstantBuffers(0, 1, m_pWVPBuffer.GetAddressOf());
+ pContext->PSSetConstantBuffers(1, 1, m_pVPBuffer.GetAddressOf());
+
+ pContext->PSSetSamplers(0, 1, m_pSampLinear.GetAddressOf());
+
+ RestoreBuffers();
+}
+
+void CGUIShaderDX::Begin(unsigned int flags)
+{
+ if (!m_bCreated)
+ return;
+
+ if (m_currentShader != flags)
+ {
+ m_currentShader = flags;
+ m_pixelShader[m_currentShader].BindShader();
+ }
+ ClipToScissorParams();
+}
+
+void CGUIShaderDX::End()
+{
+ if (!m_bCreated)
+ return;
+}
+
+void CGUIShaderDX::DrawQuad(Vertex& v1, Vertex& v2, Vertex& v3, Vertex& v4)
+{
+ if (!m_bCreated)
+ return;
+
+ ApplyChanges();
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+
+ // update vertex buffer
+ D3D11_MAPPED_SUBRESOURCE resource;
+ if (SUCCEEDED(pContext->Map(m_pVertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &resource)))
+ {
+ // we are using strip topology
+ Vertex vertices[4] = { v2, v3, v1, v4 };
+ memcpy(resource.pData, &vertices, sizeof(Vertex) * 4);
+ pContext->Unmap(m_pVertexBuffer.Get(), 0);
+ // Draw primitives
+ pContext->Draw(4, 0);
+ }
+}
+
+void CGUIShaderDX::DrawIndexed(unsigned int indexCount, unsigned int startIndex, unsigned int startVertex)
+{
+ if (!m_bCreated)
+ return;
+
+ ApplyChanges();
+ DX::DeviceResources::Get()->GetD3DContext()->DrawIndexed(indexCount, startIndex, startVertex);
+}
+
+void CGUIShaderDX::Draw(unsigned int vertexCount, unsigned int startVertex)
+{
+ if (!m_bCreated)
+ return;
+
+ ApplyChanges();
+ DX::DeviceResources::Get()->GetD3DContext()->Draw(vertexCount, startVertex);
+}
+
+void CGUIShaderDX::SetShaderViews(unsigned int numViews, ID3D11ShaderResourceView** views)
+{
+ if (!m_bCreated)
+ return;
+
+ DX::DeviceResources::Get()->GetD3DContext()->PSSetShaderResources(0, numViews, views);
+}
+
+void CGUIShaderDX::Release()
+{
+ m_pVertexBuffer = nullptr;
+ m_pWVPBuffer = nullptr;
+ m_pVPBuffer = nullptr;
+ m_pSampLinear = nullptr;
+ m_bCreated = false;
+}
+
+void CGUIShaderDX::SetViewPort(D3D11_VIEWPORT viewPort)
+{
+ if (!m_pVPBuffer)
+ return;
+
+ if ( viewPort.TopLeftX != m_cbViewPort.TopLeftX
+ || viewPort.TopLeftY != m_cbViewPort.TopLeftY
+ || viewPort.Width != m_cbViewPort.Width
+ || viewPort.Height != m_cbViewPort.Height)
+ {
+ m_cbViewPort.TopLeftX = viewPort.TopLeftX;
+ m_cbViewPort.TopLeftY = viewPort.TopLeftY;
+ m_cbViewPort.Width = viewPort.Width;
+ m_cbViewPort.Height = viewPort.Height;
+ m_bIsVPDirty = true;
+ }
+}
+
+void CGUIShaderDX::Project(float &x, float &y, float &z)
+{
+#if defined(_XM_SSE_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
+ XMVECTOR vLocation = { x, y, z };
+#elif defined(_XM_ARM_NEON_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
+ XMVECTOR vLocation = { x, y };
+#endif
+ XMVECTOR vScreenCoord = XMVector3Project(vLocation, m_cbViewPort.TopLeftX, m_cbViewPort.TopLeftY,
+ m_cbViewPort.Width, m_cbViewPort.Height, 0, 1,
+ m_cbWorldViewProj.projection, m_cbWorldViewProj.view, m_cbWorldViewProj.world);
+ x = XMVectorGetX(vScreenCoord);
+ y = XMVectorGetY(vScreenCoord);
+ z = 0;
+}
+
+void XM_CALLCONV CGUIShaderDX::SetWVP(const XMMATRIX &w, const XMMATRIX &v, const XMMATRIX &p)
+{
+ m_bIsWVPDirty = true;
+ m_cbWorldViewProj.world = w;
+ m_cbWorldViewProj.view = v;
+ m_cbWorldViewProj.projection = p;
+}
+
+void CGUIShaderDX::SetWorld(const XMMATRIX &value)
+{
+ m_bIsWVPDirty = true;
+ m_cbWorldViewProj.world = value;
+}
+
+void CGUIShaderDX::SetView(const XMMATRIX &value)
+{
+ m_bIsWVPDirty = true;
+ m_cbWorldViewProj.view = value;
+}
+
+void CGUIShaderDX::SetProjection(const XMMATRIX &value)
+{
+ m_bIsWVPDirty = true;
+ m_cbWorldViewProj.projection = value;
+}
+
+void CGUIShaderDX::ApplyChanges(void)
+{
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ D3D11_MAPPED_SUBRESOURCE res;
+
+ if (m_bIsWVPDirty)
+ {
+ if (SUCCEEDED(pContext->Map(m_pWVPBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res)))
+ {
+ XMMATRIX worldView = XMMatrixMultiply(m_cbWorldViewProj.world, m_cbWorldViewProj.view);
+ XMMATRIX worldViewProj = XMMatrixMultiplyTranspose(worldView, m_cbWorldViewProj.projection);
+
+ cbWorld* buffer = (cbWorld*)res.pData;
+ buffer->wvp = worldViewProj;
+ buffer->blackLevel = (DX::Windowing()->UseLimitedColor() ? 16.f / 255.f : 0.f);
+ buffer->colorRange = (DX::Windowing()->UseLimitedColor() ? (235.f - 16.f) / 255.f : 1.0f);
+ buffer->sdrPeakLum = (100 - DX::Windowing()->GetGuiSdrPeakLuminance()) + 10;
+ buffer->PQ = (DX::Windowing()->IsTransferPQ() ? 1 : 0);
+
+ pContext->Unmap(m_pWVPBuffer.Get(), 0);
+ m_bIsWVPDirty = false;
+ }
+ }
+
+ // update view port buffer
+ if (m_bIsVPDirty)
+ {
+ if (SUCCEEDED(pContext->Map(m_pVPBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res)))
+ {
+ *(cbViewPort*)res.pData = m_cbViewPort;
+ pContext->Unmap(m_pVPBuffer.Get(), 0);
+ m_bIsVPDirty = false;
+ }
+ }
+}
+
+void CGUIShaderDX::RestoreBuffers(void)
+{
+ const unsigned stride = sizeof(Vertex);
+ const unsigned offset = 0;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+ // Set the vertex buffer to active in the input assembler so it can be rendered.
+ pContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);
+ // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
+ pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
+}
+
+void CGUIShaderDX::ClipToScissorParams(void)
+{
+ CRect viewPort; // absolute positions of corners
+ DX::Windowing()->GetViewPort(viewPort);
+
+ // get current GUI transform
+ const TransformMatrix &guiMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIMatrix();
+ // get current GPU transforms
+ XMFLOAT4X4 world, view, projection;
+ XMStoreFloat4x4(&world, m_cbWorldViewProj.world);
+ XMStoreFloat4x4(&view, m_cbWorldViewProj.view);
+ XMStoreFloat4x4(&projection, m_cbWorldViewProj.projection);
+
+ m_clipPossible = guiMatrix.m[0][1] == 0 &&
+ guiMatrix.m[1][0] == 0 &&
+ guiMatrix.m[2][0] == 0 &&
+ guiMatrix.m[2][1] == 0 &&
+ view.m[0][1] == 0 &&
+ view.m[0][2] == 0 &&
+ view.m[1][0] == 0 &&
+ view.m[1][2] == 0 &&
+ view.m[2][0] == 0 &&
+ view.m[2][1] == 0 &&
+ projection.m[0][1] == 0 &&
+ projection.m[0][2] == 0 &&
+ projection.m[0][3] == 0 &&
+ projection.m[1][0] == 0 &&
+ projection.m[1][2] == 0 &&
+ projection.m[1][3] == 0 &&
+ projection.m[3][0] == 0 &&
+ projection.m[3][1] == 0 &&
+ projection.m[3][3] == 0;
+
+ m_clipXFactor = 0.0f;
+ m_clipXOffset = 0.0f;
+ m_clipYFactor = 0.0f;
+ m_clipYOffset = 0.0f;
+
+ if (m_clipPossible)
+ {
+ m_clipXFactor = guiMatrix.m[0][0] * view.m[0][0] * projection.m[0][0];
+ m_clipXOffset = (guiMatrix.m[0][3] * view.m[0][0] + view.m[3][0]) * projection.m[0][0];
+ m_clipYFactor = guiMatrix.m[1][1] * view.m[1][1] * projection.m[1][1];
+ m_clipYOffset = (guiMatrix.m[1][3] * view.m[1][1] + view.m[3][1]) * projection.m[1][1];
+
+ float clipW = (guiMatrix.m[2][3] * view.m[2][2] + view.m[3][2]) * projection.m[2][3];
+ float xMult = (viewPort.x2 - viewPort.x1) / (2 * clipW);
+ float yMult = (viewPort.y1 - viewPort.y2) / (2 * clipW); // correct for inverted window coordinate scheme
+
+ m_clipXFactor = m_clipXFactor * xMult;
+ m_clipXOffset = m_clipXOffset * xMult + (viewPort.x2 + viewPort.x1) / 2;
+ m_clipYFactor = m_clipYFactor * yMult;
+ m_clipYOffset = m_clipYOffset * yMult + (viewPort.y2 + viewPort.y1) / 2;
+ }
+}
diff --git a/xbmc/guilib/GUIShaderDX.h b/xbmc/guilib/GUIShaderDX.h
new file mode 100644
index 0000000..7b6a170
--- /dev/null
+++ b/xbmc/guilib/GUIShaderDX.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "D3DResource.h"
+#include "Texture.h"
+#include "utils/Geometry.h"
+#include "utils/MemUtils.h"
+
+#include <DirectXMath.h>
+#include <wrl/client.h>
+
+struct Vertex {
+ Vertex() {}
+
+ Vertex(DirectX::XMFLOAT3 p, DirectX::XMFLOAT4 c)
+ : pos(p), color(c) {}
+
+ Vertex(DirectX::XMFLOAT3 p, DirectX::XMFLOAT4 c, DirectX::XMFLOAT2 t1, DirectX::XMFLOAT2 t2)
+ : pos(p), color(c), texCoord(t1), texCoord2(t2) {}
+
+ DirectX::XMFLOAT3 pos;
+ DirectX::XMFLOAT4 color;
+ DirectX::XMFLOAT2 texCoord;
+ DirectX::XMFLOAT2 texCoord2;
+};
+
+class ID3DResource;
+
+class CGUIShaderDX
+{
+public:
+ CGUIShaderDX();
+ ~CGUIShaderDX();
+
+ bool Initialize();
+ void Begin(unsigned int flags);
+ void End(void);
+ void ApplyStateBlock(void);
+ void RestoreBuffers(void);
+
+ void SetShaderViews(unsigned int numViews, ID3D11ShaderResourceView** views);
+ void SetViewPort(D3D11_VIEWPORT viewPort);
+
+ void XM_CALLCONV GetWVP(DirectX::XMMATRIX &w, DirectX::XMMATRIX &v, DirectX::XMMATRIX &p)
+ {
+ w = m_cbWorldViewProj.world;
+ v = m_cbWorldViewProj.view;
+ p = m_cbWorldViewProj.projection;
+ }
+ DirectX::XMMATRIX XM_CALLCONV GetWorld() const { return m_cbWorldViewProj.world; }
+ DirectX::XMMATRIX XM_CALLCONV GetView() const { return m_cbWorldViewProj.view; }
+ DirectX::XMMATRIX XM_CALLCONV GetProjection() const { return m_cbWorldViewProj.projection; }
+ void XM_CALLCONV SetWVP(const DirectX::XMMATRIX &w, const DirectX::XMMATRIX &v, const DirectX::XMMATRIX &p);
+ void XM_CALLCONV SetWorld(const DirectX::XMMATRIX &value);
+ void XM_CALLCONV SetView(const DirectX::XMMATRIX &value);
+ void XM_CALLCONV SetProjection(const DirectX::XMMATRIX &value);
+ void Project(float &x, float &y, float &z);
+
+ void DrawQuad(Vertex& v1, Vertex& v2, Vertex& v3, Vertex& v4);
+ void DrawIndexed(unsigned int indexCount, unsigned int startIndex, unsigned int startVertex);
+ void Draw(unsigned int vertexCount, unsigned int startVertex);
+
+ bool HardwareClipIsPossible(void) const { return m_clipPossible; }
+ float GetClipXFactor(void) const { return m_clipXFactor; }
+ float GetClipXOffset(void) const { return m_clipXOffset; }
+ float GetClipYFactor(void) const { return m_clipYFactor; }
+ float GetClipYOffset(void) const { return m_clipYOffset; }
+
+ // need to use aligned allocation because we use XMMATRIX in structures.
+ void* operator new (size_t size)
+ {
+ void* ptr = KODI::MEMORY::AlignedMalloc(size, __alignof(CGUIShaderDX));
+ if (!ptr)
+ throw std::bad_alloc();
+ return ptr;
+ }
+ // free aligned memory.
+ void operator delete (void* ptr)
+ {
+ KODI::MEMORY::AlignedFree(ptr);
+ }
+
+private:
+ struct cbWorldViewProj
+ {
+ DirectX::XMMATRIX world;
+ DirectX::XMMATRIX view;
+ DirectX::XMMATRIX projection;
+ };
+ struct cbViewPort
+ {
+ float TopLeftX;
+ float TopLeftY;
+ float Width;
+ float Height;
+ };
+ struct cbWorld
+ {
+ DirectX::XMMATRIX wvp;
+ float blackLevel;
+ float colorRange;
+ float sdrPeakLum;
+ int PQ;
+ };
+
+ void Release(void);
+ bool CreateBuffers(void);
+ bool CreateSamplers(void);
+ void ApplyChanges(void);
+ void ClipToScissorParams(void);
+
+ // GUI constants
+ cbViewPort m_cbViewPort = {};
+ cbWorldViewProj m_cbWorldViewProj = {};
+
+ bool m_bCreated;
+ size_t m_currentShader;
+ CD3DVertexShader m_vertexShader;
+ CD3DPixelShader m_pixelShader[SHADER_METHOD_RENDER_COUNT];
+ Microsoft::WRL::ComPtr<ID3D11SamplerState> m_pSampLinear;
+
+ // GUI buffers
+ bool m_bIsWVPDirty;
+ bool m_bIsVPDirty;
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_pWVPBuffer;
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_pVPBuffer;
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_pVertexBuffer;
+
+ // clip to scissors params
+ bool m_clipPossible;
+ float m_clipXFactor;
+ float m_clipXOffset;
+ float m_clipYFactor;
+ float m_clipYOffset;
+};
diff --git a/xbmc/guilib/GUISliderControl.cpp b/xbmc/guilib/GUISliderControl.cpp
new file mode 100644
index 0000000..7fd5b6e
--- /dev/null
+++ b/xbmc/guilib/GUISliderControl.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUISliderControl.h"
+
+#include "GUIComponent.h"
+#include "GUIInfoManager.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
+
+static const SliderAction actions[] = {
+ {"seek", "PlayerControl(SeekPercentage({:2f}))", PLAYER_PROGRESS, false},
+ {"pvr.seek", "PVR.SeekPercentage({:2f})", PVR_TIMESHIFT_PROGRESS_PLAY_POS, false},
+ {"volume", "SetVolume({:2f})", PLAYER_VOLUME, true}};
+
+CGUISliderControl::CGUISliderControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& backGroundTexture,
+ const CTextureInfo& nibTexture,
+ const CTextureInfo& nibTextureFocus,
+ int iType,
+ ORIENTATION orientation)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_guiBackground(CGUITexture::CreateTexture(posX, posY, width, height, backGroundTexture)),
+ m_guiSelectorLower(CGUITexture::CreateTexture(posX, posY, width, height, nibTexture)),
+ m_guiSelectorUpper(CGUITexture::CreateTexture(posX, posY, width, height, nibTexture)),
+ m_guiSelectorLowerFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTextureFocus)),
+ m_guiSelectorUpperFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTextureFocus))
+{
+ m_iType = iType;
+ m_rangeSelection = false;
+ m_currentSelector = RangeSelectorLower; // use lower selector by default
+ m_percentValues[0] = 0;
+ m_percentValues[1] = 100;
+ m_iStart = 0;
+ m_iEnd = 100;
+ m_iInterval = 1;
+ m_fStart = 0.0f;
+ m_fEnd = 1.0f;
+ m_fInterval = 0.1f;
+ m_intValues[0] = m_iStart;
+ m_intValues[1] = m_iEnd;
+ m_floatValues[0] = m_fStart;
+ m_floatValues[1] = m_fEnd;
+ ControlType = GUICONTROL_SLIDER;
+ m_orientation = orientation;
+ m_iInfoCode = 0;
+ m_dragging = false;
+ m_action = NULL;
+}
+
+CGUISliderControl::CGUISliderControl(const CGUISliderControl& control)
+ : CGUIControl(control),
+ m_guiBackground(control.m_guiBackground->Clone()),
+ m_guiSelectorLower(control.m_guiSelectorLower->Clone()),
+ m_guiSelectorUpper(control.m_guiSelectorUpper->Clone()),
+ m_guiSelectorLowerFocus(control.m_guiSelectorLowerFocus->Clone()),
+ m_guiSelectorUpperFocus(control.m_guiSelectorUpperFocus->Clone()),
+ m_iType(control.m_iType),
+ m_rangeSelection(control.m_rangeSelection),
+ m_currentSelector(control.m_currentSelector),
+ m_percentValues(control.m_percentValues),
+ m_intValues(control.m_intValues),
+ m_iStart(control.m_iStart),
+ m_iInterval(control.m_iInterval),
+ m_iEnd(control.m_iEnd),
+ m_floatValues(control.m_floatValues),
+ m_fStart(control.m_fStart),
+ m_fInterval(control.m_fInterval),
+ m_fEnd(control.m_fEnd),
+ m_iInfoCode(control.m_iInfoCode),
+ m_textValue(control.m_textValue),
+ m_action(control.m_action),
+ m_dragging(control.m_dragging),
+ m_orientation(control.m_orientation)
+{
+}
+
+void CGUISliderControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool dirty = false;
+
+ dirty |= m_guiBackground->SetPosition(m_posX, m_posY);
+ int infoCode = m_iInfoCode;
+ if (m_action && (!m_dragging || m_action->fireOnDrag))
+ infoCode = m_action->infoCode;
+ if (infoCode)
+ {
+ int val;
+ if (CServiceBroker::GetGUI()->GetInfoManager().GetInt(val, infoCode, INFO::DEFAULT_CONTEXT))
+ SetIntValue(val);
+ }
+
+ dirty |= m_guiBackground->SetHeight(m_height);
+ dirty |= m_guiBackground->SetWidth(m_width);
+ dirty |= m_guiBackground->Process(currentTime);
+
+ CGUITexture* nibLower =
+ (IsActive() && m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorLower)
+ ? m_guiSelectorLowerFocus.get()
+ : m_guiSelectorLower.get();
+
+ float fScale = 1.0f;
+
+ if (m_orientation == HORIZONTAL && m_guiBackground->GetTextureHeight() != 0)
+ fScale = m_height / m_guiBackground->GetTextureHeight();
+ else if (m_width != 0 && nibLower->GetTextureWidth() != 0)
+ fScale = m_width / nibLower->GetTextureWidth();
+ dirty |= ProcessSelector(nibLower, currentTime, fScale, RangeSelectorLower);
+ if (m_rangeSelection)
+ {
+ CGUITexture* nibUpper =
+ (IsActive() && m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorUpper)
+ ? m_guiSelectorUpperFocus.get()
+ : m_guiSelectorUpper.get();
+
+ if (m_orientation == HORIZONTAL && m_guiBackground->GetTextureHeight() != 0)
+ fScale = m_height / m_guiBackground->GetTextureHeight();
+ else if (m_width != 0 && nibUpper->GetTextureWidth() != 0)
+ fScale = m_width / nibUpper->GetTextureWidth();
+
+ dirty |= ProcessSelector(nibUpper, currentTime, fScale, RangeSelectorUpper);
+ }
+
+ if (dirty)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+bool CGUISliderControl::ProcessSelector(CGUITexture* nib,
+ unsigned int currentTime,
+ float fScale,
+ RangeSelector selector)
+{
+ bool dirty = false;
+ // we render the nib centered at the appropriate percentage, except where the nib
+ // would overflow the background image
+ if (m_orientation == HORIZONTAL)
+ {
+ dirty |= nib->SetHeight(nib->GetTextureHeight() * fScale);
+ dirty |= nib->SetWidth(nib->GetHeight() * 2);
+ }
+ else
+ {
+ dirty |= nib->SetWidth(nib->GetTextureWidth() * fScale);
+ dirty |= nib->SetHeight(nib->GetWidth() * 2);
+ }
+ CAspectRatio ratio(CAspectRatio::AR_KEEP);
+ ratio.align = ASPECT_ALIGN_LEFT | ASPECT_ALIGNY_CENTER;
+ dirty |= nib->SetAspectRatio(ratio);
+ dirty |= nib->Process(currentTime);
+ CRect rect = nib->GetRenderRect();
+
+ float offset;
+ if (m_orientation == HORIZONTAL)
+ {
+ offset = GetProportion(selector) * m_width - rect.Width() / 2;
+ if (offset > m_width - rect.Width())
+ offset = m_width - rect.Width();
+ if (offset < 0)
+ offset = 0;
+ dirty |=
+ nib->SetPosition(m_guiBackground->GetXPosition() + offset, m_guiBackground->GetYPosition());
+ }
+ else
+ {
+ offset = GetProportion(selector) * m_height - rect.Height() / 2;
+ if (offset > m_height - rect.Height())
+ offset = m_height - rect.Height();
+ if (offset < 0)
+ offset = 0;
+ dirty |=
+ nib->SetPosition(m_guiBackground->GetXPosition(),
+ m_guiBackground->GetYPosition() + m_guiBackground->GetHeight() - offset -
+ ((nib->GetHeight() - rect.Height()) / 2 + rect.Height()));
+ }
+ dirty |= nib->Process(currentTime); // need to process again as the position may have changed
+
+ return dirty;
+}
+
+void CGUISliderControl::Render()
+{
+ m_guiBackground->Render();
+ CGUITexture* nibLower =
+ (IsActive() && m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorLower)
+ ? m_guiSelectorLowerFocus.get()
+ : m_guiSelectorLower.get();
+ nibLower->Render();
+ if (m_rangeSelection)
+ {
+ CGUITexture* nibUpper =
+ (IsActive() && m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorUpper)
+ ? m_guiSelectorUpperFocus.get()
+ : m_guiSelectorUpper.get();
+ nibUpper->Render();
+ }
+ CGUIControl::Render();
+}
+
+bool CGUISliderControl::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_ITEM_SELECT:
+ SetPercentage( (float)message.GetParam1() );
+ return true;
+ break;
+
+ case GUI_MSG_LABEL_RESET:
+ {
+ SetPercentage(0, RangeSelectorLower);
+ SetPercentage(100, RangeSelectorUpper);
+ return true;
+ }
+ break;
+ }
+ }
+
+ return CGUIControl::OnMessage(message);
+}
+
+bool CGUISliderControl::OnAction(const CAction &action)
+{
+ switch ( action.GetID() )
+ {
+ case ACTION_MOVE_LEFT:
+ if (IsActive() && m_orientation == HORIZONTAL)
+ {
+ Move(-1);
+ return true;
+ }
+ break;
+
+ case ACTION_MOVE_RIGHT:
+ if (IsActive() && m_orientation == HORIZONTAL)
+ {
+ Move(1);
+ return true;
+ }
+ break;
+
+ case ACTION_MOVE_UP:
+ if (IsActive() && m_orientation == VERTICAL)
+ {
+ Move(1);
+ return true;
+ }
+ break;
+
+ case ACTION_MOVE_DOWN:
+ if (IsActive() && m_orientation == VERTICAL)
+ {
+ Move(-1);
+ return true;
+ }
+ break;
+
+ case ACTION_SELECT_ITEM:
+ if (m_rangeSelection)
+ SwitchRangeSelector();
+ return true;
+
+ default:
+ break;
+ }
+ return CGUIControl::OnAction(action);
+}
+
+void CGUISliderControl::Move(int iNumSteps)
+{
+ bool rangeSwap = false;
+ switch (m_iType)
+ {
+ case SLIDER_CONTROL_TYPE_FLOAT:
+ {
+ float &value = m_floatValues[m_currentSelector];
+ value += m_fInterval * iNumSteps;
+ if (value < m_fStart) value = m_fStart;
+ if (value > m_fEnd) value = m_fEnd;
+ if (m_floatValues[0] > m_floatValues[1])
+ {
+ float valueLower = m_floatValues[0];
+ m_floatValues[0] = m_floatValues[1];
+ m_floatValues[1] = valueLower;
+ rangeSwap = true;
+ }
+ break;
+ }
+
+ case SLIDER_CONTROL_TYPE_INT:
+ {
+ int &value = m_intValues[m_currentSelector];
+ value += m_iInterval * iNumSteps;
+ if (value < m_iStart) value = m_iStart;
+ if (value > m_iEnd) value = m_iEnd;
+ if (m_intValues[0] > m_intValues[1])
+ {
+ int valueLower = m_intValues[0];
+ m_intValues[0] = m_intValues[1];
+ m_intValues[1] = valueLower;
+ rangeSwap = true;
+ }
+ break;
+ }
+
+ case SLIDER_CONTROL_TYPE_PERCENTAGE:
+ default:
+ {
+ float &value = m_percentValues[m_currentSelector];
+ value += m_iInterval * iNumSteps;
+ if (value < 0) value = 0;
+ if (value > 100) value = 100;
+ if (m_percentValues[0] > m_percentValues[1])
+ {
+ float valueLower = m_percentValues[0];
+ m_percentValues[0] = m_percentValues[1];
+ m_percentValues[1] = valueLower;
+ rangeSwap = true;
+ }
+ break;
+ }
+ }
+
+ if (rangeSwap)
+ SwitchRangeSelector();
+
+ SendClick();
+}
+
+void CGUISliderControl::SendClick()
+{
+ float percent = 100*GetProportion();
+ SEND_CLICK_MESSAGE(GetID(), GetParentID(), MathUtils::round_int(static_cast<double>(percent)));
+ if (m_action && (!m_dragging || m_action->fireOnDrag))
+ {
+ std::string action = StringUtils::Format(m_action->formatString, percent);
+ CGUIMessage message(GUI_MSG_EXECUTE, m_controlID, m_parentID);
+ message.SetStringParam(action);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+ }
+}
+
+void CGUISliderControl::SetRangeSelection(bool rangeSelection)
+{
+ if (m_rangeSelection == rangeSelection)
+ return;
+
+ m_rangeSelection = rangeSelection;
+ SetRangeSelector(RangeSelectorLower);
+ SetInvalid();
+}
+
+void CGUISliderControl::SetRangeSelector(RangeSelector selector)
+{
+ if (m_currentSelector == selector)
+ return;
+
+ m_currentSelector = selector;
+ SetInvalid();
+}
+
+void CGUISliderControl::SwitchRangeSelector()
+{
+ if (m_currentSelector == RangeSelectorLower)
+ SetRangeSelector(RangeSelectorUpper);
+ else
+ SetRangeSelector(RangeSelectorLower);
+}
+
+void CGUISliderControl::SetPercentage(float percent, RangeSelector selector /* = RangeSelectorLower */, bool updateCurrent /* = false */)
+{
+ if (percent > 100) percent = 100;
+ else if (percent < 0) percent = 0;
+
+ float percentLower = selector == RangeSelectorLower ? percent : m_percentValues[0];
+ float percentUpper = selector == RangeSelectorUpper ? percent : m_percentValues[1];
+ const float oldValues[2] = {m_percentValues[0], m_percentValues[1]};
+
+ if (!m_rangeSelection || percentLower <= percentUpper)
+ {
+ m_percentValues[0] = percentLower;
+ m_percentValues[1] = percentUpper;
+ if (updateCurrent)
+ m_currentSelector = selector;
+ }
+ else
+ {
+ m_percentValues[0] = percentUpper;
+ m_percentValues[1] = percentLower;
+ if (updateCurrent)
+ m_currentSelector = (selector == RangeSelectorLower ? RangeSelectorUpper : RangeSelectorLower);
+ }
+ if (oldValues[0] != m_percentValues[0] || oldValues[1] != m_percentValues[1])
+ MarkDirtyRegion();
+}
+
+float CGUISliderControl::GetPercentage(RangeSelector selector /* = RangeSelectorLower */) const
+{
+ return m_percentValues[selector];
+}
+
+void CGUISliderControl::SetIntValue(int iValue, RangeSelector selector /* = RangeSelectorLower */, bool updateCurrent /* = false */)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ SetFloatValue((float)iValue, selector, updateCurrent);
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ {
+ if (iValue > m_iEnd) iValue = m_iEnd;
+ else if (iValue < m_iStart) iValue = m_iStart;
+
+ int iValueLower = selector == RangeSelectorLower ? iValue : m_intValues[0];
+ int iValueUpper = selector == RangeSelectorUpper ? iValue : m_intValues[1];
+
+ if (!m_rangeSelection || iValueLower <= iValueUpper)
+ {
+ m_intValues[0] = iValueLower;
+ m_intValues[1] = iValueUpper;
+ if (updateCurrent)
+ m_currentSelector = selector;
+ }
+ else
+ {
+ m_intValues[0] = iValueUpper;
+ m_intValues[1] = iValueLower;
+ if (updateCurrent)
+ m_currentSelector = (selector == RangeSelectorLower ? RangeSelectorUpper : RangeSelectorLower);
+ }
+ }
+ else
+ SetPercentage((float)iValue, selector, updateCurrent);
+}
+
+int CGUISliderControl::GetIntValue(RangeSelector selector /* = RangeSelectorLower */) const
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ return (int)m_floatValues[selector];
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ return m_intValues[selector];
+ else
+ return MathUtils::round_int(static_cast<double>(m_percentValues[selector]));
+}
+
+void CGUISliderControl::SetFloatValue(float fValue, RangeSelector selector /* = RangeSelectorLower */, bool updateCurrent /* = false */)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ {
+ if (fValue > m_fEnd) fValue = m_fEnd;
+ else if (fValue < m_fStart) fValue = m_fStart;
+
+ float fValueLower = selector == RangeSelectorLower ? fValue : m_floatValues[0];
+ float fValueUpper = selector == RangeSelectorUpper ? fValue : m_floatValues[1];
+
+ if (!m_rangeSelection || fValueLower <= fValueUpper)
+ {
+ m_floatValues[0] = fValueLower;
+ m_floatValues[1] = fValueUpper;
+ if (updateCurrent)
+ m_currentSelector = selector;
+ }
+ else
+ {
+ m_floatValues[0] = fValueUpper;
+ m_floatValues[1] = fValueLower;
+ if (updateCurrent)
+ m_currentSelector = (selector == RangeSelectorLower ? RangeSelectorUpper : RangeSelectorLower);
+ }
+ }
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ SetIntValue((int)fValue, selector, updateCurrent);
+ else
+ SetPercentage(fValue, selector, updateCurrent);
+}
+
+float CGUISliderControl::GetFloatValue(RangeSelector selector /* = RangeSelectorLower */) const
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ return m_floatValues[selector];
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ return (float)m_intValues[selector];
+ else
+ return m_percentValues[selector];
+}
+
+void CGUISliderControl::SetIntInterval(int iInterval)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ m_fInterval = (float)iInterval;
+ else
+ m_iInterval = iInterval;
+}
+
+void CGUISliderControl::SetFloatInterval(float fInterval)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ m_fInterval = fInterval;
+ else
+ m_iInterval = (int)fInterval;
+}
+
+void CGUISliderControl::SetRange(int iStart, int iEnd)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ SetFloatRange((float)iStart,(float)iEnd);
+ else
+ {
+ m_intValues[0] = m_iStart = iStart;
+ m_intValues[1] = m_iEnd = iEnd;
+ }
+}
+
+void CGUISliderControl::SetFloatRange(float fStart, float fEnd)
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ SetRange((int)fStart, (int)fEnd);
+ else
+ {
+ m_floatValues[0] = m_fStart = fStart;
+ m_floatValues[1] = m_fEnd = fEnd;
+ }
+}
+
+void CGUISliderControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_guiBackground->FreeResources(immediately);
+ m_guiSelectorLower->FreeResources(immediately);
+ m_guiSelectorUpper->FreeResources(immediately);
+ m_guiSelectorLowerFocus->FreeResources(immediately);
+ m_guiSelectorUpperFocus->FreeResources(immediately);
+}
+
+void CGUISliderControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_guiBackground->DynamicResourceAlloc(bOnOff);
+ m_guiSelectorLower->DynamicResourceAlloc(bOnOff);
+ m_guiSelectorUpper->DynamicResourceAlloc(bOnOff);
+ m_guiSelectorLowerFocus->DynamicResourceAlloc(bOnOff);
+ m_guiSelectorUpperFocus->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUISliderControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_guiBackground->AllocResources();
+ m_guiSelectorLower->AllocResources();
+ m_guiSelectorUpper->AllocResources();
+ m_guiSelectorLowerFocus->AllocResources();
+ m_guiSelectorUpperFocus->AllocResources();
+}
+
+void CGUISliderControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_guiBackground->SetInvalid();
+ m_guiSelectorLower->SetInvalid();
+ m_guiSelectorUpper->SetInvalid();
+ m_guiSelectorLowerFocus->SetInvalid();
+ m_guiSelectorUpperFocus->SetInvalid();
+}
+
+bool CGUISliderControl::HitTest(const CPoint &point) const
+{
+ if (m_guiBackground->HitTest(point))
+ return true;
+ if (m_guiSelectorLower->HitTest(point))
+ return true;
+ if (m_rangeSelection && m_guiSelectorUpper->HitTest(point))
+ return true;
+ return false;
+}
+
+void CGUISliderControl::SetFromPosition(const CPoint &point, bool guessSelector /* = false */)
+{
+
+ float fPercent;
+ if (m_orientation == HORIZONTAL)
+ fPercent = (point.x - m_guiBackground->GetXPosition()) / m_guiBackground->GetWidth();
+ else
+ fPercent = (m_guiBackground->GetYPosition() + m_guiBackground->GetHeight() - point.y) /
+ m_guiBackground->GetHeight();
+
+ if (fPercent < 0) fPercent = 0;
+ if (fPercent > 1) fPercent = 1;
+
+ if (m_rangeSelection && guessSelector)
+ {
+ // choose selector which value is closer to value calculated from position
+ if (fabs(GetPercentage(RangeSelectorLower) - 100 * fPercent) <= fabs(GetPercentage(RangeSelectorUpper) - 100 * fPercent))
+ m_currentSelector = RangeSelectorLower;
+ else
+ m_currentSelector = RangeSelectorUpper;
+ }
+
+ switch (m_iType)
+ {
+ case SLIDER_CONTROL_TYPE_FLOAT:
+ {
+ float fValue = m_fStart + (m_fEnd - m_fStart) * fPercent;
+ SetFloatValue(MathUtils::RoundF(fValue, m_fInterval), m_currentSelector, true);
+ break;
+ }
+
+ case SLIDER_CONTROL_TYPE_INT:
+ {
+ int iValue = (int)(m_iStart + (float)(m_iEnd - m_iStart) * fPercent + 0.49f);
+ SetIntValue(iValue, m_currentSelector, true);
+ break;
+ }
+
+ case SLIDER_CONTROL_TYPE_PERCENTAGE:
+ default:
+ {
+ SetPercentage(fPercent * 100, m_currentSelector, true);
+ break;
+ }
+ }
+ SendClick();
+}
+
+EVENT_RESULT CGUISliderControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ m_dragging = false;
+ if (event.m_id == ACTION_MOUSE_DRAG || event.m_id == ACTION_MOUSE_DRAG_END)
+ {
+ m_dragging = true;
+ bool guessSelector = false;
+ if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ guessSelector = true;
+ }
+ else if (static_cast<HoldAction>(event.m_state) == HoldAction::DRAG_END)
+ { // release exclusive access
+ m_dragging = false;
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ }
+ SetFromPosition(point, guessSelector);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_LEFT_CLICK && m_guiBackground->HitTest(point))
+ {
+ SetFromPosition(point, true);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
+ {
+ if (m_guiBackground->HitTest(point))
+ {
+ Move(10);
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ if (m_guiBackground->HitTest(point))
+ {
+ Move(-10);
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ else if (event.m_id == ACTION_GESTURE_NOTIFY)
+ {
+ return EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA;
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ { // grab exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ { // do the drag
+ SetFromPosition(point);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+void CGUISliderControl::SetInfo(int iInfo)
+{
+ m_iInfoCode = iInfo;
+}
+
+std::string CGUISliderControl::GetDescription() const
+{
+ if (!m_textValue.empty())
+ return m_textValue;
+ std::string description;
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ {
+ if (m_rangeSelection)
+ description = StringUtils::Format("[{:2.2f}, {:2.2f}]", m_floatValues[0], m_floatValues[1]);
+ else
+ description = StringUtils::Format("{:2.2f}", m_floatValues[0]);
+ }
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ {
+ if (m_rangeSelection)
+ description = StringUtils::Format("[{}, {}]", m_intValues[0], m_intValues[1]);
+ else
+ description = std::to_string(m_intValues[0]);
+ }
+ else
+ {
+ if (m_rangeSelection)
+ description = StringUtils::Format("[{}%, {}%]", MathUtils::round_int(static_cast<double>(m_percentValues[0])),
+ MathUtils::round_int(static_cast<double>(m_percentValues[1])));
+ else
+ description = StringUtils::Format("{}%", MathUtils::round_int(static_cast<double>(m_percentValues[0])));
+ }
+ return description;
+}
+
+bool CGUISliderControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_guiBackground->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiSelectorLower->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiSelectorUpper->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiSelectorLowerFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_guiSelectorUpperFocus->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+float CGUISliderControl::GetProportion(RangeSelector selector /* = RangeSelectorLower */) const
+{
+ if (m_iType == SLIDER_CONTROL_TYPE_FLOAT)
+ return m_fStart != m_fEnd ? (GetFloatValue(selector) - m_fStart) / (m_fEnd - m_fStart) : 0.0f;
+ else if (m_iType == SLIDER_CONTROL_TYPE_INT)
+ return m_iStart != m_iEnd ? (float)(GetIntValue(selector) - m_iStart) / (float)(m_iEnd - m_iStart) : 0.0f;
+ return 0.01f * GetPercentage(selector);
+}
+
+void CGUISliderControl::SetAction(const std::string &action)
+{
+ for (const SliderAction& a : actions)
+ {
+ if (StringUtils::EqualsNoCase(action, a.action))
+ {
+ m_action = &a;
+ return;
+ }
+ }
+ m_action = NULL;
+}
diff --git a/xbmc/guilib/GUISliderControl.dox b/xbmc/guilib/GUISliderControl.dox
new file mode 100644
index 0000000..10bd2ba
--- /dev/null
+++ b/xbmc/guilib/GUISliderControl.dox
@@ -0,0 +1,71 @@
+/*!
+
+\page Slider_Control Slider Control
+\brief **Used for a volume slider.**
+
+\tableofcontents
+
+The slider control is used for things where a sliding bar best represents the
+operation at hand (such as a volume control or seek control). You can choose
+the position, size, and look of the slider control.
+
+
+--------------------------------------------------------------------------------
+\section Slider_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="slider" id="17">
+ <description>My first slider control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>30</height>
+ <visible>true</visible>
+ <texturesliderbar>mybackgroundtexture.png</texturesliderbar>
+ <textureslidernib>mydowntexture.png</textureslidernib>
+ <textureslidernibfocus>mydownfocustexture.png</textureslidernibfocus>
+ <info></info>
+ <action></action>
+ <controloffsetx></controloffsetx>
+ <controloffsety></controloffsety>
+ <pulseonselect></pulseonselect>
+ <orientation>vertical</orientation>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Slider_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|----------------------:|:--------------------------------------------------------------|
+| texturesliderbar | Specifies the image file which should be displayed in the background of the slider control. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| textureslidernib | Specifies the image file which should be displayed for the slider nib.
+| textureslidernibfocus | Specifies the image file which should be displayed for the slider nib when it has focus.
+| controloffsetx | Amount to offset the slider background texture from the left edge of the control. Only useful if a value is being rendered as well (ie in int or float mode).
+| controloffsety | Amount to offset the slider background texture from the top edge of the control.
+| info | Specifies the information that the slider controls. [See here for more information](http://kodi.wiki/view/InfoLabels).
+| orientation | Specifies whether this scrollbar is horizontal or vertical. Defaults to vertical.
+| action | Can be <b>`volume`</b> to adjust the volume, <b>`seek`</b> to change the seek position, <b>`pvr.seek`</b> for timeshifting in PVR.
+
+\section Slider_Control_revhistory Revision History
+
+@skinning_v18 <b>[Slider Control]</b> Added <b>`pvr.seek`</b> as possible <b>action</b> tag value (timeshifting in PVR).
+
+--------------------------------------------------------------------------------
+\section Slider_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUISliderControl.h b/xbmc/guilib/GUISliderControl.h
new file mode 100644
index 0000000..196adb2
--- /dev/null
+++ b/xbmc/guilib/GUISliderControl.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUISliderControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUITexture.h"
+
+#include <array>
+
+#define SLIDER_CONTROL_TYPE_INT 1
+#define SLIDER_CONTROL_TYPE_FLOAT 2
+#define SLIDER_CONTROL_TYPE_PERCENTAGE 3
+
+typedef struct
+{
+ const char *action;
+ const char *formatString;
+ int infoCode;
+ bool fireOnDrag;
+} SliderAction;
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUISliderControl :
+ public CGUIControl
+{
+public:
+ typedef enum {
+ RangeSelectorLower = 0,
+ RangeSelectorUpper = 1
+ } RangeSelector;
+
+ CGUISliderControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& backGroundTexture, const CTextureInfo& mibTexture, const CTextureInfo& nibTextureFocus, int iType, ORIENTATION orientation);
+ ~CGUISliderControl() override = default;
+ CGUISliderControl* Clone() const override { return new CGUISliderControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ virtual bool IsActive() const { return true; }
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ virtual void SetRange(int iStart, int iEnd);
+ virtual void SetFloatRange(float fStart, float fEnd);
+ bool OnMessage(CGUIMessage& message) override;
+ bool ProcessSelector(CGUITexture* nib,
+ unsigned int currentTime,
+ float fScale,
+ RangeSelector selector);
+ void SetRangeSelection(bool rangeSelection);
+ bool GetRangeSelection() const { return m_rangeSelection; }
+ void SetRangeSelector(RangeSelector selector);
+ void SwitchRangeSelector();
+ void SetInfo(int iInfo);
+ void SetPercentage(float iPercent, RangeSelector selector = RangeSelectorLower, bool updateCurrent = false);
+ float GetPercentage(RangeSelector selector = RangeSelectorLower) const;
+ void SetIntValue(int iValue, RangeSelector selector = RangeSelectorLower, bool updateCurrent = false);
+ int GetIntValue(RangeSelector selector = RangeSelectorLower) const;
+ void SetFloatValue(float fValue, RangeSelector selector = RangeSelectorLower, bool updateCurrent = false);
+ float GetFloatValue(RangeSelector selector = RangeSelectorLower) const;
+ void SetIntInterval(int iInterval);
+ void SetFloatInterval(float fInterval);
+ void SetType(int iType) { m_iType = iType; }
+ int GetType() const { return m_iType; }
+ std::string GetDescription() const override;
+ void SetTextValue(const std::string& textValue) { m_textValue = textValue; }
+ void SetAction(const std::string &action);
+
+protected:
+ CGUISliderControl(const CGUISliderControl& control);
+
+ bool HitTest(const CPoint &point) const override;
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ virtual void Move(int iNumSteps);
+ virtual void SetFromPosition(const CPoint &point, bool guessSelector = false);
+ /*! \brief Get the current position of the slider as a proportion
+ \return slider position in the range [0,1]
+ */
+ float GetProportion(RangeSelector selector = RangeSelectorLower) const;
+
+ /*! \brief Send a click message (and/or action) to the app in response to a slider move
+ */
+ void SendClick();
+
+ std::unique_ptr<CGUITexture> m_guiBackground;
+ std::unique_ptr<CGUITexture> m_guiSelectorLower;
+ std::unique_ptr<CGUITexture> m_guiSelectorUpper;
+ std::unique_ptr<CGUITexture> m_guiSelectorLowerFocus;
+ std::unique_ptr<CGUITexture> m_guiSelectorUpperFocus;
+ int m_iType;
+
+ bool m_rangeSelection;
+ RangeSelector m_currentSelector;
+
+ std::array<float, 2> m_percentValues;
+
+ std::array<int, 2> m_intValues;
+ int m_iStart;
+ int m_iInterval;
+ int m_iEnd;
+
+ std::array<float, 2> m_floatValues;
+ float m_fStart;
+ float m_fInterval;
+ float m_fEnd;
+
+ int m_iInfoCode;
+ std::string m_textValue; ///< Allows overriding of the text value to be displayed (parent must update when the slider updates)
+ const SliderAction *m_action; ///< Allows the skin to configure the action of a click on the slider \sa SendClick
+ bool m_dragging; ///< Whether we're in a (mouse/touch) drag operation or not - some actions are sent only on release.
+ ORIENTATION m_orientation;
+};
+
diff --git a/xbmc/guilib/GUISpinControl.cpp b/xbmc/guilib/GUISpinControl.cpp
new file mode 100644
index 0000000..f0a0f39
--- /dev/null
+++ b/xbmc/guilib/GUISpinControl.cpp
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUISpinControl.h"
+
+#include "GUIMessage.h"
+#include "input/Key.h"
+#include "utils/StringUtils.h"
+
+#include <stdio.h>
+
+#define SPIN_BUTTON_DOWN 1
+#define SPIN_BUTTON_UP 2
+
+namespace
+{
+// Additional space between text and spin buttons
+constexpr float TEXT_SPACE = 5.0f;
+} // unnamed namespace
+
+CGUISpinControl::CGUISpinControl(int parentID,
+ int controlID,
+ float posX,
+ float posY,
+ float width,
+ float height,
+ const CTextureInfo& textureUp,
+ const CTextureInfo& textureDown,
+ const CTextureInfo& textureUpFocus,
+ const CTextureInfo& textureDownFocus,
+ const CTextureInfo& textureUpDisabled,
+ const CTextureInfo& textureDownDisabled,
+ const CLabelInfo& labelInfo,
+ int iType)
+ : CGUIControl(parentID, controlID, posX, posY, width, height),
+ m_imgspinUp(CGUITexture::CreateTexture(posX, posY, width, height, textureUp)),
+ m_imgspinDown(CGUITexture::CreateTexture(posX, posY, width, height, textureDown)),
+ m_imgspinUpFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureUpFocus)),
+ m_imgspinDownFocus(CGUITexture::CreateTexture(posX, posY, width, height, textureDownFocus)),
+ m_imgspinUpDisabled(CGUITexture::CreateTexture(posX, posY, width, height, textureUpDisabled)),
+ m_imgspinDownDisabled(
+ CGUITexture::CreateTexture(posX, posY, width, height, textureDownDisabled)),
+ m_label(posX, posY, width, height, labelInfo)
+{
+ m_bReverse = false;
+ m_iStart = 0;
+ m_iEnd = 100;
+ m_fStart = 0.0f;
+ m_fEnd = 1.0f;
+ m_fInterval = 0.1f;
+ m_iValue = 0;
+ m_fValue = 0.0;
+ m_iType = iType;
+ m_iSelect = SPIN_BUTTON_DOWN;
+ m_bShowRange = false;
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+ ControlType = GUICONTROL_SPIN;
+ m_currentItem = 0;
+ m_numItems = 10;
+ m_itemsPerPage = 10;
+ m_showOnePage = true;
+}
+
+CGUISpinControl::CGUISpinControl(const CGUISpinControl& control)
+ : CGUIControl(control),
+ m_iStart(control.m_iStart),
+ m_iEnd(control.m_iEnd),
+ m_fStart(control.m_fStart),
+ m_fEnd(control.m_fEnd),
+ m_iValue(control.m_iValue),
+ m_fValue(control.m_fValue),
+ m_iType(control.m_iType),
+ m_iSelect(control.m_iSelect),
+ m_bReverse(control.m_bReverse),
+ m_fInterval(control.m_fInterval),
+ m_vecLabels(control.m_vecLabels),
+ m_vecValues(control.m_vecValues),
+ m_vecStrValues(control.m_vecStrValues),
+ m_imgspinUp(control.m_imgspinUp->Clone()),
+ m_imgspinDown(control.m_imgspinDown->Clone()),
+ m_imgspinUpFocus(control.m_imgspinUpFocus->Clone()),
+ m_imgspinDownFocus(control.m_imgspinDownFocus->Clone()),
+ m_imgspinUpDisabled(control.m_imgspinUpDisabled->Clone()),
+ m_imgspinDownDisabled(control.m_imgspinDownDisabled->Clone()),
+ m_label(control.m_label),
+ m_bShowRange(control.m_bShowRange),
+ m_iTypedPos(control.m_iTypedPos),
+ m_currentItem(control.m_currentItem),
+ m_itemsPerPage(control.m_itemsPerPage),
+ m_numItems(control.m_numItems),
+ m_showOnePage(control.m_showOnePage)
+{
+ std::strcpy(m_szTyped, control.m_szTyped);
+}
+
+bool CGUISpinControl::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case REMOTE_0:
+ case REMOTE_1:
+ case REMOTE_2:
+ case REMOTE_3:
+ case REMOTE_4:
+ case REMOTE_5:
+ case REMOTE_6:
+ case REMOTE_7:
+ case REMOTE_8:
+ case REMOTE_9:
+ {
+ if (strlen(m_szTyped) >= 3)
+ {
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+ }
+ int iNumber = action.GetID() - REMOTE_0;
+
+ m_szTyped[m_iTypedPos] = iNumber + '0';
+ m_iTypedPos++;
+ m_szTyped[m_iTypedPos] = 0;
+ int iValue;
+ sscanf(m_szTyped, "%i", &iValue);
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (iValue < m_iStart || iValue > m_iEnd)
+ {
+ m_iTypedPos = 0;
+ m_szTyped[m_iTypedPos] = iNumber + '0';
+ m_iTypedPos++;
+ m_szTyped[m_iTypedPos] = 0;
+ sscanf(m_szTyped, "%i", &iValue);
+ if (iValue < m_iStart || iValue > m_iEnd)
+ {
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+ return true;
+ }
+ }
+ m_iValue = iValue;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (iValue < 0 || iValue >= (int)m_vecLabels.size())
+ {
+ m_iTypedPos = 0;
+ m_szTyped[m_iTypedPos] = iNumber + '0';
+ m_iTypedPos++;
+ m_szTyped[m_iTypedPos] = 0;
+ sscanf(m_szTyped, "%i", &iValue);
+ if (iValue < 0 || iValue >= (int)m_vecLabels.size())
+ {
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+ return true;
+ }
+ }
+ m_iValue = iValue;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ break;
+
+ }
+ return true;
+ }
+ break;
+ case ACTION_PAGE_UP:
+ if (!m_bReverse)
+ PageDown();
+ else
+ PageUp();
+ return true;
+ break;
+ case ACTION_PAGE_DOWN:
+ if (!m_bReverse)
+ PageUp();
+ else
+ PageDown();
+ return true;
+ break;
+ case ACTION_SELECT_ITEM:
+ if (m_iSelect == SPIN_BUTTON_UP)
+ {
+ MoveUp();
+ return true;
+ }
+ if (m_iSelect == SPIN_BUTTON_DOWN)
+ {
+ MoveDown();
+ return true;
+ }
+ break;
+ }
+/* static float m_fSmoothScrollOffset = 0.0f;
+ if (action.GetID() == ACTION_SCROLL_UP)
+ {
+ m_fSmoothScrollOffset += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_fSmoothScrollOffset > 0.4)
+ {
+ handled = true;
+ m_fSmoothScrollOffset -= 0.4f;
+ MoveDown();
+ }
+ return handled;
+ }*/
+ return CGUIControl::OnAction(action);
+}
+
+void CGUISpinControl::OnLeft()
+{
+ if (m_iSelect == SPIN_BUTTON_UP)
+ {
+ // select the down button
+ m_iSelect = SPIN_BUTTON_DOWN;
+ MarkDirtyRegion();
+ }
+ else
+ { // base class
+ CGUIControl::OnLeft();
+ }
+}
+
+void CGUISpinControl::OnRight()
+{
+ if (m_iSelect == SPIN_BUTTON_DOWN)
+ {
+ // select the up button
+ m_iSelect = SPIN_BUTTON_UP;
+ MarkDirtyRegion();
+ }
+ else
+ { // base class
+ CGUIControl::OnRight();
+ }
+}
+
+void CGUISpinControl::Clear()
+{
+ m_vecLabels.clear();
+ m_vecValues.clear();
+ m_vecStrValues.clear();
+ SetValue(0);
+}
+
+bool CGUISpinControl::OnMessage(CGUIMessage& message)
+{
+ if (CGUIControl::OnMessage(message) )
+ return true;
+ if (message.GetControlId() == GetID() )
+ {
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_ITEM_SELECT:
+ if (SPIN_CONTROL_TYPE_PAGE == m_iType)
+ {
+ m_currentItem = message.GetParam1();
+ return true;
+ }
+ SetValue( message.GetParam1());
+ if (message.GetParam2() == SPIN_BUTTON_DOWN || message.GetParam2() == SPIN_BUTTON_UP)
+ m_iSelect = message.GetParam2();
+ return true;
+ break;
+
+ case GUI_MSG_LABEL_RESET:
+ if (SPIN_CONTROL_TYPE_PAGE == m_iType)
+ {
+ m_itemsPerPage = message.GetParam1();
+ m_numItems = message.GetParam2();
+ return true;
+ }
+ {
+ Clear();
+ return true;
+ }
+ break;
+
+ case GUI_MSG_SHOWRANGE:
+ if (message.GetParam1() )
+ m_bShowRange = true;
+ else
+ m_bShowRange = false;
+ break;
+
+ case GUI_MSG_SET_LABELS:
+ if (message.GetPointer())
+ {
+ auto labels =
+ static_cast<const std::vector<std::pair<std::string, int>>*>(message.GetPointer());
+ Clear();
+ for (const auto& i : *labels)
+ AddLabel(i.first, i.second);
+ SetValue( message.GetParam1());
+ }
+ break;
+
+ case GUI_MSG_LABEL_ADD:
+ {
+ AddLabel(message.GetLabel(), message.GetParam1());
+ return true;
+ }
+ break;
+
+ case GUI_MSG_ITEM_SELECTED:
+ {
+ message.SetParam1( GetValue() );
+ message.SetParam2(m_iSelect);
+
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT)
+ {
+ if ( m_iValue >= 0 && m_iValue < (int)m_vecLabels.size() )
+ message.SetLabel( m_vecLabels[m_iValue]);
+ }
+ return true;
+ }
+
+ case GUI_MSG_PAGE_UP:
+ if (CanMoveUp())
+ MoveUp();
+ return true;
+
+ case GUI_MSG_PAGE_DOWN:
+ if (CanMoveDown())
+ MoveDown();
+ return true;
+
+ case GUI_MSG_MOVE_OFFSET:
+ {
+ int count = message.GetParam1();
+ while (count < 0)
+ {
+ MoveUp();
+ count++;
+ }
+ while (count > 0)
+ {
+ MoveDown();
+ count--;
+ }
+ return true;
+ }
+
+ }
+ }
+ return false;
+}
+
+void CGUISpinControl::AllocResources()
+{
+ CGUIControl::AllocResources();
+ m_imgspinUp->AllocResources();
+ m_imgspinUpFocus->AllocResources();
+ m_imgspinDown->AllocResources();
+ m_imgspinDownFocus->AllocResources();
+ m_imgspinUpDisabled->AllocResources();
+ m_imgspinDownDisabled->AllocResources();
+
+ m_imgspinDownFocus->SetPosition(m_posX, m_posY);
+ m_imgspinDown->SetPosition(m_posX, m_posY);
+ m_imgspinDownDisabled->SetPosition(m_posX, m_posY);
+ m_imgspinUp->SetPosition(m_posX + m_imgspinDown->GetWidth(), m_posY);
+ m_imgspinUpFocus->SetPosition(m_posX + m_imgspinDownFocus->GetWidth(), m_posY);
+ m_imgspinUpDisabled->SetPosition(m_posX + m_imgspinDownDisabled->GetWidth(), m_posY);
+}
+
+void CGUISpinControl::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ m_imgspinUp->FreeResources(immediately);
+ m_imgspinUpFocus->FreeResources(immediately);
+ m_imgspinDown->FreeResources(immediately);
+ m_imgspinDownFocus->FreeResources(immediately);
+ m_imgspinUpDisabled->FreeResources(immediately);
+ m_imgspinDownDisabled->FreeResources(immediately);
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+}
+
+void CGUISpinControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIControl::DynamicResourceAlloc(bOnOff);
+ m_imgspinUp->DynamicResourceAlloc(bOnOff);
+ m_imgspinUpFocus->DynamicResourceAlloc(bOnOff);
+ m_imgspinDown->DynamicResourceAlloc(bOnOff);
+ m_imgspinDownFocus->DynamicResourceAlloc(bOnOff);
+ m_imgspinUpDisabled->DynamicResourceAlloc(bOnOff);
+ m_imgspinDownDisabled->DynamicResourceAlloc(bOnOff);
+}
+
+void CGUISpinControl::SetInvalid()
+{
+ CGUIControl::SetInvalid();
+ m_label.SetInvalid();
+ m_imgspinUp->SetInvalid();
+ m_imgspinUpFocus->SetInvalid();
+ m_imgspinDown->SetInvalid();
+ m_imgspinDownFocus->SetInvalid();
+ m_imgspinUpDisabled->SetInvalid();
+ m_imgspinDownDisabled->SetInvalid();
+}
+
+void CGUISpinControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ bool changed = false;
+
+ if (!HasFocus())
+ {
+ m_iTypedPos = 0;
+ strcpy(m_szTyped, "");
+ }
+
+ std::string text;
+
+ if (m_iType == SPIN_CONTROL_TYPE_INT)
+ {
+ if (m_bShowRange)
+ {
+ text = StringUtils::Format("{}/{}", m_iValue, m_iEnd);
+ }
+ else
+ {
+ text = std::to_string(m_iValue);
+ }
+ }
+ else if (m_iType == SPIN_CONTROL_TYPE_PAGE)
+ {
+ // work out number of pages and current page
+ int numPages = (m_numItems + m_itemsPerPage - 1) / m_itemsPerPage;
+ int currentPage = m_currentItem / m_itemsPerPage + 1;
+ if (m_currentItem >= m_numItems - m_itemsPerPage)
+ currentPage = numPages;
+ text = StringUtils::Format("{}/{}", currentPage, numPages);
+ }
+ else if (m_iType == SPIN_CONTROL_TYPE_FLOAT)
+ {
+ if (m_bShowRange)
+ {
+ text = StringUtils::Format("{:02.2f}/{:02.2f}", m_fValue, m_fEnd);
+ }
+ else
+ {
+ text = StringUtils::Format("{:02.2f}", m_fValue);
+ }
+ }
+ else
+ {
+ if (m_iValue >= 0 && m_iValue < (int)m_vecLabels.size() )
+ {
+ if (m_bShowRange)
+ {
+ text = StringUtils::Format("({}/{}) {}", m_iValue + 1, (int)m_vecLabels.size(),
+ m_vecLabels[m_iValue]);
+ }
+ else
+ {
+ text = m_vecLabels[m_iValue];
+ }
+ }
+ else
+ text = StringUtils::Format("?{}?", m_iValue);
+ }
+
+ changed |= m_label.SetText(text);
+
+ float textWidth = m_label.GetTextWidth() + 2 * m_label.GetLabelInfo().offsetX;
+ // Position the arrows
+ bool arrowsOnRight(0 != (m_label.GetLabelInfo().align & (XBFONT_RIGHT | XBFONT_CENTER_X)));
+ if (!arrowsOnRight)
+ {
+ changed |= m_imgspinDownFocus->SetPosition(m_posX + textWidth + TEXT_SPACE, m_posY);
+ changed |= m_imgspinDown->SetPosition(m_posX + textWidth + TEXT_SPACE, m_posY);
+ changed |= m_imgspinDownDisabled->SetPosition(m_posX + textWidth + TEXT_SPACE, m_posY);
+ changed |= m_imgspinUpFocus->SetPosition(
+ m_posX + textWidth + TEXT_SPACE + m_imgspinDown->GetWidth(), m_posY);
+ changed |= m_imgspinUp->SetPosition(m_posX + textWidth + TEXT_SPACE + m_imgspinDown->GetWidth(),
+ m_posY);
+ changed |= m_imgspinUpDisabled->SetPosition(
+ m_posX + textWidth + TEXT_SPACE + m_imgspinDownDisabled->GetWidth(), m_posY);
+ }
+
+ changed |= m_imgspinDownFocus->Process(currentTime);
+ changed |= m_imgspinDown->Process(currentTime);
+ changed |= m_imgspinUp->Process(currentTime);
+ changed |= m_imgspinUpFocus->Process(currentTime);
+ changed |= m_imgspinUpDisabled->Process(currentTime);
+ changed |= m_imgspinDownDisabled->Process(currentTime);
+ changed |= m_label.Process(currentTime);
+
+ if (changed)
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUISpinControl::Render()
+{
+ if (m_label.GetLabelInfo().font)
+ {
+ float textWidth = m_label.GetTextWidth() + 2 * m_label.GetLabelInfo().offsetX;
+ // Position the arrows
+ bool arrowsOnRight(0 != (m_label.GetLabelInfo().align & (XBFONT_RIGHT | XBFONT_CENTER_X)));
+
+ if (arrowsOnRight)
+ RenderText(m_posX - TEXT_SPACE - textWidth, m_posY, textWidth, m_height);
+ else
+ RenderText(m_posX + m_imgspinDown->GetWidth() + m_imgspinUp->GetWidth() + TEXT_SPACE, m_posY,
+ textWidth, m_height);
+
+ // set our hit rectangle for MouseOver events
+ m_hitRect = m_label.GetRenderRect();
+ }
+
+ if (HasFocus())
+ {
+ if (m_iSelect == SPIN_BUTTON_UP)
+ m_imgspinUpFocus->Render();
+ else
+ m_imgspinUp->Render();
+
+ if (m_iSelect == SPIN_BUTTON_DOWN)
+ m_imgspinDownFocus->Render();
+ else
+ m_imgspinDown->Render();
+ }
+ else if (!HasFocus() && !IsDisabled())
+ {
+ m_imgspinUp->Render();
+ m_imgspinDown->Render();
+ }
+ else
+ {
+ m_imgspinUpDisabled->Render();
+ m_imgspinDownDisabled->Render();
+ }
+
+ CGUIControl::Render();
+}
+
+void CGUISpinControl::RenderText(float posX, float posY, float width, float height)
+{
+ m_label.SetMaxRect(posX, posY, width, height);
+ m_label.SetColor(GetTextColor());
+ m_label.Render();
+}
+
+CGUILabel::COLOR CGUISpinControl::GetTextColor() const
+{
+ if (IsDisabled())
+ return CGUILabel::COLOR_DISABLED;
+ else if (HasFocus())
+ return CGUILabel::COLOR_FOCUSED;
+ return CGUILabel::COLOR_TEXT;
+}
+
+void CGUISpinControl::SetRange(int iStart, int iEnd)
+{
+ m_iStart = iStart;
+ m_iEnd = iEnd;
+}
+
+void CGUISpinControl::SetFloatRange(float fStart, float fEnd)
+{
+ m_fStart = fStart;
+ m_fEnd = fEnd;
+}
+
+void CGUISpinControl::SetValueFromLabel(const std::string &label)
+{
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT)
+ {
+ m_iValue = 0;
+ for (unsigned int i = 0; i < m_vecLabels.size(); i++)
+ if (label == m_vecLabels[i])
+ m_iValue = i;
+ }
+ else
+ m_iValue = atoi(label.c_str());
+
+ MarkDirtyRegion();
+ SetInvalid();
+}
+
+void CGUISpinControl::SetValue(int iValue)
+{
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT)
+ {
+ m_iValue = 0;
+ for (unsigned int i = 0; i < m_vecValues.size(); i++)
+ if (iValue == m_vecValues[i])
+ m_iValue = i;
+ }
+ else
+ m_iValue = iValue;
+
+ MarkDirtyRegion();
+ SetInvalid();
+}
+
+void CGUISpinControl::SetFloatValue(float fValue)
+{
+ m_fValue = fValue;
+}
+
+void CGUISpinControl::SetStringValue(const std::string& strValue)
+{
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT)
+ {
+ m_iValue = 0;
+ for (unsigned int i = 0; i < m_vecStrValues.size(); i++)
+ if (strValue == m_vecStrValues[i])
+ m_iValue = i;
+ }
+
+ SetInvalid();
+}
+
+int CGUISpinControl::GetValue() const
+{
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT)
+ {
+ if (m_iValue >= 0 && m_iValue < (int)m_vecValues.size())
+ return m_vecValues[m_iValue];
+ }
+ return m_iValue;
+}
+
+float CGUISpinControl::GetFloatValue() const
+{
+ return m_fValue;
+}
+
+std::string CGUISpinControl::GetStringValue() const
+{
+ if (m_iType == SPIN_CONTROL_TYPE_TEXT && m_iValue >= 0 && m_iValue < (int)m_vecLabels.size())
+ {
+ if (m_iValue < (int)m_vecStrValues.size())
+ return m_vecStrValues[m_iValue];
+
+ return m_vecLabels[m_iValue];
+ }
+ return "";
+}
+
+void CGUISpinControl::AddLabel(const std::string& strLabel, int iValue)
+{
+ m_vecLabels.push_back(strLabel);
+ m_vecValues.push_back(iValue);
+}
+
+void CGUISpinControl::AddLabel(const std::string& strLabel, const std::string& strValue)
+{
+ m_vecLabels.push_back(strLabel);
+ m_vecStrValues.push_back(strValue);
+}
+
+const std::string CGUISpinControl::GetLabel() const
+{
+ if (m_iValue >= 0 && m_iValue < (int)m_vecLabels.size())
+ {
+ return m_vecLabels[m_iValue];
+ }
+ return "";
+}
+
+void CGUISpinControl::SetPosition(float posX, float posY)
+{
+ CGUIControl::SetPosition(posX, posY);
+
+ m_imgspinDownFocus->SetPosition(posX, posY);
+ m_imgspinDown->SetPosition(posX, posY);
+ m_imgspinDownDisabled->SetPosition(posX, posY);
+
+ m_imgspinUp->SetPosition(m_posX + m_imgspinDown->GetWidth(), m_posY);
+ m_imgspinUpFocus->SetPosition(m_posX + m_imgspinDownFocus->GetWidth(), m_posY);
+ m_imgspinUpDisabled->SetPosition(m_posX + m_imgspinDownDisabled->GetWidth(), m_posY);
+}
+
+float CGUISpinControl::GetWidth() const
+{
+ return m_imgspinDown->GetWidth() * 2;
+}
+
+bool CGUISpinControl::CanMoveUp(bool bTestReverse)
+{
+ // test for reverse...
+ if (bTestReverse && m_bReverse) return CanMoveDown(false);
+
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_PAGE:
+ return m_currentItem > 0;
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue - 1 >= m_iStart)
+ return true;
+ return false;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ {
+ if (m_fValue - m_fInterval >= m_fStart)
+ return true;
+ return false;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue - 1 >= 0)
+ return true;
+ return false;
+ }
+ break;
+ }
+ return false;
+}
+
+bool CGUISpinControl::CanMoveDown(bool bTestReverse)
+{
+ // test for reverse...
+ if (bTestReverse && m_bReverse) return CanMoveUp(false);
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_PAGE:
+ return m_currentItem < m_numItems;
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue + 1 <= m_iEnd)
+ return true;
+ return false;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ {
+ if (m_fValue + m_fInterval <= m_fEnd)
+ return true;
+ return false;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue + 1 < (int)m_vecLabels.size())
+ return true;
+ return false;
+ }
+ break;
+ }
+ return false;
+}
+
+void CGUISpinControl::PageUp()
+{
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue - 10 >= m_iStart)
+ m_iValue -= 10;
+ else
+ m_iValue = m_iStart;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+ case SPIN_CONTROL_TYPE_PAGE:
+ ChangePage(-10);
+ break;
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue - 10 >= 0)
+ m_iValue -= 10;
+ else
+ m_iValue = 0;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+ }
+
+}
+
+void CGUISpinControl::PageDown()
+{
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue + 10 <= m_iEnd)
+ m_iValue += 10;
+ else
+ m_iValue = m_iEnd;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+ case SPIN_CONTROL_TYPE_PAGE:
+ ChangePage(10);
+ break;
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue + 10 < (int)m_vecLabels.size() )
+ m_iValue += 10;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ }
+ break;
+ }
+}
+
+void CGUISpinControl::MoveUp(bool bTestReverse)
+{
+ if (bTestReverse && m_bReverse)
+ { // actually should move down.
+ MoveDown(false);
+ return ;
+ }
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue - 1 >= m_iStart)
+ m_iValue--;
+ else if (m_iValue == m_iStart)
+ m_iValue = m_iEnd;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_PAGE:
+ ChangePage(-1);
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ {
+ if (m_fValue - m_fInterval >= m_fStart)
+ m_fValue -= m_fInterval;
+ else
+ m_fValue = m_fEnd;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue - 1 >= 0)
+ m_iValue--;
+ else if (m_iValue == 0)
+ m_iValue = (int)m_vecLabels.size() - 1;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+ }
+}
+
+void CGUISpinControl::MoveDown(bool bTestReverse)
+{
+ if (bTestReverse && m_bReverse)
+ { // actually should move up.
+ MoveUp(false);
+ return ;
+ }
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_INT:
+ {
+ if (m_iValue + 1 <= m_iEnd)
+ m_iValue++;
+ else if (m_iValue == m_iEnd)
+ m_iValue = m_iStart;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_PAGE:
+ ChangePage(1);
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ {
+ if (m_fValue + m_fInterval <= m_fEnd)
+ m_fValue += m_fInterval;
+ else
+ m_fValue = m_fStart;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ {
+ if (m_iValue + 1 < (int)m_vecLabels.size() )
+ m_iValue++;
+ else if (m_iValue == (int)m_vecLabels.size() - 1)
+ m_iValue = 0;
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return ;
+ }
+ break;
+ }
+}
+void CGUISpinControl::SetReverse(bool bReverse)
+{
+ m_bReverse = bReverse;
+}
+
+void CGUISpinControl::SetFloatInterval(float fInterval)
+{
+ m_fInterval = fInterval;
+}
+
+void CGUISpinControl::SetShowRange(bool bOnoff)
+{
+ m_bShowRange = bOnoff;
+}
+
+int CGUISpinControl::GetMinimum() const
+{
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_PAGE:
+ return 0;
+ case SPIN_CONTROL_TYPE_INT:
+ return m_iStart;
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ return 1;
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ return (int)(m_fStart*10.0f);
+ break;
+ }
+ return 0;
+}
+
+int CGUISpinControl::GetMaximum() const
+{
+ switch (m_iType)
+ {
+ case SPIN_CONTROL_TYPE_PAGE:
+ return m_numItems;
+ case SPIN_CONTROL_TYPE_INT:
+ return m_iEnd;
+ break;
+
+ case SPIN_CONTROL_TYPE_TEXT:
+ return (int)m_vecLabels.size();
+ break;
+
+ case SPIN_CONTROL_TYPE_FLOAT:
+ return (int)(m_fEnd*10.0f);
+ break;
+ }
+ return 100;
+}
+
+bool CGUISpinControl::HitTest(const CPoint &point) const
+{
+ if (m_imgspinUpFocus->HitTest(point) || m_imgspinDownFocus->HitTest(point))
+ return true;
+ return CGUIControl::HitTest(point);
+}
+
+bool CGUISpinControl::OnMouseOver(const CPoint &point)
+{
+ int select = m_iSelect;
+ if (m_imgspinDownFocus->HitTest(point))
+ m_iSelect = SPIN_BUTTON_DOWN;
+ else
+ m_iSelect = SPIN_BUTTON_UP;
+
+ if (select != m_iSelect)
+ MarkDirtyRegion();
+
+ return CGUIControl::OnMouseOver(point);
+}
+
+EVENT_RESULT CGUISpinControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_LEFT_CLICK)
+ {
+ if (m_imgspinUpFocus->HitTest(point))
+ MoveUp();
+ else if (m_imgspinDownFocus->HitTest(point))
+ MoveDown();
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
+ {
+ if (m_imgspinUpFocus->HitTest(point) || m_imgspinDownFocus->HitTest(point))
+ {
+ MoveUp();
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ if (m_imgspinUpFocus->HitTest(point) || m_imgspinDownFocus->HitTest(point))
+ {
+ MoveDown();
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+std::string CGUISpinControl::GetDescription() const
+{
+ return StringUtils::Format("{}/{}", 1 + GetValue(), GetMaximum());
+}
+
+bool CGUISpinControl::IsFocusedOnUp() const
+{
+ return (m_iSelect == SPIN_BUTTON_UP);
+}
+
+void CGUISpinControl::ChangePage(int amount)
+{
+ m_currentItem += amount * m_itemsPerPage;
+ if (m_currentItem > m_numItems - m_itemsPerPage)
+ m_currentItem = m_numItems - m_itemsPerPage;
+ if (m_currentItem < 0)
+ m_currentItem = 0;
+ CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetParentID(), GetID(), GUI_MSG_PAGE_CHANGE, m_currentItem);
+ SendWindowMessage(message);
+}
+
+bool CGUISpinControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+ changed |= m_imgspinDownFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgspinDown->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgspinUp->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgspinUpFocus->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgspinUpDisabled->SetDiffuseColor(m_diffuseColor);
+ changed |= m_imgspinDownDisabled->SetDiffuseColor(m_diffuseColor);
+
+ return changed;
+}
+
+bool CGUISpinControl::IsVisible() const
+{
+ // page controls can be optionally disabled if the number of pages is 1
+ if (m_iType == SPIN_CONTROL_TYPE_PAGE && m_numItems <= m_itemsPerPage && !m_showOnePage)
+ return false;
+ return CGUIControl::IsVisible();
+}
diff --git a/xbmc/guilib/GUISpinControl.dox b/xbmc/guilib/GUISpinControl.dox
new file mode 100644
index 0000000..0c42fcf
--- /dev/null
+++ b/xbmc/guilib/GUISpinControl.dox
@@ -0,0 +1,83 @@
+/*!
+
+\page Spin_Control Spin Control
+\brief **Used for cycling up/down controls.**
+
+\tableofcontents
+
+The spin control is used for when a list of options can be chosen (such as a
+page up/down control). You can choose the position, size, and look of the
+spin control.
+
+
+--------------------------------------------------------------------------------
+\section Spin_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="spincontrol" id="14">
+ <description>My first spin control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <textureup colordiffuse="FFFFAAFF">myuptexture.png</textureup>
+ <textureupfocus colordiffuse="FFFFAAFF">myupfocustexture.png</textureupfocus>
+ <texturedown colordiffuse="FFFFAAFF">mydowntexture.png</texturedown>
+ <texturedownfocus colordiffuse="FFFFAAFF">mydownfocustexture.png</texturedownfocus>
+ <textureupdisabled colordiffuse="AAFFAAFF">mydowntexture.png</textureupdisabled>
+ <texturedowndisabled colordiffuse="AAFFAAFF">mydownfocustexture.png</texturedowndisabled>
+ <subtype>page</subtype>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <align></align>
+ <aligny></aligny>
+ <textoffsetx></textoffsetx>
+ <textoffsety></textoffsety>
+ <pulseonselect></pulseonselect>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Spin_Control_sect2 Available tags
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------------:|:--------------------------------------------------------------|
+| textureup | Specifies the image file which should be displayed for the up arrow when it doesn't have focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| textureupfocus | Specifies the image file which should be displayed for the up button when it has focus.
+| textureupdisabled | Specifies the image file which should be displayed for the up arrow when the button is disabled.
+| texturedown | Specifies the image file which should be displayed for the down button when it is not focused.
+| texturedownfocus | Specifies the image file which should be displayed for the down button when it has focus.
+| texturedowndisabled | Specifies the image file which should be displayed for the up arrow when the button is disabled.
+| font | Font used for the button label. From fonts.xml.
+| spincolor | The colour of the text used for this spin control. In **AARRGGBB** hex format. As of Helix, this doesn't actually get processed, use textcolor
+| textcolor | Color used for displaying the label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the label if the control is disabled. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text. In **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| subtype | Defines what type of information the spinner holds. Can be int, float, text or page. Defaults to text. Make sure you use page for a page control.
+| align | Label horizontal alignment on the control. Defaults to right, can also be left.
+| aligny | Label vertical alignment on the control. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| textwidth | Will truncate any text that's too long.
+
+
+--------------------------------------------------------------------------------
+\section Spin_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUISpinControl.h b/xbmc/guilib/GUISpinControl.h
new file mode 100644
index 0000000..f5aecd2
--- /dev/null
+++ b/xbmc/guilib/GUISpinControl.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUISpinControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "GUITexture.h"
+
+#include <vector>
+
+#define SPIN_CONTROL_TYPE_INT 1
+#define SPIN_CONTROL_TYPE_FLOAT 2
+#define SPIN_CONTROL_TYPE_TEXT 3
+#define SPIN_CONTROL_TYPE_PAGE 4
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUISpinControl : public CGUIControl
+{
+public:
+ CGUISpinControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& textureUp, const CTextureInfo& textureDown, const CTextureInfo& textureUpFocus, const CTextureInfo& textureDownFocus, const CTextureInfo& textureUpDisabled, const CTextureInfo& textureDownDisabled, const CLabelInfo& labelInfo, int iType);
+ ~CGUISpinControl() override = default;
+ CGUISpinControl* Clone() const override { return new CGUISpinControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void OnLeft() override;
+ void OnRight() override;
+ bool HitTest(const CPoint &point) const override;
+ bool OnMouseOver(const CPoint &point) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ float GetWidth() const override;
+ void SetRange(int iStart, int iEnd);
+ void SetFloatRange(float fStart, float fEnd);
+ void SetValue(int iValue);
+ void SetValueFromLabel(const std::string &label);
+ void SetFloatValue(float fValue);
+ void SetStringValue(const std::string& strValue);
+ int GetValue() const;
+ float GetFloatValue() const;
+ std::string GetStringValue() const;
+ void AddLabel(const std::string& strLabel, int iValue);
+ void AddLabel(const std::string& strLabel, const std::string& strValue);
+ const std::string GetLabel() const;
+ void SetReverse(bool bOnOff);
+ int GetMaximum() const;
+ int GetMinimum() const;
+ void SetSpinAlign(uint32_t align, float offsetX)
+ {
+ m_label.GetLabelInfo().align = align;
+ m_label.GetLabelInfo().offsetX = offsetX;
+ }
+ void SetType(int iType) { m_iType = iType; }
+ float GetSpinWidth() const { return m_imgspinUp->GetWidth(); }
+ float GetSpinHeight() const { return m_imgspinUp->GetHeight(); }
+ void SetFloatInterval(float fInterval);
+ void SetShowRange(bool bOnoff) ;
+ void SetShowOnePage(bool showOnePage) { m_showOnePage = showOnePage; }
+ void Clear();
+ std::string GetDescription() const override;
+ bool IsFocusedOnUp() const;
+
+ bool IsVisible() const override;
+
+protected:
+ CGUISpinControl(const CGUISpinControl& control);
+
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ /*! \brief Render the spinner text
+ \param posX position of the left edge of the text
+ \param posY positing of the top edge of the text
+ \param width width of the text
+ \param height height of the text
+ */
+ virtual void RenderText(float posX, float posY, float width, float height);
+ CGUILabel::COLOR GetTextColor() const;
+ void PageUp();
+ void PageDown();
+ bool CanMoveDown(bool bTestReverse = true);
+ bool CanMoveUp(bool bTestReverse = true);
+ void MoveUp(bool bTestReverse = true);
+ void MoveDown(bool bTestReverse = true);
+ void ChangePage(int amount);
+ int m_iStart;
+ int m_iEnd;
+ float m_fStart;
+ float m_fEnd;
+ int m_iValue;
+ float m_fValue;
+ int m_iType;
+ int m_iSelect;
+ bool m_bReverse;
+ float m_fInterval;
+ std::vector<std::string> m_vecLabels;
+ std::vector<int> m_vecValues;
+ std::vector<std::string> m_vecStrValues;
+ std::unique_ptr<CGUITexture> m_imgspinUp;
+ std::unique_ptr<CGUITexture> m_imgspinDown;
+ std::unique_ptr<CGUITexture> m_imgspinUpFocus;
+ std::unique_ptr<CGUITexture> m_imgspinDownFocus;
+ std::unique_ptr<CGUITexture> m_imgspinUpDisabled;
+ std::unique_ptr<CGUITexture> m_imgspinDownDisabled;
+ CGUILabel m_label;
+ bool m_bShowRange;
+ char m_szTyped[10];
+ int m_iTypedPos;
+
+ int m_currentItem;
+ int m_itemsPerPage;
+ int m_numItems;
+ bool m_showOnePage;
+};
+
diff --git a/xbmc/guilib/GUISpinControlEx.cpp b/xbmc/guilib/GUISpinControlEx.cpp
new file mode 100644
index 0000000..709c2af
--- /dev/null
+++ b/xbmc/guilib/GUISpinControlEx.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUISpinControlEx.h"
+
+#include "utils/StringUtils.h"
+
+CGUISpinControlEx::CGUISpinControlEx(int parentID, int controlID, float posX, float posY, float width, float height, float spinWidth, float spinHeight, const CLabelInfo& spinInfo, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus, const CTextureInfo& textureUp, const CTextureInfo& textureDown, const CTextureInfo& textureUpFocus, const CTextureInfo& textureDownFocus, const CTextureInfo& textureUpDisabled, const CTextureInfo& textureDownDisabled, const CLabelInfo& labelInfo, int iType)
+ : CGUISpinControl(parentID, controlID, posX, posY, spinWidth, spinHeight, textureUp, textureDown, textureUpFocus, textureDownFocus, textureUpDisabled, textureDownDisabled, spinInfo, iType)
+ , m_buttonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
+{
+ ControlType = GUICONTROL_SPINEX;
+ m_spinPosX = 0;
+}
+
+CGUISpinControlEx::~CGUISpinControlEx(void) = default;
+
+void CGUISpinControlEx::AllocResources()
+{
+ // Correct alignment - we always align the spincontrol on the right
+ m_label.GetLabelInfo().align = (m_label.GetLabelInfo().align & XBFONT_CENTER_Y) | XBFONT_RIGHT;
+ CGUISpinControl::AllocResources();
+ m_buttonControl.AllocResources();
+ if (m_height == 0)
+ m_height = GetSpinHeight();
+}
+
+void CGUISpinControlEx::FreeResources(bool immediately)
+{
+ CGUISpinControl::FreeResources(immediately);
+ m_buttonControl.FreeResources(immediately);
+}
+
+void CGUISpinControlEx::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUISpinControl::DynamicResourceAlloc(bOnOff);
+ m_buttonControl.DynamicResourceAlloc(bOnOff);
+}
+
+
+void CGUISpinControlEx::SetInvalid()
+{
+ CGUISpinControl::SetInvalid();
+ m_buttonControl.SetInvalid();
+}
+
+void CGUISpinControlEx::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // make sure the button has focus if it should have...
+ m_buttonControl.SetFocus(HasFocus());
+ m_buttonControl.SetPulseOnSelect(m_pulseOnSelect);
+ m_buttonControl.SetEnabled(m_enabled);
+ if (m_bInvalidated)
+ {
+ float spinPosX = m_buttonControl.GetXPosition() + m_buttonControl.GetWidth() - GetSpinWidth() * 2 - (m_spinPosX ? m_spinPosX : m_buttonControl.GetLabelInfo().offsetX);
+ float spinPosY = m_buttonControl.GetYPosition() + (m_buttonControl.GetHeight() - GetSpinHeight()) * 0.5f;
+ CGUISpinControl::SetPosition(spinPosX, spinPosY);
+ }
+ m_buttonControl.DoProcess(currentTime, dirtyregions);
+ CGUISpinControl::Process(currentTime, dirtyregions);
+}
+
+void CGUISpinControlEx::Render()
+{
+ CGUISpinControl::Render();
+}
+
+void CGUISpinControlEx::SetPosition(float posX, float posY)
+{
+ m_buttonControl.SetPosition(posX, posY);
+ CGUISpinControl::SetInvalid();
+}
+
+void CGUISpinControlEx::SetWidth(float width)
+{
+ m_buttonControl.SetWidth(width);
+ CGUISpinControl::SetInvalid();
+}
+
+void CGUISpinControlEx::SetHeight(float height)
+{
+ m_buttonControl.SetHeight(height);
+ CGUISpinControl::SetInvalid();
+}
+
+bool CGUISpinControlEx::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUISpinControl::UpdateColors(nullptr);
+ changed |= m_buttonControl.SetColorDiffuse(m_diffuseColor);
+ changed |= m_buttonControl.UpdateColors(nullptr);
+
+ return changed;
+}
+
+void CGUISpinControlEx::SetEnabled(bool bEnable)
+{
+ m_buttonControl.SetEnabled(bEnable);
+ CGUISpinControl::SetEnabled(bEnable);
+}
+
+const std::string CGUISpinControlEx::GetCurrentLabel() const
+{
+ return CGUISpinControl::GetLabel();
+}
+
+std::string CGUISpinControlEx::GetDescription() const
+{
+ return StringUtils::Format("{} ({})", m_buttonControl.GetDescription(), GetLabel());
+}
+
+void CGUISpinControlEx::SetItemInvalid(bool invalid)
+{
+ if (invalid)
+ {
+ m_label.GetLabelInfo().textColor = m_buttonControl.GetLabelInfo().disabledColor;
+ m_label.GetLabelInfo().focusedColor = m_buttonControl.GetLabelInfo().disabledColor;
+ }
+ else
+ {
+ m_label.GetLabelInfo().textColor = m_buttonControl.GetLabelInfo().textColor;
+ m_label.GetLabelInfo().focusedColor = m_buttonControl.GetLabelInfo().focusedColor;
+ }
+}
+
+void CGUISpinControlEx::SetSpinPosition(float spinPosX)
+{
+ m_spinPosX = spinPosX;
+ SetPosition(m_buttonControl.GetXPosition(), m_buttonControl.GetYPosition());
+}
+
+void CGUISpinControlEx::RenderText(float posX, float posY, float width, float height)
+{
+ const float freeSpaceWidth{m_buttonControl.GetWidth() - GetSpinWidth() * 2};
+
+ // Limit right label text width to max 50% of free space
+ // (will be slightly shifted due to offsetX padding)
+ const float rightTextMaxWidth{freeSpaceWidth * 0.5f};
+
+ float rightTextWidth{width};
+ if (rightTextWidth > rightTextMaxWidth)
+ {
+ rightTextWidth = rightTextMaxWidth - m_label.GetLabelInfo().offsetX;
+ }
+
+ m_label.SetScrolling(HasFocus());
+ // Replace posX by using our button position
+ posX = m_buttonControl.GetXPosition() + freeSpaceWidth - rightTextWidth -
+ m_label.GetLabelInfo().offsetX;
+
+ // Limit the max width for the left label to avoid text overlapping
+ m_buttonControl.SetMaxWidth(posX + m_label.GetLabelInfo().offsetX);
+ m_buttonControl.Render();
+
+ CGUISpinControl::RenderText(posX, m_buttonControl.GetYPosition(), rightTextWidth,
+ m_buttonControl.GetHeight());
+}
diff --git a/xbmc/guilib/GUISpinControlEx.dox b/xbmc/guilib/GUISpinControlEx.dox
new file mode 100644
index 0000000..a1ba4a0
--- /dev/null
+++ b/xbmc/guilib/GUISpinControlEx.dox
@@ -0,0 +1,96 @@
+/*!
+
+\page Settings_Spin_Control Settings Spin Control
+\brief **Used for cycling up/down controls in the settings menus.**
+
+\tableofcontents
+
+The settings spin control is used in the settings screens for when a list of
+options can be chosen from using up/down arrows. You can choose the position,
+size, and look of the spin control. It is basically a cross between the button
+control and a spin control. It has a label and focus and non focus textures, as
+well as a spin control on the right.
+
+--------------------------------------------------------------------------------
+\section Settings_Spin_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="spincontrolex" id="12">
+ <description>My first settings spin control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <spinposx>220</spinposx>
+ <spinposy>180</spinposy>
+ <spinwidth>16</spinwidth>
+ <spinheight>16</spinheight>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <texturefocus>myfocustexture.png</texturefocus>
+ <texturenofocus>mynofocustexture.png</texturenofocus>
+ <textureup>myuptexture.png</textureup>
+ <textureupfocus>myupfocustexture.png</textureupfocus>
+ <texturedown>mydowntexture.png</texturedown>
+ <texturedownfocus>mydownfocustexture.png</texturedownfocus>
+ <textureupdisabled colordiffuse="AAFFAAFF">mydowntexture.png</textureupdisabled>
+ <texturedowndisabled colordiffuse="AAFFAAFF">mydownfocustexture.png</texturedowndisabled>
+ <label>46</label>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <align></align>
+ <aligny></aligny>
+ <textoffsetx></textoffsetx>
+ <textoffsety></textoffsety>
+ <pulseonselect></pulseonselect>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Settings_Spin_Control_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------------:|:--------------------------------------------------------------|
+| spinposx | The horizontal position of the spin control for multipage lists. This is offset from the top left of the list.
+| spinposy | The vertical position of the spin control for multipage lists. This is offset from the top left of the list.
+| spinwidth | The width of one of the spin control buttons. The textures for this spin control will be scaled to fit this width.
+| spinheight | The height of one of the spin control buttons. The textures for this spin control will be scaled to fit this height.
+| texturefocus | Specifies the image file which should be displayed for the control when it has focus. [See here for additional information about textures](http://kodi.wiki/view/Texture_Attributes).
+| texturenofocus | Specifies the image file which should be displayed for the control when it doesn't focus.
+| textureup | Specifies the image file which should be displayed for the up arrow when it doesn't have focus. It is displayed to the left of the down arrow.
+| textureupfocus | Specifies the image file which should be displayed for the up arrow when it has focus.
+| textureupdisabled | Specifies the image file which should be displayed for the up arrow when the button is disabled.
+| texturedown | Specifies the image file which should be displayed for the down arrow when it is not focused. It is displayed to the right of the up arrow so that it's right edge is <b>`<textoffsetx>`</b> pixels away from the right edge of the control.
+| texturedownfocus | Specifies the image file which should be displayed for the down arrow when it has focus.
+| texturedowndisabled | Specifies the image file which should be displayed for the up arrow when the button is disabled.
+| label | Either a numeric reference into strings.po (for localization), or a string that will be shown on the left of the control.
+| font | Font used for the controls label. From fonts.xml.
+| textcolor | Color used for displaying the label. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| disabledcolor | Color used for the label if the control is disabled. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| align | Label horizontal alignment on the control. Defaults to left.
+| aligny | Label vertical alignment on the control. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| textwidth | Will truncate any text that's too long.
+
+
+--------------------------------------------------------------------------------
+\section Settings_Spin_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUISpinControlEx.h b/xbmc/guilib/GUISpinControlEx.h
new file mode 100644
index 0000000..75c3963
--- /dev/null
+++ b/xbmc/guilib/GUISpinControlEx.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUISpinControlEx.h
+\brief
+*/
+
+#include "GUIButtonControl.h"
+#include "GUISpinControl.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUISpinControlEx : public CGUISpinControl
+{
+public:
+ CGUISpinControlEx(int parentID, int controlID, float posX, float posY, float width, float height, float spinWidth, float spinHeight, const CLabelInfo& spinInfo, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus, const CTextureInfo& textureUp, const CTextureInfo& textureDown, const CTextureInfo& textureUpFocus, const CTextureInfo& textureDownFocus, const CTextureInfo& textureUpDisabled, const CTextureInfo& textureDownDisabled, const CLabelInfo& labelInfo, int iType);
+ ~CGUISpinControlEx(void) override;
+ CGUISpinControlEx* Clone() const override { return new CGUISpinControlEx(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void SetPosition(float posX, float posY) override;
+ float GetWidth() const override { return m_buttonControl.GetWidth(); }
+ void SetWidth(float width) override;
+ float GetHeight() const override { return m_buttonControl.GetHeight(); }
+ void SetHeight(float height) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ const std::string GetCurrentLabel() const;
+ void SetText(const std::string& aLabel) { m_buttonControl.SetLabel(aLabel); }
+ void SetEnabled(bool bEnable) override;
+ float GetXPosition() const override { return m_buttonControl.GetXPosition(); }
+ float GetYPosition() const override { return m_buttonControl.GetYPosition(); }
+ std::string GetDescription() const override;
+ bool HitTest(const CPoint& point) const override { return m_buttonControl.HitTest(point); }
+ void SetSpinPosition(float spinPosX);
+
+ void SetItemInvalid(bool invalid);
+protected:
+ void RenderText(float posX, float posY, float width, float height) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ CGUIButtonControl m_buttonControl;
+ float m_spinPosX;
+};
+
diff --git a/xbmc/guilib/GUIStaticItem.cpp b/xbmc/guilib/GUIStaticItem.cpp
new file mode 100644
index 0000000..97cd754
--- /dev/null
+++ b/xbmc/guilib/GUIStaticItem.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIStaticItem.h"
+
+#include "GUIControlFactory.h"
+#include "GUIInfoManager.h"
+#include "guilib/GUIComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/XMLUtils.h"
+
+using namespace KODI::GUILIB;
+
+CGUIStaticItem::CGUIStaticItem(const TiXmlElement *item, int parentID) : CFileItem()
+{
+ m_visState = false;
+
+ assert(item);
+
+ GUIINFO::CGUIInfoLabel label, label2, thumb, icon;
+ CGUIControlFactory::GetInfoLabel(item, "label", label, parentID);
+ CGUIControlFactory::GetInfoLabel(item, "label2", label2, parentID);
+ CGUIControlFactory::GetInfoLabel(item, "thumb", thumb, parentID);
+ CGUIControlFactory::GetInfoLabel(item, "icon", icon, parentID);
+ const char *id = item->Attribute("id");
+ std::string condition;
+ CGUIControlFactory::GetConditionalVisibility(item, condition);
+ SetVisibleCondition(condition, parentID);
+ CGUIControlFactory::GetActions(item, "onclick", m_clickActions);
+ SetLabel(label.GetLabel(parentID));
+ SetLabel2(label2.GetLabel(parentID));
+ SetArt("thumb", thumb.GetLabel(parentID, true));
+ SetArt("icon", icon.GetLabel(parentID, true));
+ if (!label.IsConstant()) m_info.push_back(std::make_pair(label, "label"));
+ if (!label2.IsConstant()) m_info.push_back(std::make_pair(label2, "label2"));
+ if (!thumb.IsConstant()) m_info.push_back(std::make_pair(thumb, "thumb"));
+ if (!icon.IsConstant()) m_info.push_back(std::make_pair(icon, "icon"));
+ m_iprogramCount = id ? atoi(id) : 0;
+ // add any properties
+ const TiXmlElement *property = item->FirstChildElement("property");
+ while (property)
+ {
+ std::string name = XMLUtils::GetAttribute(property, "name");
+ GUIINFO::CGUIInfoLabel prop;
+ if (!name.empty() && CGUIControlFactory::GetInfoLabelFromElement(property, prop, parentID))
+ {
+ SetProperty(name, prop.GetLabel(parentID, true).c_str());
+ if (!prop.IsConstant())
+ m_info.push_back(std::make_pair(prop, name));
+ }
+ property = property->NextSiblingElement("property");
+ }
+}
+
+CGUIStaticItem::CGUIStaticItem(const CFileItem &item)
+: CFileItem(item)
+{
+ m_visState = false;
+}
+
+CGUIStaticItem::CGUIStaticItem(const CGUIStaticItem& other)
+ : CFileItem(other),
+ m_info(other.m_info),
+ m_visCondition(other.m_visCondition),
+ m_visState(other.m_visState),
+ m_clickActions(other.m_clickActions)
+{
+}
+
+void CGUIStaticItem::UpdateProperties(int contextWindow)
+{
+ for (const auto& i : m_info)
+ {
+ const GUIINFO::CGUIInfoLabel& info = i.first;
+ const std::string& name = i.second;
+ bool preferTexture = StringUtils::CompareNoCase("label", name, 5) != 0;
+ const std::string& value(info.GetLabel(contextWindow, preferTexture));
+ if (StringUtils::EqualsNoCase(name, "label"))
+ SetLabel(value);
+ else if (StringUtils::EqualsNoCase(name, "label2"))
+ SetLabel2(value);
+ else if (StringUtils::EqualsNoCase(name, "thumb"))
+ SetArt("thumb", value);
+ else if (StringUtils::EqualsNoCase(name, "icon"))
+ SetArt("icon", value);
+ else
+ SetProperty(name, value.c_str());
+ }
+}
+
+bool CGUIStaticItem::UpdateVisibility(int contextWindow)
+{
+ if (!m_visCondition)
+ return false;
+ bool state = m_visCondition->Get(contextWindow);
+ if (state != m_visState)
+ {
+ m_visState = state;
+ return true;
+ }
+ return false;
+}
+
+bool CGUIStaticItem::IsVisible() const
+{
+ if (m_visCondition)
+ return m_visState;
+ return true;
+}
+
+void CGUIStaticItem::SetVisibleCondition(const std::string &condition, int context)
+{
+ m_visCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context);
+ m_visState = false;
+}
diff --git a/xbmc/guilib/GUIStaticItem.h b/xbmc/guilib/GUIStaticItem.h
new file mode 100644
index 0000000..a68f739
--- /dev/null
+++ b/xbmc/guilib/GUIStaticItem.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+ \file GUIStaticItem.h
+ \brief
+ */
+
+#include "FileItem.h"
+#include "GUIAction.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+#include "interfaces/info/InfoBool.h"
+
+#include <utility>
+#include <vector>
+
+class TiXmlElement;
+
+/*!
+ \ingroup lists,items
+ \brief wrapper class for a static item in a list container
+
+ A wrapper class for the items in a container specified via the <content>
+ flag. Handles constructing items from XML and updating item labels, icons
+ and properties.
+
+ \sa CFileItem, CGUIBaseContainer
+ */
+class CGUIStaticItem : public CFileItem
+{
+public:
+ /*! \brief constructor
+ Construct an item based on an XML description:
+ <item>
+ <label>$INFO[MusicPlayer.Artist]</label>
+ <label2>$INFO[MusicPlayer.Album]</label2>
+ <thumb>bar.png</thumb>
+ <icon>foo.jpg</icon>
+ <onclick>ActivateWindow(Home)</onclick>
+ </item>
+
+ \param element XML element to construct from
+ \param contextWindow window context to use for any info labels
+ */
+ CGUIStaticItem(const TiXmlElement *element, int contextWindow);
+ explicit CGUIStaticItem(const CFileItem &item); // for python
+ explicit CGUIStaticItem(const CGUIStaticItem& other);
+ ~CGUIStaticItem() override = default;
+ CGUIListItem* Clone() const override { return new CGUIStaticItem(*this); }
+
+ /*! \brief update any infolabels in the items properties
+ Runs through all the items properties, updating any that should be
+ periodically recomputed
+ \param contextWindow window context to use for any info labels
+ */
+ void UpdateProperties(int contextWindow);
+
+ /*! \brief update visibility of this item
+ \param contextWindow window context to use for any info labels
+ \return true if visible state has changed, false otherwise
+ */
+ bool UpdateVisibility(int contextWindow);
+
+ /*! \brief whether this item is visible or not
+ */
+ bool IsVisible() const;
+
+ /*! \brief set a visible condition for this item.
+ \param condition the condition to use.
+ \param context the context for the condition (typically a window id).
+ */
+ void SetVisibleCondition(const std::string &condition, int context);
+
+ const CGUIAction& GetClickActions() const { return m_clickActions; }
+
+private:
+ typedef std::vector< std::pair<KODI::GUILIB::GUIINFO::CGUIInfoLabel, std::string> > InfoVector;
+ InfoVector m_info;
+ INFO::InfoPtr m_visCondition;
+ bool m_visState;
+ CGUIAction m_clickActions;
+};
+
+typedef std::shared_ptr<CGUIStaticItem> CGUIStaticItemPtr;
diff --git a/xbmc/guilib/GUITextBox.cpp b/xbmc/guilib/GUITextBox.cpp
new file mode 100644
index 0000000..dcfbb8e
--- /dev/null
+++ b/xbmc/guilib/GUITextBox.cpp
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextBox.h"
+
+#include "GUIInfoManager.h"
+#include "GUIMessage.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+
+#include <algorithm>
+
+using namespace KODI::GUILIB;
+
+CGUITextBox::CGUITextBox(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo& labelInfo, int scrollTime,
+ const CLabelInfo* labelInfoMono)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+ , CGUITextLayout(labelInfo.font, true)
+ , m_label(labelInfo)
+{
+ m_offset = 0;
+ m_scrollOffset = 0;
+ m_scrollSpeed = 0;
+ m_itemsPerPage = 10;
+ m_itemHeight = 10;
+ ControlType = GUICONTROL_TEXTBOX;
+ m_pageControl = 0;
+ m_lastRenderTime = 0;
+ m_scrollTime = scrollTime;
+ m_autoScrollTime = 0;
+ m_autoScrollDelay = 3000;
+ m_autoScrollDelayTime = 0;
+ m_autoScrollRepeatAnim = NULL;
+ m_minHeight = 0;
+ m_renderHeight = height;
+ if (labelInfoMono)
+ SetMonoFont(labelInfoMono->font);
+}
+
+CGUITextBox::CGUITextBox(const CGUITextBox& from)
+ : CGUIControl(from), CGUITextLayout(from), m_autoScrollCondition(from.m_autoScrollCondition)
+{
+ m_pageControl = from.m_pageControl;
+ m_scrollTime = from.m_scrollTime;
+ m_autoScrollTime = from.m_autoScrollTime;
+ m_autoScrollDelay = from.m_autoScrollDelay;
+ m_minHeight = from.m_minHeight;
+ m_renderHeight = from.m_renderHeight;
+ m_autoScrollRepeatAnim = NULL;
+ if (from.m_autoScrollRepeatAnim)
+ m_autoScrollRepeatAnim = new CAnimation(*from.m_autoScrollRepeatAnim);
+ m_label = from.m_label;
+ m_info = from.m_info;
+ // defaults
+ m_offset = 0;
+ m_scrollOffset = 0;
+ m_scrollSpeed = 0;
+ m_itemsPerPage = 10;
+ m_itemHeight = 10;
+ m_lastRenderTime = 0;
+ m_autoScrollDelayTime = 0;
+ ControlType = GUICONTROL_TEXTBOX;
+}
+
+CGUITextBox::~CGUITextBox(void)
+{
+ delete m_autoScrollRepeatAnim;
+ m_autoScrollRepeatAnim = NULL;
+}
+
+bool CGUITextBox::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIControl::UpdateColors(nullptr);
+ changed |= m_label.UpdateColors();
+
+ return changed;
+}
+
+#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+void CGUITextBox::UpdateInfo(const CGUIListItem *item)
+{
+ m_textColor = m_label.textColor;
+ if (!CGUITextLayout::Update(item ? m_info.GetItemLabel(item) : m_info.GetLabel(m_parentID), m_width))
+ return; // nothing changed
+
+ // needed update, so reset to the top of the textbox and update our sizing/page control
+ SetInvalid();
+ m_offset = 0;
+ m_scrollOffset = 0;
+ ResetAutoScrolling();
+
+ m_itemHeight = m_font ? m_font->GetLineHeight() : 10;
+ float textHeight = m_font ? m_font->GetTextHeight(m_lines.size()) : m_itemHeight * m_lines.size();
+ float maxHeight = m_height ? m_height : textHeight;
+ m_renderHeight = m_minHeight ? CLAMP(textHeight, m_minHeight, maxHeight) : m_height;
+ m_itemsPerPage = (unsigned int)(m_renderHeight / m_itemHeight);
+
+ UpdatePageControl();
+}
+
+void CGUITextBox::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CGUIControl::DoProcess(currentTime, dirtyregions);
+
+ // if not visible, we reset the autoscroll timer and positioning
+ if (!IsVisible() && m_autoScrollTime)
+ {
+ ResetAutoScrolling();
+ m_lastRenderTime = 0;
+ m_offset = 0;
+ m_scrollOffset = 0;
+ m_scrollSpeed = 0;
+ }
+}
+
+void CGUITextBox::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // update our auto-scrolling as necessary
+ if (m_autoScrollTime && m_lines.size() > m_itemsPerPage)
+ {
+ if (!m_autoScrollCondition || m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
+ {
+ if (m_lastRenderTime)
+ m_autoScrollDelayTime += currentTime - m_lastRenderTime;
+ if (m_autoScrollDelayTime > (unsigned int)m_autoScrollDelay && m_scrollSpeed == 0)
+ { // delay is finished - start scrolling
+ MarkDirtyRegion();
+ if (m_offset < (int)m_lines.size() - m_itemsPerPage)
+ ScrollToOffset(m_offset + 1, true);
+ else
+ { // at the end, run a delay and restart
+ if (m_autoScrollRepeatAnim)
+ {
+ if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_NONE)
+ m_autoScrollRepeatAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
+ else if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_APPLIED)
+ { // reset to the start of the list and start the scrolling again
+ m_offset = 0;
+ m_scrollOffset = 0;
+ ResetAutoScrolling();
+ }
+ }
+ }
+ }
+ }
+ else if (m_autoScrollCondition)
+ ResetAutoScrolling(); // conditional is false, so reset the autoscrolling
+ }
+
+ // render the repeat anim as appropriate
+ if (m_autoScrollRepeatAnim)
+ {
+ if (m_autoScrollRepeatAnim->GetProcess() != ANIM_PROCESS_NONE)
+ MarkDirtyRegion();
+ m_autoScrollRepeatAnim->Animate(currentTime, true);
+ TransformMatrix matrix;
+ m_autoScrollRepeatAnim->RenderAnimation(matrix);
+ m_cachedTextMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(matrix);
+ }
+
+ // update our scroll position as necessary
+ if (m_scrollSpeed != 0)
+ MarkDirtyRegion();
+
+ if (m_lastRenderTime)
+ m_scrollOffset += m_scrollSpeed * (currentTime - m_lastRenderTime);
+ if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset * m_itemHeight) ||
+ (m_scrollSpeed > 0 && m_scrollOffset > m_offset * m_itemHeight))
+ {
+ m_scrollOffset = m_offset * m_itemHeight;
+ m_scrollSpeed = 0;
+ }
+ m_lastRenderTime = currentTime;
+
+ if (m_pageControl)
+ {
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl,
+ MathUtils::round_int(static_cast<double>(m_scrollOffset / m_itemHeight)));
+ SendWindowMessage(msg);
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+
+ if (m_autoScrollRepeatAnim)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+void CGUITextBox::Render()
+{
+ // render the repeat anim as appropriate
+ if (m_autoScrollRepeatAnim)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTextMatrix);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_renderHeight))
+ {
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ int offset = (int)(m_scrollOffset / m_itemHeight);
+ float posX = m_posX;
+ float posY = m_posY + offset * m_itemHeight - m_scrollOffset;
+
+ uint32_t alignment = m_label.align;
+
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ if (m_font)
+ {
+ float textHeight = m_font->GetTextHeight(std::min((unsigned int)m_lines.size(), m_itemsPerPage));
+
+ if (textHeight <= m_renderHeight)
+ posY += (m_renderHeight - textHeight) * 0.5f;
+ }
+
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+
+ // alignment correction
+ if (alignment & XBFONT_CENTER_X)
+ posX += m_width * 0.5f;
+ if (alignment & XBFONT_RIGHT)
+ posX += m_width;
+
+ if (m_font)
+ {
+ m_font->Begin();
+ int current = offset;
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = m_label.textColor;
+
+ while (posY < m_posY + m_renderHeight && current < (int)m_lines.size())
+ {
+ uint32_t align = alignment;
+ if (m_lines[current].m_text.size() && m_lines[current].m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED; // last line of a paragraph shouldn't be justified
+ m_font->DrawText(posX, posY, m_colors, m_label.shadowColor, m_lines[current].m_text, align, m_width);
+ posY += m_itemHeight;
+ current++;
+ }
+ m_font->End();
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ if (m_autoScrollRepeatAnim)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ CGUIControl::Render();
+}
+
+bool CGUITextBox::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID())
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_SET)
+ {
+ m_offset = 0;
+ m_scrollOffset = 0;
+ ResetAutoScrolling();
+ CGUITextLayout::Reset();
+ m_info.SetLabel(message.GetLabel(), "", GetParentID());
+ }
+
+ if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ m_offset = 0;
+ m_scrollOffset = 0;
+ ResetAutoScrolling();
+ CGUITextLayout::Reset();
+ UpdatePageControl();
+ SetInvalid();
+ }
+
+ if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
+ {
+ if (message.GetSenderId() == m_pageControl)
+ { // update our page
+ Scroll(message.GetParam1());
+ return true;
+ }
+ }
+
+ if (message.GetMessage() == GUI_MSG_SET_TYPE)
+ {
+ UseMonoFont(message.GetParam1() == 1 ? true : false);
+ return true;
+ }
+ }
+
+ return CGUIControl::OnMessage(message);
+}
+
+float CGUITextBox::GetHeight() const
+{
+ return m_renderHeight;
+}
+
+void CGUITextBox::SetMinHeight(float minHeight)
+{
+ if (m_minHeight != minHeight)
+ SetInvalid();
+
+ m_minHeight = minHeight;
+}
+
+void CGUITextBox::UpdatePageControl()
+{
+ if (m_pageControl)
+ {
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, m_lines.size());
+ SendWindowMessage(msg);
+ }
+}
+
+bool CGUITextBox::CanFocus() const
+{
+ return false;
+}
+
+void CGUITextBox::SetPageControl(int pageControl)
+{
+ m_pageControl = pageControl;
+}
+
+void CGUITextBox::SetInfo(const GUIINFO::CGUIInfoLabel &infoLabel)
+{
+ m_info = infoLabel;
+}
+
+void CGUITextBox::Scroll(unsigned int offset)
+{
+ ResetAutoScrolling();
+ if (m_lines.size() <= m_itemsPerPage)
+ return; // no need to scroll
+ if (offset > m_lines.size() - m_itemsPerPage)
+ offset = m_lines.size() - m_itemsPerPage; // on last page
+ ScrollToOffset(offset);
+}
+
+void CGUITextBox::ScrollToOffset(int offset, bool autoScroll)
+{
+ m_scrollOffset = m_offset * m_itemHeight;
+ int timeToScroll = autoScroll ? m_autoScrollTime : m_scrollTime;
+ m_scrollSpeed = (offset * m_itemHeight - m_scrollOffset) / timeToScroll;
+ m_offset = offset;
+}
+
+void CGUITextBox::SetAutoScrolling(const TiXmlNode *node)
+{
+ if (!node) return;
+ const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
+ if (scroll)
+ {
+ scroll->Attribute("delay", &m_autoScrollDelay);
+ scroll->Attribute("time", &m_autoScrollTime);
+ if (scroll->FirstChild())
+ m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
+ int repeatTime;
+ if (scroll->Attribute("repeat", &repeatTime))
+ m_autoScrollRepeatAnim = new CAnimation(CAnimation::CreateFader(100, 0, repeatTime, 1000));
+ }
+}
+
+void CGUITextBox::SetAutoScrolling(int delay, int time, int repeatTime, const std::string &condition /* = "" */)
+{
+ m_autoScrollDelay = delay;
+ m_autoScrollTime = time;
+ if (!condition.empty())
+ m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, GetParentID());
+ m_autoScrollRepeatAnim = new CAnimation(CAnimation::CreateFader(100, 0, repeatTime, 1000));
+}
+
+void CGUITextBox::ResetAutoScrolling()
+{
+ m_autoScrollDelayTime = 0;
+ if (m_autoScrollRepeatAnim)
+ m_autoScrollRepeatAnim->ResetAnimation();
+}
+
+unsigned int CGUITextBox::GetRows() const
+{
+ return m_lines.size();
+}
+
+int CGUITextBox::GetNumPages() const
+{
+ return m_itemsPerPage > 0 ? (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage : 0;
+}
+
+int CGUITextBox::GetCurrentPage() const
+{
+ if (m_offset + m_itemsPerPage >= GetRows()) // last page
+ return GetNumPages();
+ return m_offset / m_itemsPerPage + 1;
+}
+
+std::string CGUITextBox::GetLabel(int info) const
+{
+ std::string label;
+ switch (info)
+ {
+ case CONTAINER_NUM_PAGES:
+ label = std::to_string(GetNumPages());
+ break;
+ case CONTAINER_CURRENT_PAGE:
+ label = std::to_string(GetCurrentPage());
+ break;
+ default:
+ break;
+ }
+ return label;
+}
+
+bool CGUITextBox::GetCondition(int condition, int data) const
+{
+ switch (condition)
+ {
+ case CONTAINER_HAS_NEXT:
+ return (GetCurrentPage() < GetNumPages());
+ case CONTAINER_HAS_PREVIOUS:
+ return (GetCurrentPage() > 1);
+ default:
+ return false;
+ }
+}
+
+std::string CGUITextBox::GetDescription() const
+{
+ return GetText();
+}
+
+void CGUITextBox::UpdateVisibility(const CGUIListItem *item)
+{
+ // we have to update the page control when we become visible
+ // as another control may be sharing the same page control when we're
+ // not visible
+ bool wasVisible = IsVisible();
+ CGUIControl::UpdateVisibility(item);
+ if (IsVisible() && !wasVisible)
+ UpdatePageControl();
+}
diff --git a/xbmc/guilib/GUITextBox.dox b/xbmc/guilib/GUITextBox.dox
new file mode 100644
index 0000000..aba63ca
--- /dev/null
+++ b/xbmc/guilib/GUITextBox.dox
@@ -0,0 +1,63 @@
+/*!
+
+\page Text_Box Text Box
+\brief **Used to show a multi-page piece of text.**
+
+\tableofcontents
+
+The text box is used for showing a large multipage piece of text in Kodi. You
+can choose the position, size, and look of the text.
+
+
+--------------------------------------------------------------------------------
+\section Text_Box_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="textbox" id="2">
+ <description>My first text box control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <font>font13</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <pulseonselect></pulseonselect>
+ <pagecontrol>13</pagecontrol>
+ <scrolltime>200</scrolltime>
+ <autoscroll delay="3000" time="1000" repeat="10000">!Control.HasFocus(13)</autoscroll>
+ <label>Text to display goes here [CR] next line...</label>
+ <align>center</align>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Text_Box_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is lower case only. This is
+important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| height | <b>`<height>auto</height>`</b> is supported in textbox controls
+| font | Font used for the items first label. From `fonts.xml`.
+| textcolor | Color used for displaying the text. In **AARRGGBB** hex format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| shadowcolor | Specifies the color of the drop shadow on the text. In **AARRGGBB** format, or a name from the [colour theme](http://kodi.wiki/view/Colour_Themes).
+| pagecontrol | Specifies the <b>`<id>`</b> of the page control used to control this textbox. The page control can either be a \ref Spin_Control "Spin Control" or a \ref Scroll_Bar_Control "Scroll Bar Control".
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is *200ms*. The list will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling.
+| align | possible values for text alignment: left, right, center, justify
+| autoscroll | Specifies the timing and conditions of any autoscrolling this textbox should have. Times are in milliseconds. The content is delayed for the given delay, then scrolls at a rate of one line per time interval until the end. If the repeat tag is present, it then delays for the repeat time, fades out over 1 second, and repeats. It does not wrap or reset to the top at the end of the scroll. You can use any [bool condition](http://kodi.wiki/view/List_of_Boolean_Conditions) to specify when autoscrolling should be allowed.
+
+
+--------------------------------------------------------------------------------
+\section Text_Box_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUITextBox.h b/xbmc/guilib/GUITextBox.h
new file mode 100644
index 0000000..7027731
--- /dev/null
+++ b/xbmc/guilib/GUITextBox.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUITextBox.h
+\brief
+*/
+
+#include "GUIControl.h"
+#include "GUILabel.h"
+#include "GUITextLayout.h"
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+
+class TiXmlNode;
+
+class CGUITextBox : public CGUIControl, public CGUITextLayout
+{
+public:
+ CGUITextBox(int parentID, int controlID, float posX, float posY, float width, float height,
+ const CLabelInfo &labelInfo, int scrollTime = 200,
+ const CLabelInfo* labelInfoMono = nullptr);
+ CGUITextBox(const CGUITextBox &from);
+ ~CGUITextBox(void) override;
+ CGUITextBox* Clone() const override { return new CGUITextBox(*this); }
+
+ void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnMessage(CGUIMessage& message) override;
+ float GetHeight() const override;
+ void SetMinHeight(float minHeight);
+
+ void SetPageControl(int pageControl);
+
+ bool CanFocus() const override;
+ void SetInfo(const KODI::GUILIB::GUIINFO::CGUIInfoLabel &info);
+ void SetAutoScrolling(const TiXmlNode *node);
+ void SetAutoScrolling(int delay, int time, int repeatTime, const std::string &condition = "");
+ void ResetAutoScrolling();
+
+ bool GetCondition(int condition, int data) const override;
+ virtual std::string GetLabel(int info) const;
+ std::string GetDescription() const override;
+
+ void Scroll(unsigned int offset);
+
+protected:
+ void UpdateVisibility(const CGUIListItem *item = NULL) override;
+ bool UpdateColors(const CGUIListItem* item) override;
+ void UpdateInfo(const CGUIListItem *item = NULL) override;
+ void UpdatePageControl();
+ void ScrollToOffset(int offset, bool autoScroll = false);
+ unsigned int GetRows() const;
+ int GetCurrentPage() const;
+ int GetNumPages() const;
+
+ // auto-height
+ float m_minHeight;
+ float m_renderHeight;
+
+ // offset of text in the control for scrolling
+ unsigned int m_offset;
+ float m_scrollOffset;
+ float m_scrollSpeed;
+ int m_scrollTime;
+ unsigned int m_itemsPerPage;
+ float m_itemHeight;
+ unsigned int m_lastRenderTime;
+
+ CLabelInfo m_label;
+
+ TransformMatrix m_cachedTextMatrix;
+
+ // autoscrolling
+ INFO::InfoPtr m_autoScrollCondition;
+ int m_autoScrollTime; // time to scroll 1 line (ms)
+ int m_autoScrollDelay; // delay before scroll (ms)
+ unsigned int m_autoScrollDelayTime; // current offset into the delay
+ CAnimation *m_autoScrollRepeatAnim;
+
+ int m_pageControl;
+
+ KODI::GUILIB::GUIINFO::CGUIInfoLabel m_info;
+};
+
diff --git a/xbmc/guilib/GUITextLayout.cpp b/xbmc/guilib/GUITextLayout.cpp
new file mode 100644
index 0000000..5058853
--- /dev/null
+++ b/xbmc/guilib/GUITextLayout.cpp
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextLayout.h"
+
+#include "GUIColorManager.h"
+#include "GUIComponent.h"
+#include "GUIControl.h"
+#include "GUIFont.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+
+CGUIString::CGUIString(iString start, iString end, bool carriageReturn)
+{
+ m_text.assign(start, end);
+ m_carriageReturn = carriageReturn;
+}
+
+std::string CGUIString::GetAsString() const
+{
+ std::string text;
+ for (unsigned int i = 0; i < m_text.size(); i++)
+ text += (char)(m_text[i] & 0xff);
+ return text;
+}
+
+CGUITextLayout::CGUITextLayout(CGUIFont *font, bool wrap, float fHeight, CGUIFont *borderFont)
+{
+ m_varFont = m_font = font;
+ m_borderFont = borderFont;
+ m_textColor = 0;
+ m_wrap = wrap;
+ m_maxHeight = fHeight;
+ m_textWidth = 0;
+ m_textHeight = 0;
+ m_lastUpdateW = false;
+}
+
+void CGUITextLayout::SetWrap(bool bWrap)
+{
+ m_wrap = bWrap;
+}
+
+void CGUITextLayout::Render(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ bool solid)
+{
+ if (!m_font)
+ return;
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ // render the text at the required location, angle, and size
+ if (angle)
+ {
+ static const float degrees_to_radians = 0.01745329252f;
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio()));
+ }
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ m_font->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+ if (solid)
+ m_font->DrawText(x, y, m_colors[0], shadowColor, string.m_text, align, maxWidth);
+ else
+ m_font->DrawText(x, y, m_colors, shadowColor, string.m_text, align, maxWidth);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+ if (angle)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+bool CGUITextLayout::UpdateScrollinfo(CScrollInfo &scrollInfo)
+{
+ if (!m_font)
+ return false;
+ if (m_lines.empty())
+ return false;
+
+ return m_font->UpdateScrollInfo(m_lines[0].m_text, scrollInfo);
+}
+
+
+void CGUITextLayout::RenderScrolling(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ const CScrollInfo& scrollInfo)
+{
+ if (!m_font)
+ return;
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ // render the text at the required location, angle, and size
+ if (angle)
+ {
+ static const float degrees_to_radians = 0.01745329252f;
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(TransformMatrix::CreateZRotation(angle * degrees_to_radians, x, y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio()));
+ }
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ m_font->Begin();
+ // NOTE: This workaround is needed as otherwise multi-line text that scrolls
+ // will scroll in proportion to the number of lines. Ideally we should
+ // do the DrawScrollingText calculation here. This probably won't make
+ // any difference to the smoothness of scrolling though which will be
+ // jumpy with this sort of thing. It's not exactly a well used situation
+ // though, so this hack is probably OK.
+ for (const auto& string : m_lines)
+ {
+ m_font->DrawScrollingText(x, y, m_colors, shadowColor, string.m_text, alignment, maxWidth, scrollInfo);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+ if (angle)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+}
+
+void CGUITextLayout::RenderOutline(float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color outlineColor,
+ uint32_t alignment,
+ float maxWidth)
+{
+ if (!m_font)
+ return;
+
+ // set the outline color
+ std::vector<UTILS::COLOR::Color> outlineColors;
+ if (m_colors.size())
+ outlineColors.push_back(outlineColor);
+
+ // center our text vertically
+ if (alignment & XBFONT_CENTER_Y)
+ {
+ y -= m_font->GetTextHeight(m_lines.size()) * 0.5f;
+ alignment &= ~XBFONT_CENTER_Y;
+ }
+ if (m_borderFont)
+ {
+ // adjust so the baselines of the fonts align
+ float by = y + m_font->GetTextBaseLine() - m_borderFont->GetTextBaseLine();
+ m_borderFont->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+ // text centered horizontally must be computed using the original font, not the bordered
+ // font, as the bordered font will be wider, and thus will end up uncentered.
+ //! @todo We should really have a better way to handle text extent - at the moment we assume
+ //! that text is rendered from a posx, posy, width, and height which isn't enough to
+ //! accurately position text. We need a vertical and horizontal offset of the baseline
+ //! and cursor as well.
+ float bx = x;
+ if (align & XBFONT_CENTER_X)
+ {
+ bx -= m_font->GetTextWidth(string.m_text) * 0.5f;
+ align &= ~XBFONT_CENTER_X;
+ }
+
+ // don't pass maxWidth through to the renderer for the same reason above: it will cause clipping
+ // on the left.
+ m_borderFont->DrawText(bx, by, outlineColors, 0, string.m_text, align, 0);
+ by += m_borderFont->GetLineHeight();
+ }
+ m_borderFont->End();
+ }
+
+ // set the main text color
+ if (m_colors.size())
+ m_colors[0] = color;
+
+ m_font->Begin();
+ for (const auto& string : m_lines)
+ {
+ uint32_t align = alignment;
+ if (align & XBFONT_JUSTIFIED && string.m_carriageReturn)
+ align &= ~XBFONT_JUSTIFIED;
+
+ // don't pass maxWidth through to the renderer for the reason above.
+ m_font->DrawText(x, y, m_colors, 0, string.m_text, align, 0);
+ y += m_font->GetLineHeight();
+ }
+ m_font->End();
+}
+
+bool CGUITextLayout::Update(const std::string &text, float maxWidth, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
+{
+ if (text == m_lastUtf8Text && !forceUpdate && !m_lastUpdateW)
+ return false;
+
+ m_lastUtf8Text = text;
+ m_lastUpdateW = false;
+ std::wstring utf16;
+ g_charsetConverter.utf8ToW(text, utf16, false);
+ UpdateCommon(utf16, maxWidth, forceLTRReadingOrder);
+ return true;
+}
+
+bool CGUITextLayout::UpdateW(const std::wstring &text, float maxWidth /*= 0*/, bool forceUpdate /*= false*/, bool forceLTRReadingOrder /*= false*/)
+{
+ if (text == m_lastText && !forceUpdate && m_lastUpdateW)
+ return false;
+
+ m_lastText = text;
+ m_lastUpdateW = true;
+ UpdateCommon(text, maxWidth, forceLTRReadingOrder);
+ return true;
+}
+
+void CGUITextLayout::UpdateCommon(const std::wstring &text, float maxWidth, bool forceLTRReadingOrder)
+{
+ // parse the text for style information
+ vecText parsedText;
+ std::vector<UTILS::COLOR::Color> colors;
+ ParseText(text, m_font ? m_font->GetStyle() : 0, m_textColor, colors, parsedText);
+
+ // and update
+ UpdateStyled(parsedText, colors, maxWidth, forceLTRReadingOrder);
+}
+
+void CGUITextLayout::UpdateStyled(const vecText& text,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ float maxWidth,
+ bool forceLTRReadingOrder)
+{
+ // empty out our previous string
+ m_lines.clear();
+ m_colors = colors;
+
+ // if we need to wrap the text, then do so
+ if (m_wrap && maxWidth > 0)
+ WrapText(text, maxWidth);
+ else
+ LineBreakText(text, m_lines);
+
+ // remove any trailing blank lines
+ while (!m_lines.empty() && m_lines.back().m_text.empty())
+ m_lines.pop_back();
+
+ BidiTransform(m_lines, forceLTRReadingOrder);
+
+ // and cache the width and height for later reading
+ CalcTextExtent();
+}
+
+// BidiTransform is used to handle RTL text flipping in the string
+void CGUITextLayout::BidiTransform(std::vector<CGUIString>& lines, bool forceLTRReadingOrder)
+{
+ for (unsigned int i = 0; i < lines.size(); i++)
+ {
+ CGUIString& line = lines[i];
+ unsigned int lineLength = line.m_text.size();
+ std::wstring logicalText;
+ vecText style;
+
+ logicalText.reserve(lineLength);
+ style.reserve(lineLength);
+
+ // Separate the text and style for the input styled text
+ for (const auto& it : line.m_text)
+ {
+ logicalText.push_back((wchar_t)(it & 0xffff));
+ style.push_back(it & 0xffff0000);
+ }
+
+ // Allocate memory for visual to logical map and call bidi
+ int* visualToLogicalMap = new (std::nothrow) int[lineLength + 1]();
+ std::wstring visualText = BidiFlip(logicalText, forceLTRReadingOrder, visualToLogicalMap);
+
+ vecText styledVisualText;
+ styledVisualText.reserve(lineLength);
+
+ // If memory allocation failed, fallback to text with no styling
+ if (!visualToLogicalMap)
+ {
+ for (unsigned int j = 0; j < visualText.size(); j++)
+ {
+ styledVisualText.push_back(visualText[j]);
+ }
+ }
+ else
+ {
+ for (unsigned int j = 0; j < visualText.size(); j++)
+ {
+ styledVisualText.push_back(style[visualToLogicalMap[j]] | visualText[j]);
+ }
+ }
+
+ delete[] visualToLogicalMap;
+
+ // replace the original line with the processed one
+ lines[i] = CGUIString(styledVisualText.begin(), styledVisualText.end(), line.m_carriageReturn);
+ }
+}
+
+std::wstring CGUITextLayout::BidiFlip(const std::wstring& text,
+ bool forceLTRReadingOrder,
+ int* visualToLogicalMap /*= nullptr*/)
+{
+ std::wstring visualText;
+ std::u32string utf32logical;
+ std::u32string utf32visual;
+
+ // Convert to utf32, call bidi then convert the result back to utf16
+ g_charsetConverter.wToUtf32(text, utf32logical);
+ g_charsetConverter.utf32logicalToVisualBiDi(utf32logical, utf32visual, forceLTRReadingOrder,
+ false,
+ visualToLogicalMap);
+ g_charsetConverter.utf32ToW(utf32visual, visualText);
+
+ return visualText;
+}
+
+void CGUITextLayout::Filter(std::string &text)
+{
+ std::wstring utf16;
+ g_charsetConverter.utf8ToW(text, utf16, false);
+ std::vector<UTILS::COLOR::Color> colors;
+ vecText parsedText;
+ ParseText(utf16, 0, 0xffffffff, colors, parsedText);
+ utf16.clear();
+ for (unsigned int i = 0; i < parsedText.size(); i++)
+ utf16 += (wchar_t)(0xffff & parsedText[i]);
+ g_charsetConverter.wToUTF8(utf16, text);
+}
+
+void CGUITextLayout::ParseText(const std::wstring& text,
+ uint32_t defaultStyle,
+ UTILS::COLOR::Color defaultColor,
+ std::vector<UTILS::COLOR::Color>& colors,
+ vecText& parsedText)
+{
+ // run through the string, searching for:
+ // [B] or [/B] -> toggle bold on and off
+ // [I] or [/I] -> toggle italics on and off
+ // [COLOR ffab007f] or [/COLOR] -> toggle color on and off
+ // [CAPS <option>] or [/CAPS] -> toggle capatilization on and off
+ // [TABS] tab amount [/TABS] -> add tabulator space in view
+
+ uint32_t currentStyle = defaultStyle; // start with the default font's style
+ UTILS::COLOR::Color currentColor = 0;
+
+ colors.push_back(defaultColor);
+ std::stack<UTILS::COLOR::Color> colorStack;
+ colorStack.push(0);
+
+ // these aren't independent, but that's probably not too much of an issue
+ // eg [UPPERCASE]Glah[LOWERCASE]FReD[/LOWERCASE]Georeg[/UPPERCASE] will work (lower case >> upper case)
+ // but [LOWERCASE]Glah[UPPERCASE]FReD[/UPPERCASE]Georeg[/LOWERCASE] won't
+
+ int startPos = 0;
+ size_t pos = text.find(L'[');
+ while (pos != std::string::npos && pos + 1 < text.size())
+ {
+ uint32_t newStyle = 0;
+ UTILS::COLOR::Color newColor = currentColor;
+ bool colorTagChange = false;
+ bool newLine = false;
+ int tabs = 0;
+ // have a [ - check if it's an ON or OFF switch
+ bool on(true);
+ size_t endPos = pos++; // finish of string
+ if (text[pos] == L'/')
+ {
+ on = false;
+ pos++;
+ }
+ // check for each type
+ if (text.compare(pos, 2, L"B]") == 0)
+ { // bold - finish the current text block and assign the bold state
+ pos += 2;
+ if ((on && text.find(L"[/B]",pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_BOLD))) // or matching start point
+ newStyle = FONT_STYLE_BOLD;
+ }
+ else if (text.compare(pos, 2, L"I]") == 0)
+ { // italics
+ pos += 2;
+ if ((on && text.find(L"[/I]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_ITALICS))) // or matching start point
+ newStyle = FONT_STYLE_ITALICS;
+ }
+ else if (text.compare(pos, 10, L"UPPERCASE]") == 0)
+ {
+ pos += 10;
+ if ((on && text.find(L"[/UPPERCASE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_UPPERCASE))) // or matching start point
+ newStyle = FONT_STYLE_UPPERCASE;
+ }
+ else if (text.compare(pos, 10, L"LOWERCASE]") == 0)
+ {
+ pos += 10;
+ if ((on && text.find(L"[/LOWERCASE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_LOWERCASE))) // or matching start point
+ newStyle = FONT_STYLE_LOWERCASE;
+ }
+ else if (text.compare(pos, 11, L"CAPITALIZE]") == 0)
+ {
+ pos += 11;
+ if ((on && text.find(L"[/CAPITALIZE]", pos) != std::string::npos) || // check for a matching end point
+ (!on && (currentStyle & FONT_STYLE_CAPITALIZE))) // or matching start point
+ newStyle = FONT_STYLE_CAPITALIZE;
+ }
+ else if (text.compare(pos, 6, L"LIGHT]") == 0)
+ {
+ pos += 6;
+ if ((on && text.find(L"[/LIGHT]", pos) != std::string::npos) ||
+ (!on && (currentStyle & FONT_STYLE_LIGHT)))
+ newStyle = FONT_STYLE_LIGHT;
+ }
+ else if (text.compare(pos, 5, L"TABS]") == 0 && on)
+ {
+ pos += 5;
+ const size_t end = text.find(L"[/TABS]", pos);
+ if (end != std::string::npos)
+ {
+ std::string t;
+ g_charsetConverter.wToUTF8(text.substr(pos), t);
+ tabs = atoi(t.c_str());
+ pos = end + 7;
+ }
+ }
+ else if (text.compare(pos, 3, L"CR]") == 0 && on)
+ {
+ newLine = true;
+ pos += 3;
+ }
+ else if (text.compare(pos,5, L"COLOR") == 0)
+ { // color
+ size_t finish = text.find(L']', pos + 5);
+ if (on && finish != std::string::npos && text.find(L"[/COLOR]",finish) != std::string::npos)
+ {
+ std::string t;
+ g_charsetConverter.wToUTF8(text.substr(pos + 5, finish - pos - 5), t);
+ UTILS::COLOR::Color color = CServiceBroker::GetGUI()->GetColorManager().GetColor(t);
+ const auto& it = std::find(colors.begin(), colors.end(), color);
+ if (it == colors.end())
+ { // create new color
+ if (colors.size() <= 0xFF)
+ {
+ newColor = colors.size();
+ colors.push_back(color);
+ }
+ else // we have only 8 bits for color index, fallback to first color if reach max.
+ newColor = 0;
+ }
+ else
+ // reuse existing color
+ newColor = it - colors.begin();
+ colorStack.push(newColor);
+ colorTagChange = true;
+ }
+ else if (!on && finish == pos + 5 && colorStack.size() > 1)
+ { // revert to previous color
+ colorStack.pop();
+ newColor = colorStack.top();
+ colorTagChange = true;
+ }
+ if (finish != std::string::npos)
+ pos = finish + 1;
+ }
+
+ if (newStyle || colorTagChange || newLine || tabs)
+ { // we have a new style or a new color, so format up the previous segment
+ std::wstring subText = text.substr(startPos, endPos - startPos);
+ if (currentStyle & FONT_STYLE_UPPERCASE)
+ StringUtils::ToUpper(subText);
+ if (currentStyle & FONT_STYLE_LOWERCASE)
+ StringUtils::ToLower(subText);
+ if (currentStyle & FONT_STYLE_CAPITALIZE)
+ StringUtils::ToCapitalize(subText);
+ AppendToUTF32(subText, ((currentStyle & FONT_STYLE_MASK) << 24) | (currentColor << 16), parsedText);
+ if (newLine)
+ parsedText.push_back(L'\n');
+ for (int i = 0; i < tabs; ++i)
+ parsedText.push_back(L'\t');
+
+ // and switch to the new style
+ startPos = pos;
+ currentColor = newColor;
+ if (on)
+ currentStyle |= newStyle;
+ else
+ currentStyle &= ~newStyle;
+ }
+ pos = text.find(L'[', pos);
+ }
+ // now grab the remainder of the string
+ std::wstring subText = text.substr(startPos);
+ if (currentStyle & FONT_STYLE_UPPERCASE)
+ StringUtils::ToUpper(subText);
+ if (currentStyle & FONT_STYLE_LOWERCASE)
+ StringUtils::ToLower(subText);
+ if (currentStyle & FONT_STYLE_CAPITALIZE)
+ StringUtils::ToCapitalize(subText);
+ AppendToUTF32(subText, ((currentStyle & FONT_STYLE_MASK) << 24) | (currentColor << 16), parsedText);
+}
+
+void CGUITextLayout::SetMaxHeight(float fHeight)
+{
+ m_maxHeight = fHeight;
+}
+
+void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
+{
+ if (!m_font)
+ return;
+
+ int nMaxLines = (m_maxHeight > 0 && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
+
+ m_lines.clear();
+
+ std::vector<CGUIString> lines;
+ LineBreakText(text, lines);
+
+ for (unsigned int i = 0; i < lines.size(); i++)
+ {
+ const CGUIString &line = lines[i];
+ vecText::const_iterator lastSpace = line.m_text.begin();
+ vecText::const_iterator pos = line.m_text.begin();
+ unsigned int lastSpaceInLine = 0;
+ vecText curLine;
+ while (pos != line.m_text.end())
+ {
+ // Get the current letter in the string
+ character_t letter = *pos;
+ // check for a space
+ if (CanWrapAtLetter(letter))
+ {
+ float width = m_font->GetTextWidth(curLine);
+ if (width > maxWidth)
+ {
+ if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
+ {
+ CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ // skip over spaces
+ pos = lastSpace;
+ while (pos != line.m_text.end() && IsSpace(*pos))
+ ++pos;
+ curLine.clear();
+ lastSpaceInLine = 0;
+ lastSpace = line.m_text.begin();
+ continue;
+ }
+ }
+ lastSpace = pos;
+ lastSpaceInLine = curLine.size();
+ }
+ curLine.push_back(letter);
+ ++pos;
+ }
+ // now add whatever we have left to the string
+ float width = m_font->GetTextWidth(curLine);
+ if (width > maxWidth)
+ {
+ // too long - put up to the last space on if we can + remove it from what's left.
+ if (lastSpace != line.m_text.begin() && lastSpaceInLine > 0)
+ {
+ CGUIString string(curLine.begin(), curLine.begin() + lastSpaceInLine, false);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ curLine.erase(curLine.begin(), curLine.begin() + lastSpaceInLine);
+ while (curLine.size() && IsSpace(curLine.at(0)))
+ curLine.erase(curLine.begin());
+ }
+ }
+ CGUIString string(curLine.begin(), curLine.end(), true);
+ m_lines.push_back(string);
+ // check for exceeding our number of lines
+ if (nMaxLines > 0 && m_lines.size() >= (size_t)nMaxLines)
+ return;
+ }
+}
+
+void CGUITextLayout::LineBreakText(const vecText &text, std::vector<CGUIString> &lines)
+{
+ int nMaxLines = (m_maxHeight > 0 && m_font && m_font->GetLineHeight() > 0)?(int)ceilf(m_maxHeight / m_font->GetLineHeight()):-1;
+ vecText::const_iterator lineStart = text.begin();
+ vecText::const_iterator pos = text.begin();
+ while (pos != text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
+ {
+ // Get the current letter in the string
+ character_t letter = *pos;
+
+ // Handle the newline character
+ if ((letter & 0xffff) == L'\n' )
+ { // push back everything up till now
+ CGUIString string(lineStart, pos, true);
+ lines.push_back(string);
+ lineStart = pos + 1;
+ }
+ ++pos;
+ }
+ // handle the last line if non-empty
+ if (lineStart < text.end() && (nMaxLines <= 0 || lines.size() < (size_t)nMaxLines))
+ {
+ CGUIString string(lineStart, text.end(), true);
+ lines.push_back(string);
+ }
+}
+
+void CGUITextLayout::GetTextExtent(float &width, float &height) const
+{
+ width = m_textWidth;
+ height = m_textHeight;
+}
+
+void CGUITextLayout::CalcTextExtent()
+{
+ m_textWidth = 0;
+ m_textHeight = 0;
+ if (!m_font) return;
+
+ for (const auto& string : m_lines)
+ {
+ float w = m_font->GetTextWidth(string.m_text);
+ if (w > m_textWidth)
+ m_textWidth = w;
+ }
+ m_textHeight = m_font->GetTextHeight(m_lines.size());
+}
+
+unsigned int CGUITextLayout::GetTextLength() const
+{
+ unsigned int length = 0;
+ for (const auto& string : m_lines)
+ length += string.m_text.size();
+ return length;
+}
+
+void CGUITextLayout::GetFirstText(vecText &text) const
+{
+ text.clear();
+ if (m_lines.size())
+ text = m_lines[0].m_text;
+}
+
+float CGUITextLayout::GetTextWidth(const std::wstring &text) const
+{
+ // NOTE: Assumes a single line of text
+ if (!m_font) return 0;
+ vecText utf32;
+ AppendToUTF32(text, (m_font->GetStyle() & FONT_STYLE_MASK) << 24, utf32);
+ return m_font->GetTextWidth(utf32);
+}
+
+std::string CGUITextLayout::GetText() const
+{
+ if (m_lastUpdateW)
+ {
+ std::string utf8;
+ g_charsetConverter.wToUTF8(m_lastText, utf8);
+ return utf8;
+ }
+ return m_lastUtf8Text;
+}
+
+void CGUITextLayout::DrawText(CGUIFont* font,
+ float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ const std::string& text,
+ uint32_t align)
+{
+ if (!font) return;
+ vecText utf32;
+ AppendToUTF32(text, 0, utf32);
+ font->DrawText(x, y, color, shadowColor, utf32, align, 0);
+}
+
+void CGUITextLayout::AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32)
+{
+ // NOTE: Assumes a single line of text
+ utf32.reserve(utf32.size() + utf16.size());
+ for (unsigned int i = 0; i < utf16.size(); i++)
+ utf32.push_back(utf16[i] | colStyle);
+}
+
+void CGUITextLayout::AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32)
+{
+ std::wstring utf16;
+ // no need to bidiflip here - it's done in BidiTransform above
+ g_charsetConverter.utf8ToW(utf8, utf16, false);
+ AppendToUTF32(utf16, colStyle, utf32);
+}
+
+void CGUITextLayout::Reset()
+{
+ m_lines.clear();
+ m_lastText.clear();
+ m_lastUtf8Text.clear();
+ m_textWidth = m_textHeight = 0;
+}
+
+
diff --git a/xbmc/guilib/GUITextLayout.h b/xbmc/guilib/GUITextLayout.h
new file mode 100644
index 0000000..a3768a4
--- /dev/null
+++ b/xbmc/guilib/GUITextLayout.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "utils/ColorUtils.h"
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#ifdef __GNUC__
+// under gcc, inline will only take place if optimizations are applied (-O). this will force inline even without optimizations.
+#define XBMC_FORCE_INLINE __attribute__((always_inline))
+#else
+#define XBMC_FORCE_INLINE
+#endif
+
+class CGUIFont;
+class CScrollInfo;
+
+// Process will be:
+
+// 1. String is divided up into a "multiinfo" vector via the infomanager.
+// 2. The multiinfo vector is then parsed by the infomanager at rendertime and the resultant string is constructed.
+// 3. This is saved for comparison perhaps. If the same, we are done. If not, go to 4.
+// 4. The string is then parsed into a vector<CGUIString>.
+// 5. Each item in the vector is length-calculated, and then layout occurs governed by alignment and wrapping rules.
+// 6. A new vector<CGUIString> is constructed
+
+typedef uint32_t character_t;
+typedef std::vector<character_t> vecText;
+
+class CGUIString
+{
+public:
+ typedef vecText::const_iterator iString;
+
+ CGUIString(iString start, iString end, bool carriageReturn);
+
+ std::string GetAsString() const;
+
+ vecText m_text;
+ bool m_carriageReturn; // true if we have a carriage return here
+};
+
+class CGUITextLayout
+{
+public:
+ CGUITextLayout(CGUIFont *font, bool wrap, float fHeight=0.0f, CGUIFont *borderFont = NULL); // this may need changing - we may just use this class to replace CLabelInfo completely
+
+ bool UpdateScrollinfo(CScrollInfo &scrollInfo);
+
+ // main function to render strings
+ void Render(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ bool solid = false);
+ void RenderScrolling(float x,
+ float y,
+ float angle,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ uint32_t alignment,
+ float maxWidth,
+ const CScrollInfo& scrollInfo);
+ void RenderOutline(float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color outlineColor,
+ uint32_t alignment,
+ float maxWidth);
+
+ /*! \brief Returns the precalculated width and height of the text to be rendered (in constant time).
+ \param width [out] width of text
+ \param height [out] height of text
+ \sa GetTextWidth, CalcTextExtent
+ */
+ void GetTextExtent(float &width, float &height) const;
+
+ /*! \brief Returns the precalculated width of the text to be rendered (in constant time).
+ \return width of text
+ \sa GetTextExtent, CalcTextExtent
+ */
+ float GetTextWidth() const { return m_textWidth; }
+
+ float GetTextWidth(const std::wstring &text) const;
+
+ /*! \brief Returns the precalculated height of the text to be rendered (in constant time).
+ \return height of text
+ */
+ float GetTextHeight() const { return m_textHeight; }
+
+ bool Update(const std::string &text, float maxWidth = 0, bool forceUpdate = false, bool forceLTRReadingOrder = false);
+ bool UpdateW(const std::wstring &text, float maxWidth = 0, bool forceUpdate = false, bool forceLTRReadingOrder = false);
+
+ /*! \brief Update text from a pre-styled vecText/std::vector<UTILS::COLOR::Color> combination
+ Allows styled text to be passed directly to the text layout.
+ \param text the styled text to set.
+ \param colors the colors used on the text.
+ \param maxWidth the maximum width for wrapping text, defaults to 0 (no max width).
+ \param forceLTRReadingOrder whether to force left to right reading order, defaults to false.
+ */
+ void UpdateStyled(const vecText& text,
+ const std::vector<UTILS::COLOR::Color>& colors,
+ float maxWidth = 0,
+ bool forceLTRReadingOrder = false);
+
+ unsigned int GetTextLength() const;
+ void GetFirstText(vecText &text) const;
+ void Reset();
+
+ void SetWrap(bool bWrap=true);
+ void SetMaxHeight(float fHeight);
+
+
+ static void DrawText(CGUIFont* font,
+ float x,
+ float y,
+ UTILS::COLOR::Color color,
+ UTILS::COLOR::Color shadowColor,
+ const std::string& text,
+ uint32_t align);
+ static void Filter(std::string &text);
+
+protected:
+ void LineBreakText(const vecText &text, std::vector<CGUIString> &lines);
+ void WrapText(const vecText &text, float maxWidth);
+ static void BidiTransform(std::vector<CGUIString> &lines, bool forceLTRReadingOrder);
+ static std::wstring BidiFlip(const std::wstring& text,
+ bool forceLTRReadingOrder,
+ int* visualToLogicalMap = nullptr);
+ void CalcTextExtent();
+ void UpdateCommon(const std::wstring &text, float maxWidth, bool forceLTRReadingOrder);
+
+ /*! \brief Returns the text, utf8 encoded
+ \return utf8 text
+ */
+ std::string GetText() const;
+
+ //! \brief Set the monospaced font to use
+ void SetMonoFont(CGUIFont* font) { m_monoFont = font; }
+
+ //! \brief Set whether or not to use the monospace font
+ void UseMonoFont(bool use) { m_font = use && m_monoFont ? m_monoFont : m_varFont; }
+
+ // our text to render
+ std::vector<UTILS::COLOR::Color> m_colors;
+ std::vector<CGUIString> m_lines;
+ typedef std::vector<CGUIString>::iterator iLine;
+
+ // the layout and font details
+ CGUIFont *m_font; // has style, colour info
+ CGUIFont *m_borderFont; // only used for outlined text
+ CGUIFont* m_monoFont = nullptr; //!< Mono-space font to use
+ CGUIFont* m_varFont; //!< Varible-space font to use
+
+ bool m_wrap; // wrapping (true if justify is enabled!)
+ float m_maxHeight;
+ // the default color (may differ from the font objects defaults)
+ UTILS::COLOR::Color m_textColor;
+
+ std::string m_lastUtf8Text;
+ std::wstring m_lastText;
+ bool m_lastUpdateW; ///< true if the last string we updated was the wstring version
+ float m_textWidth;
+ float m_textHeight;
+private:
+ inline bool IsSpace(character_t letter) const XBMC_FORCE_INLINE
+ {
+ return (letter & 0xffff) == L' ';
+ };
+ inline bool CanWrapAtLetter(character_t letter) const XBMC_FORCE_INLINE
+ {
+ character_t ch = letter & 0xffff;
+ return ch == L' ' || (ch >=0x4e00 && ch <= 0x9fff);
+ };
+ static void AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32);
+ static void AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32);
+ static void ParseText(const std::wstring& text,
+ uint32_t defaultStyle,
+ UTILS::COLOR::Color defaultColor,
+ std::vector<UTILS::COLOR::Color>& colors,
+ vecText& parsedText);
+};
+
diff --git a/xbmc/guilib/GUITexture.cpp b/xbmc/guilib/GUITexture.cpp
new file mode 100644
index 0000000..7f2f51d
--- /dev/null
+++ b/xbmc/guilib/GUITexture.cpp
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITexture.h"
+
+#include "GUILargeTextureManager.h"
+#include "TextureManager.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
+#include "windowing/GraphicContext.h"
+
+#include <stdexcept>
+
+CreateGUITextureFunc CGUITexture::m_createGUITextureFunc;
+DrawQuadFunc CGUITexture::m_drawQuadFunc;
+
+CTextureInfo::CTextureInfo()
+{
+ orientation = 0;
+ useLarge = false;
+}
+
+CTextureInfo::CTextureInfo(const std::string &file):
+ filename(file)
+{
+ orientation = 0;
+ useLarge = false;
+}
+
+void CGUITexture::Register(const CreateGUITextureFunc& createFunction,
+ const DrawQuadFunc& drawQuadFunction)
+{
+ m_createGUITextureFunc = createFunction;
+ m_drawQuadFunc = drawQuadFunction;
+}
+
+CGUITexture* CGUITexture::CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+{
+ if (!m_createGUITextureFunc)
+ throw std::runtime_error(
+ "No GUITexture Create function available. Did you forget to register?");
+
+ return m_createGUITextureFunc(posX, posY, width, height, texture);
+}
+
+void CGUITexture::DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CTexture* texture,
+ const CRect* texCoords)
+{
+ if (!m_drawQuadFunc)
+ throw std::runtime_error(
+ "No GUITexture DrawQuad function available. Did you forget to register?");
+
+ m_drawQuadFunc(coords, color, texture, texCoords);
+}
+
+CGUITexture::CGUITexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+ : m_height(height), m_info(texture)
+{
+ m_posX = posX;
+ m_posY = posY;
+ m_width = width;
+
+ // defaults
+ m_visible = true;
+ m_diffuseColor = 0xffffffff;
+ m_alpha = 0xff;
+
+ m_vertex.SetRect(m_posX, m_posY, m_posX + m_width, m_posY + m_height);
+
+ m_frameWidth = 0;
+ m_frameHeight = 0;
+
+ m_texCoordsScaleU = 1.0f;
+ m_texCoordsScaleV = 1.0f;
+ m_diffuseU = 1.0f;
+ m_diffuseV = 1.0f;
+ m_diffuseScaleU = 1.0f;
+ m_diffuseScaleV = 1.0f;
+
+ // anim gifs
+ ResetAnimState();
+
+ m_allocateDynamically = false;
+ m_isAllocated = NO;
+ m_invalid = true;
+ m_use_cache = true;
+}
+
+CGUITexture::CGUITexture(const CGUITexture& right)
+ : m_visible(right.m_visible),
+ m_diffuseColor(right.m_diffuseColor),
+ m_posX(right.m_posX),
+ m_posY(right.m_posY),
+ m_width(right.m_width),
+ m_height(right.m_height),
+ m_use_cache(right.m_use_cache),
+ m_alpha(right.m_alpha),
+ m_allocateDynamically(right.m_allocateDynamically),
+ m_info(right.m_info),
+ m_aspect(right.m_aspect)
+{
+ // defaults
+ m_vertex.SetRect(m_posX, m_posY, m_posX + m_width, m_posY + m_height);
+
+ m_frameWidth = 0;
+ m_frameHeight = 0;
+
+ m_texCoordsScaleU = 1.0f;
+ m_texCoordsScaleV = 1.0f;
+ m_diffuseU = 1.0f;
+ m_diffuseV = 1.0f;
+ m_diffuseScaleU = 1.0f;
+ m_diffuseScaleV = 1.0f;
+
+ ResetAnimState();
+
+ m_isAllocated = NO;
+ m_invalid = true;
+}
+
+bool CGUITexture::AllocateOnDemand()
+{
+ if (m_visible)
+ { // visible, so make sure we're allocated
+ if (!IsAllocated() || (m_isAllocated == LARGE && !m_texture.size()))
+ return AllocResources();
+ }
+ else
+ { // hidden, so deallocate as applicable
+ if (m_allocateDynamically && IsAllocated())
+ FreeResources();
+ // reset animated textures (animgifs)
+ ResetAnimState();
+ }
+
+ return false;
+}
+
+bool CGUITexture::Process(unsigned int currentTime)
+{
+ bool changed = false;
+ // check if we need to allocate our resources
+ changed |= AllocateOnDemand();
+
+ if (m_texture.size() > 1)
+ changed |= UpdateAnimFrame(currentTime);
+
+ if (m_invalid)
+ changed |= CalculateSize();
+
+ if (m_isAllocated)
+ changed |= !ReadyToRender();
+
+ return changed;
+}
+
+void CGUITexture::Render()
+{
+ if (!m_visible || !m_texture.size())
+ return;
+
+ // see if we need to clip the image
+ if (m_vertex.Width() > m_width || m_vertex.Height() > m_height)
+ {
+ if (!CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ return;
+ }
+
+ // set our draw color
+ #define MIX_ALPHA(a,c) (((a * (c >> 24)) / 255) << 24) | (c & 0x00ffffff)
+
+ // diffuse color
+ UTILS::COLOR::Color color =
+ (m_info.diffuseColor) ? (UTILS::COLOR::Color)m_info.diffuseColor : m_diffuseColor;
+ if (m_alpha != 0xFF)
+ color = MIX_ALPHA(m_alpha, color);
+
+ color = CServiceBroker::GetWinSystem()->GetGfxContext().MergeColor(color);
+
+ // setup our renderer
+ Begin(color);
+
+ // compute the texture coordinates
+ float u1, u2, u3, v1, v2, v3;
+ u1 = m_info.border.x1;
+ u2 = m_frameWidth - m_info.border.x2;
+ u3 = m_frameWidth;
+ v1 = m_info.border.y1;
+ v2 = m_frameHeight - m_info.border.y2;
+ v3 = m_frameHeight;
+
+ if (!m_texture.m_texCoordsArePixels)
+ {
+ u1 *= m_texCoordsScaleU;
+ u2 *= m_texCoordsScaleU;
+ u3 *= m_texCoordsScaleU;
+ v1 *= m_texCoordsScaleV;
+ v2 *= m_texCoordsScaleV;
+ v3 *= m_texCoordsScaleV;
+ }
+
+ //! @todo The diffuse coloring applies to all vertices, which will
+ //! look weird for stuff with borders, as will the -ve height/width
+ //! for flipping
+
+ // left segment (0,0,u1,v3)
+ if (m_info.border.x1)
+ {
+ if (m_info.border.y1)
+ Render(m_vertex.x1, m_vertex.y1, m_vertex.x1 + m_info.border.x1, m_vertex.y1 + m_info.border.y1, 0, 0, u1, v1, u3, v3);
+ Render(m_vertex.x1, m_vertex.y1 + m_info.border.y1, m_vertex.x1 + m_info.border.x1, m_vertex.y2 - m_info.border.y2, 0, v1, u1, v2, u3, v3);
+ if (m_info.border.y2)
+ Render(m_vertex.x1, m_vertex.y2 - m_info.border.y2, m_vertex.x1 + m_info.border.x1, m_vertex.y2, 0, v2, u1, v3, u3, v3);
+ }
+ // middle segment (u1,0,u2,v3)
+ if (m_info.border.y1)
+ Render(m_vertex.x1 + m_info.border.x1, m_vertex.y1, m_vertex.x2 - m_info.border.x2, m_vertex.y1 + m_info.border.y1, u1, 0, u2, v1, u3, v3);
+ if (m_info.m_infill)
+ Render(m_vertex.x1 + m_info.border.x1, m_vertex.y1 + m_info.border.y1,
+ m_vertex.x2 - m_info.border.x2, m_vertex.y2 - m_info.border.y2, u1, v1, u2, v2, u3, v3);
+ if (m_info.border.y2)
+ Render(m_vertex.x1 + m_info.border.x1, m_vertex.y2 - m_info.border.y2, m_vertex.x2 - m_info.border.x2, m_vertex.y2, u1, v2, u2, v3, u3, v3);
+ // right segment
+ if (m_info.border.x2)
+ { // have a left border
+ if (m_info.border.y1)
+ Render(m_vertex.x2 - m_info.border.x2, m_vertex.y1, m_vertex.x2, m_vertex.y1 + m_info.border.y1, u2, 0, u3, v1, u3, v3);
+ Render(m_vertex.x2 - m_info.border.x2, m_vertex.y1 + m_info.border.y1, m_vertex.x2, m_vertex.y2 - m_info.border.y2, u2, v1, u3, v2, u3, v3);
+ if (m_info.border.y2)
+ Render(m_vertex.x2 - m_info.border.x2, m_vertex.y2 - m_info.border.y2, m_vertex.x2, m_vertex.y2, u2, v2, u3, v3, u3, v3);
+ }
+
+ // close off our renderer
+ End();
+
+ if (m_vertex.Width() > m_width || m_vertex.Height() > m_height)
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+}
+
+void CGUITexture::Render(float left,
+ float top,
+ float right,
+ float bottom,
+ float u1,
+ float v1,
+ float u2,
+ float v2,
+ float u3,
+ float v3)
+{
+ CRect diffuse(u1, v1, u2, v2);
+ CRect texture(u1, v1, u2, v2);
+ CRect vertex(left, top, right, bottom);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ClipRect(vertex, texture, m_diffuse.size() ? &diffuse : NULL);
+
+ if (vertex.IsEmpty())
+ return; // nothing to render
+
+ int orientation = GetOrientation();
+ OrientateTexture(texture, u3, v3, orientation);
+
+ if (m_diffuse.size())
+ {
+ // flip the texture as necessary. Diffuse just gets flipped according to m_info.orientation.
+ // Main texture gets flipped according to GetOrientation().
+ diffuse.x1 *= m_diffuseScaleU / u3; diffuse.x2 *= m_diffuseScaleU / u3;
+ diffuse.y1 *= m_diffuseScaleV / v3; diffuse.y2 *= m_diffuseScaleV / v3;
+ diffuse += m_diffuseOffset;
+ OrientateTexture(diffuse, m_diffuseU, m_diffuseV, m_info.orientation);
+ }
+
+ float x[4], y[4], z[4];
+
+#define ROUND_TO_PIXEL(x) static_cast<float>(MathUtils::round_int(static_cast<double>(x)))
+
+ x[0] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y1));
+ y[0] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y1));
+ z[0] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y1));
+ x[1] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y1));
+ y[1] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y1));
+ z[1] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y1));
+ x[2] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y2));
+ y[2] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y2));
+ z[2] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y2));
+ x[3] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y2));
+ y[3] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y2));
+ z[3] = ROUND_TO_PIXEL(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y2));
+
+ if (y[2] == y[0]) y[2] += 1.0f;
+ if (x[2] == x[0]) x[2] += 1.0f;
+ if (y[3] == y[1]) y[3] += 1.0f;
+ if (x[3] == x[1]) x[3] += 1.0f;
+
+ Draw(x, y, z, texture, diffuse, orientation);
+}
+
+bool CGUITexture::AllocResources()
+{
+ if (m_info.filename.empty())
+ return false;
+
+ if (m_texture.size())
+ return false; // already have our texture
+
+ // reset our animstate
+ ResetAnimState();
+
+ bool changed = false;
+ bool useLarge = m_info.useLarge || !CServiceBroker::GetGUI()->GetTextureManager().CanLoad(m_info.filename);
+ if (useLarge)
+ { // we want to use the large image loader, but we first check for bundled textures
+ if (!IsAllocated())
+ {
+ CTextureArray texture;
+ texture = CServiceBroker::GetGUI()->GetTextureManager().Load(m_info.filename, true);
+ if (texture.size())
+ {
+ m_isAllocated = NORMAL;
+ m_texture = texture;
+ changed = true;
+ }
+ }
+ if (m_isAllocated != NORMAL)
+ { // use our large image background loader
+ CTextureArray texture;
+ if (CServiceBroker::GetGUI()->GetLargeTextureManager().GetImage(m_info.filename, texture, !IsAllocated(), m_use_cache))
+ {
+ m_isAllocated = LARGE;
+
+ if (!texture.size()) // not ready as yet
+ return false;
+
+ m_texture = texture;
+
+ changed = true;
+ }
+ else
+ m_isAllocated = LARGE_FAILED;
+ }
+ }
+ else if (!IsAllocated())
+ {
+ CTextureArray texture = CServiceBroker::GetGUI()->GetTextureManager().Load(m_info.filename);
+
+ // set allocated to true even if we couldn't load the image to save
+ // us hitting the disk every frame
+ m_isAllocated = texture.size() ? NORMAL : NORMAL_FAILED;
+ if (!texture.size())
+ return false;
+ m_texture = texture;
+ changed = true;
+ }
+ m_frameWidth = (float)m_texture.m_width;
+ m_frameHeight = (float)m_texture.m_height;
+
+ // load the diffuse texture (if necessary)
+ if (!m_info.diffuse.empty())
+ {
+ m_diffuse = CServiceBroker::GetGUI()->GetTextureManager().Load(m_info.diffuse);
+ }
+
+ CalculateSize();
+
+ // call our implementation
+ Allocate();
+
+ return changed;
+}
+
+bool CGUITexture::CalculateSize()
+{
+ if (m_currentFrame >= m_texture.size())
+ return false;
+
+ m_texCoordsScaleU = 1.0f / m_texture.m_texWidth;
+ m_texCoordsScaleV = 1.0f / m_texture.m_texHeight;
+
+ if (m_width == 0)
+ m_width = m_frameWidth;
+ if (m_height == 0)
+ m_height = m_frameHeight;
+
+ float newPosX = m_posX;
+ float newPosY = m_posY;
+ float newWidth = m_width;
+ float newHeight = m_height;
+
+ if (m_aspect.ratio != CAspectRatio::AR_STRETCH && m_frameWidth && m_frameHeight)
+ {
+ // to get the pixel ratio, we must use the SCALED output sizes
+ float pixelRatio = CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio();
+
+ float fSourceFrameRatio = m_frameWidth / m_frameHeight;
+ if (GetOrientation() & 4)
+ fSourceFrameRatio = m_frameHeight / m_frameWidth;
+ float fOutputFrameRatio = fSourceFrameRatio / pixelRatio;
+
+ // maximize the width
+ newHeight = m_width / fOutputFrameRatio;
+
+ if ((m_aspect.ratio == CAspectRatio::AR_SCALE && newHeight < m_height) ||
+ (m_aspect.ratio == CAspectRatio::AR_KEEP && newHeight > m_height))
+ {
+ newHeight = m_height;
+ newWidth = newHeight * fOutputFrameRatio;
+ }
+ if (m_aspect.ratio == CAspectRatio::AR_CENTER)
+ { // keep original size + center
+ newWidth = m_frameWidth / sqrt(pixelRatio);
+ newHeight = m_frameHeight * sqrt(pixelRatio);
+ }
+
+ if (m_aspect.align & ASPECT_ALIGN_LEFT)
+ newPosX = m_posX;
+ else if (m_aspect.align & ASPECT_ALIGN_RIGHT)
+ newPosX = m_posX + m_width - newWidth;
+ else
+ newPosX = m_posX + (m_width - newWidth) * 0.5f;
+ if (m_aspect.align & ASPECT_ALIGNY_TOP)
+ newPosY = m_posY;
+ else if (m_aspect.align & ASPECT_ALIGNY_BOTTOM)
+ newPosY = m_posY + m_height - newHeight;
+ else
+ newPosY = m_posY + (m_height - newHeight) * 0.5f;
+ }
+
+ m_vertex.SetRect(newPosX, newPosY, newPosX + newWidth, newPosY + newHeight);
+
+ // scale the diffuse coords as well
+ if (m_diffuse.size())
+ { // calculate scaling for the texcoords
+ if (m_diffuse.m_texCoordsArePixels)
+ {
+ m_diffuseU = float(m_diffuse.m_width);
+ m_diffuseV = float(m_diffuse.m_height);
+ }
+ else
+ {
+ m_diffuseU = float(m_diffuse.m_width) / float(m_diffuse.m_texWidth);
+ m_diffuseV = float(m_diffuse.m_height) / float(m_diffuse.m_texHeight);
+ }
+
+ if (m_aspect.scaleDiffuse)
+ {
+ m_diffuseScaleU = m_diffuseU;
+ m_diffuseScaleV = m_diffuseV;
+ m_diffuseOffset = CPoint(0,0);
+ }
+ else // stretching diffuse
+ { // scale diffuse up or down to match output rect size, rather than image size
+ //(m_fX, mfY) -> (m_fX + m_fNW, m_fY + m_fNH)
+ //(0,0) -> (m_fU*m_diffuseScaleU, m_fV*m_diffuseScaleV)
+ // x = u/(m_fU*m_diffuseScaleU)*m_fNW + m_fX
+ // -> u = (m_posX - m_fX) * m_fU * m_diffuseScaleU / m_fNW
+ m_diffuseScaleU = m_diffuseU * m_vertex.Width() / m_width;
+ m_diffuseScaleV = m_diffuseV * m_vertex.Height() / m_height;
+ m_diffuseOffset = CPoint((m_vertex.x1 - m_posX) / m_vertex.Width() * m_diffuseScaleU, (m_vertex.y1 - m_posY) / m_vertex.Height() * m_diffuseScaleV);
+ }
+ }
+
+ m_invalid = false;
+ return true;
+}
+
+void CGUITexture::FreeResources(bool immediately /* = false */)
+{
+ if (m_isAllocated == LARGE || m_isAllocated == LARGE_FAILED)
+ CServiceBroker::GetGUI()->GetLargeTextureManager().ReleaseImage(m_info.filename, immediately || (m_isAllocated == LARGE_FAILED));
+ else if (m_isAllocated == NORMAL && m_texture.size())
+ CServiceBroker::GetGUI()->GetTextureManager().ReleaseTexture(m_info.filename, immediately);
+
+ if (m_diffuse.size())
+ CServiceBroker::GetGUI()->GetTextureManager().ReleaseTexture(m_info.diffuse, immediately);
+ m_diffuse.Reset();
+
+ m_texture.Reset();
+
+ ResetAnimState();
+
+ m_texCoordsScaleU = 1.0f;
+ m_texCoordsScaleV = 1.0f;
+
+ // call our implementation
+ Free();
+
+ m_isAllocated = NO;
+}
+
+void CGUITexture::DynamicResourceAlloc(bool allocateDynamically)
+{
+ m_allocateDynamically = allocateDynamically;
+}
+
+void CGUITexture::SetInvalid()
+{
+ m_invalid = true;
+}
+
+bool CGUITexture::UpdateAnimFrame(unsigned int currentTime)
+{
+ bool changed = false;
+ unsigned int delay = m_texture.m_delays[m_currentFrame];
+
+ if (m_lasttime == 0)
+ {
+ m_lasttime = currentTime;
+ }
+ else
+ {
+ if ((currentTime - m_lasttime) >= delay)
+ {
+ if (m_currentFrame + 1 >= m_texture.size())
+ {
+ if (m_texture.m_loops > 0)
+ {
+ if (m_currentLoop + 1 < m_texture.m_loops)
+ {
+ m_currentLoop++;
+ m_currentFrame = 0;
+ m_lasttime = currentTime;
+ changed = true;
+ }
+ }
+ else
+ {
+ // 0 == loop forever
+ m_currentFrame = 0;
+ m_lasttime = currentTime;
+ changed = true;
+ }
+ }
+ else
+ {
+ m_currentFrame++;
+ m_lasttime = currentTime;
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+}
+
+bool CGUITexture::SetVisible(bool visible)
+{
+ bool changed = m_visible != visible;
+ m_visible = visible;
+ return changed;
+}
+
+bool CGUITexture::SetAlpha(unsigned char alpha)
+{
+ bool changed = m_alpha != alpha;
+ m_alpha = alpha;
+ return changed;
+}
+
+bool CGUITexture::SetDiffuseColor(UTILS::COLOR::Color color,
+ const CGUIListItem* item /* = nullptr */)
+{
+ bool changed = m_diffuseColor != color;
+ m_diffuseColor = color;
+ changed |= m_info.diffuseColor.Update(item);
+ return changed;
+}
+
+bool CGUITexture::ReadyToRender() const
+{
+ return m_texture.size() > 0;
+}
+
+void CGUITexture::OrientateTexture(CRect& rect, float width, float height, int orientation)
+{
+ switch (orientation & 3)
+ {
+ case 0:
+ // default
+ break;
+ case 1:
+ // flip in X direction
+ rect.x1 = width - rect.x1;
+ rect.x2 = width - rect.x2;
+ break;
+ case 2:
+ // rotate 180 degrees
+ rect.x1 = width - rect.x1;
+ rect.x2 = width - rect.x2;
+ rect.y1 = height - rect.y1;
+ rect.y2 = height - rect.y2;
+ break;
+ case 3:
+ // flip in Y direction
+ rect.y1 = height - rect.y1;
+ rect.y2 = height - rect.y2;
+ break;
+ }
+ if (orientation & 4)
+ {
+ // we need to swap x and y coordinates but only within the width,height block
+ float temp = rect.x1;
+ rect.x1 = rect.y1 * width/height;
+ rect.y1 = temp * height/width;
+ temp = rect.x2;
+ rect.x2 = rect.y2 * width/height;
+ rect.y2 = temp * height/width;
+ }
+}
+
+void CGUITexture::ResetAnimState()
+{
+ m_lasttime = 0;
+ m_currentFrame = 0;
+ m_currentLoop = 0;
+}
+
+bool CGUITexture::SetWidth(float width)
+{
+ if (width < m_info.border.x1 + m_info.border.x2)
+ width = m_info.border.x1 + m_info.border.x2;
+ if (m_width != width)
+ {
+ m_width = width;
+ m_invalid = true;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool CGUITexture::SetHeight(float height)
+{
+ if (height < m_info.border.y1 + m_info.border.y2)
+ height = m_info.border.y1 + m_info.border.y2;
+ if (m_height != height)
+ {
+ m_height = height;
+ m_invalid = true;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool CGUITexture::SetPosition(float posX, float posY)
+{
+ if (m_posX != posX || m_posY != posY)
+ {
+ m_posX = posX;
+ m_posY = posY;
+ m_invalid = true;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool CGUITexture::SetAspectRatio(const CAspectRatio& aspect)
+{
+ if (m_aspect != aspect)
+ {
+ m_aspect = aspect;
+ m_invalid = true;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool CGUITexture::SetFileName(const std::string& filename)
+{
+ if (m_info.filename == filename) return false;
+ // Don't completely free resources here - we may be just changing
+ // filenames mid-animation
+ FreeResources();
+ m_info.filename = filename;
+
+ // disable large loader and cache for gifs
+ if (StringUtils::EndsWithNoCase(m_info.filename, ".gif"))
+ {
+ m_info.useLarge = false;
+ SetUseCache(false);
+ }
+
+ // Don't allocate resources here as this is done at render time
+ return true;
+}
+
+void CGUITexture::SetUseCache(const bool useCache)
+{
+ m_use_cache = useCache;
+}
+
+int CGUITexture::GetOrientation() const
+{
+ // multiply our orientations
+ static char orient_table[] = { 0, 1, 2, 3, 4, 5, 6, 7,
+ 1, 0, 3, 2, 5, 4, 7, 6,
+ 2, 3, 0, 1, 6, 7, 4, 5,
+ 3, 2, 1, 0, 7, 6, 5, 4,
+ 4, 7, 6, 5, 0, 3, 2, 1,
+ 5, 6, 7, 4, 1, 2, 3, 0,
+ 6, 5, 4, 7, 2, 1, 0, 3,
+ 7, 4, 5, 6, 3, 0, 1, 2 };
+ return (int)orient_table[8 * m_info.orientation + m_texture.m_orientation];
+}
diff --git a/xbmc/guilib/GUITexture.h b/xbmc/guilib/GUITexture.h
new file mode 100644
index 0000000..e530233
--- /dev/null
+++ b/xbmc/guilib/GUITexture.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "TextureManager.h"
+#include "guiinfo/GUIInfoColor.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h"
+
+#include <functional>
+
+// image alignment for <aspect>keep</aspect>, <aspect>scale</aspect> or <aspect>center</aspect>
+#define ASPECT_ALIGN_CENTER 0
+#define ASPECT_ALIGN_LEFT 1
+#define ASPECT_ALIGN_RIGHT 2
+#define ASPECT_ALIGNY_CENTER 0
+#define ASPECT_ALIGNY_TOP 4
+#define ASPECT_ALIGNY_BOTTOM 8
+#define ASPECT_ALIGN_MASK 3
+#define ASPECT_ALIGNY_MASK ~3
+
+class CAspectRatio
+{
+public:
+ enum ASPECT_RATIO { AR_STRETCH = 0, AR_SCALE, AR_KEEP, AR_CENTER };
+ CAspectRatio(ASPECT_RATIO aspect = AR_STRETCH)
+ {
+ ratio = aspect;
+ align = ASPECT_ALIGN_CENTER | ASPECT_ALIGNY_CENTER;
+ scaleDiffuse = true;
+ };
+ bool operator!=(const CAspectRatio &right) const
+ {
+ if (ratio != right.ratio) return true;
+ if (align != right.align) return true;
+ if (scaleDiffuse != right.scaleDiffuse) return true;
+ return false;
+ };
+
+ ASPECT_RATIO ratio;
+ uint32_t align;
+ bool scaleDiffuse;
+};
+
+class CTextureInfo
+{
+public:
+ CTextureInfo();
+ explicit CTextureInfo(const std::string &file);
+ bool useLarge;
+ CRect border; // scaled - unneeded if we get rid of scale on load
+ bool m_infill{
+ true}; // if false, the main body of a texture is not drawn. useful for borders with no inner filling
+ int orientation; // orientation of the texture (0 - 7 == EXIForientation - 1)
+ std::string diffuse; // diffuse overlay texture
+ KODI::GUILIB::GUIINFO::CGUIInfoColor diffuseColor; // diffuse color
+ std::string filename; // main texture file
+};
+
+class CGUITexture;
+
+using CreateGUITextureFunc = std::function<CGUITexture*(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)>;
+using DrawQuadFunc = std::function<void(
+ const CRect& coords, UTILS::COLOR::Color color, CTexture* texture, const CRect* texCoords)>;
+
+class CGUITexture
+{
+public:
+ virtual ~CGUITexture() = default;
+
+ static void Register(const CreateGUITextureFunc& createFunction,
+ const DrawQuadFunc& drawQuadFunction);
+
+ static CGUITexture* CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture);
+ virtual CGUITexture* Clone() const = 0;
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CTexture* texture = nullptr,
+ const CRect* texCoords = nullptr);
+
+ bool Process(unsigned int currentTime);
+ void Render();
+
+ void DynamicResourceAlloc(bool bOnOff);
+ bool AllocResources();
+ void FreeResources(bool immediately = false);
+ void SetInvalid();
+
+ bool SetVisible(bool visible);
+ bool SetAlpha(unsigned char alpha);
+ bool SetDiffuseColor(UTILS::COLOR::Color color, const CGUIListItem* item = nullptr);
+ bool SetPosition(float x, float y);
+ bool SetWidth(float width);
+ bool SetHeight(float height);
+ bool SetFileName(const std::string &filename);
+ void SetUseCache(const bool useCache = true);
+ bool SetAspectRatio(const CAspectRatio &aspect);
+
+ const std::string& GetFileName() const { return m_info.filename; }
+ float GetTextureWidth() const { return m_frameWidth; }
+ float GetTextureHeight() const { return m_frameHeight; }
+ float GetWidth() const { return m_width; }
+ float GetHeight() const { return m_height; }
+ float GetXPosition() const { return m_posX; }
+ float GetYPosition() const { return m_posY; }
+ int GetOrientation() const;
+ const CRect& GetRenderRect() const { return m_vertex; }
+ bool IsLazyLoaded() const { return m_info.useLarge; }
+
+ /*!
+ * @brief Get the diffuse color (info color) associated to this texture
+ * @return the infocolor associated to this texture
+ */
+ KODI::GUILIB::GUIINFO::CGUIInfoColor GetDiffuseColor() const { return m_info.diffuseColor; }
+
+ bool HitTest(const CPoint& point) const
+ {
+ return CRect(m_posX, m_posY, m_posX + m_width, m_posY + m_height).PtInRect(point);
+ }
+ bool IsAllocated() const { return m_isAllocated != NO; }
+ bool FailedToAlloc() const
+ {
+ return m_isAllocated == NORMAL_FAILED || m_isAllocated == LARGE_FAILED;
+ }
+ bool ReadyToRender() const;
+
+protected:
+ CGUITexture(float posX, float posY, float width, float height, const CTextureInfo& texture);
+ CGUITexture(const CGUITexture& left);
+
+ bool CalculateSize();
+ void LoadDiffuseImage();
+ bool AllocateOnDemand();
+ bool UpdateAnimFrame(unsigned int currentTime);
+ void Render(float left,
+ float top,
+ float right,
+ float bottom,
+ float u1,
+ float v1,
+ float u2,
+ float v2,
+ float u3,
+ float v3);
+ static void OrientateTexture(CRect &rect, float width, float height, int orientation);
+ void ResetAnimState();
+
+ // functions that our implementation classes handle
+ virtual void Allocate() {}; ///< called after our textures have been allocated
+ virtual void Free() {}; ///< called after our textures have been freed
+ virtual void Begin(UTILS::COLOR::Color color) = 0;
+ virtual void Draw(float* x,
+ float* y,
+ float* z,
+ const CRect& texture,
+ const CRect& diffuse,
+ int orientation) = 0;
+ virtual void End() = 0;
+
+ bool m_visible;
+ UTILS::COLOR::Color m_diffuseColor;
+
+ float m_posX; // size of the frame
+ float m_posY;
+ float m_width;
+ float m_height;
+
+ CRect m_vertex; // vertex coords to render
+ bool m_invalid; // if true, we need to recalculate
+ bool m_use_cache;
+ unsigned char m_alpha;
+
+ float m_frameWidth, m_frameHeight; // size in pixels of the actual frame within the texture
+ float m_texCoordsScaleU, m_texCoordsScaleV; // scale factor for pixel->texture coordinates
+
+ // animations
+ int m_currentLoop;
+ unsigned int m_currentFrame;
+ uint32_t m_lasttime;
+
+ float m_diffuseU, m_diffuseV; // size of the diffuse frame (in tex coords)
+ float m_diffuseScaleU, m_diffuseScaleV; // scale factor of the diffuse frame (from texture coords to diffuse tex coords)
+ CPoint m_diffuseOffset; // offset into the diffuse frame (it's not always the origin)
+
+ bool m_allocateDynamically;
+ enum ALLOCATE_TYPE { NO = 0, NORMAL, LARGE, NORMAL_FAILED, LARGE_FAILED };
+ ALLOCATE_TYPE m_isAllocated;
+
+ CTextureInfo m_info;
+ CAspectRatio m_aspect;
+
+ CTextureArray m_diffuse;
+ CTextureArray m_texture;
+
+private:
+ static CreateGUITextureFunc m_createGUITextureFunc;
+ static DrawQuadFunc m_drawQuadFunc;
+};
diff --git a/xbmc/guilib/GUITextureD3D.cpp b/xbmc/guilib/GUITextureD3D.cpp
new file mode 100644
index 0000000..d031cac
--- /dev/null
+++ b/xbmc/guilib/GUITextureD3D.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextureD3D.h"
+
+#include "D3DResource.h"
+#include "GUIShaderDX.h"
+#include "TextureDX.h"
+#include "rendering/dx/RenderContext.h"
+
+#include <DirectXMath.h>
+
+using namespace DirectX;
+
+void CGUITextureD3D::Register()
+{
+ CGUITexture::Register(CGUITextureD3D::CreateTexture, CGUITextureD3D::DrawQuad);
+}
+
+CGUITexture* CGUITextureD3D::CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+{
+ return new CGUITextureD3D(posX, posY, width, height, texture);
+}
+
+CGUITextureD3D::CGUITextureD3D(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+ : CGUITexture(posX, posY, width, height, texture)
+{
+}
+
+CGUITextureD3D* CGUITextureD3D::Clone() const
+{
+ return new CGUITextureD3D(*this);
+}
+
+void CGUITextureD3D::Begin(UTILS::COLOR::Color color)
+{
+ CTexture* texture = m_texture.m_textures[m_currentFrame].get();
+ texture->LoadToGPU();
+
+ if (m_diffuse.size())
+ m_diffuse.m_textures[0]->LoadToGPU();
+
+ m_col = color;
+
+ DX::Windowing()->SetAlphaBlendEnable(true);
+}
+
+void CGUITextureD3D::End()
+{
+}
+
+void CGUITextureD3D::Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation)
+{
+ XMFLOAT4 xcolor;
+ CD3DHelper::XMStoreColor(&xcolor, m_col);
+
+ Vertex verts[4];
+ verts[0].pos.x = x[0]; verts[0].pos.y = y[0]; verts[0].pos.z = z[0];
+ verts[0].texCoord.x = texture.x1; verts[0].texCoord.y = texture.y1;
+ verts[0].texCoord2.x = diffuse.x1; verts[0].texCoord2.y = diffuse.y1;
+ verts[0].color = xcolor;
+
+ verts[1].pos.x = x[1]; verts[1].pos.y = y[1]; verts[1].pos.z = z[1];
+ if (orientation & 4)
+ {
+ verts[1].texCoord.x = texture.x1;
+ verts[1].texCoord.y = texture.y2;
+ }
+ else
+ {
+ verts[1].texCoord.x = texture.x2;
+ verts[1].texCoord.y = texture.y1;
+ }
+ if (m_info.orientation & 4)
+ {
+ verts[1].texCoord2.x = diffuse.x1;
+ verts[1].texCoord2.y = diffuse.y2;
+ }
+ else
+ {
+ verts[1].texCoord2.x = diffuse.x2;
+ verts[1].texCoord2.y = diffuse.y1;
+ }
+ verts[1].color = xcolor;
+
+ verts[2].pos.x = x[2]; verts[2].pos.y = y[2]; verts[2].pos.z = z[2];
+ verts[2].texCoord.x = texture.x2; verts[2].texCoord.y = texture.y2;
+ verts[2].texCoord2.x = diffuse.x2; verts[2].texCoord2.y = diffuse.y2;
+ verts[2].color = xcolor;
+
+ verts[3].pos.x = x[3]; verts[3].pos.y = y[3]; verts[3].pos.z = z[3];
+ if (orientation & 4)
+ {
+ verts[3].texCoord.x = texture.x2;
+ verts[3].texCoord.y = texture.y1;
+ }
+ else
+ {
+ verts[3].texCoord.x = texture.x1;
+ verts[3].texCoord.y = texture.y2;
+ }
+ if (m_info.orientation & 4)
+ {
+ verts[3].texCoord2.x = diffuse.x2;
+ verts[3].texCoord2.y = diffuse.y1;
+ }
+ else
+ {
+ verts[3].texCoord2.x = diffuse.x1;
+ verts[3].texCoord2.y = diffuse.y2;
+ }
+ verts[3].color = xcolor;
+
+ CDXTexture* tex = static_cast<CDXTexture*>(m_texture.m_textures[m_currentFrame].get());
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+
+ pGUIShader->Begin(m_diffuse.size() ? SHADER_METHOD_RENDER_MULTI_TEXTURE_BLEND : SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ if (m_diffuse.size())
+ {
+ CDXTexture* diff = static_cast<CDXTexture*>(m_diffuse.m_textures[0].get());
+ ID3D11ShaderResourceView* resource[] = { tex->GetShaderResource(), diff->GetShaderResource() };
+ pGUIShader->SetShaderViews(ARRAYSIZE(resource), resource);
+ }
+ else
+ {
+ ID3D11ShaderResourceView* resource = tex->GetShaderResource();
+ pGUIShader->SetShaderViews(1, &resource);
+ }
+ pGUIShader->DrawQuad(verts[0], verts[1], verts[2], verts[3]);
+}
+
+void CGUITextureD3D::DrawQuad(const CRect& rect,
+ UTILS::COLOR::Color color,
+ CTexture* texture,
+ const CRect* texCoords)
+{
+ unsigned numViews = 0;
+ ID3D11ShaderResourceView* views = nullptr;
+
+ if (texture)
+ {
+ texture->LoadToGPU();
+ numViews = 1;
+ views = ((CDXTexture *)texture)->GetShaderResource();
+ }
+
+ CD3DTexture::DrawQuad(rect, color, numViews, &views, texCoords, texture ? SHADER_METHOD_RENDER_TEXTURE_BLEND : SHADER_METHOD_RENDER_DEFAULT);
+}
diff --git a/xbmc/guilib/GUITextureD3D.h b/xbmc/guilib/GUITextureD3D.h
new file mode 100644
index 0000000..a649f85
--- /dev/null
+++ b/xbmc/guilib/GUITextureD3D.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUITexture.h"
+#include "utils/ColorUtils.h"
+
+class CGUITextureD3D : public CGUITexture
+{
+public:
+ static void Register();
+ static CGUITexture* CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture);
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CTexture* texture = nullptr,
+ const CRect* texCoords = nullptr);
+
+ CGUITextureD3D(float posX, float posY, float width, float height, const CTextureInfo& texture);
+ ~CGUITextureD3D() override = default;
+
+ CGUITextureD3D* Clone() const override;
+
+protected:
+ void Begin(UTILS::COLOR::Color color);
+ void Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation);
+ void End();
+
+private:
+ CGUITextureD3D(const CGUITextureD3D& texture) = default;
+
+ UTILS::COLOR::Color m_col;
+};
+
diff --git a/xbmc/guilib/GUITextureGL.cpp b/xbmc/guilib/GUITextureGL.cpp
new file mode 100644
index 0000000..6e46aa7
--- /dev/null
+++ b/xbmc/guilib/GUITextureGL.cpp
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextureGL.h"
+
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "rendering/gl/RenderSystemGL.h"
+#include "utils/GLUtils.h"
+#include "utils/Geometry.h"
+#include "utils/log.h"
+#include "windowing/WinSystem.h"
+
+#include <cstddef>
+
+#include "PlatformDefs.h"
+
+void CGUITextureGL::Register()
+{
+ CGUITexture::Register(CGUITextureGL::CreateTexture, CGUITextureGL::DrawQuad);
+}
+
+CGUITexture* CGUITextureGL::CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+{
+ return new CGUITextureGL(posX, posY, width, height, texture);
+}
+
+CGUITextureGL::CGUITextureGL(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+ : CGUITexture(posX, posY, width, height, texture)
+{
+ m_renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+}
+
+CGUITextureGL* CGUITextureGL::Clone() const
+{
+ return new CGUITextureGL(*this);
+}
+
+void CGUITextureGL::Begin(UTILS::COLOR::Color color)
+{
+ CTexture* texture = m_texture.m_textures[m_currentFrame].get();
+ texture->LoadToGPU();
+ if (m_diffuse.size())
+ m_diffuse.m_textures[0]->LoadToGPU();
+
+ texture->BindToUnit(0);
+
+ // Setup Colors
+ m_col[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ m_col[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ m_col[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ m_col[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ bool hasAlpha = m_texture.m_textures[m_currentFrame]->HasAlpha() || m_col[3] < 255;
+
+ if (m_diffuse.size())
+ {
+ if (m_col[0] == 255 && m_col[1] == 255 && m_col[2] == 255 && m_col[3] == 255 )
+ {
+ m_renderSystem->EnableShader(ShaderMethodGL::SM_MULTI);
+ }
+ else
+ {
+ m_renderSystem->EnableShader(ShaderMethodGL::SM_MULTI_BLENDCOLOR);
+ }
+
+ hasAlpha |= m_diffuse.m_textures[0]->HasAlpha();
+
+ m_diffuse.m_textures[0]->BindToUnit(1);
+ }
+ else
+ {
+ if (m_col[0] == 255 && m_col[1] == 255 && m_col[2] == 255 && m_col[3] == 255)
+ {
+ m_renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE_NOBLEND);
+ }
+ else
+ {
+ m_renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE);
+ }
+ }
+
+ if (hasAlpha)
+ {
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE);
+ glEnable(GL_BLEND);
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+ m_packedVertices.clear();
+ m_idx.clear();
+}
+
+void CGUITextureGL::End()
+{
+ if (m_packedVertices.size())
+ {
+ GLint posLoc = m_renderSystem->ShaderGetPos();
+ GLint tex0Loc = m_renderSystem->ShaderGetCoord0();
+ GLint tex1Loc = m_renderSystem->ShaderGetCoord1();
+ GLint uniColLoc = m_renderSystem->ShaderGetUniCol();
+
+ GLuint VertexVBO;
+ GLuint IndexVBO;
+
+ glGenBuffers(1, &VertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, VertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*m_packedVertices.size(), &m_packedVertices[0], GL_STATIC_DRAW);
+
+ if (uniColLoc >= 0)
+ {
+ glUniform4f(uniColLoc,(m_col[0] / 255.0f), (m_col[1] / 255.0f), (m_col[2] / 255.0f), (m_col[3] / 255.0f));
+ }
+
+ if (m_diffuse.size())
+ {
+ glVertexAttribPointer(tex1Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u2)));
+ glEnableVertexAttribArray(tex1Loc);
+ }
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glEnableVertexAttribArray(posLoc);
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+ glEnableVertexAttribArray(tex0Loc);
+
+ glGenBuffers(1, &IndexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ushort)*m_idx.size(), m_idx.data(), GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLES, m_packedVertices.size()*6 / 4, GL_UNSIGNED_SHORT, 0);
+
+ if (m_diffuse.size())
+ glDisableVertexAttribArray(tex1Loc);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &VertexVBO);
+ glDeleteBuffers(1, &IndexVBO);
+ }
+
+ if (m_diffuse.size())
+ glActiveTexture(GL_TEXTURE0);
+ glEnable(GL_BLEND);
+
+ m_renderSystem->DisableShader();
+}
+
+void CGUITextureGL::Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation)
+{
+ PackedVertex vertices[4];
+
+ // Setup texture coordinates
+ // TopLeft
+ vertices[0].u1 = texture.x1;
+ vertices[0].v1 = texture.y1;
+
+ // TopRight
+ if (orientation & 4)
+ {
+ vertices[1].u1 = texture.x1;
+ vertices[1].v1 = texture.y2;
+ }
+ else
+ {
+ vertices[1].u1 = texture.x2;
+ vertices[1].v1 = texture.y1;
+ }
+
+ // BottomRight
+ vertices[2].u1 = texture.x2;
+ vertices[2].v1 = texture.y2;
+
+ // BottomLeft
+ if (orientation & 4)
+ {
+ vertices[3].u1 = texture.x2;
+ vertices[3].v1 = texture.y1;
+ }
+ else
+ {
+ vertices[3].u1 = texture.x1;
+ vertices[3].v1 = texture.y2;
+ }
+
+ if (m_diffuse.size())
+ {
+ // TopLeft
+ vertices[0].u2 = diffuse.x1;
+ vertices[0].v2 = diffuse.y1;
+
+ // TopRight
+ if (m_info.orientation & 4)
+ {
+ vertices[1].u2 = diffuse.x1;
+ vertices[1].v2 = diffuse.y2;
+ }
+ else
+ {
+ vertices[1].u2 = diffuse.x2;
+ vertices[1].v2 = diffuse.y1;
+ }
+
+ // BottomRight
+ vertices[2].u2 = diffuse.x2;
+ vertices[2].v2 = diffuse.y2;
+
+ // BottomLeft
+ if (m_info.orientation & 4)
+ {
+ vertices[3].u2 = diffuse.x2;
+ vertices[3].v2 = diffuse.y1;
+ }
+ else
+ {
+ vertices[3].u2 = diffuse.x1;
+ vertices[3].v2 = diffuse.y2;
+ }
+ }
+
+ for (int i=0; i<4; i++)
+ {
+ vertices[i].x = x[i];
+ vertices[i].y = y[i];
+ vertices[i].z = z[i];
+ m_packedVertices.push_back(vertices[i]);
+ }
+
+ if ((m_packedVertices.size() / 4) > (m_idx.size() / 6))
+ {
+ size_t i = m_packedVertices.size() - 4;
+ m_idx.push_back(i+0);
+ m_idx.push_back(i+1);
+ m_idx.push_back(i+2);
+ m_idx.push_back(i+2);
+ m_idx.push_back(i+3);
+ m_idx.push_back(i+0);
+ }
+}
+
+void CGUITextureGL::DrawQuad(const CRect& rect,
+ UTILS::COLOR::Color color,
+ CTexture* texture,
+ const CRect* texCoords)
+{
+ CRenderSystemGL *renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+ if (texture)
+ {
+ texture->LoadToGPU();
+ texture->BindToUnit(0);
+ }
+
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND); // Turn Blending On
+
+ VerifyGLState();
+
+ GLubyte col[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ }vertex[4];
+
+ if (texture)
+ renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE);
+ else
+ renderSystem->EnableShader(ShaderMethodGL::SM_DEFAULT);
+
+ GLint posLoc = renderSystem->ShaderGetPos();
+ GLint tex0Loc = renderSystem->ShaderGetCoord0();
+ GLint uniColLoc = renderSystem->ShaderGetUniCol();
+
+ // Setup Colors
+ col[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ col[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ col[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ col[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ glUniform4f(uniColLoc, col[0] / 255.0f, col[1] / 255.0f, col[2] / 255.0f, col[3] / 255.0f);
+
+ // bottom left
+ vertex[0].x = rect.x1;
+ vertex[0].y = rect.y1;
+ vertex[0].z = 0;
+
+ // bottom right
+ vertex[1].x = rect.x2;
+ vertex[1].y = rect.y1;
+ vertex[1].z = 0;
+
+ // top right
+ vertex[2].x = rect.x2;
+ vertex[2].y = rect.y2;
+ vertex[2].z = 0;
+
+ // top left
+ vertex[3].x = rect.x1;
+ vertex[3].y = rect.y2;
+ vertex[3].z = 0;
+
+ if (texture)
+ {
+ CRect coords = texCoords ? *texCoords : CRect(0.0f, 0.0f, 1.0f, 1.0f);
+ vertex[0].u1 = vertex[3].u1 = coords.x1;
+ vertex[0].v1 = vertex[1].v1 = coords.y1;
+ vertex[1].u1 = vertex[2].u1 = coords.x2;
+ vertex[2].v1 = vertex[3].v1 = coords.y2;
+ }
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glEnableVertexAttribArray(posLoc);
+
+ if (texture)
+ {
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+ glEnableVertexAttribArray(tex0Loc);
+ }
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(posLoc);
+ if (texture)
+ glDisableVertexAttribArray(tex0Loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ renderSystem->DisableShader();
+}
+
diff --git a/xbmc/guilib/GUITextureGL.h b/xbmc/guilib/GUITextureGL.h
new file mode 100644
index 0000000..4acba1e
--- /dev/null
+++ b/xbmc/guilib/GUITextureGL.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUITexture.h"
+#include "utils/ColorUtils.h"
+
+#include <array>
+
+#include "system_gl.h"
+
+class CRenderSystemGL;
+
+class CGUITextureGL : public CGUITexture
+{
+public:
+ static void Register();
+ static CGUITexture* CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture);
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CTexture* texture = nullptr,
+ const CRect* texCoords = nullptr);
+
+ CGUITextureGL(float posX, float posY, float width, float height, const CTextureInfo& texture);
+ ~CGUITextureGL() override = default;
+
+ CGUITextureGL* Clone() const override;
+
+protected:
+ void Begin(UTILS::COLOR::Color color) override;
+ void Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation) override;
+ void End() override;
+
+private:
+ CGUITextureGL(const CGUITextureGL& texture) = default;
+
+ std::array<GLubyte, 4> m_col;
+
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ float u2, v2;
+ };
+
+ std::vector<PackedVertex> m_packedVertices;
+ std::vector<GLushort> m_idx;
+ CRenderSystemGL *m_renderSystem;
+};
+
diff --git a/xbmc/guilib/GUITextureGLES.cpp b/xbmc/guilib/GUITextureGLES.cpp
new file mode 100644
index 0000000..d92201a
--- /dev/null
+++ b/xbmc/guilib/GUITextureGLES.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUITextureGLES.h"
+
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/GLUtils.h"
+#include "utils/MathUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <cstddef>
+
+void CGUITextureGLES::Register()
+{
+ CGUITexture::Register(CGUITextureGLES::CreateTexture, CGUITextureGLES::DrawQuad);
+}
+
+CGUITexture* CGUITextureGLES::CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+{
+ return new CGUITextureGLES(posX, posY, width, height, texture);
+}
+
+CGUITextureGLES::CGUITextureGLES(
+ float posX, float posY, float width, float height, const CTextureInfo& texture)
+ : CGUITexture(posX, posY, width, height, texture)
+{
+ m_renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+}
+
+CGUITextureGLES* CGUITextureGLES::Clone() const
+{
+ return new CGUITextureGLES(*this);
+}
+
+void CGUITextureGLES::Begin(UTILS::COLOR::Color color)
+{
+ CTexture* texture = m_texture.m_textures[m_currentFrame].get();
+ texture->LoadToGPU();
+ if (m_diffuse.size())
+ m_diffuse.m_textures[0]->LoadToGPU();
+
+ texture->BindToUnit(0);
+
+ // Setup Colors
+ m_col[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ m_col[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ m_col[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ m_col[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ if (CServiceBroker::GetWinSystem()->UseLimitedColor())
+ {
+ m_col[0] = (235 - 16) * m_col[0] / 255 + 16;
+ m_col[1] = (235 - 16) * m_col[1] / 255 + 16;
+ m_col[2] = (235 - 16) * m_col[2] / 255 + 16;
+ }
+
+ bool hasAlpha = m_texture.m_textures[m_currentFrame]->HasAlpha() || m_col[3] < 255;
+
+ if (m_diffuse.size())
+ {
+ if (m_col[0] == 255 && m_col[1] == 255 && m_col[2] == 255 && m_col[3] == 255 )
+ {
+ m_renderSystem->EnableGUIShader(ShaderMethodGLES::SM_MULTI);
+ }
+ else
+ {
+ m_renderSystem->EnableGUIShader(ShaderMethodGLES::SM_MULTI_BLENDCOLOR);
+ }
+
+ hasAlpha |= m_diffuse.m_textures[0]->HasAlpha();
+
+ m_diffuse.m_textures[0]->BindToUnit(1);
+
+ }
+ else
+ {
+ if (m_col[0] == 255 && m_col[1] == 255 && m_col[2] == 255 && m_col[3] == 255)
+ {
+ m_renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE_NOBLEND);
+ }
+ else
+ {
+ m_renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE);
+ }
+ }
+
+ if ( hasAlpha )
+ {
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE);
+ glEnable( GL_BLEND );
+ }
+ else
+ {
+ glDisable(GL_BLEND);
+ }
+ m_packedVertices.clear();
+}
+
+void CGUITextureGLES::End()
+{
+ if (m_packedVertices.size())
+ {
+ GLint posLoc = m_renderSystem->GUIShaderGetPos();
+ GLint tex0Loc = m_renderSystem->GUIShaderGetCoord0();
+ GLint tex1Loc = m_renderSystem->GUIShaderGetCoord1();
+ GLint uniColLoc = m_renderSystem->GUIShaderGetUniCol();
+
+ if(uniColLoc >= 0)
+ {
+ glUniform4f(uniColLoc,(m_col[0] / 255.0f), (m_col[1] / 255.0f), (m_col[2] / 255.0f), (m_col[3] / 255.0f));
+ }
+
+ if(m_diffuse.size())
+ {
+ glVertexAttribPointer(tex1Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex), (char*)&m_packedVertices[0] + offsetof(PackedVertex, u2));
+ glEnableVertexAttribArray(tex1Loc);
+ }
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex), (char*)&m_packedVertices[0] + offsetof(PackedVertex, x));
+ glEnableVertexAttribArray(posLoc);
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex), (char*)&m_packedVertices[0] + offsetof(PackedVertex, u1));
+ glEnableVertexAttribArray(tex0Loc);
+
+ glDrawElements(GL_TRIANGLES, m_packedVertices.size()*6 / 4, GL_UNSIGNED_SHORT, m_idx.data());
+
+ if (m_diffuse.size())
+ glDisableVertexAttribArray(tex1Loc);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+ }
+
+ if (m_diffuse.size())
+ glActiveTexture(GL_TEXTURE0);
+ glEnable(GL_BLEND);
+ m_renderSystem->DisableGUIShader();
+}
+
+void CGUITextureGLES::Draw(float *x, float *y, float *z, const CRect &texture, const CRect &diffuse, int orientation)
+{
+ PackedVertex vertices[4];
+
+ // Setup texture coordinates
+ //TopLeft
+ vertices[0].u1 = texture.x1;
+ vertices[0].v1 = texture.y1;
+ //TopRight
+ if (orientation & 4)
+ {
+ vertices[1].u1 = texture.x1;
+ vertices[1].v1 = texture.y2;
+ }
+ else
+ {
+ vertices[1].u1 = texture.x2;
+ vertices[1].v1 = texture.y1;
+ }
+ //BottomRight
+ vertices[2].u1 = texture.x2;
+ vertices[2].v1 = texture.y2;
+ //BottomLeft
+ if (orientation & 4)
+ {
+ vertices[3].u1 = texture.x2;
+ vertices[3].v1 = texture.y1;
+ }
+ else
+ {
+ vertices[3].u1 = texture.x1;
+ vertices[3].v1 = texture.y2;
+ }
+
+ if (m_diffuse.size())
+ {
+ //TopLeft
+ vertices[0].u2 = diffuse.x1;
+ vertices[0].v2 = diffuse.y1;
+ //TopRight
+ if (m_info.orientation & 4)
+ {
+ vertices[1].u2 = diffuse.x1;
+ vertices[1].v2 = diffuse.y2;
+ }
+ else
+ {
+ vertices[1].u2 = diffuse.x2;
+ vertices[1].v2 = diffuse.y1;
+ }
+ //BottomRight
+ vertices[2].u2 = diffuse.x2;
+ vertices[2].v2 = diffuse.y2;
+ //BottomLeft
+ if (m_info.orientation & 4)
+ {
+ vertices[3].u2 = diffuse.x2;
+ vertices[3].v2 = diffuse.y1;
+ }
+ else
+ {
+ vertices[3].u2 = diffuse.x1;
+ vertices[3].v2 = diffuse.y2;
+ }
+ }
+
+ for (int i=0; i<4; i++)
+ {
+ vertices[i].x = x[i];
+ vertices[i].y = y[i];
+ vertices[i].z = z[i];
+ m_packedVertices.push_back(vertices[i]);
+ }
+
+ if ((m_packedVertices.size() / 4) > (m_idx.size() / 6))
+ {
+ size_t i = m_packedVertices.size() - 4;
+ m_idx.push_back(i+0);
+ m_idx.push_back(i+1);
+ m_idx.push_back(i+2);
+ m_idx.push_back(i+2);
+ m_idx.push_back(i+3);
+ m_idx.push_back(i+0);
+ }
+}
+
+void CGUITextureGLES::DrawQuad(const CRect& rect,
+ UTILS::COLOR::Color color,
+ CTexture* texture,
+ const CRect* texCoords)
+{
+ CRenderSystemGLES *renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ if (texture)
+ {
+ texture->LoadToGPU();
+ texture->BindToUnit(0);
+ }
+
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND); // Turn Blending On
+
+ VerifyGLState();
+
+ GLubyte col[4];
+ GLfloat ver[4][3];
+ GLfloat tex[4][2];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of triangle strip
+
+ if (texture)
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE);
+ else
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_DEFAULT);
+
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint tex0Loc = renderSystem->GUIShaderGetCoord0();
+ GLint uniColLoc= renderSystem->GUIShaderGetUniCol();
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, 0, ver);
+ if (texture)
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(posLoc);
+ if (texture)
+ glEnableVertexAttribArray(tex0Loc);
+
+ // Setup Colors
+ col[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ col[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ col[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ col[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ glUniform4f(uniColLoc, col[0] / 255.0f, col[1] / 255.0f, col[2] / 255.0f, col[3] / 255.0f);
+
+ ver[0][0] = ver[3][0] = rect.x1;
+ ver[0][1] = ver[1][1] = rect.y1;
+ ver[1][0] = ver[2][0] = rect.x2;
+ ver[2][1] = ver[3][1] = rect.y2;
+ ver[0][2] = ver[1][2] = ver[2][2] = ver[3][2]= 0;
+
+ if (texture)
+ {
+ // Setup texture coordinates
+ CRect coords = texCoords ? *texCoords : CRect(0.0f, 0.0f, 1.0f, 1.0f);
+ tex[0][0] = tex[3][0] = coords.x1;
+ tex[0][1] = tex[1][1] = coords.y1;
+ tex[1][0] = tex[2][0] = coords.x2;
+ tex[2][1] = tex[3][1] = coords.y2;
+ }
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ glDisableVertexAttribArray(posLoc);
+ if (texture)
+ glDisableVertexAttribArray(tex0Loc);
+
+ renderSystem->DisableGUIShader();
+}
+
diff --git a/xbmc/guilib/GUITextureGLES.h b/xbmc/guilib/GUITextureGLES.h
new file mode 100644
index 0000000..a9b3610
--- /dev/null
+++ b/xbmc/guilib/GUITextureGLES.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUITexture.h"
+#include "utils/ColorUtils.h"
+
+#include <array>
+#include <vector>
+
+#include "system_gl.h"
+
+struct PackedVertex
+{
+ float x, y, z;
+ float u1, v1;
+ float u2, v2;
+};
+typedef std::vector<PackedVertex> PackedVertices;
+
+class CRenderSystemGLES;
+
+class CGUITextureGLES : public CGUITexture
+{
+public:
+ static void Register();
+ static CGUITexture* CreateTexture(
+ float posX, float posY, float width, float height, const CTextureInfo& texture);
+
+ static void DrawQuad(const CRect& coords,
+ UTILS::COLOR::Color color,
+ CTexture* texture = nullptr,
+ const CRect* texCoords = nullptr);
+
+ CGUITextureGLES(float posX, float posY, float width, float height, const CTextureInfo& texture);
+ ~CGUITextureGLES() override = default;
+
+ CGUITextureGLES* Clone() const override;
+
+protected:
+ void Begin(UTILS::COLOR::Color color) override;
+ void Draw(float* x, float* y, float* z, const CRect& texture, const CRect& diffuse, int orientation) override;
+ void End() override;
+
+private:
+ CGUITextureGLES(const CGUITextureGLES& texture) = default;
+
+ std::array<GLubyte, 4> m_col;
+
+ PackedVertices m_packedVertices;
+ std::vector<GLushort> m_idx;
+ CRenderSystemGLES *m_renderSystem;
+};
+
diff --git a/xbmc/guilib/GUIToggleButtonControl.cpp b/xbmc/guilib/GUIToggleButtonControl.cpp
new file mode 100644
index 0000000..42bd7ee
--- /dev/null
+++ b/xbmc/guilib/GUIToggleButtonControl.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIToggleButtonControl.h"
+
+#include "GUIDialog.h"
+#include "GUIInfoManager.h"
+#include "GUIWindowManager.h"
+#include "input/Key.h"
+
+CGUIToggleButtonControl::CGUIToggleButtonControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, const CTextureInfo& altTextureFocus, const CTextureInfo& altTextureNoFocus, const CLabelInfo &labelInfo, bool wrapMultiLine)
+ : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo, wrapMultiLine)
+ , m_selectButton(parentID, controlID, posX, posY, width, height, altTextureFocus, altTextureNoFocus, labelInfo, wrapMultiLine)
+{
+ ControlType = GUICONTROL_TOGGLEBUTTON;
+}
+
+CGUIToggleButtonControl::~CGUIToggleButtonControl(void) = default;
+
+void CGUIToggleButtonControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // ask our infoManager whether we are selected or not...
+ if (m_toggleSelect)
+ m_bSelected = m_toggleSelect->Get(INFO::DEFAULT_CONTEXT);
+
+ if (m_bSelected)
+ {
+ // render our Alternate textures...
+ m_selectButton.SetFocus(HasFocus());
+ m_selectButton.SetVisible(IsVisible());
+ m_selectButton.SetEnabled(!IsDisabled());
+ m_selectButton.SetPulseOnSelect(m_pulseOnSelect);
+ ProcessToggle(currentTime);
+ m_selectButton.DoProcess(currentTime, dirtyregions);
+ }
+ else
+ CGUIButtonControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIToggleButtonControl::ProcessToggle(unsigned int currentTime)
+{
+ bool changed = false;
+
+ changed |= m_label.SetMaxRect(m_posX, m_posY, GetWidth(), m_height);
+ changed |= m_label.SetText(GetDescription());
+ changed |= m_label.SetColor(GetTextColor());
+ changed |= m_label.SetScrolling(HasFocus());
+ changed |= m_label.Process(currentTime);
+
+ if (changed)
+ MarkDirtyRegion();
+}
+
+void CGUIToggleButtonControl::Render()
+{
+ if (m_bSelected)
+ {
+ m_selectButton.Render();
+ CGUIControl::Render();
+ }
+ else
+ { // render our Normal textures...
+ CGUIButtonControl::Render();
+ }
+}
+
+bool CGUIToggleButtonControl::OnAction(const CAction &action)
+{
+ if (action.GetID() == ACTION_SELECT_ITEM)
+ {
+ m_bSelected = !m_bSelected;
+ SetInvalid();
+ }
+ return CGUIButtonControl::OnAction(action);
+}
+
+void CGUIToggleButtonControl::AllocResources()
+{
+ CGUIButtonControl::AllocResources();
+ m_selectButton.AllocResources();
+}
+
+void CGUIToggleButtonControl::FreeResources(bool immediately)
+{
+ CGUIButtonControl::FreeResources(immediately);
+ m_selectButton.FreeResources(immediately);
+}
+
+void CGUIToggleButtonControl::DynamicResourceAlloc(bool bOnOff)
+{
+ CGUIButtonControl::DynamicResourceAlloc(bOnOff);
+ m_selectButton.DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIToggleButtonControl::SetInvalid()
+{
+ CGUIButtonControl::SetInvalid();
+ m_selectButton.SetInvalid();
+}
+
+void CGUIToggleButtonControl::SetPosition(float posX, float posY)
+{
+ CGUIButtonControl::SetPosition(posX, posY);
+ m_selectButton.SetPosition(posX, posY);
+}
+
+void CGUIToggleButtonControl::SetWidth(float width)
+{
+ CGUIButtonControl::SetWidth(width);
+ m_selectButton.SetWidth(width);
+}
+
+void CGUIToggleButtonControl::SetHeight(float height)
+{
+ CGUIButtonControl::SetHeight(height);
+ m_selectButton.SetHeight(height);
+}
+
+void CGUIToggleButtonControl::SetMinWidth(float minWidth)
+{
+ CGUIButtonControl::SetMinWidth(minWidth);
+ m_selectButton.SetMinWidth(minWidth);
+}
+
+bool CGUIToggleButtonControl::UpdateColors(const CGUIListItem* item)
+{
+ bool changed = CGUIButtonControl::UpdateColors(nullptr);
+ changed |= m_selectButton.SetColorDiffuse(m_diffuseColor);
+ changed |= m_selectButton.UpdateColors(nullptr);
+
+ return changed;
+}
+
+void CGUIToggleButtonControl::SetLabel(const std::string &label)
+{
+ CGUIButtonControl::SetLabel(label);
+ m_selectButton.SetLabel(label);
+}
+
+void CGUIToggleButtonControl::SetAltLabel(const std::string &label)
+{
+ if (label.size())
+ m_selectButton.SetLabel(label);
+}
+
+std::string CGUIToggleButtonControl::GetDescription() const
+{
+ if (m_bSelected)
+ return m_selectButton.GetDescription();
+ return CGUIButtonControl::GetDescription();
+}
+
+void CGUIToggleButtonControl::SetAltClickActions(const CGUIAction &clickActions)
+{
+ m_selectButton.SetClickActions(clickActions);
+}
+
+void CGUIToggleButtonControl::OnClick()
+{
+ // the ! is here as m_bSelected gets updated before this is called
+ if (!m_bSelected && m_selectButton.HasClickActions())
+ m_selectButton.OnClick();
+ else
+ CGUIButtonControl::OnClick();
+}
+
+void CGUIToggleButtonControl::SetToggleSelect(const std::string &toggleSelect)
+{
+ m_toggleSelect = CServiceBroker::GetGUI()->GetInfoManager().Register(toggleSelect, GetParentID());
+}
diff --git a/xbmc/guilib/GUIToggleButtonControl.dox b/xbmc/guilib/GUIToggleButtonControl.dox
new file mode 100644
index 0000000..0605d20
--- /dev/null
+++ b/xbmc/guilib/GUIToggleButtonControl.dox
@@ -0,0 +1,91 @@
+/*!
+
+\page skin_Toggle_button_control Toggle button control
+\brief **A toggle on/off button that can take 2 different states.**
+
+\tableofcontents
+
+The toggle button control is used for creating buttons that have 2 states. You
+can choose the position, size, and look of the button. When the user clicks on
+the toggle button, the state will change, toggling the extra textures
+(alttexturefocus and alttexturenofocus). Used for controls where two states are
+needed (pushed in and pushed out for instance).
+
+--------------------------------------------------------------------------------
+\section skin_Toggle_button_control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="togglebutton" id="25">
+ <description>My first togglebutton control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <colordiffuse>FFFFFFFF</colordiffuse>
+ <texturefocus>myfocustexture.png</texturefocus>
+ <texturenofocus>mynormaltexture.png</texturenofocus>
+ <alttexturefocus>myselectedTexture.png</alttexturefocus>
+ <alttexturenofocus>myselectedTexture_nf.png</alttexturenofocus>
+ <usealttexture>!Player.IsPaused</usealttexture>
+ <label>29</label>
+ <altlabel>29</altlabel>
+ <font>font12</font>
+ <textcolor>FFFFFFFF</textcolor>
+ <disabledcolor>80FFFFFF</disabledcolor>
+ <align>left</align>
+ <aligny>center</aligny>
+ <textoffsetx>4</textoffsetx>
+ <textoffsety>5</textoffsety>
+ <pulseonselect>false</pulseonselect>
+ <onclick>Player.Pause</onclick>
+ <onfocus>-</onfocus>
+ <onunfocus>-</onunfocus>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ <wrapmultiline>false</wrapmultiline>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section skin_Toggle_button_control_sect2 Available tags
+
+In addition to the Default Control Tags the following tags are available. Note
+that each tag is lower case only. This is important, as xml tags are case-sensitive.
+
+| Tag | Description |
+|------------------:|:--------------------------------------------------------------|
+| texturefocus | Specifies the image file which should be displayed when the button has focus. See here for additional information about texture tags.
+| texturenofocus | Specifies the image file which should be displayed when the button does not have focus.
+| alttexturefocus | Specifies the image file which should be displayed when the toggle button is in it's selected state. This texture replaces the <b>`<texturefocus>`</b> texture when the toggle button is selected.
+| alttexturenofocus | Specifies the image file which should be displayed when the button is in it's selected state but unfocused.
+| usealttexture | Specifies the conditions under which the Alternative Textures should be shown. Some toggle button controls are handled by Kodi internally, but any extra ones that the skinner has can be controlled using this tag. See here for more information.
+| label | The label used on the button. It can be a link into strings.po, or an actual text label.
+| altlabel | The alternate label used on the button. It can be a link into strings.po, or an actual text label.
+| altclick | The alternate action to perform when the button is pressed. Should be a built in function. See here for more information. You may have more than one <b>`<altclick>`</b> tag, and they'll be executed in sequence.
+| font | Font used for the button label. From fonts.xml.
+| textcolor | Color used for displaying the button label. In AARRGGBB hex format, or a name from the colour theme.
+| disabledcolor | Color used for the button label if the button is disabled. In AARRGGBB hex format, or a name from the colour theme.
+| shadowcolor | Specifies the color of the drop shadow on the text. In AARRGGBB hex format, or a name from the colour theme.
+| align | Label horizontal alignment on the button. Defaults to left, can also be center or right.
+| aligny | Label vertical alignment on the button. Defaults to top, can also be center.
+| textoffsetx | Amount to offset the label from the left (or right) edge of the button when using left or right alignment.
+| textoffsety | Amount to offset the label from the top edge of the button when using top alignment.
+| textwidth | Will truncate any text that's too long.
+| onclick | Specifies the action to perform when the button is pressed. Should be a built in function. See here for more information. You may have more than one <b>`<onclick>`</b> tag, and they'll be executed in sequence.
+| onfocus | Specifies the action to perform when the button is focused. Should be a built in function. The action is performed after any focus animations have completed. See here for more information.
+| onunfocus | Specifies the action to perform when the button loses focus. Should be a built in function.
+| wrapmultiline | Allows wrapping on the label across multiple lines. Defaults to false.
+
+
+--------------------------------------------------------------------------------
+\section skin_Toggle_button_control_sect3 See also
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIToggleButtonControl.h b/xbmc/guilib/GUIToggleButtonControl.h
new file mode 100644
index 0000000..732adc7
--- /dev/null
+++ b/xbmc/guilib/GUIToggleButtonControl.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIToggleButtonControl.h
+\brief
+*/
+
+#include "GUIButtonControl.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIToggleButtonControl : public CGUIButtonControl
+{
+public:
+ CGUIToggleButtonControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, const CTextureInfo& altTextureFocus, const CTextureInfo& altTextureNoFocus, const CLabelInfo &labelInfo, bool wrapMultiline = false);
+ ~CGUIToggleButtonControl(void) override;
+ CGUIToggleButtonControl* Clone() const override { return new CGUIToggleButtonControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ bool OnAction(const CAction &action) override;
+ void AllocResources() override;
+ void FreeResources(bool immediately = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ void SetInvalid() override;
+ void SetPosition(float posX, float posY) override;
+ void SetWidth(float width) override;
+ void SetHeight(float height) override;
+ void SetMinWidth(float minWidth) override;
+ void SetLabel(const std::string& label) override;
+ void SetAltLabel(const std::string& label);
+ std::string GetDescription() const override;
+ void SetToggleSelect(const std::string &toggleSelect);
+ void SetAltClickActions(const CGUIAction &clickActions);
+
+protected:
+ bool UpdateColors(const CGUIListItem* item) override;
+ void OnClick() override;
+ CGUIButtonControl m_selectButton;
+ INFO::InfoPtr m_toggleSelect;
+
+private:
+ void ProcessToggle(unsigned int currentTime);
+ std::string m_altLabel;
+};
+
diff --git a/xbmc/guilib/GUIVideoControl.cpp b/xbmc/guilib/GUIVideoControl.cpp
new file mode 100644
index 0000000..91a949f
--- /dev/null
+++ b/xbmc/guilib/GUIVideoControl.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIVideoControl.h"
+
+#include "GUIComponent.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "input/Key.h"
+#include "utils/ColorUtils.h"
+
+CGUIVideoControl::CGUIVideoControl(int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+{
+ ControlType = GUICONTROL_VIDEO;
+}
+
+CGUIVideoControl::~CGUIVideoControl(void) = default;
+
+void CGUIVideoControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ //! @todo Proper processing which marks when its actually changed. Just mark always for now.
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsRenderingGuiLayer())
+ MarkDirtyRegion();
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIVideoControl::Render()
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsRenderingVideo())
+ {
+ if (!appPlayer->IsPausedPlayback())
+ {
+ auto& appComponents = CServiceBroker::GetAppComponents();
+ const auto appPower = appComponents.GetComponent<CApplicationPowerHandling>();
+ appPower->ResetScreenSaver();
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetViewWindow(m_posX, m_posY, m_posX + m_width, m_posY + m_height);
+ TransformMatrix mat;
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(mat, 1.0, 1.0);
+
+ UTILS::COLOR::Color alpha =
+ CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(0xFF000000) >> 24;
+ if (appPlayer->IsRenderingVideoLayer())
+ {
+ CRect old = CServiceBroker::GetWinSystem()->GetGfxContext().GetScissors();
+ CRect region = GetRenderRegion();
+ region.Intersect(old);
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScissors(region);
+ CServiceBroker::GetWinSystem()->GetGfxContext().Clear(0);
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScissors(old);
+ }
+ else
+ appPlayer->Render(false, alpha);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+ }
+ CGUIControl::Render();
+}
+
+void CGUIVideoControl::RenderEx()
+{
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsRenderingVideo())
+ appPlayer->Render(false, 255, false);
+
+ CGUIControl::RenderEx();
+}
+
+EVENT_RESULT CGUIVideoControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlayingVideo())
+ return EVENT_RESULT_UNHANDLED;
+ if (event.m_id == ACTION_MOUSE_LEFT_CLICK)
+ { // switch to fullscreen
+ CGUIMessage message(GUI_MSG_FULLSCREEN, GetID(), GetParentID());
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message);
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+bool CGUIVideoControl::CanFocus() const
+{ // unfocusable
+ return false;
+}
+
+bool CGUIVideoControl::CanFocusFromPoint(const CPoint &point) const
+{ // mouse is allowed to focus this control, but it doesn't actually receive focus
+ return IsVisible() && HitTest(point);
+}
diff --git a/xbmc/guilib/GUIVideoControl.dox b/xbmc/guilib/GUIVideoControl.dox
new file mode 100644
index 0000000..24a2042
--- /dev/null
+++ b/xbmc/guilib/GUIVideoControl.dox
@@ -0,0 +1,42 @@
+/*!
+
+\page Video_Control Video Control
+\brief **Used to display the currently playing video whilst in the GUI.**
+
+\tableofcontents
+
+The videowindow control is used for displaying the currently playing video
+elsewhere in the Kodi GUI. You can choose the position, and size of the video
+displayed. Note that the control is only rendered if video is being played.
+
+
+--------------------------------------------------------------------------------
+\section Video_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="videowindow" id="2">
+ <description>My first video control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Video_Control_sect2 Available tags
+
+Only the [default control](http://kodi.wiki/view/Default_Control_Tags) tags are applicable to this control.
+
+
+--------------------------------------------------------------------------------
+\section Video_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIVideoControl.h b/xbmc/guilib/GUIVideoControl.h
new file mode 100644
index 0000000..70c20dc
--- /dev/null
+++ b/xbmc/guilib/GUIVideoControl.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIVideoControl.h
+\brief
+*/
+
+#include "GUIControl.h"
+
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIVideoControl :
+ public CGUIControl
+{
+public:
+ CGUIVideoControl(int parentID, int controlID, float posX, float posY, float width, float height);
+ ~CGUIVideoControl(void) override;
+ CGUIVideoControl* Clone() const override { return new CGUIVideoControl(*this); }
+
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+ void Render() override;
+ void RenderEx() override;
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool CanFocus() const override;
+ bool CanFocusFromPoint(const CPoint &point) const override;
+};
+
diff --git a/xbmc/guilib/GUIVisualisationControl.cpp b/xbmc/guilib/GUIVisualisationControl.cpp
new file mode 100644
index 0000000..0399edd
--- /dev/null
+++ b/xbmc/guilib/GUIVisualisationControl.cpp
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIVisualisationControl.h"
+
+#include "GUIComponent.h"
+#include "GUIInfoManager.h"
+#include "GUIUserMessages.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/Visualization.h"
+#include "addons/addoninfo/AddonType.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+namespace
+{
+constexpr unsigned int MAX_AUDIO_BUFFERS = 16;
+} // namespace
+
+CAudioBuffer::CAudioBuffer(int iSize)
+{
+ m_iLen = iSize;
+ m_pBuffer = new float[iSize];
+}
+
+CAudioBuffer::~CAudioBuffer()
+{
+ delete[] m_pBuffer;
+}
+
+const float* CAudioBuffer::Get() const
+{
+ return m_pBuffer;
+}
+
+int CAudioBuffer::Size() const
+{
+ return m_iLen;
+}
+
+void CAudioBuffer::Set(const float* psBuffer, int iSize)
+{
+ if (iSize < 0)
+ return;
+
+ memcpy(m_pBuffer, psBuffer, iSize * sizeof(float));
+ for (int i = iSize; i < m_iLen; ++i)
+ m_pBuffer[i] = 0;
+}
+
+CGUIVisualisationControl::CGUIVisualisationControl(
+ int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+{
+ ControlType = GUICONTROL_VISUALISATION;
+}
+
+CGUIVisualisationControl::CGUIVisualisationControl(const CGUIVisualisationControl& from)
+ : CGUIControl(from)
+{
+ ControlType = GUICONTROL_VISUALISATION;
+}
+
+std::string CGUIVisualisationControl::Name()
+{
+ if (m_instance == nullptr)
+ return "";
+ return m_instance->Name();
+}
+
+bool CGUIVisualisationControl::OnMessage(CGUIMessage& message)
+{
+ if (m_alreadyStarted)
+ {
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_GET_VISUALISATION:
+ message.SetPointer(this);
+ return true;
+ case GUI_MSG_VISUALISATION_RELOAD:
+ FreeResources(true);
+ return true;
+ case GUI_MSG_PLAYBACK_STARTED:
+ m_updateTrack = true;
+ return true;
+ default:
+ break;
+ }
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+bool CGUIVisualisationControl::OnAction(const CAction& action)
+{
+ if (m_alreadyStarted)
+ {
+ switch (action.GetID())
+ {
+ case ACTION_VIS_PRESET_NEXT:
+ m_instance->NextPreset();
+ break;
+ case ACTION_VIS_PRESET_PREV:
+ m_instance->PrevPreset();
+ break;
+ case ACTION_VIS_PRESET_RANDOM:
+ m_instance->RandomPreset();
+ break;
+ case ACTION_VIS_RATE_PRESET_PLUS:
+ m_instance->RatePreset(true);
+ break;
+ case ACTION_VIS_RATE_PRESET_MINUS:
+ m_instance->RatePreset(false);
+ break;
+ case ACTION_VIS_PRESET_LOCK:
+ m_instance->LockPreset();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ return CGUIControl::OnAction(action);
+}
+
+void CGUIVisualisationControl::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingAudio())
+ {
+ if (m_bInvalidated)
+ FreeResources(true);
+
+ if (!m_instance && !m_attemptedLoad)
+ {
+ InitVisualization();
+
+ m_attemptedLoad = true;
+ }
+ else if (m_callStart && m_instance)
+ {
+ auto& context = CServiceBroker::GetWinSystem()->GetGfxContext();
+
+ context.CaptureStateBlock();
+ if (m_alreadyStarted)
+ {
+ m_instance->Stop();
+ m_alreadyStarted = false;
+ }
+
+ std::string songTitle = URIUtils::GetFileName(g_application.CurrentFile());
+ const MUSIC_INFO::CMusicInfoTag* tag =
+ CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
+ if (tag && !tag->GetTitle().empty())
+ songTitle = tag->GetTitle();
+ m_alreadyStarted = m_instance->Start(m_channels, m_samplesPerSec, m_bitsPerSample, songTitle);
+ context.ApplyStateBlock();
+ m_callStart = false;
+ m_updateTrack = true;
+ }
+ else if (m_updateTrack)
+ {
+ /* Initial update of currently processed track */
+ UpdateTrack();
+ m_updateTrack = false;
+ }
+
+ if (m_instance && m_instance->IsDirty())
+ MarkDirtyRegion();
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIVisualisationControl::Render()
+{
+ if (m_instance && m_alreadyStarted)
+ {
+ auto& context = CServiceBroker::GetWinSystem()->GetGfxContext();
+
+ /*
+ * set the viewport - note: We currently don't have any control over how
+ * the addon renders, so the best we can do is attempt to define
+ * a viewport??
+ */
+ context.SetViewPort(m_posX, m_posY, m_width, m_height);
+ context.CaptureStateBlock();
+ m_instance->Render();
+ context.ApplyStateBlock();
+ context.RestoreViewPort();
+ }
+
+ CGUIControl::Render();
+}
+
+void CGUIVisualisationControl::UpdateVisibility(const CGUIListItem* item /* = nullptr*/)
+{
+ // if made invisible, start timer, only free addonptr after
+ // some period, configurable by window class
+ CGUIControl::UpdateVisibility(item);
+ if (!IsVisible() && m_attemptedLoad)
+ FreeResources();
+}
+
+bool CGUIVisualisationControl::CanFocusFromPoint(const CPoint& point) const
+{ // mouse is allowed to focus this control, but it doesn't actually receive focus
+ return IsVisible() && HitTest(point);
+}
+
+void CGUIVisualisationControl::FreeResources(bool immediately)
+{
+ DeInitVisualization();
+
+ CGUIControl::FreeResources(immediately);
+
+ CLog::Log(LOGDEBUG, "FreeVisualisation() done");
+}
+
+void CGUIVisualisationControl::OnInitialize(int channels, int samplesPerSec, int bitsPerSample)
+{
+ m_channels = channels;
+ m_samplesPerSec = samplesPerSec;
+ m_bitsPerSample = bitsPerSample;
+ m_callStart = true;
+}
+
+void CGUIVisualisationControl::OnAudioData(const float* audioData, unsigned int audioDataLength)
+{
+ if (!m_instance || !m_alreadyStarted || !audioData || audioDataLength == 0)
+ return;
+
+ // Save our audio data in the buffers
+ std::unique_ptr<CAudioBuffer> pBuffer(new CAudioBuffer(audioDataLength));
+ pBuffer->Set(audioData, audioDataLength);
+ m_vecBuffers.emplace_back(std::move(pBuffer));
+
+ if (m_vecBuffers.size() < m_numBuffers)
+ return;
+
+ std::unique_ptr<CAudioBuffer> ptrAudioBuffer = std::move(m_vecBuffers.front());
+ m_vecBuffers.pop_front();
+
+ // Transfer data to our visualisation
+ m_instance->AudioData(ptrAudioBuffer->Get(), ptrAudioBuffer->Size());
+}
+
+void CGUIVisualisationControl::UpdateTrack()
+{
+ if (!m_instance || !m_alreadyStarted)
+ return;
+
+ // get the current album art filename
+ m_albumThumb = CSpecialProtocol::TranslatePath(
+ CServiceBroker::GetGUI()->GetInfoManager().GetImage(MUSICPLAYER_COVER, WINDOW_INVALID));
+ if (m_albumThumb == "DefaultAlbumCover.png")
+ m_albumThumb = "";
+ else
+ CLog::Log(LOGDEBUG, "Updating visualization albumart: {}", m_albumThumb);
+
+ m_instance->UpdateAlbumart(m_albumThumb.c_str());
+
+ const MUSIC_INFO::CMusicInfoTag* tag =
+ CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
+ if (!tag)
+ return;
+
+ const std::string artist(tag->GetArtistString());
+ const std::string albumArtist(tag->GetAlbumArtistString());
+ const std::string genre(StringUtils::Join(
+ tag->GetGenre(),
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator));
+
+ KODI_ADDON_VISUALIZATION_TRACK track = {};
+ track.title = tag->GetTitle().c_str();
+ track.artist = artist.c_str();
+ track.album = tag->GetAlbum().c_str();
+ track.albumArtist = albumArtist.c_str();
+ track.genre = genre.c_str();
+ track.comment = tag->GetComment().c_str();
+ track.lyrics = tag->GetLyrics().c_str();
+ track.trackNumber = tag->GetTrackNumber();
+ track.discNumber = tag->GetDiscNumber();
+ track.duration = tag->GetDuration();
+ track.year = tag->GetYear();
+ track.rating = tag->GetUserrating();
+
+ m_instance->UpdateTrack(&track);
+}
+
+bool CGUIVisualisationControl::IsLocked()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->IsLocked();
+
+ return false;
+}
+
+bool CGUIVisualisationControl::HasPresets()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->HasPresets();
+
+ return false;
+}
+
+int CGUIVisualisationControl::GetActivePreset()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetActivePreset();
+
+ return -1;
+}
+
+void CGUIVisualisationControl::SetPreset(int idx)
+{
+ if (m_instance && m_alreadyStarted)
+ m_instance->LoadPreset(idx);
+}
+
+std::string CGUIVisualisationControl::GetActivePresetName()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetActivePresetName();
+
+ return "";
+}
+
+bool CGUIVisualisationControl::GetPresetList(std::vector<std::string>& vecpresets)
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetPresetList(vecpresets);
+
+ return false;
+}
+
+bool CGUIVisualisationControl::InitVisualization()
+{
+ IAE* ae = CServiceBroker::GetActiveAE();
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!ae || !winSystem)
+ return false;
+
+ const std::string addon = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_MUSICPLAYER_VISUALISATION);
+ const ADDON::AddonInfoPtr addonBase =
+ CServiceBroker::GetAddonMgr().GetAddonInfo(addon, ADDON::AddonType::VISUALIZATION);
+ if (!addonBase)
+ return false;
+
+ ae->RegisterAudioCallback(this);
+
+ auto& context = winSystem->GetGfxContext();
+
+ context.CaptureStateBlock();
+
+ float x = context.ScaleFinalXCoord(GetXPosition(), GetYPosition());
+ float y = context.ScaleFinalYCoord(GetXPosition(), GetYPosition());
+ float w = context.ScaleFinalXCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - x;
+ float h = context.ScaleFinalYCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - y;
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+ if (x + w > context.GetWidth())
+ w = context.GetWidth() - x;
+ if (y + h > context.GetHeight())
+ h = context.GetHeight() - y;
+
+ m_instance.reset(new KODI::ADDONS::CVisualization(addonBase, x, y, w, h));
+ CreateBuffers();
+
+ m_alreadyStarted = false;
+ context.ApplyStateBlock();
+ return true;
+}
+
+void CGUIVisualisationControl::DeInitVisualization()
+{
+ if (!m_attemptedLoad)
+ return;
+
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->UnregisterAudioCallback(this);
+
+ m_attemptedLoad = false;
+
+ CGUIMessage msg(GUI_MSG_VISUALISATION_UNLOADING, m_controlID, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+
+ CLog::Log(LOGDEBUG, "FreeVisualisation() started");
+
+ if (m_instance)
+ {
+ if (m_alreadyStarted)
+ {
+ auto& context = winSystem->GetGfxContext();
+
+ context.CaptureStateBlock();
+ m_instance->Stop();
+ context.ApplyStateBlock();
+ m_alreadyStarted = false;
+ }
+
+ m_instance.reset();
+ }
+
+ ClearBuffers();
+}
+
+void CGUIVisualisationControl::CreateBuffers()
+{
+ ClearBuffers();
+
+ m_numBuffers = 1;
+ if (m_instance)
+ m_numBuffers += m_instance->GetSyncDelay();
+ if (m_numBuffers > MAX_AUDIO_BUFFERS)
+ m_numBuffers = MAX_AUDIO_BUFFERS;
+ if (m_numBuffers < 1)
+ m_numBuffers = 1;
+}
+
+void CGUIVisualisationControl::ClearBuffers()
+{
+ m_numBuffers = 0;
+ m_vecBuffers.clear();
+}
diff --git a/xbmc/guilib/GUIVisualisationControl.dox b/xbmc/guilib/GUIVisualisationControl.dox
new file mode 100644
index 0000000..256b684
--- /dev/null
+++ b/xbmc/guilib/GUIVisualisationControl.dox
@@ -0,0 +1,42 @@
+/*!
+
+\page Visualisation_Control Visualisation Control
+\brief **Used to display a visualisation while music is playing.**
+
+\tableofcontents
+
+The visualisation control is used for displaying those funky patterns that jump
+to the music in Kodi. You can choose the position, and size of the visualisation
+displayed. Note that the control is only rendered if music is being played.
+
+
+--------------------------------------------------------------------------------
+\section Visualisation_Control_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="visualisation" id ="3">
+ <description>My first visualisation control</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+</control>
+~~~~~~~~~~~~~
+
+
+--------------------------------------------------------------------------------
+\section Visualisation_Control_sect2 Available tags
+Only the [default control](http://kodi.wiki/view/Default_Control_Tags) tags are
+applicable to this control.
+
+
+--------------------------------------------------------------------------------
+\section Visualisation_Control_sect3 See also
+
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIVisualisationControl.h b/xbmc/guilib/GUIVisualisationControl.h
new file mode 100644
index 0000000..5d674aa
--- /dev/null
+++ b/xbmc/guilib/GUIVisualisationControl.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+#include "cores/AudioEngine/Interfaces/IAudioCallback.h"
+
+#include <list>
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace ADDONS
+{
+class CVisualization;
+} // namespace ADDONS
+} // namespace KODI
+
+class CAudioBuffer
+{
+public:
+ explicit CAudioBuffer(int iSize);
+ virtual ~CAudioBuffer();
+ const float* Get() const;
+ int Size() const;
+ void Set(const float* psBuffer, int iSize);
+
+private:
+ CAudioBuffer(const CAudioBuffer&) = delete;
+ CAudioBuffer& operator=(const CAudioBuffer&) = delete;
+ CAudioBuffer();
+ float* m_pBuffer;
+ int m_iLen;
+};
+
+class CGUIVisualisationControl : public CGUIControl, public IAudioCallback
+{
+public:
+ CGUIVisualisationControl(
+ int parentID, int controlID, float posX, float posY, float width, float height);
+ CGUIVisualisationControl(const CGUIVisualisationControl& from);
+ CGUIVisualisationControl* Clone() const override
+ {
+ return new CGUIVisualisationControl(*this);
+ }; //! @todo check for naughties
+
+ // Child functions related to IAudioCallback
+ void OnInitialize(int channels, int samplesPerSec, int bitsPerSample) override;
+ void OnAudioData(const float* audioData, unsigned int audioDataLength) override;
+
+ // Child functions related to CGUIControl
+ void FreeResources(bool immediately = false) override;
+ void Process(unsigned int currentTime, CDirtyRegionList& dirtyregions) override;
+ void Render() override;
+ void UpdateVisibility(const CGUIListItem* item = nullptr) override;
+ bool OnAction(const CAction& action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ bool CanFocus() const override { return false; }
+ bool CanFocusFromPoint(const CPoint& point) const override;
+
+ std::string Name();
+ void UpdateTrack();
+ bool HasPresets();
+ void SetPreset(int idx);
+ bool IsLocked();
+ int GetActivePreset();
+ std::string GetActivePresetName();
+ bool GetPresetList(std::vector<std::string>& vecpresets);
+
+private:
+ bool InitVisualization();
+ void DeInitVisualization();
+ inline void CreateBuffers();
+ inline void ClearBuffers();
+
+ bool m_callStart{false};
+ bool m_alreadyStarted{false};
+ bool m_attemptedLoad{false};
+ bool m_updateTrack{false};
+
+ std::list<std::unique_ptr<CAudioBuffer>> m_vecBuffers;
+ unsigned int m_numBuffers; /*!< Number of Audio buffers */
+ std::vector<std::string> m_presets; /*!< cached preset list */
+
+ /* values set from "OnInitialize" IAudioCallback */
+ int m_channels;
+ int m_samplesPerSec;
+ int m_bitsPerSample;
+
+ std::string m_albumThumb; /*!< track information */
+ std::string m_name; /*!< To add-on sended name */
+ std::string m_presetsPath; /*!< To add-on sended preset path */
+ std::string m_profilePath; /*!< To add-on sended profile path */
+
+ std::unique_ptr<KODI::ADDONS::CVisualization> m_instance;
+};
diff --git a/xbmc/guilib/GUIWindow.cpp b/xbmc/guilib/GUIWindow.cpp
new file mode 100644
index 0000000..a7aa0fe
--- /dev/null
+++ b/xbmc/guilib/GUIWindow.cpp
@@ -0,0 +1,1089 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWindow.h"
+
+#include "GUIAudioManager.h"
+#include "GUIComponent.h"
+#include "GUIControlFactory.h"
+#include "GUIControlGroup.h"
+#include "GUIControlProfiler.h"
+#include "GUIInfoManager.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "addons/Skin.h"
+#include "input/Key.h"
+#include "input/WindowTranslator.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "utils/ColorUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/Variant.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+bool CGUIWindow::icompare::operator()(const std::string &s1, const std::string &s2) const
+{
+ return StringUtils::CompareNoCase(s1, s2) < 0;
+}
+
+CGUIWindow::CGUIWindow(int id, const std::string &xmlFile)
+{
+ CGUIWindow::SetID(id);
+ SetProperty("xmlfile", xmlFile);
+ m_lastControlID = 0;
+ m_needsScaling = true;
+ m_windowLoaded = false;
+ m_loadType = LOAD_EVERY_TIME;
+ m_closing = false;
+ m_active = false;
+ m_renderOrder = RENDER_ORDER_WINDOW;
+ m_dynamicResourceAlloc = true;
+ m_previousWindow = WINDOW_INVALID;
+ m_animationsEnabled = true;
+ m_manualRunActions = false;
+ m_exclusiveMouseControl = 0;
+ m_clearBackground = 0xff000000; // opaque black -> always clear
+ m_menuControlID = 0;
+ m_menuLastFocusedControlID = 0;
+ m_custom = false;
+}
+
+CGUIWindow::~CGUIWindow()
+{
+}
+
+bool CGUIWindow::Load(const std::string& strFileName, bool bContainsPath)
+{
+ if (m_windowLoaded || !g_SkinInfo)
+ return true; // no point loading if it's already there
+
+#ifdef _DEBUG
+ const auto start = std::chrono::steady_clock::now();
+#endif
+
+ const char* strLoadType;
+ switch (m_loadType)
+ {
+ case LOAD_ON_GUI_INIT:
+ strLoadType = "LOAD_ON_GUI_INIT";
+ break;
+ case KEEP_IN_MEMORY:
+ strLoadType = "KEEP_IN_MEMORY";
+ break;
+ case LOAD_EVERY_TIME:
+ default:
+ strLoadType = "LOAD_EVERY_TIME";
+ break;
+ }
+ CLog::Log(LOGINFO, "Loading skin file: {}, load type: {}", strFileName, strLoadType);
+
+ // Find appropriate skin folder + resolution to load from
+ std::string strPath;
+ std::string strLowerPath;
+ if (bContainsPath)
+ strPath = strFileName;
+ else
+ {
+ // FIXME: strLowerPath needs to eventually go since resToUse can get incorrectly overridden
+ std::string strFileNameLower = strFileName;
+ StringUtils::ToLower(strFileNameLower);
+ strLowerPath = g_SkinInfo->GetSkinPath(strFileNameLower, &m_coordsRes);
+ strPath = g_SkinInfo->GetSkinPath(strFileName, &m_coordsRes);
+ }
+
+ bool ret = LoadXML(strPath, strLowerPath);
+ if (ret)
+ {
+ m_windowLoaded = true;
+ OnWindowLoaded();
+
+#ifdef _DEBUG
+ const auto end = std::chrono::steady_clock::now();
+ const std::chrono::duration<double, std::milli> duration = end - start;
+ CLog::Log(LOGDEBUG, "Skin file {} loaded in {:.2f} ms", strPath, duration.count());
+#endif
+ }
+
+ return ret;
+}
+
+bool CGUIWindow::LoadXML(const std::string &strPath, const std::string &strLowerPath)
+{
+ // load window xml if we don't have it stored yet
+ if (!m_windowXMLRootElement)
+ {
+ CXBMCTinyXML xmlDoc;
+ std::string strPathLower = strPath;
+ StringUtils::ToLower(strPathLower);
+ if (!xmlDoc.LoadFile(strPath) && !xmlDoc.LoadFile(strPathLower) && !xmlDoc.LoadFile(strLowerPath))
+ {
+ CLog::Log(LOGERROR, "Unable to load window XML: {}. Line {}\n{}", strPath, xmlDoc.ErrorRow(),
+ xmlDoc.ErrorDesc());
+ SetID(WINDOW_INVALID);
+ return false;
+ }
+
+ // xml need a <window> root element
+ if (!StringUtils::EqualsNoCase(xmlDoc.RootElement()->Value(), "window"))
+ {
+ CLog::Log(LOGERROR, "XML file {} does not contain a <window> root element",
+ GetProperty("xmlfile").asString());
+ return false;
+ }
+
+ // store XML for further processing if window's load type is LOAD_EVERY_TIME or a reload is needed
+ m_windowXMLRootElement.reset(static_cast<TiXmlElement*>(xmlDoc.RootElement()->Clone()));
+ }
+ else
+ CLog::Log(LOGDEBUG, "Using already stored xml root node for {}", strPath);
+
+ return Load(Prepare(m_windowXMLRootElement).get());
+}
+
+std::unique_ptr<TiXmlElement> CGUIWindow::Prepare(const std::unique_ptr<TiXmlElement>& rootElement)
+{
+ if (!rootElement)
+ return nullptr;
+
+ // copy the root element as we will manipulate it
+ auto preparedRoot = std::make_unique<TiXmlElement>(*rootElement);
+
+ // Resolve any includes, constants, expressions that may be present
+ // and save include's conditions to the given map
+ g_SkinInfo->ResolveIncludes(preparedRoot.get(), &m_xmlIncludeConditions);
+
+ return preparedRoot;
+}
+
+bool CGUIWindow::Load(TiXmlElement *pRootElement)
+{
+ if (!pRootElement)
+ return false;
+
+ // set the scaling resolution so that any control creation or initialisation can
+ // be done with respect to the correct aspect ratio
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
+
+ // now load in the skin file
+ SetDefaults();
+
+ CGUIControlFactory::GetInfoColor(pRootElement, "backgroundcolor", m_clearBackground, GetID());
+ CGUIControlFactory::GetActions(pRootElement, "onload", m_loadActions);
+ CGUIControlFactory::GetActions(pRootElement, "onunload", m_unloadActions);
+ CRect parentRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
+ CGUIControlFactory::GetHitRect(pRootElement, m_hitRect, parentRect);
+
+ TiXmlElement *pChild = pRootElement->FirstChildElement();
+ while (pChild)
+ {
+ std::string strValue = pChild->Value();
+ if (strValue == "previouswindow" && pChild->FirstChild())
+ {
+ m_previousWindow = CWindowTranslator::TranslateWindow(pChild->FirstChild()->Value());
+ }
+ else if (strValue == "defaultcontrol" && pChild->FirstChild())
+ {
+ const char *always = pChild->Attribute("always");
+ if (always && StringUtils::EqualsNoCase(always, "true"))
+ m_defaultAlways = true;
+ m_defaultControl = atoi(pChild->FirstChild()->Value());
+ }
+ else if(strValue == "menucontrol" && pChild->FirstChild())
+ {
+ m_menuControlID = atoi(pChild->FirstChild()->Value());
+ }
+ else if (strValue == "visible" && pChild->FirstChild())
+ {
+ std::string condition;
+ CGUIControlFactory::GetConditionalVisibility(pRootElement, condition);
+ m_visibleCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, GetID());
+ }
+ else if (strValue == "animation" && pChild->FirstChild())
+ {
+ CRect rect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
+ CAnimation anim;
+ anim.Create(pChild, rect, GetID());
+ m_animations.push_back(anim);
+ }
+ else if (strValue == "zorder" && pChild->FirstChild())
+ {
+ m_renderOrder = atoi(pChild->FirstChild()->Value());
+ }
+ else if (strValue == "coordinates")
+ {
+ XMLUtils::GetFloat(pChild, "posx", m_posX);
+ XMLUtils::GetFloat(pChild, "posy", m_posY);
+ XMLUtils::GetFloat(pChild, "left", m_posX);
+ XMLUtils::GetFloat(pChild, "top", m_posY);
+
+ TiXmlElement *originElement = pChild->FirstChildElement("origin");
+ while (originElement)
+ {
+ COrigin origin;
+ origin.x = CGUIControlFactory::ParsePosition(originElement->Attribute("x"), static_cast<float>(m_coordsRes.iWidth));
+ origin.y = CGUIControlFactory::ParsePosition(originElement->Attribute("y"), static_cast<float>(m_coordsRes.iHeight));
+ if (originElement->FirstChild())
+ origin.condition = CServiceBroker::GetGUI()->GetInfoManager().Register(originElement->FirstChild()->Value(), GetID());
+ m_origins.push_back(origin);
+ originElement = originElement->NextSiblingElement("origin");
+ }
+ }
+ else if (strValue == "camera")
+ { // z is fixed
+ m_camera.x = CGUIControlFactory::ParsePosition(pChild->Attribute("x"), static_cast<float>(m_coordsRes.iWidth));
+ m_camera.y = CGUIControlFactory::ParsePosition(pChild->Attribute("y"), static_cast<float>(m_coordsRes.iHeight));
+ m_hasCamera = true;
+ }
+ else if (strValue == "depth" && pChild->FirstChild())
+ {
+ float stereo = static_cast<float>(atof(pChild->FirstChild()->Value()));
+ m_stereo = std::max(-1.f, std::min(1.f, stereo));
+ }
+ else if (strValue == "controls")
+ {
+ TiXmlElement *pControl = pChild->FirstChildElement();
+ while (pControl)
+ {
+ if (StringUtils::EqualsNoCase(pControl->Value(), "control"))
+ {
+ LoadControl(pControl, nullptr, CRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight)));
+ }
+ pControl = pControl->NextSiblingElement();
+ }
+ }
+
+ pChild = pChild->NextSiblingElement();
+ }
+
+ return true;
+}
+
+void CGUIWindow::LoadControl(TiXmlElement* pControl, CGUIControlGroup *pGroup, const CRect &rect)
+{
+ // get control type
+ CGUIControlFactory factory;
+
+ CGUIControl* pGUIControl = factory.Create(GetID(), rect, pControl);
+ if (pGUIControl)
+ {
+ float maxX = pGUIControl->GetXPosition() + pGUIControl->GetWidth();
+ if (maxX > m_width)
+ {
+ m_width = maxX;
+ }
+
+ float maxY = pGUIControl->GetYPosition() + pGUIControl->GetHeight();
+ if (maxY > m_height)
+ {
+ m_height = maxY;
+ }
+ // if we are in a group, add to the group, else add to our window
+ if (pGroup)
+ pGroup->AddControl(pGUIControl);
+ else
+ AddControl(pGUIControl);
+ // if the new control is a group, then add it's controls
+ if (pGUIControl->IsGroup())
+ {
+ CGUIControlGroup *grp = static_cast<CGUIControlGroup*>(pGUIControl);
+ TiXmlElement *pSubControl = pControl->FirstChildElement("control");
+ CRect grpRect(grp->GetXPosition(), grp->GetYPosition(),
+ grp->GetXPosition() + grp->GetWidth(), grp->GetYPosition() + grp->GetHeight());
+ while (pSubControl)
+ {
+ LoadControl(pSubControl, grp, grpRect);
+ pSubControl = pSubControl->NextSiblingElement("control");
+ }
+ }
+ }
+}
+
+void CGUIWindow::OnWindowLoaded()
+{
+ DynamicResourceAlloc(true);
+}
+
+void CGUIWindow::CenterWindow()
+{
+ m_posX = (m_coordsRes.iWidth - GetWidth()) / 2;
+ m_posY = (m_coordsRes.iHeight - GetHeight()) / 2;
+}
+
+void CGUIWindow::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (!IsControlDirty() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiSmartRedraw)
+ return;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling);
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform();
+ CGUIControlGroup::DoProcess(currentTime, dirtyregions);
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+
+ // check if currently focused control can have it
+ // and fallback to default control if not
+ CGUIControl* focusedControl = GetFocusedControl();
+ if (focusedControl && !focusedControl->CanFocus())
+ SET_CONTROL_FOCUS(m_defaultControl, 0);
+}
+
+void CGUIWindow::DoRender()
+{
+ // If we're rendering from a different thread, then we should wait for the main
+ // app thread to finish AllocResources(), as dynamic resources (images in particular)
+ // will try and be allocated from 2 different threads, which causes nasty things
+ // to occur.
+ if (!m_bAllocated) return;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_coordsRes, m_needsScaling);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform();
+ CGUIControlGroup::DoRender();
+ CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
+
+ if (CGUIControlProfiler::IsRunning()) CGUIControlProfiler::Instance().EndFrame();
+}
+
+void CGUIWindow::AfterRender()
+{
+ // Check to see if we should close at this point
+ // We check after the controls have finished rendering, as we may have to close due to
+ // the controls rendering after the window has finished it's animation
+ // we call the base class instead of this class so that we can find the change
+ if (m_closing && !CGUIControlGroup::IsAnimating(ANIM_TYPE_WINDOW_CLOSE))
+ Close(true);
+
+}
+
+void CGUIWindow::Close_Internal(bool forceClose /*= false*/, int nextWindowID /*= 0*/, bool enableSound /*= true*/)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ if (!m_active)
+ return;
+
+ forceClose |= (nextWindowID == WINDOW_FULLSCREEN_VIDEO ||
+ nextWindowID == WINDOW_FULLSCREEN_GAME);
+
+ if (!forceClose && HasAnimation(ANIM_TYPE_WINDOW_CLOSE))
+ {
+ if (!m_closing)
+ {
+ if (enableSound && IsSoundEnabled())
+ CServiceBroker::GetGUI()->GetAudioManager().PlayWindowSound(GetID(), SOUND_DEINIT);
+
+ // Perform the window out effect
+ QueueAnimation(ANIM_TYPE_WINDOW_CLOSE);
+ m_closing = true;
+ }
+ return;
+ }
+
+ m_closing = false;
+ CGUIMessage msg(GUI_MSG_WINDOW_DEINIT, 0, 0, nextWindowID);
+ OnMessage(msg);
+}
+
+void CGUIWindow::Close(bool forceClose /*= false*/, int nextWindowID /*= 0*/, bool enableSound /*= true*/, bool bWait /* = true */)
+{
+ if (!CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ // make sure graphics lock is not held
+ CSingleExit leaveIt(CServiceBroker::GetWinSystem()->GetGfxContext());
+ int param2 = (forceClose ? 0x01 : 0) | (enableSound ? 0x02 : 0);
+ if (bWait)
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_WINDOW_CLOSE, nextWindowID, param2,
+ static_cast<void*>(this));
+ else
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_WINDOW_CLOSE, nextWindowID, param2,
+ static_cast<void*>(this));
+ }
+ else
+ Close_Internal(forceClose, nextWindowID, enableSound);
+}
+
+bool CGUIWindow::OnAction(const CAction &action)
+{
+ if (action.IsMouse() || action.IsGesture())
+ return EVENT_RESULT_UNHANDLED != OnMouseAction(action);
+
+ CGUIControl *focusedControl = GetFocusedControl();
+ if (focusedControl)
+ {
+ if (focusedControl->OnAction(action))
+ return true;
+ }
+ else
+ {
+ // no control has focus?
+ // set focus to the default control then
+ CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), m_defaultControl);
+ OnMessage(msg);
+ }
+
+ // default implementations
+ switch(action.GetID())
+ {
+ case ACTION_NAV_BACK:
+ case ACTION_PREVIOUS_MENU:
+ return OnBack(action.GetID());
+ case ACTION_SHOW_INFO:
+ return OnInfo(action.GetID());
+ case ACTION_MENU:
+ if (m_menuControlID > 0)
+ {
+ CGUIControl *menu = GetControl(m_menuControlID);
+ if (menu)
+ {
+ int focusControlId;
+ if (!menu->HasFocus())
+ {
+ // focus the menu control
+ focusControlId = m_menuControlID;
+ // To support a toggle behaviour we store the last focused control id
+ // to restore (focus) this control if the menu control has the focus
+ // while you press the menu button again.
+ m_menuLastFocusedControlID = GetFocusedControlID();
+ }
+ else
+ {
+ // restore the last focused control or if not exists use the default control
+ focusControlId = m_menuLastFocusedControlID > 0 ? m_menuLastFocusedControlID : m_defaultControl;
+ }
+
+ CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS, GetID(), focusControlId);
+ return OnMessage(msg);
+ }
+ }
+ break;
+ }
+
+ return false;
+}
+
+CPoint CGUIWindow::GetPosition() const
+{
+ for (unsigned int i = 0; i < m_origins.size(); i++)
+ {
+ // no condition implies true
+ if (!m_origins[i].condition || m_origins[i].condition->Get(INFO::DEFAULT_CONTEXT))
+ { // found origin
+ return CPoint(m_origins[i].x, m_origins[i].y);
+ }
+ }
+ return CGUIControlGroup::GetPosition();
+}
+
+// OnMouseAction - called by OnAction()
+EVENT_RESULT CGUIWindow::OnMouseAction(const CAction &action)
+{
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
+ CPoint mousePoint(action.GetAmount(0), action.GetAmount(1));
+ CServiceBroker::GetWinSystem()->GetGfxContext().InvertFinalCoords(mousePoint.x, mousePoint.y);
+
+ // create the mouse event
+ CMouseEvent event(action.GetID(), action.GetHoldTime(), action.GetAmount(2), action.GetAmount(3));
+ if (m_exclusiveMouseControl)
+ {
+ CGUIControl *child = GetControl(m_exclusiveMouseControl);
+ if (child)
+ {
+ CPoint renderPos = child->GetRenderPosition() - CPoint(child->GetXPosition(), child->GetYPosition());
+ return child->OnMouseEvent(mousePoint - renderPos, event);
+ }
+ }
+
+ UnfocusFromPoint(mousePoint);
+
+ return SendMouseEvent(mousePoint, event);
+}
+
+EVENT_RESULT CGUIWindow::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_RIGHT_CLICK)
+ { // no control found to absorb this click - go to previous menu
+ return OnAction(CAction(ACTION_PREVIOUS_MENU)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+/// \brief Called on window open.
+/// * Restores the control state(s)
+/// * Sets initial visibility of controls
+/// * Queue WindowOpen animation
+/// * Set overlay state
+/// Override this function and do any window-specific initialisation such
+/// as filling control contents and setting control focus before
+/// calling the base method.
+void CGUIWindow::OnInitWindow()
+{
+ // Play the window specific init sound
+ if (IsSoundEnabled())
+ CServiceBroker::GetGUI()->GetAudioManager().PlayWindowSound(GetID(), SOUND_INIT);
+
+ // set our rendered state
+ m_hasProcessed = false;
+ m_closing = false;
+ m_active = true;
+ ResetAnimations(); // we need to reset our animations as those windows that don't dynamically allocate
+ // need their anims reset. An alternative solution is turning off all non-dynamic
+ // allocation (which in some respects may be nicer, but it kills hdd spindown and the like)
+
+ // set our initial control visibility before restoring control state and
+ // focusing the default control, and again afterward to make sure that
+ // any controls that depend on the state of the focused control (and or on
+ // control states) are active.
+ SetInitialVisibility();
+ RestoreControlStates();
+ SetInitialVisibility();
+ QueueAnimation(ANIM_TYPE_WINDOW_OPEN);
+
+ if (!m_manualRunActions)
+ {
+ RunLoadActions();
+ }
+}
+
+// Called on window close.
+// * Saves control state(s)
+// Override this function and call the base class before doing any dynamic memory freeing
+void CGUIWindow::OnDeinitWindow(int nextWindowID)
+{
+ if (!m_manualRunActions)
+ {
+ RunUnloadActions();
+ }
+
+ SaveControlStates();
+ m_active = false;
+}
+
+bool CGUIWindow::OnMessage(CGUIMessage& message)
+{
+ switch ( message.GetMessage() )
+ {
+ case GUI_MSG_WINDOW_LOAD:
+ {
+ Initialize();
+ return true;
+ }
+ break;
+
+ case GUI_MSG_WINDOW_INIT:
+ {
+ CLog::Log(LOGDEBUG, "------ Window Init ({}) ------", GetProperty("xmlfile").asString());
+ if (m_dynamicResourceAlloc || !m_bAllocated) AllocResources(false);
+ OnInitWindow();
+ return true;
+ }
+ break;
+
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ CLog::Log(LOGDEBUG, "------ Window Deinit ({}) ------", GetProperty("xmlfile").asString());
+ OnDeinitWindow(message.GetParam1());
+ // now free the window
+ if (m_dynamicResourceAlloc) FreeResources();
+ return true;
+ }
+ break;
+
+ case GUI_MSG_UNFOCUS_ALL:
+ {
+ //unfocus the current focused control in this window
+ CGUIControl *control = GetFocusedControl();
+ if(control)
+ {
+ //tell focused control that it has lost the focus
+ CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), control->GetID(), control->GetID());
+ control->OnMessage(msgLostFocus);
+ CLog::Log(LOGDEBUG, "Unfocus WindowID: {}, ControlID: {}", GetID(), control->GetID());
+ }
+ return true;
+ }
+
+ case GUI_MSG_FOCUSED:
+ { // a control has been focused
+ if (HasID(message.GetSenderId()))
+ {
+ m_focusedControl = message.GetControlId();
+ // We ensure that all others childrens have focus disabled,
+ // this can happen when one control overlap the other one in the same
+ // coordinates with similar sizes and the mouse pointer is over them
+ // in this case only the control in the highest layer will have the focus
+ for (CGUIControl* control : m_children)
+ {
+ if (control->GetID() != m_focusedControl)
+ control->SetFocus(false);
+ }
+ return true;
+ }
+ break;
+ }
+ case GUI_MSG_LOSTFOCUS:
+ {
+ // nothing to do at the window level when we lose focus
+ return true;
+ }
+ case GUI_MSG_MOVE:
+ {
+ if (HasID(message.GetSenderId()))
+ return OnMove(message.GetControlId(), message.GetParam1());
+ break;
+ }
+ case GUI_MSG_SETFOCUS:
+ {
+ // CLog::Log(LOGDEBUG,"set focus to control:{} window:{} ({})", message.GetControlId(),message.GetSenderId(), GetID());
+ if ( message.GetControlId() )
+ {
+ // first unfocus the current control
+ CGUIControl *control = GetFocusedControl();
+ if (control)
+ {
+ CGUIMessage msgLostFocus(GUI_MSG_LOSTFOCUS, GetID(), control->GetID(), message.GetControlId());
+ control->OnMessage(msgLostFocus);
+ }
+
+ // get the control to focus
+ CGUIControl* pFocusedControl = GetFirstFocusableControl(message.GetControlId());
+ if (!pFocusedControl) pFocusedControl = GetControl(message.GetControlId());
+
+ // and focus it
+ if (pFocusedControl)
+ return pFocusedControl->OnMessage(message);
+ }
+ return true;
+ }
+ break;
+ case GUI_MSG_EXCLUSIVE_MOUSE:
+ {
+ m_exclusiveMouseControl = message.GetSenderId();
+ return true;
+ }
+ break;
+ case GUI_MSG_GESTURE_NOTIFY:
+ {
+ CAction action(ACTION_GESTURE_NOTIFY, 0, static_cast<float>(message.GetParam1()), static_cast<float>(message.GetParam2()), 0, 0);
+ EVENT_RESULT result = OnMouseAction(action);
+ auto res = new int(result);
+ message.SetPointer(static_cast<void*>(res));
+ return result != EVENT_RESULT_UNHANDLED;
+ }
+ case GUI_MSG_ADD_CONTROL:
+ {
+ if (message.GetPointer())
+ {
+ CGUIControl *control = static_cast<CGUIControl*>(message.GetPointer());
+ control->AllocResources();
+ AddControl(control);
+ }
+ return true;
+ }
+ case GUI_MSG_REMOVE_CONTROL:
+ {
+ if (message.GetPointer())
+ {
+ CGUIControl *control = static_cast<CGUIControl*>(message.GetPointer());
+ RemoveControl(control);
+ control->FreeResources(true);
+ delete control;
+ }
+ return true;
+ }
+ case GUI_MSG_NOTIFY_ALL:
+ {
+ // only process those notifications that come from this window, or those intended for every window
+ if (HasID(message.GetSenderId()) || !message.GetSenderId())
+ {
+ if (message.GetParam1() == GUI_MSG_PAGE_CHANGE ||
+ message.GetParam1() == GUI_MSG_REFRESH_THUMBS ||
+ message.GetParam1() == GUI_MSG_REFRESH_LIST ||
+ message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
+ { // alter the message accordingly, and send to all controls
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ {
+ CGUIControl *control = *it;
+ CGUIMessage msg(message.GetParam1(), message.GetControlId(), control->GetID(), message.GetParam2());
+ control->OnMessage(msg);
+ }
+ }
+ else if (message.GetParam1() == GUI_MSG_STATE_CHANGED)
+ MarkDirtyRegion(DIRTY_STATE_CHILD); //Don't force an dirtyRect, we don't know if / what has changed.
+ }
+ }
+ break;
+ }
+
+ return CGUIControlGroup::OnMessage(message);
+}
+
+bool CGUIWindow::NeedLoad() const
+{
+ return !m_windowLoaded || CServiceBroker::GetGUI()->GetInfoManager().ConditionsChangedValues(m_xmlIncludeConditions);
+}
+
+void CGUIWindow::AllocResources(bool forceLoad /*= false */)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+#ifdef _DEBUG
+ const auto start = std::chrono::steady_clock::now();
+#endif
+ // use forceLoad to determine if window needs (re)loading
+ forceLoad |= NeedLoad() || (m_loadType == LOAD_EVERY_TIME);
+
+ // if window is loaded and load is forced we have to free window resources first
+ if (m_windowLoaded && forceLoad)
+ FreeResources(true);
+
+ if (forceLoad)
+ {
+ std::string xmlFile = GetProperty("xmlfile").asString();
+ if (xmlFile.size())
+ {
+ bool bHasPath =
+ xmlFile.find('\\') != std::string::npos || xmlFile.find('/') != std::string::npos;
+ Load(xmlFile, bHasPath);
+ }
+ }
+
+#ifdef _DEBUG
+ const auto skinLoadEnd = std::chrono::steady_clock::now();
+#endif
+
+ // and now allocate resources
+ CGUIControlGroup::AllocResources();
+
+#ifdef _DEBUG
+ const auto end = std::chrono::steady_clock::now();
+ const std::chrono::duration<double, std::milli> skinLoadDuration = skinLoadEnd - start;
+ const std::chrono::duration<double, std::milli> duration = end - start;
+
+ if (forceLoad)
+ CLog::Log(LOGDEBUG, "Alloc resources: {:.2f} ms ({:.2f} ms skin load)", duration.count(),
+ skinLoadDuration.count());
+ else
+ {
+ CLog::Log(LOGDEBUG, "Window {} was already loaded", GetProperty("xmlfile").asString());
+ CLog::Log(LOGDEBUG, "Alloc resources: {:.2f} ms", duration.count());
+ }
+#endif
+ m_bAllocated = true;
+}
+
+void CGUIWindow::FreeResources(bool forceUnload /*= false */)
+{
+ m_bAllocated = false;
+ CGUIControlGroup::FreeResources();
+ //CServiceBroker::GetGUI()->GetTextureManager().Dump();
+ // unload the skin
+ if (m_loadType == LOAD_EVERY_TIME || forceUnload) ClearAll();
+ if (forceUnload)
+ {
+ m_windowXMLRootElement.reset();
+ m_xmlIncludeConditions.clear();
+ }
+}
+
+void CGUIWindow::DynamicResourceAlloc(bool bOnOff)
+{
+ m_dynamicResourceAlloc = bOnOff;
+ CGUIControlGroup::DynamicResourceAlloc(bOnOff);
+}
+
+void CGUIWindow::ClearAll()
+{
+ OnWindowUnload();
+ CGUIControlGroup::ClearAll();
+ m_windowLoaded = false;
+ m_dynamicResourceAlloc = true;
+ m_visibleCondition.reset();
+}
+
+bool CGUIWindow::Initialize()
+{
+ if (!CServiceBroker::GetGUI()->GetWindowManager().Initialized())
+ return false;
+ if (!NeedLoad())
+ return true;
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ AllocResources(false);
+ else
+ {
+ // if not app thread, send gui msg via app messenger
+ // and wait for results, so windowLoaded flag would be updated
+ CGUIMessage msg(GUI_MSG_WINDOW_LOAD, 0, 0);
+ CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, GetID(), true);
+ }
+ return m_windowLoaded;
+}
+
+void CGUIWindow::SetInitialVisibility()
+{
+ // reset our info manager caches
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ infoMgr.ResetCache();
+ infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache();
+ CGUIControlGroup::SetInitialVisibility();
+}
+
+bool CGUIWindow::IsActive() const
+{
+ return CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(GetID());
+}
+
+bool CGUIWindow::CheckAnimation(ANIMATION_TYPE animType)
+{
+ // special cases first
+ if (animType == ANIM_TYPE_WINDOW_CLOSE)
+ {
+ if (!m_bAllocated || !HasProcessed()) // can't process an animation if we aren't allocated or haven't processed
+ return false;
+ // make sure we update our visibility prior to queuing the window close anim
+ for (unsigned int i = 0; i < m_children.size(); i++)
+ m_children[i]->UpdateVisibility(nullptr);
+ }
+ return true;
+}
+
+bool CGUIWindow::IsAnimating(ANIMATION_TYPE animType)
+{
+ if (!m_animationsEnabled)
+ return false;
+ if (animType == ANIM_TYPE_WINDOW_CLOSE)
+ return m_closing;
+ return CGUIControlGroup::IsAnimating(animType);
+}
+
+bool CGUIWindow::Animate(unsigned int currentTime)
+{
+ if (m_animationsEnabled)
+ return CGUIControlGroup::Animate(currentTime);
+ else
+ {
+ m_transform.Reset();
+ return false;
+ }
+}
+
+void CGUIWindow::DisableAnimations()
+{
+ m_animationsEnabled = false;
+}
+
+// returns true if the control group with id groupID has controlID as
+// its focused control
+bool CGUIWindow::ControlGroupHasFocus(int groupID, int controlID)
+{
+ // 1. Run through and get control with groupID (assume unique)
+ // 2. Get it's selected item.
+ CGUIControl *group = GetFirstFocusableControl(groupID);
+ if (!group) group = GetControl(groupID);
+
+ if (group && group->IsGroup())
+ {
+ if (controlID == 0)
+ { // just want to know if the group is focused
+ return group->HasFocus();
+ }
+ else
+ {
+ CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), group->GetID());
+ group->OnMessage(message);
+ return (controlID == message.GetParam1());
+ }
+ }
+ return false;
+}
+
+void CGUIWindow::SaveControlStates()
+{
+ ResetControlStates();
+ if (!m_defaultAlways)
+ m_lastControlID = GetFocusedControlID();
+ for (iControls it = m_children.begin(); it != m_children.end(); ++it)
+ (*it)->SaveStates(m_controlStates);
+}
+
+void CGUIWindow::RestoreControlStates()
+{
+ for (const auto& it : m_controlStates)
+ {
+ CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), it.m_id, it.m_data);
+ OnMessage(message);
+ }
+ int focusControl = (!m_defaultAlways && m_lastControlID) ? m_lastControlID : m_defaultControl;
+ SET_CONTROL_FOCUS(focusControl, 0);
+}
+
+void CGUIWindow::ResetControlStates()
+{
+ m_lastControlID = 0;
+ m_focusedControl = 0;
+ m_controlStates.clear();
+}
+
+bool CGUIWindow::OnBack(int actionID)
+{
+ CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
+ return true;
+}
+
+bool CGUIWindow::OnMove(int fromControl, int moveAction)
+{
+ const CGUIControl *control = GetFirstFocusableControl(fromControl);
+ if (!control) control = GetControl(fromControl);
+ if (!control)
+ { // no current control??
+ CLog::Log(LOGERROR, "Unable to find control {} in window {}", fromControl, GetID());
+ return false;
+ }
+ std::vector<int> moveHistory;
+ int nextControl = fromControl;
+ while (control)
+ { // grab the next control direction
+ moveHistory.push_back(nextControl);
+ CGUIAction action = control->GetAction(moveAction);
+ action.ExecuteActions(nextControl, GetParentID());
+ nextControl = action.GetNavigation();
+ if (!nextControl) // 0 isn't valid control id
+ return false;
+ // check our history - if the nextControl is in it, we can't focus it
+ for (unsigned int i = 0; i < moveHistory.size(); i++)
+ {
+ if (nextControl == moveHistory[i])
+ return false; // no control to focus so do nothing
+ }
+ control = GetFirstFocusableControl(nextControl);
+ if (control)
+ break; // found a focusable control
+ control = GetControl(nextControl); // grab the next control and try again
+ }
+ if (!control)
+ return false; // no control to focus
+ // if we get here we have our new control so focus it (and unfocus the current control)
+ SET_CONTROL_FOCUS(nextControl, 0);
+ return true;
+}
+
+void CGUIWindow::SetDefaults()
+{
+ m_renderOrder = RENDER_ORDER_WINDOW;
+ m_defaultAlways = false;
+ m_defaultControl = 0;
+ m_posX = m_posY = m_width = m_height = 0;
+ m_previousWindow = WINDOW_INVALID;
+ m_animations.clear();
+ m_origins.clear();
+ m_hasCamera = false;
+ m_stereo = 0.f;
+ m_animationsEnabled = true;
+ m_clearBackground = 0xff000000; // opaque black -> clear
+ m_hitRect.SetRect(0, 0, static_cast<float>(m_coordsRes.iWidth), static_cast<float>(m_coordsRes.iHeight));
+ m_menuControlID = 0;
+ m_menuLastFocusedControlID = 0;
+}
+
+CRect CGUIWindow::GetScaledBounds() const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling);
+ CPoint pos(GetPosition());
+ CRect rect(pos.x, pos.y, pos.x + m_width, pos.y + m_height);
+ float z = 0;
+ CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalCoords(rect.x1, rect.y1, z);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalCoords(rect.x2, rect.y2, z);
+ return rect;
+}
+
+void CGUIWindow::OnEditChanged(int id, std::string &text)
+{
+ CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), id);
+ OnMessage(msg);
+ text = msg.GetLabel();
+}
+
+bool CGUIWindow::SendMessage(int message, int id, int param1 /* = 0*/, int param2 /* = 0*/)
+{
+ CGUIMessage msg(message, GetID(), id, param1, param2);
+ return OnMessage(msg);
+}
+#ifdef _DEBUG
+void CGUIWindow::DumpTextureUse()
+{
+ CLog::Log(LOGDEBUG, "{} for window {}", __FUNCTION__, GetID());
+ CGUIControlGroup::DumpTextureUse();
+}
+#endif
+
+void CGUIWindow::SetProperty(const std::string &strKey, const CVariant &value)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_mapProperties[strKey] = value;
+}
+
+CVariant CGUIWindow::GetProperty(const std::string &strKey) const
+{
+ std::unique_lock<CCriticalSection> lock(const_cast<CGUIWindow&>(*this));
+ std::map<std::string, CVariant, icompare>::const_iterator iter = m_mapProperties.find(strKey);
+ if (iter == m_mapProperties.end())
+ return CVariant(CVariant::VariantTypeNull);
+
+ return iter->second;
+}
+
+void CGUIWindow::ClearProperties()
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+ m_mapProperties.clear();
+}
+
+void CGUIWindow::SetRunActionsManually()
+{
+ m_manualRunActions = true;
+}
+
+void CGUIWindow::RunLoadActions() const
+{
+ m_loadActions.ExecuteActions(GetID(), GetParentID());
+}
+
+void CGUIWindow::RunUnloadActions() const
+{
+ m_unloadActions.ExecuteActions(GetID(), GetParentID());
+}
+
+void CGUIWindow::ClearBackground()
+{
+ m_clearBackground.Update();
+ UTILS::COLOR::Color color = m_clearBackground;
+ if (color)
+ CServiceBroker::GetWinSystem()->GetGfxContext().Clear(color);
+}
+
+void CGUIWindow::SetID(int id)
+{
+ CGUIControlGroup::SetID(id);
+ m_idRange.clear();
+ m_idRange.push_back(id);
+}
+
+bool CGUIWindow::HasID(int controlID) const
+{
+ for (const auto& it : m_idRange)
+ {
+ if (controlID == it)
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/guilib/GUIWindow.h b/xbmc/guilib/GUIWindow.h
new file mode 100644
index 0000000..20d92f1
--- /dev/null
+++ b/xbmc/guilib/GUIWindow.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIWindow.h
+\brief
+*/
+
+#include "GUIAction.h"
+#include "GUIControlGroup.h"
+#include <memory>
+#include "threads/CriticalSection.h"
+
+class CFileItem; typedef std::shared_ptr<CFileItem> CFileItemPtr;
+
+#include <limits.h>
+#include <map>
+#include <vector>
+
+enum RenderOrder {
+ RENDER_ORDER_WINDOW = 0,
+ RENDER_ORDER_DIALOG = 1,
+ RENDER_ORDER_WINDOW_SCREENSAVER = INT_MAX,
+ RENDER_ORDER_WINDOW_POINTER = INT_MAX - 1,
+ RENDER_ORDER_WINDOW_DEBUG = INT_MAX - 2,
+ RENDER_ORDER_DIALOG_TELETEXT = INT_MAX - 3
+};
+
+// forward
+class TiXmlNode;
+class TiXmlElement;
+class CXBMCTinyXML;
+class CVariant;
+
+class COrigin
+{
+public:
+ COrigin()
+ {
+ x = y = 0;
+ };
+ float x;
+ float y;
+ INFO::InfoPtr condition;
+};
+
+/*!
+ \ingroup winmsg
+ \brief
+ */
+class CGUIWindow : public CGUIControlGroup, protected CCriticalSection
+{
+public:
+ enum LOAD_TYPE { LOAD_EVERY_TIME, LOAD_ON_GUI_INIT, KEEP_IN_MEMORY };
+
+ CGUIWindow(int id, const std::string &xmlFile);
+ ~CGUIWindow(void) override;
+
+ bool Initialize(); // loads the window
+ bool Load(const std::string& strFileName, bool bContainsPath = false);
+
+ void CenterWindow();
+
+ void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override;
+
+ /*! \brief Main render function, called every frame.
+ Window classes should override this only if they need to alter how something is rendered.
+ General updating on a per-frame basis should be handled in FrameMove instead, as DoRender
+ is not necessarily re-entrant.
+ \sa FrameMove
+ */
+ void DoRender() override;
+
+ /*! \brief Do any post render activities.
+ Check if window closing animation is finished and finalize window closing.
+ */
+ void AfterRender();
+
+ /*! \brief Main update function, called every frame prior to rendering
+ Any window that requires updating on a frame by frame basis (such as to maintain
+ timers and the like) should override this function.
+ */
+ virtual void FrameMove() {}
+
+ void Close(bool forceClose = false, int nextWindowID = 0, bool enableSound = true, bool bWait = true);
+
+ // OnAction() is called by our window manager. We should process any messages
+ // that should be handled at the window level in the derived classes, and any
+ // unhandled messages should be dropped through to here where we send the message
+ // on to the currently focused control. Returns true if the action has been handled
+ // and does not need to be passed further down the line (to our global action handlers)
+ bool OnAction(const CAction &action) override;
+
+ using CGUIControlGroup::OnBack;
+ virtual bool OnBack(int actionID);
+ using CGUIControlGroup::OnInfo;
+ virtual bool OnInfo(int actionID) { return false; }
+
+ /*! \brief Clear the background (if necessary) prior to rendering the window
+ */
+ virtual void ClearBackground();
+
+ bool OnMove(int fromControl, int moveAction);
+ bool OnMessage(CGUIMessage& message) override;
+
+ bool ControlGroupHasFocus(int groupID, int controlID);
+ void SetID(int id) override;
+ virtual bool HasID(int controlID) const;
+ const std::vector<int>& GetIDRange() const { return m_idRange; }
+ int GetPreviousWindow() { return m_previousWindow; }
+ CRect GetScaledBounds() const;
+ void ClearAll() override;
+ using CGUIControlGroup::AllocResources;
+ virtual void AllocResources(bool forceLoad = false);
+ void FreeResources(bool forceUnLoad = false) override;
+ void DynamicResourceAlloc(bool bOnOff) override;
+ virtual bool IsDialog() const { return false; }
+ virtual bool IsDialogRunning() const { return false; }
+ virtual bool IsModalDialog() const { return false; }
+ virtual bool IsMediaWindow() const { return false; }
+ virtual bool HasListItems() const { return false; }
+ virtual bool IsSoundEnabled() const { return true; }
+ virtual CFileItemPtr GetCurrentListItem(int offset = 0) { return CFileItemPtr(); }
+ virtual int GetViewContainerID() const { return 0; }
+ virtual int GetViewCount() const { return 0; }
+ virtual bool CanBeActivated() const { return true; }
+ virtual bool IsActive() const;
+ void SetCoordsRes(const RESOLUTION_INFO& res) { m_coordsRes = res; }
+ const RESOLUTION_INFO& GetCoordsRes() const { return m_coordsRes; }
+ void SetLoadType(LOAD_TYPE loadType) { m_loadType = loadType; }
+ LOAD_TYPE GetLoadType() { return m_loadType; }
+ int GetRenderOrder() { return m_renderOrder; }
+ void SetInitialVisibility() override;
+ bool IsVisible() const override { return true; }; // windows are always considered visible as they implement their own
+ // versions of UpdateVisibility, and are deemed visible if they're in
+ // the window manager's active list.
+ virtual bool HasVisibleControls() { return true; }; //Assume that window always has visible controls
+
+ bool IsAnimating(ANIMATION_TYPE animType) override;
+
+ /*!
+ \brief Return if the window is a custom window
+ \return true if the window is an custom window otherwise false
+ */
+ bool IsCustom() const { return m_custom; }
+
+ /*!
+ \brief Mark this window as custom window
+ \param custom true if this window is a custom window, false if not
+ */
+ void SetCustom(bool custom) { m_custom = custom; }
+
+ void DisableAnimations();
+
+ virtual void ResetControlStates();
+ void UpdateControlStats() override {}; // Do not count window itself
+
+ void SetRunActionsManually();
+ void RunLoadActions() const;
+ void RunUnloadActions() const;
+
+ /*! \brief Set a property
+ Sets the value of a property referenced by a key.
+ \param key name of the property to set
+ \param value value to set, may be a string, integer, boolean or double.
+ \sa GetProperty
+ */
+ void SetProperty(const std::string &key, const CVariant &value);
+
+ /*! \brief Retrieve a property
+ \param key name of the property to retrieve
+ \return value of the property, empty if it doesn't exist
+ */
+ CVariant GetProperty(const std::string &key) const;
+
+ /*! \brief Clear a all the window's properties
+ \sa SetProperty, HasProperty, GetProperty
+ */
+ void ClearProperties();
+
+#ifdef _DEBUG
+ void DumpTextureUse() override;
+#endif
+ bool HasSaveLastControl() const { return !m_defaultAlways; }
+
+ virtual void OnDeinitWindow(int nextWindowID);
+protected:
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+
+ /*!
+ \brief Load the window XML from the given path
+ \param strPath the path to the window XML
+ \param strLowerPath a lowered path to the window XML
+ */
+ virtual bool LoadXML(const std::string& strPath, const std::string &strLowerPath);
+
+ /*!
+ \brief Loads the window from the given XML element
+ \param pRootElement the XML element
+ \return true if the window is loaded from the given XML otherwise false.
+ */
+ virtual bool Load(TiXmlElement *pRootElement);
+
+ /*!
+ \brief Prepare the XML for load
+ \param rootElement the original XML element
+ \return the prepared XML (resolved includes, constants and expression)
+ */
+ virtual std::unique_ptr<TiXmlElement> Prepare(const std::unique_ptr<TiXmlElement>& rootElement);
+
+ /*!
+ \brief Check if window needs a (re)load. The window need to be (re)loaded when window is not loaded or include conditions values were changed
+ */
+ bool NeedLoad() const;
+
+ virtual void SetDefaults();
+ virtual void OnWindowUnload() {}
+ virtual void OnWindowLoaded();
+ virtual void OnInitWindow();
+ void Close_Internal(bool forceClose = false, int nextWindowID = 0, bool enableSound = true);
+ EVENT_RESULT OnMouseAction(const CAction &action);
+ bool Animate(unsigned int currentTime) override;
+ bool CheckAnimation(ANIMATION_TYPE animType) override;
+
+ // control state saving on window close
+ virtual void SaveControlStates();
+ virtual void RestoreControlStates();
+
+ // methods for updating controls and sending messages
+ void OnEditChanged(int id, std::string &text);
+ bool SendMessage(int message, int id, int param1 = 0, int param2 = 0);
+
+ void LoadControl(TiXmlElement* pControl, CGUIControlGroup *pGroup, const CRect &rect);
+
+ std::vector<int> m_idRange;
+ RESOLUTION_INFO m_coordsRes; // resolution that the window coordinates are in.
+ bool m_needsScaling;
+ bool m_windowLoaded; // true if the window's xml file has been loaded
+ LOAD_TYPE m_loadType;
+ bool m_dynamicResourceAlloc;
+ bool m_closing;
+ bool m_active; // true if window is active or dialog is running
+ KODI::GUILIB::GUIINFO::CGUIInfoColor m_clearBackground; // colour to clear the window
+
+ int m_renderOrder; // for render order of dialogs
+
+ /*! \brief Grabs the window's top,left position in skin coordinates
+ The window origin may change based on `<origin>` tag conditions in the skin.
+
+ \return the window's origin in skin coordinates
+ */
+ CPoint GetPosition() const override;
+ std::vector<COrigin> m_origins; // positions of dialogs depending on base window
+
+ // control states
+ int m_lastControlID;
+ std::vector<CControlState> m_controlStates;
+ int m_previousWindow;
+
+ bool m_animationsEnabled;
+ struct icompare
+ {
+ bool operator()(const std::string &s1, const std::string &s2) const;
+ };
+
+ CGUIAction m_loadActions;
+ CGUIAction m_unloadActions;
+
+ /*! \brief window root xml definition after resolving any skin includes.
+ Stored to avoid parsing the XML every time the window is loaded.
+ */
+ std::unique_ptr<TiXmlElement> m_windowXMLRootElement;
+
+ bool m_manualRunActions;
+
+ int m_exclusiveMouseControl; ///< \brief id of child control that wishes to receive all mouse events \sa GUI_MSG_EXCLUSIVE_MOUSE
+
+ int m_menuControlID;
+ int m_menuLastFocusedControlID;
+ bool m_custom;
+
+private:
+ std::map<std::string, CVariant, icompare> m_mapProperties;
+ std::map<INFO::InfoPtr, bool> m_xmlIncludeConditions; ///< \brief used to store conditions used to resolve includes for this window
+};
+
diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp
new file mode 100644
index 0000000..f15489e
--- /dev/null
+++ b/xbmc/guilib/GUIWindowManager.cpp
@@ -0,0 +1,1808 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWindowManager.h"
+
+#include "GUIAudioManager.h"
+#include "GUIDialog.h"
+#include "GUIInfoManager.h"
+#include "GUIPassword.h"
+#include "GUITexture.h"
+#include "ServiceBroker.h"
+#include "WindowIDs.h"
+#include "addons/Skin.h"
+#include "addons/gui/GUIWindowAddonBrowser.h"
+#include "addons/interfaces/gui/Window.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "events/windows/GUIWindowEventLog.h"
+#include "favourites/GUIDialogFavourites.h"
+#include "favourites/GUIWindowFavourites.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "messaging/helpers/DialogHelper.h"
+#include "music/dialogs/GUIDialogInfoProviderSettings.h"
+#include "music/dialogs/GUIDialogMusicInfo.h"
+#include "music/windows/GUIWindowMusicNav.h"
+#include "music/windows/GUIWindowMusicPlaylist.h"
+#include "music/windows/GUIWindowMusicPlaylistEditor.h"
+#include "music/windows/GUIWindowVisualisation.h"
+#include "pictures/GUIWindowPictures.h"
+#include "pictures/GUIWindowSlideShow.h"
+#include "profiles/windows/GUIWindowSettingsProfile.h"
+#include "programs/GUIWindowPrograms.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/windows/GUIWindowSettings.h"
+#include "settings/windows/GUIWindowSettingsCategory.h"
+#include "settings/windows/GUIWindowSettingsScreenCalibration.h"
+#include "threads/SingleLock.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "video/dialogs/GUIDialogVideoInfo.h"
+#include "video/dialogs/GUIDialogVideoOSD.h"
+#include "video/windows/GUIWindowFullScreen.h"
+#include "video/windows/GUIWindowVideoNav.h"
+#include "video/windows/GUIWindowVideoPlaylist.h"
+#include "weather/GUIWindowWeather.h"
+#include "windows/GUIWindowDebugInfo.h"
+#include "windows/GUIWindowFileManager.h"
+#include "windows/GUIWindowHome.h"
+#include "windows/GUIWindowLoginScreen.h"
+#include "windows/GUIWindowPointer.h"
+#include "windows/GUIWindowScreensaver.h"
+#include "windows/GUIWindowScreensaverDim.h"
+#include "windows/GUIWindowSplash.h"
+#include "windows/GUIWindowStartup.h"
+#include "windows/GUIWindowSystemInfo.h"
+
+#include <mutex>
+
+// Dialog includes
+#include "music/dialogs/GUIDialogMusicOSD.h"
+#include "music/dialogs/GUIDialogVisualisationPresetList.h"
+#include "dialogs/GUIDialogTextViewer.h"
+#include "network/GUIDialogNetworkSetup.h"
+#include "dialogs/GUIDialogMediaSource.h"
+#if defined(HAS_GL) || defined(HAS_DX)
+#include "video/dialogs/GUIDialogCMSSettings.h"
+#endif
+#include "addons/gui/GUIDialogAddonInfo.h"
+#include "addons/gui/GUIDialogAddonSettings.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogBusyNoCancel.h"
+#include "dialogs/GUIDialogButtonMenu.h"
+#include "dialogs/GUIDialogColorPicker.h"
+#include "dialogs/GUIDialogContextMenu.h"
+#include "dialogs/GUIDialogExtendedProgressBar.h"
+#include "dialogs/GUIDialogGamepad.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogKeyboardGeneric.h"
+#include "dialogs/GUIDialogKeyboardTouch.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "dialogs/GUIDialogOK.h"
+#include "dialogs/GUIDialogPlayerControls.h"
+#include "dialogs/GUIDialogPlayerProcessInfo.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "dialogs/GUIDialogSeekBar.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "dialogs/GUIDialogSmartPlaylistEditor.h"
+#include "dialogs/GUIDialogSmartPlaylistRule.h"
+#include "dialogs/GUIDialogSubMenu.h"
+#include "dialogs/GUIDialogVolumeBar.h"
+#include "dialogs/GUIDialogYesNo.h"
+#include "music/dialogs/GUIDialogSongInfo.h"
+#include "pictures/GUIDialogPictureInfo.h"
+#include "profiles/dialogs/GUIDialogLockSettings.h"
+#include "profiles/dialogs/GUIDialogProfileSettings.h"
+#include "settings/dialogs/GUIDialogContentSettings.h"
+#include "settings/dialogs/GUIDialogLibExportSettings.h"
+#include "video/dialogs/GUIDialogAudioSettings.h"
+#include "video/dialogs/GUIDialogSubtitleSettings.h"
+#include "video/dialogs/GUIDialogVideoBookmarks.h"
+#include "video/dialogs/GUIDialogVideoSettings.h"
+
+/* PVR related include Files */
+#include "pvr/dialogs/GUIDialogPVRChannelGuide.h"
+#include "pvr/dialogs/GUIDialogPVRChannelManager.h"
+#include "pvr/dialogs/GUIDialogPVRChannelsOSD.h"
+#include "pvr/dialogs/GUIDialogPVRClientPriorities.h"
+#include "pvr/dialogs/GUIDialogPVRGroupManager.h"
+#include "pvr/dialogs/GUIDialogPVRGuideControls.h"
+#include "pvr/dialogs/GUIDialogPVRGuideInfo.h"
+#include "pvr/dialogs/GUIDialogPVRGuideSearch.h"
+#include "pvr/dialogs/GUIDialogPVRRadioRDSInfo.h"
+#include "pvr/dialogs/GUIDialogPVRRecordingInfo.h"
+#include "pvr/dialogs/GUIDialogPVRRecordingSettings.h"
+#include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
+#include "pvr/windows/GUIWindowPVRChannels.h"
+#include "pvr/windows/GUIWindowPVRGuide.h"
+#include "pvr/windows/GUIWindowPVRRecordings.h"
+#include "pvr/windows/GUIWindowPVRSearch.h"
+#include "pvr/windows/GUIWindowPVRTimerRules.h"
+#include "pvr/windows/GUIWindowPVRTimers.h"
+
+#include "video/dialogs/GUIDialogTeletext.h"
+#include "dialogs/GUIDialogSlider.h"
+#ifdef HAS_DVD_DRIVE
+#include "dialogs/GUIDialogPlayEject.h"
+#endif
+#include "dialogs/GUIDialogMediaFilter.h"
+#include "video/dialogs/GUIDialogSubtitles.h"
+
+#include "peripherals/dialogs/GUIDialogPeripherals.h"
+#include "peripherals/dialogs/GUIDialogPeripheralSettings.h"
+
+/* Game related include files */
+#include "cores/RetroPlayer/guiwindows/GameWindowFullScreen.h"
+#include "games/controllers/windows/GUIControllerWindow.h"
+#include "games/dialogs/osd/DialogGameAdvancedSettings.h"
+#include "games/dialogs/osd/DialogGameOSD.h"
+#include "games/dialogs/osd/DialogGameSaves.h"
+#include "games/dialogs/osd/DialogGameStretchMode.h"
+#include "games/dialogs/osd/DialogGameVideoFilter.h"
+#include "games/dialogs/osd/DialogGameVideoRotation.h"
+#include "games/dialogs/osd/DialogGameVolume.h"
+#include "games/dialogs/osd/DialogInGameSaves.h"
+#include "games/ports/windows/GUIPortWindow.h"
+#include "games/windows/GUIWindowGames.h"
+
+using namespace KODI;
+using namespace PVR;
+using namespace PERIPHERALS;
+
+CGUIWindowManager::CGUIWindowManager()
+{
+ m_pCallback = nullptr;
+ m_iNested = 0;
+ m_initialized = false;
+}
+
+CGUIWindowManager::~CGUIWindowManager() = default;
+
+void CGUIWindowManager::Initialize()
+{
+ m_tracker.SelectAlgorithm();
+
+ m_initialized = true;
+
+ LoadNotOnDemandWindows();
+
+ CServiceBroker::GetAppMessenger()->RegisterReceiver(this);
+}
+
+void CGUIWindowManager::CreateWindows()
+{
+ Add(new CGUIWindowHome);
+ Add(new CGUIWindowPrograms);
+ Add(new CGUIWindowPictures);
+ Add(new CGUIWindowFileManager);
+ Add(new CGUIWindowSettings);
+ Add(new CGUIWindowSystemInfo);
+ Add(new CGUIWindowSettingsScreenCalibration);
+ Add(new CGUIWindowSettingsCategory);
+ Add(new CGUIWindowVideoNav);
+ Add(new CGUIWindowVideoPlaylist);
+ Add(new CGUIWindowLoginScreen);
+ Add(new CGUIWindowSettingsProfile);
+ Add(new CGUIWindow(WINDOW_SKIN_SETTINGS, "SkinSettings.xml"));
+ Add(new CGUIWindowAddonBrowser);
+ Add(new CGUIWindowScreensaverDim);
+ Add(new CGUIWindowDebugInfo);
+ Add(new CGUIWindowPointer);
+ Add(new CGUIDialogYesNo);
+ Add(new CGUIDialogProgress);
+ Add(new CGUIDialogExtendedProgressBar);
+ Add(new CGUIDialogKeyboardGeneric);
+ Add(new CGUIDialogKeyboardTouch);
+ Add(new CGUIDialogVolumeBar);
+ Add(new CGUIDialogSeekBar);
+ Add(new CGUIDialogSubMenu);
+ Add(new CGUIDialogContextMenu);
+ Add(new CGUIDialogKaiToast);
+ Add(new CGUIDialogNumeric);
+ Add(new CGUIDialogGamepad);
+ Add(new CGUIDialogButtonMenu);
+ Add(new CGUIDialogPlayerControls);
+ Add(new CGUIDialogPlayerProcessInfo);
+ Add(new CGUIDialogSlider);
+ Add(new CGUIDialogMusicOSD);
+ Add(new CGUIDialogVisualisationPresetList);
+#if defined(HAS_GL) || defined(HAS_DX)
+ Add(new CGUIDialogCMSSettings);
+#endif
+ Add(new CGUIDialogVideoSettings);
+ Add(new CGUIDialogAudioSettings);
+ Add(new CGUIDialogSubtitleSettings);
+ Add(new CGUIDialogVideoBookmarks);
+ // Don't add the filebrowser dialog - it's created and added when it's needed
+ Add(new CGUIDialogNetworkSetup);
+ Add(new CGUIDialogMediaSource);
+ Add(new CGUIDialogProfileSettings);
+ Add(new CGUIDialogFavourites);
+ Add(new CGUIDialogSongInfo);
+ Add(new CGUIDialogSmartPlaylistEditor);
+ Add(new CGUIDialogSmartPlaylistRule);
+ Add(new CGUIDialogBusy);
+ Add(new CGUIDialogBusyNoCancel);
+ Add(new CGUIDialogPictureInfo);
+ Add(new CGUIDialogAddonInfo);
+ Add(new CGUIDialogAddonSettings);
+
+ Add(new CGUIDialogLockSettings);
+
+ Add(new CGUIDialogContentSettings);
+
+ Add(new CGUIDialogLibExportSettings);
+
+ Add(new CGUIDialogInfoProviderSettings);
+
+#ifdef HAS_DVD_DRIVE
+ Add(new CGUIDialogPlayEject);
+#endif
+
+ Add(new CGUIDialogPeripherals);
+ Add(new CGUIDialogPeripheralSettings);
+
+ Add(new CGUIDialogMediaFilter);
+ Add(new CGUIDialogSubtitles);
+
+ Add(new CGUIWindowMusicPlayList);
+ Add(new CGUIWindowMusicNav);
+ Add(new CGUIWindowMusicPlaylistEditor);
+
+ /* Load PVR related Windows and Dialogs */
+ Add(new CGUIDialogTeletext);
+ Add(new CGUIWindowPVRTVChannels);
+ Add(new CGUIWindowPVRTVRecordings);
+ Add(new CGUIWindowPVRTVGuide);
+ Add(new CGUIWindowPVRTVTimers);
+ Add(new CGUIWindowPVRTVTimerRules);
+ Add(new CGUIWindowPVRTVSearch);
+ Add(new CGUIWindowPVRRadioChannels);
+ Add(new CGUIWindowPVRRadioRecordings);
+ Add(new CGUIWindowPVRRadioGuide);
+ Add(new CGUIWindowPVRRadioTimers);
+ Add(new CGUIWindowPVRRadioTimerRules);
+ Add(new CGUIWindowPVRRadioSearch);
+ Add(new CGUIDialogPVRRadioRDSInfo);
+ Add(new CGUIDialogPVRGuideInfo);
+ Add(new CGUIDialogPVRRecordingInfo);
+ Add(new CGUIDialogPVRTimerSettings);
+ Add(new CGUIDialogPVRGroupManager);
+ Add(new CGUIDialogPVRChannelManager);
+ Add(new CGUIDialogPVRGuideSearch);
+ Add(new CGUIDialogPVRChannelsOSD);
+ Add(new CGUIDialogPVRChannelGuide);
+ Add(new CGUIDialogPVRRecordingSettings);
+ Add(new CGUIDialogPVRClientPriorities);
+ Add(new CGUIDialogPVRGuideControls);
+
+ Add(new CGUIDialogSelect);
+ Add(new CGUIDialogColorPicker);
+ Add(new CGUIDialogMusicInfo);
+ Add(new CGUIDialogOK);
+ Add(new CGUIDialogVideoInfo);
+ Add(new CGUIDialogTextViewer);
+ Add(new CGUIWindowFullScreen);
+ Add(new CGUIWindowVisualisation);
+ Add(new CGUIWindowSlideShow);
+
+ Add(new CGUIDialogVideoOSD);
+ Add(new CGUIWindowScreensaver);
+ Add(new CGUIWindowWeather);
+ Add(new CGUIWindowStartup);
+ Add(new CGUIWindowSplash);
+
+ Add(new CGUIWindowEventLog);
+
+ Add(new CGUIWindowFavourites);
+
+ Add(new GAME::CGUIControllerWindow);
+ Add(new GAME::CGUIPortWindow);
+ Add(new GAME::CGUIWindowGames);
+ Add(new GAME::CDialogGameOSD);
+ Add(new GAME::CDialogGameSaves);
+ Add(new GAME::CDialogGameVideoFilter);
+ Add(new GAME::CDialogGameStretchMode);
+ Add(new GAME::CDialogGameVolume);
+ Add(new GAME::CDialogGameAdvancedSettings);
+ Add(new GAME::CDialogGameVideoRotation);
+ Add(new GAME::CDialogInGameSaves);
+ Add(new RETRO::CGameWindowFullScreen);
+}
+
+bool CGUIWindowManager::DestroyWindows()
+{
+ try
+ {
+ DestroyWindow(WINDOW_SPLASH);
+ DestroyWindow(WINDOW_MUSIC_PLAYLIST);
+ DestroyWindow(WINDOW_MUSIC_PLAYLIST_EDITOR);
+ DestroyWindow(WINDOW_MUSIC_NAV);
+ DestroyWindow(WINDOW_DIALOG_MUSIC_INFO);
+ DestroyWindow(WINDOW_DIALOG_VIDEO_INFO);
+ DestroyWindow(WINDOW_VIDEO_PLAYLIST);
+ DestroyWindow(WINDOW_VIDEO_NAV);
+ DestroyWindow(WINDOW_FILES);
+ DestroyWindow(WINDOW_DIALOG_YES_NO);
+ DestroyWindow(WINDOW_DIALOG_PROGRESS);
+ DestroyWindow(WINDOW_DIALOG_NUMERIC);
+ DestroyWindow(WINDOW_DIALOG_GAMEPAD);
+ DestroyWindow(WINDOW_DIALOG_SUB_MENU);
+ DestroyWindow(WINDOW_DIALOG_BUTTON_MENU);
+ DestroyWindow(WINDOW_DIALOG_CONTEXT_MENU);
+ DestroyWindow(WINDOW_DIALOG_PLAYER_CONTROLS);
+ DestroyWindow(WINDOW_DIALOG_PLAYER_PROCESS_INFO);
+ DestroyWindow(WINDOW_DIALOG_MUSIC_OSD);
+ DestroyWindow(WINDOW_DIALOG_VIS_PRESET_LIST);
+ DestroyWindow(WINDOW_DIALOG_SELECT);
+ DestroyWindow(WINDOW_DIALOG_OK);
+ DestroyWindow(WINDOW_DIALOG_KEYBOARD);
+ DestroyWindow(WINDOW_DIALOG_KEYBOARD_TOUCH);
+ DestroyWindow(WINDOW_FULLSCREEN_VIDEO);
+ DestroyWindow(WINDOW_DIALOG_PROFILE_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_LOCK_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_NETWORK_SETUP);
+ DestroyWindow(WINDOW_DIALOG_MEDIA_SOURCE);
+ DestroyWindow(WINDOW_DIALOG_CMS_OSD_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_VIDEO_OSD_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_AUDIO_OSD_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_VIDEO_BOOKMARKS);
+ DestroyWindow(WINDOW_DIALOG_CONTENT_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_INFOPROVIDER_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_LIBEXPORT_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_FAVOURITES);
+ DestroyWindow(WINDOW_DIALOG_SONG_INFO);
+ DestroyWindow(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR);
+ DestroyWindow(WINDOW_DIALOG_SMART_PLAYLIST_RULE);
+ DestroyWindow(WINDOW_DIALOG_BUSY);
+ DestroyWindow(WINDOW_DIALOG_BUSY_NOCANCEL);
+ DestroyWindow(WINDOW_DIALOG_PICTURE_INFO);
+ DestroyWindow(WINDOW_DIALOG_ADDON_INFO);
+ DestroyWindow(WINDOW_DIALOG_ADDON_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_SLIDER);
+ DestroyWindow(WINDOW_DIALOG_MEDIA_FILTER);
+ DestroyWindow(WINDOW_DIALOG_SUBTITLES);
+ DestroyWindow(WINDOW_DIALOG_COLOR_PICKER);
+
+ /* Delete PVR related windows and dialogs */
+ DestroyWindow(WINDOW_TV_CHANNELS);
+ DestroyWindow(WINDOW_TV_RECORDINGS);
+ DestroyWindow(WINDOW_TV_GUIDE);
+ DestroyWindow(WINDOW_TV_TIMERS);
+ DestroyWindow(WINDOW_TV_TIMER_RULES);
+ DestroyWindow(WINDOW_TV_SEARCH);
+ DestroyWindow(WINDOW_RADIO_CHANNELS);
+ DestroyWindow(WINDOW_RADIO_RECORDINGS);
+ DestroyWindow(WINDOW_RADIO_GUIDE);
+ DestroyWindow(WINDOW_RADIO_TIMERS);
+ DestroyWindow(WINDOW_RADIO_TIMER_RULES);
+ DestroyWindow(WINDOW_RADIO_SEARCH);
+ DestroyWindow(WINDOW_DIALOG_PVR_GUIDE_INFO);
+ DestroyWindow(WINDOW_DIALOG_PVR_RECORDING_INFO);
+ DestroyWindow(WINDOW_DIALOG_PVR_TIMER_SETTING);
+ DestroyWindow(WINDOW_DIALOG_PVR_GROUP_MANAGER);
+ DestroyWindow(WINDOW_DIALOG_PVR_CHANNEL_MANAGER);
+ DestroyWindow(WINDOW_DIALOG_PVR_GUIDE_SEARCH);
+ DestroyWindow(WINDOW_DIALOG_PVR_CHANNEL_SCAN);
+ DestroyWindow(WINDOW_DIALOG_PVR_RADIO_RDS_INFO);
+ DestroyWindow(WINDOW_DIALOG_PVR_UPDATE_PROGRESS);
+ DestroyWindow(WINDOW_DIALOG_PVR_OSD_CHANNELS);
+ DestroyWindow(WINDOW_DIALOG_PVR_CHANNEL_GUIDE);
+ DestroyWindow(WINDOW_DIALOG_OSD_TELETEXT);
+ DestroyWindow(WINDOW_DIALOG_PVR_RECORDING_SETTING);
+ DestroyWindow(WINDOW_DIALOG_PVR_CLIENT_PRIORITIES);
+ DestroyWindow(WINDOW_DIALOG_PVR_GUIDE_CONTROLS);
+
+ DestroyWindow(WINDOW_DIALOG_TEXT_VIEWER);
+#ifdef HAS_DVD_DRIVE
+ DestroyWindow(WINDOW_DIALOG_PLAY_EJECT);
+#endif
+ DestroyWindow(WINDOW_STARTUP_ANIM);
+ DestroyWindow(WINDOW_LOGIN_SCREEN);
+ DestroyWindow(WINDOW_VISUALISATION);
+ DestroyWindow(WINDOW_SETTINGS_MENU);
+ DestroyWindow(WINDOW_SETTINGS_PROFILES);
+ DestroyWindow(WINDOW_SCREEN_CALIBRATION);
+ DestroyWindow(WINDOW_SYSTEM_INFORMATION);
+ DestroyWindow(WINDOW_SCREENSAVER);
+ DestroyWindow(WINDOW_DIALOG_VIDEO_OSD);
+ DestroyWindow(WINDOW_SLIDESHOW);
+ DestroyWindow(WINDOW_ADDON_BROWSER);
+ DestroyWindow(WINDOW_SKIN_SETTINGS);
+
+ DestroyWindow(WINDOW_HOME);
+ DestroyWindow(WINDOW_PROGRAMS);
+ DestroyWindow(WINDOW_PICTURES);
+ DestroyWindow(WINDOW_WEATHER);
+ DestroyWindow(WINDOW_DIALOG_GAME_CONTROLLERS);
+ DestroyWindow(WINDOW_DIALOG_GAME_PORTS);
+ DestroyWindow(WINDOW_GAMES);
+ DestroyWindow(WINDOW_DIALOG_GAME_OSD);
+ DestroyWindow(WINDOW_DIALOG_GAME_SAVES);
+ DestroyWindow(WINDOW_DIALOG_GAME_VIDEO_FILTER);
+ DestroyWindow(WINDOW_DIALOG_GAME_STRETCH_MODE);
+ DestroyWindow(WINDOW_DIALOG_GAME_VOLUME);
+ DestroyWindow(WINDOW_DIALOG_GAME_ADVANCED_SETTINGS);
+ DestroyWindow(WINDOW_DIALOG_GAME_VIDEO_ROTATION);
+ DestroyWindow(WINDOW_DIALOG_IN_GAME_SAVES);
+ DestroyWindow(WINDOW_FULLSCREEN_GAME);
+
+ Remove(WINDOW_SETTINGS_SERVICE);
+ Remove(WINDOW_SETTINGS_MYPVR);
+ Remove(WINDOW_SETTINGS_PLAYER);
+ Remove(WINDOW_SETTINGS_MEDIA);
+ Remove(WINDOW_SETTINGS_INTERFACE);
+ Remove(WINDOW_SETTINGS_MYGAMES);
+ DestroyWindow(WINDOW_SETTINGS_SYSTEM); // all the settings categories
+
+ Remove(WINDOW_DIALOG_KAI_TOAST);
+ Remove(WINDOW_DIALOG_SEEK_BAR);
+ Remove(WINDOW_DIALOG_VOLUME_BAR);
+
+ DestroyWindow(WINDOW_EVENT_LOG);
+
+ DestroyWindow(WINDOW_FAVOURITES);
+
+ DestroyWindow(WINDOW_DIALOG_PERIPHERALS);
+ DestroyWindow(WINDOW_DIALOG_PERIPHERAL_SETTINGS);
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "Exception in CGUIWindowManager::DestroyWindows()");
+ return false;
+ }
+
+ return true;
+}
+
+void CGUIWindowManager::DestroyWindow(int id)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CGUIWindow *pWindow = GetWindow(id);
+ if (pWindow)
+ {
+ Remove(id);
+ pWindow->FreeResources(true);
+ delete pWindow;
+ }
+}
+
+bool CGUIWindowManager::SendMessage(int message, int senderID, int destID, int param1, int param2)
+{
+ CGUIMessage msg(message, senderID, destID, param1, param2);
+ return SendMessage(msg);
+}
+
+bool CGUIWindowManager::SendMessage(CGUIMessage& message)
+{
+ bool handled = false;
+ // CLog::Log(LOGDEBUG,"SendMessage: mess={} send={} control={} param1={}", message.GetMessage(), message.GetSenderId(), message.GetControlId(), message.GetParam1());
+ // Send the message to all none window targets
+ for (int i = 0; i < int(m_vecMsgTargets.size()); i++)
+ {
+ IMsgTargetCallback* pMsgTarget = m_vecMsgTargets[i];
+
+ if (pMsgTarget)
+ {
+ if (pMsgTarget->OnMessage( message )) handled = true;
+ }
+ }
+
+ // A GUI_MSG_NOTIFY_ALL is send to any active modal dialog
+ // and all windows whether they are active or not
+ if (message.GetMessage()==GUI_MSG_NOTIFY_ALL)
+ {
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ for (auto it = m_activeDialogs.rbegin(); it != m_activeDialogs.rend(); ++it)
+ {
+ (*it)->OnMessage(message);
+ }
+
+ for (const auto& entry : m_mapWindows)
+ {
+ entry.second->OnMessage(message);
+ }
+
+ return true;
+ }
+
+ // Normal messages are sent to:
+ // 1. All active modeless dialogs
+ // 2. The topmost dialog that accepts the message
+ // 3. The underlying window (only if it is the sender or receiver if a modal dialog is active)
+
+ bool hasModalDialog(false);
+ bool modalAcceptedMessage(false);
+ // don't use an iterator for this loop, as some messages mean that m_activeDialogs is altered,
+ // which will invalidate any iterator
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ size_t topWindow = m_activeDialogs.size();
+ while (topWindow)
+ {
+ CGUIWindow* dialog = m_activeDialogs[--topWindow];
+
+ if (!modalAcceptedMessage && dialog->IsModalDialog())
+ { // modal window
+ hasModalDialog = true;
+ if (!modalAcceptedMessage && dialog->OnMessage( message ))
+ {
+ modalAcceptedMessage = handled = true;
+ }
+ }
+ else if (!dialog->IsModalDialog())
+ { // modeless
+ if (dialog->OnMessage( message ))
+ handled = true;
+ }
+
+ if (topWindow > m_activeDialogs.size())
+ topWindow = m_activeDialogs.size();
+ }
+
+ // now send to the underlying window
+ CGUIWindow* window = GetWindow(GetActiveWindow());
+ if (window)
+ {
+ if (hasModalDialog)
+ {
+ // only send the message to the underlying window if it's the recipient
+ // or sender (or we have no sender)
+ if (message.GetSenderId() == window->GetID() ||
+ message.GetControlId() == window->GetID() ||
+ message.GetSenderId() == 0 )
+ {
+ if (window->OnMessage(message)) handled = true;
+ }
+ }
+ else
+ {
+ if (window->OnMessage(message)) handled = true;
+ }
+ }
+ return handled;
+}
+
+bool CGUIWindowManager::SendMessage(CGUIMessage& message, int window)
+{
+ if (window == 0)
+ // send to no specified windows.
+ return SendMessage(message);
+ CGUIWindow* pWindow = GetWindow(window);
+ if(pWindow)
+ return pWindow->OnMessage(message);
+ else
+ return false;
+}
+
+void CGUIWindowManager::AddUniqueInstance(CGUIWindow *window)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ // increment our instance (upper word of windowID)
+ // until we get a window we don't have
+ int instance = 0;
+ while (GetWindow(window->GetID()))
+ window->SetID(window->GetID() + (++instance << 16));
+ Add(window);
+}
+
+void CGUIWindowManager::Add(CGUIWindow* pWindow)
+{
+ if (!pWindow)
+ {
+ CLog::Log(LOGERROR, "Attempted to add a NULL window pointer to the window manager.");
+ return;
+ }
+ // push back all the windows if there are more than one covered by this class
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ for (int id : pWindow->GetIDRange())
+ {
+ auto it = m_mapWindows.find(id);
+ if (it != m_mapWindows.end())
+ {
+ CLog::Log(LOGERROR,
+ "Error, trying to add a second window with id {} "
+ "to the window manager",
+ id);
+ return;
+ }
+
+ m_mapWindows.insert(std::make_pair(id, pWindow));
+ }
+}
+
+void CGUIWindowManager::AddCustomWindow(CGUIWindow* pWindow)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ Add(pWindow);
+ m_vecCustomWindows.emplace_back(pWindow);
+}
+
+void CGUIWindowManager::RegisterDialog(CGUIWindow* dialog)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ // only add the window if it does not exists
+ for (const auto& window : m_activeDialogs)
+ {
+ if (window->GetID() == dialog->GetID())
+ return;
+ }
+ m_activeDialogs.emplace_back(dialog);
+}
+
+void CGUIWindowManager::Remove(int id)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ auto it = m_mapWindows.find(id);
+ if (it != m_mapWindows.end())
+ {
+ CGUIWindow *window = it->second;
+ m_windowHistory.erase(std::remove_if(m_windowHistory.begin(),
+ m_windowHistory.end(),
+ [id](int winId){ return winId == id; }),
+ m_windowHistory.end());
+ m_activeDialogs.erase(std::remove_if(m_activeDialogs.begin(),
+ m_activeDialogs.end(),
+ [window](CGUIWindow* w){ return w == window; }),
+ m_activeDialogs.end());
+ m_mapWindows.erase(it);
+ }
+ else
+ {
+ CLog::Log(LOGWARNING,
+ "Attempted to remove window {} "
+ "from the window manager when it didn't exist",
+ id);
+ }
+}
+
+// removes and deletes the window. Should only be called
+// from the class that created the window using new.
+void CGUIWindowManager::Delete(int id)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CGUIWindow *pWindow = GetWindow(id);
+ if (pWindow)
+ {
+ Remove(id);
+ m_deleteWindows.emplace_back(pWindow);
+ }
+}
+
+void CGUIWindowManager::PreviousWindow()
+{
+ // deactivate any window
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CLog::Log(LOGDEBUG,"CGUIWindowManager::PreviousWindow: Deactivate");
+ int currentWindow = GetActiveWindow();
+ CGUIWindow *pCurrentWindow = GetWindow(currentWindow);
+ if (!pCurrentWindow)
+ return; // no windows or window history yet
+
+ // check to see whether our current window has a <previouswindow> tag
+ if (pCurrentWindow->GetPreviousWindow() != WINDOW_INVALID)
+ {
+ //! @todo we may need to test here for the
+ //! whether our history should be changed
+
+ // don't reactivate the previouswindow if it is ourselves.
+ if (currentWindow != pCurrentWindow->GetPreviousWindow())
+ ActivateWindow(pCurrentWindow->GetPreviousWindow());
+ return;
+ }
+ // get the previous window in our stack
+ if (m_windowHistory.size() < 2)
+ {
+ // no previous window history yet - check if we should just activate home
+ if (GetActiveWindow() != WINDOW_INVALID && GetActiveWindow() != WINDOW_HOME)
+ {
+ CloseWindowSync(pCurrentWindow);
+ ClearWindowHistory();
+ ActivateWindow(WINDOW_HOME);
+ }
+ return;
+ }
+ m_windowHistory.pop_back();
+ int previousWindow = GetActiveWindow();
+ m_windowHistory.emplace_back(currentWindow);
+
+ CGUIWindow *pNewWindow = GetWindow(previousWindow);
+ if (!pNewWindow)
+ {
+ CLog::Log(LOGERROR, "Unable to activate the previous window");
+ CloseWindowSync(pCurrentWindow);
+ ClearWindowHistory();
+ ActivateWindow(WINDOW_HOME);
+ return;
+ }
+
+ // ok to go to the previous window now
+
+ // tell our info manager which window we are going to
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetNextWindow(previousWindow);
+
+ // deinitialize our window
+ CloseWindowSync(pCurrentWindow);
+
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetNextWindow(WINDOW_INVALID);
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetPreviousWindow(currentWindow);
+
+ // remove the current window off our window stack
+ m_windowHistory.pop_back();
+
+ // ok, initialize the new window
+ CLog::Log(LOGDEBUG,"CGUIWindowManager::PreviousWindow: Activate new");
+ CGUIMessage msg2(GUI_MSG_WINDOW_INIT, 0, 0, WINDOW_INVALID, GetActiveWindow());
+ pNewWindow->OnMessage(msg2);
+
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetPreviousWindow(WINDOW_INVALID);
+}
+
+void CGUIWindowManager::ChangeActiveWindow(int newWindow, const std::string& strPath)
+{
+ std::vector<std::string> params;
+ if (!strPath.empty())
+ params.emplace_back(strPath);
+ ActivateWindow(newWindow, params, true);
+}
+
+void CGUIWindowManager::ActivateWindow(int iWindowID, const std::string& strPath)
+{
+ std::vector<std::string> params;
+ if (!strPath.empty())
+ params.emplace_back(strPath);
+ ActivateWindow(iWindowID, params, false);
+}
+
+void CGUIWindowManager::ForceActivateWindow(int iWindowID, const std::string& strPath)
+{
+ std::vector<std::string> params;
+ if (!strPath.empty())
+ params.emplace_back(strPath);
+ ActivateWindow(iWindowID, params, false, true);
+}
+
+void CGUIWindowManager::ActivateWindow(int iWindowID, const std::vector<std::string>& params, bool swappingWindows /* = false */, bool force /* = false */)
+{
+ if (!CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ // make sure graphics lock is not held
+ CSingleExit leaveIt(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTIVATE_WINDOW, iWindowID,
+ swappingWindows ? 1 : 0, nullptr, "", params);
+ }
+ else
+ {
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ ActivateWindow_Internal(iWindowID, params, swappingWindows, force);
+ }
+}
+
+void CGUIWindowManager::ActivateWindow_Internal(int iWindowID, const std::vector<std::string>& params, bool swappingWindows, bool force /* = false */)
+{
+ // translate virtual windows
+ if (iWindowID == WINDOW_START)
+ { // virtual start window
+ iWindowID = g_SkinInfo->GetStartWindow();
+ }
+
+ // debug
+ CLog::Log(LOGDEBUG, "Activating window ID: {}", iWindowID);
+
+ // make sure we check mediasources from home
+ if (GetActiveWindow() == WINDOW_HOME)
+ {
+ g_passwordManager.SetMediaSourcePath(!params.empty() ? params[0] : "");
+ }
+ else
+ {
+ g_passwordManager.SetMediaSourcePath("");
+ }
+
+ if (!g_passwordManager.CheckMenuLock(iWindowID))
+ {
+ CLog::Log(LOGERROR,
+ "MasterCode or MediaSource-code is wrong: Window with id {} will not be loaded! "
+ "Enter a correct code!",
+ iWindowID);
+ if (GetActiveWindow() == WINDOW_INVALID && iWindowID != WINDOW_HOME)
+ ActivateWindow(WINDOW_HOME);
+ return;
+ }
+
+ // first check existence of the window we wish to activate.
+ CGUIWindow *pNewWindow = GetWindow(iWindowID);
+ if (!pNewWindow)
+ { // nothing to see here - move along
+ CLog::Log(LOGERROR, "Unable to locate window with id {}. Check skin files",
+ iWindowID - WINDOW_HOME);
+ if (IsWindowActive(WINDOW_STARTUP_ANIM))
+ ActivateWindow(WINDOW_HOME);
+ return ;
+ }
+ else if (!pNewWindow->CanBeActivated())
+ {
+ if (IsWindowActive(WINDOW_STARTUP_ANIM))
+ ActivateWindow(WINDOW_HOME);
+ return;
+ }
+ else if (pNewWindow->IsDialog())
+ { // if we have a dialog, we do a DoModal() rather than activate the window
+ if (!pNewWindow->IsDialogRunning())
+ {
+ CSingleExit exitit(CServiceBroker::GetWinSystem()->GetGfxContext());
+ static_cast<CGUIDialog *>(pNewWindow)->Open(params.size() > 0 ? params[0] : "");
+ // Invalidate underlying windows after closing a modal dialog
+ MarkDirty();
+ }
+ return;
+ }
+
+ // don't activate a window if there are active modal dialogs of type MODAL
+ if (!force && HasModalDialog(true))
+ {
+ CLog::Log(LOGINFO, "Activate of window '{}' refused because there are active modal dialogs",
+ iWindowID);
+ CServiceBroker::GetGUI()->GetAudioManager().PlayActionSound(CAction(ACTION_ERROR));
+ return;
+ }
+
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetNextWindow(iWindowID);
+
+ // deactivate any window
+ int currentWindow = GetActiveWindow();
+ CGUIWindow *pWindow = GetWindow(currentWindow);
+ if (pWindow)
+ CloseWindowSync(pWindow, iWindowID);
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetNextWindow(WINDOW_INVALID);
+
+ // Add window to the history list (we must do this before we activate it,
+ // as all messages done in WINDOW_INIT will want to be sent to the new
+ // topmost window). If we are swapping windows, we pop the old window
+ // off the history stack
+ if (swappingWindows && !m_windowHistory.empty())
+ m_windowHistory.pop_back();
+ AddToWindowHistory(iWindowID);
+
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetPreviousWindow(currentWindow);
+ // Send the init message
+ CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0, currentWindow, iWindowID);
+ msg.SetStringParams(params);
+ pNewWindow->OnMessage(msg);
+// CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetPreviousWindow(WINDOW_INVALID);
+}
+
+void CGUIWindowManager::CloseDialogs(bool forceClose) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ //This is to avoid an assert about out of bounds iterator
+ //when m_activeDialogs happens to be empty
+ if (m_activeDialogs.empty())
+ return;
+
+ auto activeDialogs = m_activeDialogs;
+ for (const auto& window : activeDialogs)
+ {
+ if (window->IsModalDialog())
+ window->Close(forceClose);
+ }
+}
+
+void CGUIWindowManager::CloseInternalModalDialogs(bool forceClose) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ if (m_activeDialogs.empty())
+ return;
+
+ auto activeDialogs = m_activeDialogs;
+ for (const auto& window : activeDialogs)
+ {
+ if (window->IsModalDialog() && !IsAddonWindow(window->GetID()) && !IsPythonWindow(window->GetID()))
+ window->Close(forceClose);
+ }
+}
+
+// SwitchToFullScreen() returns true if a switch is made, else returns false
+bool CGUIWindowManager::SwitchToFullScreen(bool force /* = false */)
+{
+ // don't switch if the slideshow is active
+ if (IsWindowActive(WINDOW_SLIDESHOW))
+ return false;
+
+ // if playing from the video info window, close it first!
+ if (IsModalDialogTopmost(WINDOW_DIALOG_VIDEO_INFO))
+ {
+ CGUIDialogVideoInfo* pDialog = GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_VIDEO_INFO);
+ if (pDialog)
+ pDialog->Close(true);
+ }
+
+ // if playing from the album info window, close it first!
+ if (IsModalDialogTopmost(WINDOW_DIALOG_MUSIC_INFO))
+ {
+ CGUIDialogVideoInfo* pDialog = GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_MUSIC_INFO);
+ if (pDialog)
+ pDialog->Close(true);
+ }
+
+ // if playing from the song info window, close it first!
+ if (IsModalDialogTopmost(WINDOW_DIALOG_SONG_INFO))
+ {
+ CGUIDialogVideoInfo* pDialog = GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_SONG_INFO);
+ if (pDialog)
+ pDialog->Close(true);
+ }
+
+ const int activeWindowID = GetActiveWindow();
+ int windowID = WINDOW_INVALID;
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ // See if we're playing a game
+ if (activeWindowID != WINDOW_FULLSCREEN_GAME && appPlayer->IsPlayingGame())
+ windowID = WINDOW_FULLSCREEN_GAME;
+
+ // See if we're playing a video
+ else if (activeWindowID != WINDOW_FULLSCREEN_VIDEO && appPlayer->IsPlayingVideo())
+ windowID = WINDOW_FULLSCREEN_VIDEO;
+
+ // See if we're playing an audio song
+ if (activeWindowID != WINDOW_VISUALISATION && appPlayer->IsPlayingAudio())
+ windowID = WINDOW_VISUALISATION;
+
+ if (windowID != WINDOW_INVALID && (force || windowID != activeWindowID))
+ {
+ if (force)
+ ForceActivateWindow(windowID);
+ else
+ ActivateWindow(windowID);
+
+ return true;
+ }
+
+ return false;
+}
+
+void CGUIWindowManager::OnApplicationMessage(ThreadMessage* pMsg)
+{
+ switch (pMsg->dwMessage)
+ {
+ case TMSG_GUI_DIALOG_OPEN:
+ {
+ if (pMsg->lpVoid)
+ static_cast<CGUIDialog*>(pMsg->lpVoid)->Open(pMsg->param2, pMsg->strParam);
+ else
+ {
+ CGUIDialog* pDialog = static_cast<CGUIDialog*>(GetWindow(pMsg->param1));
+ if (pDialog)
+ pDialog->Open(pMsg->strParam);
+ }
+ }
+ break;
+
+ case TMSG_GUI_WINDOW_CLOSE:
+ {
+ CGUIWindow *window = static_cast<CGUIWindow *>(pMsg->lpVoid);
+ if (window)
+ window->Close((pMsg->param1 & 0x1) ? true : false, pMsg->param1, (pMsg->param1 & 0x2) ? true : false);
+ }
+ break;
+
+ case TMSG_GUI_ACTIVATE_WINDOW:
+ {
+ ActivateWindow(pMsg->param1, pMsg->params, pMsg->param2 > 0);
+ }
+ break;
+
+ case TMSG_GUI_PREVIOUS_WINDOW:
+ {
+ PreviousWindow();
+ }
+ break;
+
+ case TMSG_GUI_ADDON_DIALOG:
+ {
+ if (pMsg->lpVoid)
+ {
+ static_cast<ADDON::CGUIAddonWindowDialog*>(pMsg->lpVoid)->Show_Internal(pMsg->param2 > 0);
+ }
+ }
+ break;
+
+#ifdef HAS_PYTHON
+ case TMSG_GUI_PYTHON_DIALOG:
+ {
+ // This hack is not much better but at least I don't need to make ApplicationMessenger
+ // know about Addon (Python) specific classes.
+ CAction caction(pMsg->param1);
+ static_cast<CGUIWindow*>(pMsg->lpVoid)->OnAction(caction);
+ }
+ break;
+#endif
+
+ case TMSG_GUI_ACTION:
+ {
+ if (pMsg->lpVoid)
+ {
+ CAction *action = static_cast<CAction *>(pMsg->lpVoid);
+ if (pMsg->param1 == WINDOW_INVALID)
+ g_application.OnAction(*action);
+ else
+ {
+ CGUIWindow *pWindow = GetWindow(pMsg->param1);
+ if (pWindow)
+ pWindow->OnAction(*action);
+ else
+ CLog::Log(LOGWARNING, "Failed to get window with ID {} to send an action to",
+ pMsg->param1);
+ }
+ delete action;
+ }
+ }
+ break;
+
+ case TMSG_GUI_MESSAGE:
+ if (pMsg->lpVoid)
+ {
+ CGUIMessage *message = static_cast<CGUIMessage *>(pMsg->lpVoid);
+ SendMessage(*message, pMsg->param1);
+ delete message;
+ }
+ break;
+
+ case TMSG_GUI_DIALOG_YESNO:
+ {
+
+ if (!pMsg->lpVoid && pMsg->param1 < 0 && pMsg->param2 < 0)
+ return;
+
+ auto dialog = static_cast<CGUIDialogYesNo*>(GetWindow(WINDOW_DIALOG_YES_NO));
+ if (!dialog)
+ return;
+
+ if (pMsg->lpVoid)
+ pMsg->SetResult(dialog->ShowAndGetInput(*static_cast<HELPERS::DialogYesNoMessage*>(pMsg->lpVoid)));
+ else
+ {
+ HELPERS::DialogYesNoMessage options;
+ options.heading = pMsg->param1;
+ options.text = pMsg->param2;
+ pMsg->SetResult(dialog->ShowAndGetInput(options));
+ }
+
+ }
+ break;
+
+ case TMSG_GUI_DIALOG_OK:
+ {
+
+ if (!pMsg->lpVoid && pMsg->param1 < 0 && pMsg->param2 < 0)
+ return;
+
+ auto dialogOK = static_cast<CGUIDialogOK*>(GetWindow(WINDOW_DIALOG_OK));
+ if (!dialogOK)
+ return;
+
+ if (pMsg->lpVoid)
+ dialogOK->ShowAndGetInput(*static_cast<HELPERS::DialogOKMessage*>(pMsg->lpVoid));
+ else
+ {
+ HELPERS::DialogOKMessage options;
+ options.heading = pMsg->param1;
+ options.text = pMsg->param2;
+ dialogOK->ShowAndGetInput(options);
+ }
+ pMsg->SetResult(static_cast<int>(dialogOK->IsConfirmed()));
+ }
+ break;
+ }
+}
+
+int CGUIWindowManager::GetMessageMask()
+{
+ return TMSG_MASK_WINDOWMANAGER;
+}
+
+bool CGUIWindowManager::OnAction(const CAction &action) const
+{
+ auto actionId = action.GetID();
+ if (actionId == ACTION_GESTURE_BEGIN)
+ {
+ m_touchGestureActive = true;
+ }
+
+ bool ret;
+ if (!m_inhibitTouchGestureEvents || !action.IsGesture())
+ {
+ ret = HandleAction(action);
+ }
+ else
+ {
+ // We swallow the event, so it is handled
+ ret = true;
+ CLog::Log(LOGDEBUG, "Swallowing touch action {} due to inhibition on window switch", actionId);
+ }
+
+ if (actionId == ACTION_GESTURE_END || actionId == ACTION_GESTURE_ABORT)
+ {
+ m_touchGestureActive = false;
+ m_inhibitTouchGestureEvents = false;
+ }
+
+ return ret;
+}
+
+bool CGUIWindowManager::HandleAction(CAction const& action) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ size_t topmost = m_activeDialogs.size();
+ while (topmost)
+ {
+ CGUIWindow *dialog = m_activeDialogs[--topmost];
+ lock.unlock();
+ if (dialog->IsModalDialog())
+ { // we have the topmost modal dialog
+ if (!dialog->IsAnimating(ANIM_TYPE_WINDOW_CLOSE))
+ {
+ bool fallThrough = (dialog->GetID() == WINDOW_DIALOG_FULLSCREEN_INFO);
+ if (dialog->OnAction(action))
+ return true;
+ // dialog didn't want the action - we'd normally return false
+ // but for some dialogs we want to drop the actions through
+ if (fallThrough)
+ {
+ lock.lock();
+ break;
+ }
+ return false;
+ }
+ CLog::Log(LOGWARNING,
+ "CGUIWindowManager - {} - ignoring action {}, because topmost modal dialog closing "
+ "animation is running",
+ __FUNCTION__, action.GetID());
+ return true; // do nothing with the action until the anim is finished
+ }
+ lock.lock();
+ if (topmost > m_activeDialogs.size())
+ topmost = m_activeDialogs.size();
+ }
+ lock.unlock();
+ CGUIWindow* window = GetWindow(GetActiveWindow());
+ if (window)
+ return window->OnAction(action);
+ return false;
+}
+
+bool RenderOrderSortFunction(CGUIWindow *first, CGUIWindow *second)
+{
+ return first->GetRenderOrder() < second->GetRenderOrder();
+}
+
+void CGUIWindowManager::Process(unsigned int currentTime)
+{
+ assert(CServiceBroker::GetAppMessenger()->IsProcessThread());
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ m_dirtyregions.clear();
+
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->DoProcess(currentTime, m_dirtyregions);
+
+ // process all dialogs - visibility may change etc.
+ for (const auto& entry : m_mapWindows)
+ {
+ CGUIWindow *pWindow = entry.second;
+ if (pWindow && pWindow->IsDialog())
+ pWindow->DoProcess(currentTime, m_dirtyregions);
+ }
+
+ for (auto& itr : m_dirtyregions)
+ m_tracker.MarkDirtyRegion(itr);
+}
+
+void CGUIWindowManager::MarkDirty()
+{
+ MarkDirty(CRect(0, 0, float(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()), float(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())));
+}
+
+void CGUIWindowManager::MarkDirty(const CRect& rect)
+{
+ m_tracker.MarkDirtyRegion(CDirtyRegion(rect));
+
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->MarkDirtyRegion();
+
+ // make copy of vector as we may remove items from it as we go
+ auto activeDialogs = m_activeDialogs;
+ for (const auto& window : activeDialogs)
+ if (window->IsDialogRunning())
+ window->MarkDirtyRegion();
+}
+
+void CGUIWindowManager::RenderPass() const
+{
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ {
+ pWindow->ClearBackground();
+ pWindow->DoRender();
+ }
+
+ // we render the dialogs based on their render order.
+ auto renderList = m_activeDialogs;
+ stable_sort(renderList.begin(), renderList.end(), RenderOrderSortFunction);
+
+ for (const auto& window : renderList)
+ {
+ if (window->IsDialogRunning())
+ window->DoRender();
+ }
+}
+
+void CGUIWindowManager::RenderEx() const
+{
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->RenderEx();
+
+ // We don't call RenderEx for now on dialogs since it is used
+ // to trigger non gui video rendering. We can activate it later at any time.
+ /*
+ vector<CGUIWindow *> &activeDialogs = m_activeDialogs;
+ for (iDialog it = activeDialogs.begin(); it != activeDialogs.end(); ++it)
+ {
+ if ((*it)->IsDialogRunning())
+ (*it)->RenderEx();
+ }
+ */
+}
+
+bool CGUIWindowManager::Render()
+{
+ assert(CServiceBroker::GetAppMessenger()->IsProcessThread());
+ CSingleExit lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ CDirtyRegionList dirtyRegions = m_tracker.GetDirtyRegions();
+
+ bool hasRendered = false;
+ // If we visualize the regions we will always render the entire viewport
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiVisualizeDirtyRegions || CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_FILL_VIEWPORT_ALWAYS)
+ {
+ RenderPass();
+ hasRendered = true;
+ }
+ else if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiAlgorithmDirtyRegions == DIRTYREGION_SOLVER_FILL_VIEWPORT_ON_CHANGE)
+ {
+ if (!dirtyRegions.empty())
+ {
+ RenderPass();
+ hasRendered = true;
+ }
+ }
+ else
+ {
+ for (const auto& i : dirtyRegions)
+ {
+ if (i.IsEmpty())
+ continue;
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetScissors(i);
+ RenderPass();
+ hasRendered = true;
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetScissors();
+ }
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_guiVisualizeDirtyRegions)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(), false);
+ const CDirtyRegionList &markedRegions = m_tracker.GetMarkedRegions();
+ for (const auto& i : markedRegions)
+ CGUITexture::DrawQuad(i, 0x0fff0000);
+ for (const auto& i : dirtyRegions)
+ CGUITexture::DrawQuad(i, 0x4c00ff00);
+ }
+
+ return hasRendered;
+}
+
+void CGUIWindowManager::AfterRender()
+{
+ m_tracker.CleanMarkedRegions();
+
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->AfterRender();
+
+ // make copy of vector as we may remove items from it as we go
+ auto activeDialogs = m_activeDialogs;
+ for (const auto& window : activeDialogs)
+ {
+ if (window->IsDialogRunning())
+ {
+ window->AfterRender();
+ // Dialog state can affect visibility states
+ if (pWindow && window->IsControlDirty())
+ pWindow->MarkDirtyRegion();
+ }
+ }
+}
+
+void CGUIWindowManager::FrameMove()
+{
+ assert(CServiceBroker::GetAppMessenger()->IsProcessThread());
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ if(m_iNested == 0)
+ {
+ // delete any windows queued for deletion
+ for (const auto& window : m_deleteWindows)
+ {
+ // Free any window resources
+ window->FreeResources(true);
+ delete window;
+ }
+ m_deleteWindows.clear();
+ }
+
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->FrameMove();
+ // update any dialogs - we take a copy of the vector as some dialogs may close themselves
+ // during this call
+ auto dialogs = m_activeDialogs;
+ for (const auto& window : dialogs)
+ {
+ window->FrameMove();
+ }
+
+ CServiceBroker::GetGUI()->GetInfoManager().UpdateAVInfo();
+}
+
+CGUIDialog* CGUIWindowManager::GetDialog(int id) const
+{
+ CGUIWindow *window = GetWindow(id);
+ if (window && window->IsDialog())
+ return dynamic_cast<CGUIDialog*>(window);
+ return nullptr;
+}
+
+CGUIWindow* CGUIWindowManager::GetWindow(int id) const
+{
+ if (id == 0 || id == WINDOW_INVALID)
+ return nullptr;
+
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ auto it = m_mapWindows.find(id);
+ if (it != m_mapWindows.end())
+ return it->second;
+ return nullptr;
+}
+
+bool CGUIWindowManager::ProcessRenderLoop(bool renderOnly)
+{
+ bool renderGui = true;
+
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread() && m_pCallback)
+ {
+ renderGui = m_pCallback->GetRenderGUI();
+ m_iNested++;
+ if (!renderOnly)
+ m_pCallback->Process();
+ m_pCallback->FrameMove(!renderOnly);
+ m_pCallback->Render();
+ m_iNested--;
+ }
+ if (g_application.m_bStop || !renderGui)
+ return false;
+ else
+ return true;
+}
+
+void CGUIWindowManager::SetCallback(IWindowManagerCallback& callback)
+{
+ m_pCallback = &callback;
+}
+
+void CGUIWindowManager::DeInitialize()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ // Need a copy because addon-dialogs removes itself on Close()
+ std::unordered_map<int, CGUIWindow*> closeMap(m_mapWindows);
+ for (const auto& entry : closeMap)
+ {
+ CGUIWindow* pWindow = entry.second;
+ if (IsWindowActive(entry.first, false))
+ {
+ pWindow->DisableAnimations();
+ pWindow->Close(true);
+ }
+ pWindow->ResetControlStates();
+ pWindow->FreeResources(true);
+ }
+ UnloadNotOnDemandWindows();
+
+ m_vecMsgTargets.erase( m_vecMsgTargets.begin(), m_vecMsgTargets.end() );
+
+ // destroy our custom windows...
+ for (int i = 0; i < int(m_vecCustomWindows.size()); i++)
+ {
+ CGUIWindow *pWindow = m_vecCustomWindows[i];
+ RemoveFromWindowHistory(pWindow->GetID());
+ Remove(pWindow->GetID());
+ delete pWindow;
+ }
+
+ // clear our vectors of windows
+ m_vecCustomWindows.clear();
+ m_activeDialogs.clear();
+
+ m_initialized = false;
+}
+
+/// \brief Unroute window
+/// \param id ID of the window routed
+void CGUIWindowManager::RemoveDialog(int id)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_activeDialogs.erase(std::remove_if(m_activeDialogs.begin(),
+ m_activeDialogs.end(),
+ [id](CGUIWindow* dialog) { return dialog->GetID() == id; }),
+ m_activeDialogs.end());
+}
+
+bool CGUIWindowManager::HasModalDialog(bool ignoreClosing) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (const auto& window : m_activeDialogs)
+ {
+ if (window->IsDialog() &&
+ window->IsModalDialog() &&
+ (!ignoreClosing || !window->IsAnimating(ANIM_TYPE_WINDOW_CLOSE)))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CGUIWindowManager::HasVisibleModalDialog() const
+{
+ return HasModalDialog(false);
+}
+
+int CGUIWindowManager::GetTopmostDialog(bool modal, bool ignoreClosing) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (auto it = m_activeDialogs.rbegin(); it != m_activeDialogs.rend(); ++it)
+ {
+ CGUIWindow *dialog = *it;
+ if ((!modal || dialog->IsModalDialog()) && (!ignoreClosing || !dialog->IsAnimating(ANIM_TYPE_WINDOW_CLOSE)))
+ return dialog->GetID();
+ }
+ return WINDOW_INVALID;
+}
+
+int CGUIWindowManager::GetTopmostDialog(bool ignoreClosing /*= false*/) const
+{
+ return GetTopmostDialog(false, ignoreClosing);
+}
+
+int CGUIWindowManager::GetTopmostModalDialog(bool ignoreClosing /*= false*/) const
+{
+ return GetTopmostDialog(true, ignoreClosing);
+}
+
+void CGUIWindowManager::SendThreadMessage(CGUIMessage& message, int window /*= 0*/)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ CGUIMessage* msg = new CGUIMessage(message);
+ m_vecThreadMessages.emplace_back(std::pair<CGUIMessage*, int>(msg,window));
+}
+
+void CGUIWindowManager::DispatchThreadMessages()
+{
+ // This method only be called in the xbmc main thread.
+
+ // XXX: for more info of this method
+ // check the pr here: https://github.com/xbmc/xbmc/pull/2253
+
+ // As a thread message queue service, it should follow these rules:
+ // 1. [Must] Thread safe, message can be pushed into queue in arbitrary thread context.
+ // 2. Messages [must] be processed in dispatch message thread context with the same
+ // order as they be pushed into the queue.
+ // 3. Dispatch function [must] support call itself during message process procedure,
+ // and do not break other rules listed here. to make it clear: in the
+ // SendMessage(), it could start another xbmc main thread loop, calling
+ // DispatchThreadMessages() in it's internal loop, this must be supported.
+ // 4. During DispatchThreadMessages() processing, any new pushed message [should] not
+ // be processed by the current loop in DispatchThreadMessages(), prevent dead loop.
+ // 5. If possible, queued messages can be removed by certain filter condition
+ // and not break above.
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ while (!m_vecThreadMessages.empty())
+ {
+ // pop up one message per time to make messages be processed by order.
+ // this will ensure rule No.2 & No.3
+ CGUIMessage *pMsg = m_vecThreadMessages.front().first;
+ int window = m_vecThreadMessages.front().second;
+ m_vecThreadMessages.pop_front();
+
+ lock.unlock();
+
+ // XXX: during SendMessage(), there could be a deeper 'xbmc main loop' inited by e.g. doModal
+ // which may loop there and callback to DispatchThreadMessages() multiple times.
+ if (window)
+ SendMessage( *pMsg, window );
+ else
+ SendMessage( *pMsg );
+ delete pMsg;
+
+ lock.lock();
+ }
+}
+
+int CGUIWindowManager::RemoveThreadMessageByMessageIds(int *pMessageIDList)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ int removedMsgCount = 0;
+ for (std::list < std::pair<CGUIMessage*,int> >::iterator it = m_vecThreadMessages.begin();
+ it != m_vecThreadMessages.end();)
+ {
+ CGUIMessage *pMsg = it->first;
+ int *pMsgID;
+ for(pMsgID = pMessageIDList; *pMsgID != 0; ++pMsgID)
+ if (pMsg->GetMessage() == *pMsgID)
+ break;
+ if (*pMsgID)
+ {
+ it = m_vecThreadMessages.erase(it);
+ delete pMsg;
+ ++removedMsgCount;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+ return removedMsgCount;
+}
+
+void CGUIWindowManager::AddMsgTarget(IMsgTargetCallback* pMsgTarget)
+{
+ m_vecMsgTargets.emplace_back(pMsgTarget);
+}
+
+int CGUIWindowManager::GetActiveWindow() const
+{
+ if (!m_windowHistory.empty())
+ return m_windowHistory.back();
+ return WINDOW_INVALID;
+}
+
+int CGUIWindowManager::GetActiveWindowOrDialog() const
+{
+ // if there is a dialog active get the dialog id instead
+ int iWin = GetTopmostModalDialog() & WINDOW_ID_MASK;
+ if (iWin != WINDOW_INVALID)
+ return iWin;
+
+ // get the currently active window
+ return GetActiveWindow() & WINDOW_ID_MASK;
+}
+
+bool CGUIWindowManager::IsWindowActive(int id, bool ignoreClosing /* = true */) const
+{
+ // mask out multiple instances of the same window
+ id &= WINDOW_ID_MASK;
+ if ((GetActiveWindow() & WINDOW_ID_MASK) == id)
+ return true;
+ // run through the dialogs
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (const auto& window : m_activeDialogs)
+ {
+ if ((window->GetID() & WINDOW_ID_MASK) == id && (!ignoreClosing || !window->IsAnimating(ANIM_TYPE_WINDOW_CLOSE)))
+ return true;
+ }
+ return false; // window isn't active
+}
+
+bool CGUIWindowManager::IsWindowActive(const std::string &xmlFile, bool ignoreClosing /* = true */) const
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ CGUIWindow *window = GetWindow(GetActiveWindow());
+ if (window && StringUtils::EqualsNoCase(URIUtils::GetFileName(window->GetProperty("xmlfile").asString()), xmlFile))
+ return true;
+ // run through the dialogs
+ for (const auto& window : m_activeDialogs)
+ {
+ if (StringUtils::EqualsNoCase(URIUtils::GetFileName(window->GetProperty("xmlfile").asString()), xmlFile) &&
+ (!ignoreClosing || !window->IsAnimating(ANIM_TYPE_WINDOW_CLOSE)))
+ return true;
+ }
+ return false; // window isn't active
+}
+
+bool CGUIWindowManager::IsWindowVisible(int id) const
+{
+ return IsWindowActive(id, false);
+}
+
+bool CGUIWindowManager::IsWindowVisible(const std::string &xmlFile) const
+{
+ return IsWindowActive(xmlFile, false);
+}
+
+void CGUIWindowManager::LoadNotOnDemandWindows()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (const auto& entry : m_mapWindows)
+ {
+ CGUIWindow *pWindow = entry.second;
+ if (pWindow->GetLoadType() == CGUIWindow::LOAD_ON_GUI_INIT)
+ {
+ pWindow->FreeResources(true);
+ pWindow->Initialize();
+ }
+ }
+}
+
+void CGUIWindowManager::UnloadNotOnDemandWindows()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (const auto& entry : m_mapWindows)
+ {
+ CGUIWindow *pWindow = entry.second;
+ if (pWindow->GetLoadType() == CGUIWindow::LOAD_ON_GUI_INIT ||
+ pWindow->GetLoadType() == CGUIWindow::KEEP_IN_MEMORY)
+ {
+ pWindow->FreeResources(true);
+ }
+ }
+}
+
+void CGUIWindowManager::AddToWindowHistory(int newWindowID)
+{
+ // Check the window stack to see if this window is in our history,
+ // and if so, pop all the other windows off the stack so that we
+ // always have a predictable "Back" behaviour for each window
+ std::deque<int> history = m_windowHistory;
+ while (!history.empty())
+ {
+ if (history.back() == newWindowID)
+ break;
+ history.pop_back();
+ }
+ if (!history.empty())
+ { // found window in history
+ m_windowHistory.swap(history);
+ }
+ else
+ {
+ // didn't find window in history - add it to the stack
+ m_windowHistory.emplace_back(newWindowID);
+ }
+}
+
+void CGUIWindowManager::RemoveFromWindowHistory(int windowID)
+{
+ std::deque<int> history = m_windowHistory;
+
+ // pop windows from stack until we found the window
+ while (!history.empty())
+ {
+ if (history.back() == windowID)
+ break;
+ history.pop_back();
+ }
+
+ // found window in history
+ if (!history.empty())
+ {
+ history.pop_back(); // remove window from stack
+ m_windowHistory.swap(history);
+ }
+}
+
+bool CGUIWindowManager::IsModalDialogTopmost(int id) const
+{
+ return IsDialogTopmost(id, true);
+}
+
+bool CGUIWindowManager::IsModalDialogTopmost(const std::string &xmlFile) const
+{
+ return IsDialogTopmost(xmlFile, true);
+}
+
+bool CGUIWindowManager::IsDialogTopmost(int id, bool modal /* = false */) const
+{
+ CGUIWindow *topmost = GetWindow(GetTopmostDialog(modal, false));
+ if (topmost && (topmost->GetID() & WINDOW_ID_MASK) == id)
+ return true;
+ return false;
+}
+
+bool CGUIWindowManager::IsDialogTopmost(const std::string &xmlFile, bool modal /* = false */) const
+{
+ CGUIWindow *topmost = GetWindow(GetTopmostDialog(modal, false));
+ if (topmost && StringUtils::EqualsNoCase(URIUtils::GetFileName(topmost->GetProperty("xmlfile").asString()), xmlFile))
+ return true;
+ return false;
+}
+
+bool CGUIWindowManager::HasVisibleControls()
+{
+ CSingleExit lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ if (m_activeDialogs.empty())
+ {
+ CGUIWindow *window(GetWindow(GetActiveWindow()));
+ return !window || window->HasVisibleControls();
+ }
+ else
+ return true;
+}
+
+void CGUIWindowManager::ClearWindowHistory()
+{
+ while (!m_windowHistory.empty())
+ m_windowHistory.pop_back();
+}
+
+void CGUIWindowManager::CloseWindowSync(CGUIWindow *window, int nextWindowID /*= 0*/)
+{
+ // Abort touch action if active
+ if (m_touchGestureActive && !m_inhibitTouchGestureEvents)
+ {
+ CLog::Log(LOGDEBUG, "Closing window {} with active touch gesture, sending gesture abort event",
+ window->GetID());
+ window->OnAction({ACTION_GESTURE_ABORT});
+ // Don't send any mid-gesture events to next window until new touch starts
+ m_inhibitTouchGestureEvents = true;
+ }
+
+ window->Close(false, nextWindowID);
+
+ bool renderLoopProcessed = true;
+ while (window->IsAnimating(ANIM_TYPE_WINDOW_CLOSE) && renderLoopProcessed)
+ renderLoopProcessed = ProcessRenderLoop(true);
+}
+
+#ifdef _DEBUG
+void CGUIWindowManager::DumpTextureUse()
+{
+ CGUIWindow* pWindow = GetWindow(GetActiveWindow());
+ if (pWindow)
+ pWindow->DumpTextureUse();
+
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (const auto& window : m_activeDialogs)
+ {
+ if (window->IsDialogRunning())
+ window->DumpTextureUse();
+ }
+}
+#endif
diff --git a/xbmc/guilib/GUIWindowManager.h b/xbmc/guilib/GUIWindowManager.h
new file mode 100644
index 0000000..9bbd281
--- /dev/null
+++ b/xbmc/guilib/GUIWindowManager.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirtyRegionTracker.h"
+#include "GUIWindow.h"
+#include "IMsgTargetCallback.h"
+#include "IWindowManagerCallback.h"
+#include "guilib/WindowIDs.h"
+#include "messaging/IMessageTarget.h"
+
+#include <list>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+class CGUIDialog;
+class CGUIMediaWindow;
+
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(push, 8)
+#endif
+enum class DialogModalityType;
+#ifdef TARGET_WINDOWS_STORE
+#pragma pack(pop)
+#endif
+
+namespace KODI
+{
+ namespace MESSAGING
+ {
+ class CApplicationMessenger;
+ }
+}
+
+#define WINDOW_ID_MASK 0xffff
+
+/*!
+ \ingroup winman
+ \brief
+ */
+class CGUIWindowManager : public KODI::MESSAGING::IMessageTarget
+{
+ friend CGUIDialog;
+ friend CGUIMediaWindow;
+public:
+ CGUIWindowManager();
+ ~CGUIWindowManager() override;
+ bool SendMessage(CGUIMessage& message);
+ bool SendMessage(int message, int senderID, int destID, int param1 = 0, int param2 = 0);
+ bool SendMessage(CGUIMessage& message, int window);
+ void Initialize();
+ void Add(CGUIWindow* pWindow);
+ void AddUniqueInstance(CGUIWindow *window);
+ void AddCustomWindow(CGUIWindow* pWindow);
+ void Remove(int id);
+ void Delete(int id);
+ void ActivateWindow(int iWindowID, const std::string &strPath = "");
+ void ForceActivateWindow(int iWindowID, const std::string &strPath = "");
+ void ChangeActiveWindow(int iNewID, const std::string &strPath = "");
+ void ActivateWindow(int iWindowID, const std::vector<std::string>& params, bool swappingWindows = false, bool force = false);
+ void PreviousWindow();
+
+ /**
+ * \brief Switch window to fullscreen
+ *
+ * \param force enforce fullscreen switch
+ * \return True if switch is made to fullscreen, otherwise False
+ */
+ bool SwitchToFullScreen(bool force = false);
+
+ void CloseDialogs(bool forceClose = false) const;
+ void CloseInternalModalDialogs(bool forceClose = false) const;
+
+ void OnApplicationMessage(KODI::MESSAGING::ThreadMessage* pMsg) override;
+ int GetMessageMask() override;
+
+ // OnAction() runs through our active dialogs and windows and sends the message
+ // off to the callbacks (application, python, playlist player) and to the
+ // currently focused window(s). Returns true only if the message is handled.
+ bool OnAction(const CAction &action) const;
+
+ /*! \brief Process active controls allowing them to animate before rendering.
+ */
+ void Process(unsigned int currentTime);
+
+ /*! \brief Mark the screen as dirty, forcing a redraw at the next Render()
+ */
+ void MarkDirty();
+
+ /*! \brief Mark a region as dirty, forcing a redraw at the next Render()
+ */
+ void MarkDirty(const CRect& rect);
+
+ /*! \brief Rendering of the current window and any dialogs
+ Render is called every frame to draw the current window and any dialogs.
+ It should only be called from the application thread.
+ Returns true only if it has rendered something.
+ */
+ bool Render();
+
+ void RenderEx() const;
+
+ /*! \brief Do any post render activities.
+ */
+ void AfterRender();
+
+ /*! \brief Per-frame updating of the current window and any dialogs
+ FrameMove is called every frame to update the current window and any dialogs
+ on screen. It should only be called from the application thread.
+ */
+ void FrameMove();
+
+ /*! \brief Return whether the window manager is initialized.
+ The window manager is initialized on skin load - if the skin isn't yet loaded,
+ no windows should be able to be initialized.
+ \return true if the window manager is initialized, false otherwise.
+ */
+ bool Initialized() const { return m_initialized; }
+
+ /*! \brief Create and initialize all windows and dialogs
+ */
+ void CreateWindows();
+
+ /*! \brief Destroy and remove all windows and dialogs
+ *
+ * \return true on success, false if destruction fails for any window
+ */
+ bool DestroyWindows();
+
+ /*! \brief Destroy and remove the window or dialog with the given id
+ *
+ *\param id the window id
+ */
+ void DestroyWindow(int id);
+
+ /*! \brief Return the window of type \code{T} with the given id or
+ * null if no window exists with the given id.
+ *
+ * \tparam T the window class type
+ * \param id the window id
+ * \return the window with for the given type \code{T} or null
+ */
+ template<typename T,
+ typename std::enable_if<std::is_base_of<CGUIWindow, T>::value>::type* = nullptr>
+ T* GetWindow(int id) const
+ {
+ return dynamic_cast<T*>(GetWindow(id));
+ }
+
+ /*! \brief Return the window with the given id or null.
+ *
+ * \param id the window id
+ * \return the window with the given id or null
+ */
+ CGUIWindow* GetWindow(int id) const;
+
+ /*! \brief Return the dialog window with the given id or null.
+ *
+ * \param id the dialog window id
+ * \return the dialog window with the given id or null
+ */
+ CGUIDialog* GetDialog(int id) const;
+
+ void SetCallback(IWindowManagerCallback& callback);
+ void DeInitialize();
+
+ /*! \brief Register a dialog as active dialog
+ *
+ * \param dialog The dialog to register as active dialog
+ */
+ void RegisterDialog(CGUIWindow* dialog);
+ void RemoveDialog(int id);
+
+ /*! \brief Get the ID of the topmost dialog
+ *
+ * \param ignoreClosing ignore dialog is closing
+ * \return the ID of the topmost dialog or WINDOW_INVALID if no dialog is active
+ */
+ int GetTopmostDialog(bool ignoreClosing = false) const;
+
+ /*! \brief Get the ID of the topmost modal dialog
+ *
+ * \param ignoreClosing ignore dialog is closing
+ * \return the ID of the topmost modal dialog or WINDOW_INVALID if no modal dialog is active
+ */
+ int GetTopmostModalDialog(bool ignoreClosing = false) const;
+
+ void SendThreadMessage(CGUIMessage& message, int window = 0);
+ void DispatchThreadMessages();
+ // method to removed queued messages with message id in the requested message id list.
+ // pMessageIDList: point to first integer of a 0 ends integer array.
+ int RemoveThreadMessageByMessageIds(int *pMessageIDList);
+ void AddMsgTarget( IMsgTargetCallback* pMsgTarget );
+ int GetActiveWindow() const;
+ int GetActiveWindowOrDialog() const;
+ bool HasModalDialog(bool ignoreClosing) const;
+ bool HasVisibleModalDialog() const;
+ bool IsDialogTopmost(int id, bool modal = false) const;
+ bool IsDialogTopmost(const std::string &xmlFile, bool modal = false) const;
+ bool IsModalDialogTopmost(int id) const;
+ bool IsModalDialogTopmost(const std::string &xmlFile) const;
+ bool IsWindowActive(int id, bool ignoreClosing = true) const;
+ bool IsWindowVisible(int id) const;
+ bool IsWindowActive(const std::string &xmlFile, bool ignoreClosing = true) const;
+ bool IsWindowVisible(const std::string &xmlFile) const;
+ /*! \brief Checks if the given window is an addon window.
+ *
+ * \return true if the given window is an addon window, otherwise false.
+ */
+ bool IsAddonWindow(int id) const { return (id >= WINDOW_ADDON_START && id <= WINDOW_ADDON_END); }
+ /*! \brief Checks if the given window is a python window.
+ *
+ * \return true if the given window is a python window, otherwise false.
+ */
+ bool IsPythonWindow(int id) const
+ {
+ return (id >= WINDOW_PYTHON_START && id <= WINDOW_PYTHON_END);
+ }
+
+ bool HasVisibleControls();
+
+#ifdef _DEBUG
+ void DumpTextureUse();
+#endif
+private:
+ void RenderPass() const;
+
+ void LoadNotOnDemandWindows();
+ void UnloadNotOnDemandWindows();
+ void AddToWindowHistory(int newWindowID);
+
+ /*!
+ \brief Check if the given window id is in the window history, and if so, remove this
+ window and all overlying windows from the history so that we always have a predictable
+ "Back" behaviour for each window.
+
+ \param windowID the window id to remove from the window history
+ */
+ void RemoveFromWindowHistory(int windowID);
+ void ClearWindowHistory();
+ void CloseWindowSync(CGUIWindow *window, int nextWindowID = 0);
+ int GetTopmostDialog(bool modal, bool ignoreClosing) const;
+
+ friend class KODI::MESSAGING::CApplicationMessenger;
+
+ /*! \brief Activate the given window.
+ *
+ * \param windowID The window ID to activate.
+ * \param params Parameter
+ * \param swappingWindows True if the window should be swapped with the previous window instead of put it in the window history, otherwise false
+ * \param force True to ignore checks which refuses opening the window, otherwise false
+ */
+ void ActivateWindow_Internal(int windowID, const std::vector<std::string> &params, bool swappingWindows, bool force = false);
+
+ bool ProcessRenderLoop(bool renderOnly);
+
+ bool HandleAction(const CAction &action) const;
+
+ std::unordered_map<int, CGUIWindow*> m_mapWindows;
+ std::vector<CGUIWindow*> m_vecCustomWindows;
+ std::vector<CGUIWindow*> m_activeDialogs;
+ std::vector<CGUIWindow*> m_deleteWindows;
+
+ std::deque<int> m_windowHistory;
+
+ IWindowManagerCallback* m_pCallback;
+ std::list< std::pair<CGUIMessage*,int> > m_vecThreadMessages;
+ CCriticalSection m_critSection;
+ std::vector<IMsgTargetCallback*> m_vecMsgTargets;
+
+ int m_iNested;
+ bool m_initialized;
+ mutable bool m_touchGestureActive{false};
+ mutable bool m_inhibitTouchGestureEvents{false};
+
+ CDirtyRegionList m_dirtyregions;
+ CDirtyRegionTracker m_tracker;
+};
diff --git a/xbmc/guilib/GUIWrappingListContainer.cpp b/xbmc/guilib/GUIWrappingListContainer.cpp
new file mode 100644
index 0000000..e1bc3c0
--- /dev/null
+++ b/xbmc/guilib/GUIWrappingListContainer.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWrappingListContainer.h"
+
+#include "FileItem.h"
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "input/Key.h"
+
+CGUIWrappingListContainer::CGUIWrappingListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition)
+ : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
+{
+ SetCursor(fixedPosition);
+ ControlType = GUICONTAINER_WRAPLIST;
+ m_type = VIEW_TYPE_LIST;
+ m_extraItems = 0;
+}
+
+CGUIWrappingListContainer::~CGUIWrappingListContainer(void) = default;
+
+void CGUIWrappingListContainer::UpdatePageControl(int offset)
+{
+ if (m_pageControl)
+ { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, GetNumItems() ? CorrectOffset(offset, GetCursor()) % GetNumItems() : 0);
+ SendWindowMessage(msg);
+ }
+}
+
+bool CGUIWrappingListContainer::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PAGE_UP:
+ Scroll(-m_itemsPerPage);
+ return true;
+ case ACTION_PAGE_DOWN:
+ Scroll(m_itemsPerPage);
+ return true;
+ // smooth scrolling (for analog controls)
+ case ACTION_SCROLL_UP:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ Scroll(-1);
+ }
+ return handled;
+ }
+ break;
+ case ACTION_SCROLL_DOWN:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > 0.4f)
+ {
+ handled = true;
+ m_analogScrollCount -= 0.4f;
+ Scroll(1);
+ }
+ return handled;
+ }
+ break;
+ }
+ return CGUIBaseContainer::OnAction(action);
+}
+
+bool CGUIWrappingListContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
+ {
+ if (message.GetSenderId() == m_pageControl && IsVisible())
+ { // offset by our cursor position
+ message.SetParam1(message.GetParam1() - GetCursor());
+ }
+ }
+ }
+ return CGUIBaseContainer::OnMessage(message);
+}
+
+bool CGUIWrappingListContainer::MoveUp(bool wrapAround)
+{
+ Scroll(-1);
+ return true;
+}
+
+bool CGUIWrappingListContainer::MoveDown(bool wrapAround)
+{
+ Scroll(+1);
+ return true;
+}
+
+// scrolls the said amount
+void CGUIWrappingListContainer::Scroll(int amount)
+{
+ ScrollToOffset(GetOffset() + amount);
+}
+
+bool CGUIWrappingListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
+{
+ return false;
+}
+
+void CGUIWrappingListContainer::ValidateOffset()
+{
+ // our minimal amount of items - we need to take into account extra items to display wrapped items when scrolling
+ unsigned int minItems = (unsigned int)m_itemsPerPage + ScrollCorrectionRange() + GetCacheCount() / 2;
+ if (minItems <= m_items.size())
+ return;
+
+ // no need to check the range here, but we need to check we have
+ // more items than slots.
+ ResetExtraItems();
+ if (m_items.size())
+ {
+ size_t numItems = m_items.size();
+ while (m_items.size() < minItems)
+ {
+ // add additional copies of items, as we require extras at render time
+ for (unsigned int i = 0; i < numItems; i++)
+ {
+ m_items.push_back(CGUIListItemPtr(m_items[i]->Clone()));
+ m_extraItems++;
+ }
+ }
+ }
+}
+
+int CGUIWrappingListContainer::CorrectOffset(int offset, int cursor) const
+{
+ if (m_items.size())
+ {
+ int correctOffset = (offset + cursor) % (int)m_items.size();
+ if (correctOffset < 0) correctOffset += m_items.size();
+ return correctOffset;
+ }
+ return 0;
+}
+
+int CGUIWrappingListContainer::GetSelectedItem() const
+{
+ if (m_items.size() > m_extraItems)
+ {
+ int numItems = (int)(m_items.size() - m_extraItems);
+ int correctOffset = (GetOffset() + GetCursor()) % numItems;
+ if (correctOffset < 0) correctOffset += numItems;
+ return correctOffset;
+ }
+ return 0;
+}
+
+bool CGUIWrappingListContainer::SelectItemFromPoint(const CPoint &point)
+{
+ if (!m_focusedLayout || !m_layout)
+ return false;
+
+ const float mouse_scroll_speed = 0.05f;
+ const float mouse_max_amount = 1.0f; // max speed: 1 item per frame
+ float sizeOfItem = m_layout->Size(m_orientation);
+ // see if the point is either side of our focused item
+ float start = GetCursor() * sizeOfItem;
+ float end = start + m_focusedLayout->Size(m_orientation);
+ float pos = (m_orientation == VERTICAL) ? point.y : point.x;
+ if (pos < start - 0.5f * sizeOfItem)
+ { // scroll backward
+ if (!InsideLayout(m_layout, point))
+ return false;
+ float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
+ m_analogScrollCount += amount * amount * mouse_scroll_speed;
+ if (m_analogScrollCount > 1)
+ {
+ Scroll(-1);
+ m_analogScrollCount-=1.0f;
+ }
+ return true;
+ }
+ else if (pos > end + 0.5f * sizeOfItem)
+ { // scroll forward
+ if (!InsideLayout(m_layout, point))
+ return false;
+
+ float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
+ m_analogScrollCount += amount * amount * mouse_scroll_speed;
+ if (m_analogScrollCount > 1)
+ {
+ Scroll(1);
+ m_analogScrollCount-=1.0f;
+ }
+ return true;
+ }
+ return InsideLayout(m_focusedLayout, point);
+}
+
+void CGUIWrappingListContainer::SelectItem(int item)
+{
+ if (item >= 0 && item < (int)m_items.size())
+ ScrollToOffset(item - GetCursor());
+}
+
+void CGUIWrappingListContainer::ResetExtraItems()
+{
+ // delete any extra items
+ if (m_extraItems)
+ m_items.erase(m_items.begin() + m_items.size() - m_extraItems, m_items.end());
+ m_extraItems = 0;
+}
+
+void CGUIWrappingListContainer::Reset()
+{
+ ResetExtraItems();
+ CGUIBaseContainer::Reset();
+}
+
+int CGUIWrappingListContainer::GetCurrentPage() const
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page
+ return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
+ return offset / m_itemsPerPage + 1;
+}
+
+void CGUIWrappingListContainer::SetPageControlRange()
+{
+ if (m_pageControl)
+ {
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetNumItems());
+ SendWindowMessage(msg);
+ }
+}
diff --git a/xbmc/guilib/GUIWrappingListContainer.dox b/xbmc/guilib/GUIWrappingListContainer.dox
new file mode 100644
index 0000000..bcf8771
--- /dev/null
+++ b/xbmc/guilib/GUIWrappingListContainer.dox
@@ -0,0 +1,139 @@
+/*!
+
+\page Wrap_List_Container Wrap List Container
+\brief **Used for a wrapping list of items with fixed focus.**
+
+\tableofcontents
+
+The wrap list container is one of several containers used to display items
+fromfile lists in various ways. The wrap list container is the same as the
+\ref List_Container "List Container", with two exceptions:
+ 1. The focused item is fixed.
+ 2. The items "wrap" around once they reach the end.
+
+As with all container controls, the layout of the items within the control is
+very flexible.
+
+--------------------------------------------------------------------------------
+\section Wrap_List_Container_sect1 Example
+
+~~~~~~~~~~~~~
+<control type="wraplist" id="50">
+ <description>My first wraplist container</description>
+ <posx>80</posx>
+ <posy>60</posy>
+ <width>250</width>
+ <height>200</height>
+ <visible>true</visible>
+ <onup>2</onup>
+ <ondown>3</ondown>
+ <onleft>1</onleft>
+ <onright>1</onright>
+ <viewtype label="3D list">list</viewtype>
+ <orientation>vertical</orientation>
+ <pagecontrol>25</pagecontrol>
+ <focusposition>3</focusposition>
+ <scrolltime tween="sine" easing="out">200</scrolltime>
+ <autoscroll>true</autoscroll>
+ <itemlayout width="250" height="29">
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </itemlayout>
+ <focusedlayout height="29" width="250">
+ <control type="image">
+ <width>485</width>
+ <height>29</height>
+ <posx>0</posx>
+ <posy>0</posy>
+ <visible>Control.HasFocus(50)</visible>
+ <texture>list-focus.png</texture>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>3</posy>
+ <width>22</width>
+ <height>22</height>
+ <info>ListItem.Icon</info>
+ </control>
+ <control type="label">
+ <posx>30</posx>
+ <posy>3</posy>
+ <width>430</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <align>left</align>
+ <info>ListItem.Label</info>
+ </control>
+ <control type="label">
+ <posx>475</posx>
+ <posy>3</posy>
+ <width>300</width>
+ <height>22</height>
+ <font>font13</font>
+ <aligny>center</aligny>
+ <selectedcolor>green</selectedcolor>
+ <textcolor>grey</textcolor>
+ <align>right</align>
+ <info>ListItem.Label2</info>
+ </control>
+ </focusedlayout>
+</control>
+~~~~~~~~~~~~~
+
+--------------------------------------------------------------------------------
+\section Wrap_List_Container_sect2 Available tags
+
+In addition to the [Default Control Tags](http://kodi.wiki/view/Default_Control_Tags)
+the following tags are available. Note that each tag is **lower case** only. This is
+important, as `xml` tags are case-sensitive.
+
+| Tag | Description |
+|--------------:|:--------------------------------------------------------------|
+| viewtype | The type of view. Choices are list, icon, wide, wrap, biglist, bigicon, bigwide, bigwrap, info and biginfo. The label attribute indicates the label that will be used in the <i>"View As"</i> control within the GUI. It is localizable via strings.po. viewtype has no effect on the view itself. It is used by kodi when switching skin to automatically select a view with a similar layout. Skinners should try to set _viewtype_ to describe the layout as best as possible.
+| orientation | The orientation of the list. Defaults to vertical.
+| pagecontrol | Used to set the <b>`<id>`</b> of the page control used to control this list.
+| scrolltime | The time (in ms) to scroll from one item to another. By default, this is 200ms. The list will scroll smoothly from one item to another as needed. Set it to zero to disable the smooth scrolling. The scroll movement can be further adjusted by selecting one of the available [tween](http://kodi.wiki/view/Tweeners) methods.
+| focusposition | Specifies the index (from 0 -> number items displayable - 1) of the focused item. The focused item doesn't move - as the user moves up and down (or left and right) the items scroll instead.
+| itemlayout | Specifies the layout of items in the list. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<itemlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| focusedlayout | Specifies the layout of items in the list that have focus. Requires the height attribute set in a vertical list, and the width attribute set for a horizontal list. The <b>`<focusedlayout>`</b> then contains as many label and image controls as required. [See here for more information](http://kodi.wiki/view/Container_Item_Layout).
+| content | Used to set the item content that this list will contain. Allows the skinner to setup a list anywhere they want with a static set of content, as a useful alternative to the grouplist control. [See here for more information](http://kodi.wiki/view/Static_List_Content)
+| autoscroll | Used to make the container scroll automatically
+
+--------------------------------------------------------------------------------
+\section Wrap_List_Container_sect3 See also
+#### Development:
+
+- [Add-on development](http://kodi.wiki/view/Add-on_development)
+- [Skinning](http://kodi.wiki/view/Skinning)
+
+*/
diff --git a/xbmc/guilib/GUIWrappingListContainer.h b/xbmc/guilib/GUIWrappingListContainer.h
new file mode 100644
index 0000000..da1e106
--- /dev/null
+++ b/xbmc/guilib/GUIWrappingListContainer.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIListContainer.h
+\brief
+*/
+
+#include "GUIBaseContainer.h"
+/*!
+ \ingroup controls
+ \brief
+ */
+class CGUIWrappingListContainer : public CGUIBaseContainer
+{
+public:
+ CGUIWrappingListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition);
+ ~CGUIWrappingListContainer(void) override;
+ CGUIWrappingListContainer* Clone() const override { return new CGUIWrappingListContainer(*this); }
+
+ bool OnAction(const CAction &action) override;
+ bool OnMessage(CGUIMessage& message) override;
+ int GetSelectedItem() const override;
+
+protected:
+ void Scroll(int amount) override;
+ bool MoveDown(bool wrapAround) override;
+ bool MoveUp(bool wrapAround) override;
+ bool GetOffsetRange(int &minOffset, int &maxOffset) const override;
+ void ValidateOffset() override;
+ int CorrectOffset(int offset, int cursor) const override;
+ bool SelectItemFromPoint(const CPoint &point) override;
+ void SelectItem(int item) override;
+ void Reset() override;
+ size_t GetNumItems() const override { return m_items.size() - m_extraItems; }
+ int GetCurrentPage() const override;
+ void SetPageControlRange() override;
+ void UpdatePageControl(int offset) override;
+
+ void ResetExtraItems();
+ unsigned int m_extraItems;
+};
+
diff --git a/xbmc/guilib/IAudioDeviceChangedCallback.h b/xbmc/guilib/IAudioDeviceChangedCallback.h
new file mode 100644
index 0000000..54800fd
--- /dev/null
+++ b/xbmc/guilib/IAudioDeviceChangedCallback.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+class IAudioDeviceChangedCallback
+{
+public:
+ virtual void Initialize(int iDevice)=0;
+ virtual void DeInitialize(int iDevice)=0;
+ virtual ~IAudioDeviceChangedCallback() {}
+};
+
diff --git a/xbmc/guilib/IDirtyRegionSolver.h b/xbmc/guilib/IDirtyRegionSolver.h
new file mode 100644
index 0000000..6702d07
--- /dev/null
+++ b/xbmc/guilib/IDirtyRegionSolver.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "DirtyRegion.h"
+
+#define DIRTYREGION_SOLVER_FILL_VIEWPORT_ALWAYS 0
+#define DIRTYREGION_SOLVER_UNION 1
+#define DIRTYREGION_SOLVER_COST_REDUCTION 2
+#define DIRTYREGION_SOLVER_FILL_VIEWPORT_ON_CHANGE 3
+
+class IDirtyRegionSolver
+{
+public:
+ virtual ~IDirtyRegionSolver() = default;
+
+ // Takes a number of dirty regions which will become a number of needed rendering passes.
+ virtual void Solve(const CDirtyRegionList &input, CDirtyRegionList &output) = 0;
+};
diff --git a/xbmc/guilib/IGUIContainer.h b/xbmc/guilib/IGUIContainer.h
new file mode 100644
index 0000000..93b404f
--- /dev/null
+++ b/xbmc/guilib/IGUIContainer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIControl.h"
+
+#include <memory>
+
+typedef std::shared_ptr<CGUIListItem> CGUIListItemPtr;
+
+/*!
+ \ingroup controls
+ \brief
+ */
+
+class IGUIContainer : public CGUIControl
+{
+protected:
+ VIEW_TYPE m_type;
+ std::string m_label;
+public:
+ IGUIContainer(int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height), m_type(VIEW_TYPE_NONE) {}
+
+ bool IsContainer() const override { return true; }
+
+ VIEW_TYPE GetType() const { return m_type; }
+ const std::string& GetLabel() const { return m_label; }
+ void SetType(VIEW_TYPE type, const std::string &label)
+ {
+ m_type = type;
+ m_label = label;
+ }
+
+ virtual CGUIListItemPtr GetListItem(int offset, unsigned int flag = 0) const = 0;
+ virtual std::string GetLabel(int info) const = 0;
+};
diff --git a/xbmc/guilib/IMsgTargetCallback.h b/xbmc/guilib/IMsgTargetCallback.h
new file mode 100644
index 0000000..09d888e
--- /dev/null
+++ b/xbmc/guilib/IMsgTargetCallback.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file IMsgTargetCallback.h
+\brief
+*/
+
+#include "GUIMessage.h"
+
+/*!
+ \ingroup winman
+ \brief
+ */
+class IMsgTargetCallback
+{
+public:
+ virtual bool OnMessage(CGUIMessage& message) = 0;
+ virtual ~IMsgTargetCallback() = default;
+};
diff --git a/xbmc/guilib/IRenderingCallback.h b/xbmc/guilib/IRenderingCallback.h
new file mode 100644
index 0000000..fb86f0a
--- /dev/null
+++ b/xbmc/guilib/IRenderingCallback.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+class IRenderingCallback
+{
+public:
+ virtual ~IRenderingCallback() = default;
+ virtual bool Create(int x, int y, int w, int h, void *device) = 0;
+ virtual void Render() = 0;
+ virtual void Stop() = 0;
+ virtual bool IsDirty() { return true; }
+};
diff --git a/xbmc/guilib/ISliderCallback.h b/xbmc/guilib/ISliderCallback.h
new file mode 100644
index 0000000..20834e2
--- /dev/null
+++ b/xbmc/guilib/ISliderCallback.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+class CGUISliderControl;
+
+/*!
+ \brief Interface class for callback from the slider dialog
+
+ Used to pass feedback from the slider dialog to a caller. Users of the
+ slider dialog should derive from this class if they wish to respond to changes
+ in the slider by the user as they happen. OnSliderChange is called in response
+ to the user moving the slider. The caller may then update the text on the slider
+ and update anything that should be changed as the slider is adjusted.
+
+ \sa CGUIDialogSlider
+ */
+class ISliderCallback
+{
+public:
+ virtual ~ISliderCallback() = default;
+
+ /*!
+ \brief Callback function called whenever the user moves the slider
+
+ \param data pointer of callbackData
+ \param slider pointer to the slider control
+ */
+ virtual void OnSliderChange(void *data, CGUISliderControl *slider) = 0;
+};
diff --git a/xbmc/guilib/IWindowManagerCallback.cpp b/xbmc/guilib/IWindowManagerCallback.cpp
new file mode 100644
index 0000000..6909f1e
--- /dev/null
+++ b/xbmc/guilib/IWindowManagerCallback.cpp
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "IWindowManagerCallback.h"
+
+
+IWindowManagerCallback::IWindowManagerCallback(void) = default;
+
+IWindowManagerCallback::~IWindowManagerCallback(void) = default;
diff --git a/xbmc/guilib/IWindowManagerCallback.h b/xbmc/guilib/IWindowManagerCallback.h
new file mode 100644
index 0000000..f8cdb73
--- /dev/null
+++ b/xbmc/guilib/IWindowManagerCallback.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file IWindowManagerCallback.h
+\brief
+*/
+
+/*!
+ \ingroup winman
+ \brief
+ */
+class IWindowManagerCallback
+{
+public:
+ IWindowManagerCallback(void);
+ virtual ~IWindowManagerCallback(void);
+
+ virtual void FrameMove(bool processEvents, bool processGUI = true) = 0;
+ virtual void Render() = 0;
+ virtual void Process() = 0;
+ virtual bool GetRenderGUI() const { return false; }
+};
diff --git a/xbmc/guilib/LocalizeStrings.cpp b/xbmc/guilib/LocalizeStrings.cpp
new file mode 100644
index 0000000..659eb3d
--- /dev/null
+++ b/xbmc/guilib/LocalizeStrings.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "LocalizeStrings.h"
+
+#include "addons/LanguageResource.h"
+#include "filesystem/Directory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "threads/SharedSection.h"
+#include "utils/CharsetConverter.h"
+#include "utils/POUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+#include <shared_mutex>
+
+/*! \brief Tries to load ids and strings from a strings.po file to the `strings` map.
+ * It should only be called from the LoadStr2Mem function to have a fallback.
+ \param pathname The directory name, where we look for the strings file.
+ \param strings [out] The resulting strings map.
+ \param encoding Encoding of the strings. For PO files we only use utf-8.
+ \param offset An offset value to place strings from the id value.
+ \param bSourceLanguage If we are loading the source English strings.po.
+ \return false if no strings.po file was loaded.
+ */
+static bool LoadPO(const std::string &filename, std::map<uint32_t, LocStr>& strings,
+ std::string &encoding, uint32_t offset = 0 , bool bSourceLanguage = false)
+{
+ CPODocument PODoc;
+ if (!PODoc.LoadFile(filename))
+ return false;
+
+ int counter = 0;
+
+ while ((PODoc.GetNextEntry()))
+ {
+ uint32_t id;
+ if (PODoc.GetEntryType() == ID_FOUND)
+ {
+ bool bStrInMem = strings.find((id = PODoc.GetEntryID()) + offset) != strings.end();
+ PODoc.ParseEntry(bSourceLanguage);
+
+ if (bSourceLanguage && !PODoc.GetMsgid().empty())
+ {
+ if (bStrInMem && (strings[id + offset].strOriginal.empty() ||
+ PODoc.GetMsgid() == strings[id + offset].strOriginal))
+ continue;
+ else if (bStrInMem)
+ CLog::Log(
+ LOGDEBUG,
+ "POParser: id:{} was recently re-used in the English string file, which is not yet "
+ "changed in the translated file. Using the English string instead",
+ id);
+ strings[id + offset].strTranslated = PODoc.GetMsgid();
+ counter++;
+ }
+ else if (!bSourceLanguage && !bStrInMem && !PODoc.GetMsgstr().empty())
+ {
+ strings[id + offset].strTranslated = PODoc.GetMsgstr();
+ strings[id + offset].strOriginal = PODoc.GetMsgid();
+ counter++;
+ }
+ }
+ else if (PODoc.GetEntryType() == MSGID_FOUND)
+ {
+ //! @todo implement reading of non-id based string entries from the PO files.
+ //! These entries would go into a separate memory map, using hash codes for fast look-up.
+ //! With this memory map we can implement using gettext(), ngettext(), pgettext() calls,
+ //! so that we don't have to use new IDs for new strings. Even we can start converting
+ //! the ID based calls to normal gettext calls.
+ }
+ else if (PODoc.GetEntryType() == MSGID_PLURAL_FOUND)
+ {
+ //! @todo implement reading of non-id based pluralized string entries from the PO files.
+ //! We can store the pluralforms for each language, in the langinfo.xml files.
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "LocalizeStrings: loaded {} strings from file {}", counter, filename);
+ return true;
+}
+
+/*! \brief Loads language ids and strings to memory map `strings`.
+ \param pathname The directory name, where we look for the strings file.
+ \param language We load the strings for this language. Fallback language is always English.
+ \param strings [out] The resulting strings map.
+ \param encoding Encoding of the strings. For PO files we only use utf-8.
+ \param offset An offset value to place strings from the id value.
+ \return false if no strings.po file was loaded.
+ */
+static bool LoadStr2Mem(const std::string &pathname_in, const std::string &language,
+ std::map<uint32_t, LocStr>& strings, std::string &encoding, uint32_t offset = 0 )
+{
+ std::string pathname = CSpecialProtocol::TranslatePathConvertCase(pathname_in + language);
+ if (!XFILE::CDirectory::Exists(pathname))
+ {
+ bool exists = false;
+ std::string lang;
+ // check if there's a language addon using the old language naming convention
+ if (ADDON::CLanguageResource::FindLegacyLanguage(language, lang))
+ {
+ pathname = CSpecialProtocol::TranslatePathConvertCase(pathname_in + lang);
+ exists = XFILE::CDirectory::Exists(pathname);
+ }
+
+ if (!exists)
+ return false;
+ }
+
+ bool useSourceLang = StringUtils::EqualsNoCase(language, LANGUAGE_DEFAULT) || StringUtils::EqualsNoCase(language, LANGUAGE_OLD_DEFAULT);
+
+ return LoadPO(URIUtils::AddFileToFolder(pathname, "strings.po"), strings, encoding, offset, useSourceLang);
+}
+
+static bool LoadWithFallback(const std::string& path, const std::string& language, std::map<uint32_t, LocStr>& strings)
+{
+ std::string encoding;
+ if (!LoadStr2Mem(path, language, strings, encoding))
+ {
+ if (StringUtils::EqualsNoCase(language, LANGUAGE_DEFAULT)) // no fallback, nothing to do
+ return false;
+ }
+
+ // load the fallback
+ if (!StringUtils::EqualsNoCase(language, LANGUAGE_DEFAULT))
+ LoadStr2Mem(path, LANGUAGE_DEFAULT, strings, encoding);
+
+ return true;
+}
+
+CLocalizeStrings::CLocalizeStrings(void) = default;
+
+CLocalizeStrings::~CLocalizeStrings(void) = default;
+
+void CLocalizeStrings::ClearSkinStrings()
+{
+ // clear the skin strings
+ std::unique_lock<CSharedSection> lock(m_stringsMutex);
+ Clear(31000, 31999);
+}
+
+bool CLocalizeStrings::LoadSkinStrings(const std::string& path, const std::string& language)
+{
+ //! @todo shouldn't hold lock while loading file
+ std::unique_lock<CSharedSection> lock(m_stringsMutex);
+ ClearSkinStrings();
+ // load the skin strings in.
+ return LoadWithFallback(path, language, m_strings);
+}
+
+bool CLocalizeStrings::Load(const std::string& strPathName, const std::string& strLanguage)
+{
+ std::map<uint32_t, LocStr> strings;
+ if (!LoadWithFallback(strPathName, strLanguage, strings))
+ return false;
+
+ // fill in the constant strings
+ strings[20022].strTranslated = "";
+ strings[20027].strTranslated = "°F";
+ strings[20028].strTranslated = "K";
+ strings[20029].strTranslated = "°C";
+ strings[20030].strTranslated = "°Ré";
+ strings[20031].strTranslated = "°Ra";
+ strings[20032].strTranslated = "°Rø";
+ strings[20033].strTranslated = "°De";
+ strings[20034].strTranslated = "°N";
+
+ strings[20200].strTranslated = "km/h";
+ strings[20201].strTranslated = "m/min";
+ strings[20202].strTranslated = "m/s";
+ strings[20203].strTranslated = "ft/h";
+ strings[20204].strTranslated = "ft/min";
+ strings[20205].strTranslated = "ft/s";
+ strings[20206].strTranslated = "mph";
+ strings[20207].strTranslated = "kts";
+ strings[20208].strTranslated = "Beaufort";
+ strings[20209].strTranslated = "inch/s";
+ strings[20210].strTranslated = "yard/s";
+ strings[20211].strTranslated = "Furlong/Fortnight";
+
+ std::unique_lock<CSharedSection> lock(m_stringsMutex);
+ Clear();
+ m_strings = std::move(strings);
+ return true;
+}
+
+const std::string& CLocalizeStrings::Get(uint32_t dwCode) const
+{
+ std::shared_lock<CSharedSection> lock(m_stringsMutex);
+ ciStrings i = m_strings.find(dwCode);
+ if (i == m_strings.end())
+ {
+ return StringUtils::Empty;
+ }
+ return i->second.strTranslated;
+}
+
+void CLocalizeStrings::Clear()
+{
+ std::unique_lock<CSharedSection> lock(m_stringsMutex);
+ m_strings.clear();
+}
+
+void CLocalizeStrings::Clear(uint32_t start, uint32_t end)
+{
+ std::unique_lock<CSharedSection> lock(m_stringsMutex);
+ iStrings it = m_strings.begin();
+ while (it != m_strings.end())
+ {
+ if (it->first >= start && it->first <= end)
+ m_strings.erase(it++);
+ else
+ ++it;
+ }
+}
+
+bool CLocalizeStrings::LoadAddonStrings(const std::string& path, const std::string& language, const std::string& addonId)
+{
+ std::map<uint32_t, LocStr> strings;
+ if (!LoadWithFallback(path, language, strings))
+ return false;
+
+ std::unique_lock<CSharedSection> lock(m_addonStringsMutex);
+ auto it = m_addonStrings.find(addonId);
+ if (it != m_addonStrings.end())
+ m_addonStrings.erase(it);
+
+ return m_addonStrings.emplace(std::string(addonId), std::move(strings)).second;
+}
+
+std::string CLocalizeStrings::GetAddonString(const std::string& addonId, uint32_t code)
+{
+ std::shared_lock<CSharedSection> lock(m_addonStringsMutex);
+ auto i = m_addonStrings.find(addonId);
+ if (i == m_addonStrings.end())
+ return StringUtils::Empty;
+
+ auto j = i->second.find(code);
+ if (j == i->second.end())
+ return StringUtils::Empty;
+
+ return j->second.strTranslated;
+}
diff --git a/xbmc/guilib/LocalizeStrings.h b/xbmc/guilib/LocalizeStrings.h
new file mode 100644
index 0000000..8d73cf5
--- /dev/null
+++ b/xbmc/guilib/LocalizeStrings.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file LocalizeStrings.h
+\brief
+*/
+
+#include "threads/SharedSection.h"
+#include "utils/ILocalizer.h"
+
+#include <map>
+#include <stdint.h>
+#include <string>
+
+/*!
+ \ingroup strings
+ \brief
+ */
+
+struct LocStr
+{
+ std::string strTranslated; // string to be used in xbmc GUI
+ std::string strOriginal; // the original English string the translation is based on
+};
+
+// The default fallback language is fixed to be English
+const std::string LANGUAGE_DEFAULT = "resource.language.en_gb";
+const std::string LANGUAGE_OLD_DEFAULT = "English";
+
+class CLocalizeStrings : public ILocalizer
+{
+public:
+ CLocalizeStrings(void);
+ ~CLocalizeStrings(void) override;
+ bool Load(const std::string& strPathName, const std::string& strLanguage);
+ bool LoadSkinStrings(const std::string& path, const std::string& language);
+ bool LoadAddonStrings(const std::string& path, const std::string& language, const std::string& addonId);
+ void ClearSkinStrings();
+ const std::string& Get(uint32_t code) const;
+ std::string GetAddonString(const std::string& addonId, uint32_t code);
+ void Clear();
+
+ // implementation of ILocalizer
+ std::string Localize(std::uint32_t code) const override { return Get(code); }
+
+protected:
+ void Clear(uint32_t start, uint32_t end);
+
+ std::map<uint32_t, LocStr> m_strings;
+ std::map<std::string, std::map<uint32_t, LocStr>> m_addonStrings;
+ typedef std::map<uint32_t, LocStr>::const_iterator ciStrings;
+ typedef std::map<uint32_t, LocStr>::iterator iStrings;
+
+ mutable CSharedSection m_stringsMutex;
+ CSharedSection m_addonStringsMutex;
+};
+
+/*!
+ \ingroup strings
+ \brief
+ */
+extern CLocalizeStrings g_localizeStrings;
+extern CLocalizeStrings g_localizeStringsTemp;
+
diff --git a/xbmc/guilib/Shader.cpp b/xbmc/guilib/Shader.cpp
new file mode 100644
index 0000000..37aedcf
--- /dev/null
+++ b/xbmc/guilib/Shader.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Shader.h"
+
+#include "ServiceBroker.h"
+#include "filesystem/File.h"
+#include "rendering/RenderSystem.h"
+#include "utils/GLUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#ifdef HAS_GLES
+#define GLchar char
+#endif
+
+#define LOG_SIZE 1024
+
+using namespace Shaders;
+using namespace XFILE;
+
+//////////////////////////////////////////////////////////////////////
+// CShader
+//////////////////////////////////////////////////////////////////////
+bool CShader::LoadSource(const std::string& filename, const std::string& prefix)
+{
+ if(filename.empty())
+ return true;
+
+ CFileStream file;
+
+ std::string path = "special://xbmc/system/shaders/";
+ path += CServiceBroker::GetRenderSystem()->GetShaderPath(filename);
+ path += filename;
+ if(!file.Open(path))
+ {
+ CLog::Log(LOGERROR, "CYUVShaderGLSL::CYUVShaderGLSL - failed to open file {}", filename);
+ return false;
+ }
+ getline(file, m_source, '\0');
+
+ size_t pos = 0;
+ size_t versionPos = m_source.find("#version");
+ if (versionPos != std::string::npos)
+ {
+ versionPos = m_source.find('\n', versionPos);
+ if (versionPos != std::string::npos)
+ pos = versionPos + 1;
+ }
+ m_source.insert(pos, prefix);
+
+ m_filenames = filename;
+
+ return true;
+}
+
+bool CShader::AppendSource(const std::string& filename)
+{
+ if(filename.empty())
+ return true;
+
+ CFileStream file;
+ std::string temp;
+
+ std::string path = "special://xbmc/system/shaders/";
+ path += CServiceBroker::GetRenderSystem()->GetShaderPath(filename);
+ path += filename;
+ if(!file.Open(path))
+ {
+ CLog::Log(LOGERROR, "CShader::AppendSource - failed to open file {}", filename);
+ return false;
+ }
+ getline(file, temp, '\0');
+ m_source.append(temp);
+
+ m_filenames.append(" " + filename);
+
+ return true;
+}
+
+bool CShader::InsertSource(const std::string& filename, const std::string& loc)
+{
+ if(filename.empty())
+ return true;
+
+ CFileStream file;
+ std::string temp;
+
+ std::string path = "special://xbmc/system/shaders/";
+ path += CServiceBroker::GetRenderSystem()->GetShaderPath(filename);
+ path += filename;
+ if(!file.Open(path))
+ {
+ CLog::Log(LOGERROR, "CShader::InsertSource - failed to open file {}", filename);
+ return false;
+ }
+ getline(file, temp, '\0');
+
+ size_t locPos = m_source.find(loc);
+ if (locPos == std::string::npos)
+ {
+ CLog::Log(LOGERROR, "CShader::InsertSource - could not find location {}", loc);
+ return false;
+ }
+
+ m_source.insert(locPos, temp);
+
+ m_filenames.append(" " + filename);
+
+ return true;
+}
+
+std::string CShader::GetSourceWithLineNumbers() const
+{
+ int i{1};
+ auto lines = StringUtils::Split(m_source, "\n");
+ for (auto& line : lines)
+ {
+ line.insert(0, StringUtils::Format("{:3}: ", i));
+ i++;
+ }
+
+ auto output = StringUtils::Join(lines, "\n");
+
+ return output;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// CGLSLVertexShader
+//////////////////////////////////////////////////////////////////////
+
+bool CGLSLVertexShader::Compile()
+{
+ GLint params[4];
+
+ Free();
+
+ m_vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ const char *ptr = m_source.c_str();
+ glShaderSource(m_vertexShader, 1, &ptr, 0);
+ glCompileShader(m_vertexShader);
+ glGetShaderiv(m_vertexShader, GL_COMPILE_STATUS, params);
+ VerifyGLState();
+ if (params[0] != GL_TRUE)
+ {
+ GLchar log[LOG_SIZE];
+ CLog::Log(LOGERROR, "GL: Error compiling vertex shader");
+ glGetShaderInfoLog(m_vertexShader, LOG_SIZE, NULL, log);
+ CLog::Log(LOGERROR, "{}", log);
+ m_lastLog = log;
+ m_compiled = false;
+ }
+ else
+ {
+ GLchar log[LOG_SIZE];
+ GLsizei length;
+ glGetShaderInfoLog(m_vertexShader, LOG_SIZE, &length, log);
+ if (length > 0)
+ {
+ CLog::Log(LOGDEBUG, "GL: Vertex Shader compilation log:");
+ CLog::Log(LOGDEBUG, "{}", log);
+ }
+ m_lastLog = log;
+ m_compiled = true;
+ }
+ return m_compiled;
+}
+
+void CGLSLVertexShader::Free()
+{
+ if (m_vertexShader)
+ glDeleteShader(m_vertexShader);
+ m_vertexShader = 0;
+}
+
+//////////////////////////////////////////////////////////////////////
+// CGLSLPixelShader
+//////////////////////////////////////////////////////////////////////
+bool CGLSLPixelShader::Compile()
+{
+ GLint params[4];
+
+ Free();
+
+ // Pixel shaders are not mandatory.
+ if (m_source.length()==0)
+ {
+ CLog::Log(LOGINFO, "GL: No pixel shader, fixed pipeline in use");
+ return true;
+ }
+
+ m_pixelShader = glCreateShader(GL_FRAGMENT_SHADER);
+ const char *ptr = m_source.c_str();
+ glShaderSource(m_pixelShader, 1, &ptr, 0);
+ glCompileShader(m_pixelShader);
+ glGetShaderiv(m_pixelShader, GL_COMPILE_STATUS, params);
+ if (params[0] != GL_TRUE)
+ {
+ GLchar log[LOG_SIZE];
+ CLog::Log(LOGERROR, "GL: Error compiling pixel shader");
+ glGetShaderInfoLog(m_pixelShader, LOG_SIZE, NULL, log);
+ CLog::Log(LOGERROR, "{}", log);
+ m_lastLog = log;
+ m_compiled = false;
+ }
+ else
+ {
+ GLchar log[LOG_SIZE];
+ GLsizei length;
+ glGetShaderInfoLog(m_pixelShader, LOG_SIZE, &length, log);
+ if (length > 0)
+ {
+ CLog::Log(LOGDEBUG, "GL: Pixel Shader compilation log:");
+ CLog::Log(LOGDEBUG, "{}", log);
+ }
+ m_lastLog = log;
+ m_compiled = true;
+ }
+ return m_compiled;
+}
+
+void CGLSLPixelShader::Free()
+{
+ if (m_pixelShader)
+ glDeleteShader(m_pixelShader);
+ m_pixelShader = 0;
+}
+
+//////////////////////////////////////////////////////////////////////
+// CGLSLShaderProgram
+//////////////////////////////////////////////////////////////////////
+CGLSLShaderProgram::CGLSLShaderProgram()
+{
+ m_pFP = new CGLSLPixelShader();
+ m_pVP = new CGLSLVertexShader();
+}
+
+CGLSLShaderProgram::CGLSLShaderProgram(const std::string& vert,
+ const std::string& frag)
+{
+ m_pFP = new CGLSLPixelShader();
+ m_pFP->LoadSource(frag);
+ m_pVP = new CGLSLVertexShader();
+ m_pVP->LoadSource(vert);
+}
+
+CGLSLShaderProgram::~CGLSLShaderProgram()
+{
+ Free();
+}
+
+void CGLSLShaderProgram::Free()
+{
+ m_pVP->Free();
+ VerifyGLState();
+ m_pFP->Free();
+ VerifyGLState();
+ if (m_shaderProgram)
+ {
+ glDeleteProgram(m_shaderProgram);
+ }
+ m_shaderProgram = 0;
+ m_ok = false;
+ m_lastProgram = 0;
+}
+
+bool CGLSLShaderProgram::CompileAndLink()
+{
+ GLint params[4];
+
+ // free resources
+ Free();
+
+ // compiled vertex shader
+ if (!m_pVP->Compile())
+ {
+ CLog::Log(LOGERROR, "GL: Error compiling vertex shader: {}", m_pVP->GetName());
+ CLog::Log(LOGDEBUG, "GL: vertex shader source:\n{}", m_pVP->GetSourceWithLineNumbers());
+ return false;
+ }
+
+ // compile pixel shader
+ if (!m_pFP->Compile())
+ {
+ m_pVP->Free();
+ CLog::Log(LOGERROR, "GL: Error compiling fragment shader: {}", m_pFP->GetName());
+ CLog::Log(LOGDEBUG, "GL: fragment shader source:\n{}", m_pFP->GetSourceWithLineNumbers());
+ return false;
+ }
+
+ // create program object
+ if (!(m_shaderProgram = glCreateProgram()))
+ {
+ CLog::Log(LOGERROR, "GL: Error creating shader program handle");
+ goto error;
+ }
+
+ // attach the vertex shader
+ glAttachShader(m_shaderProgram, m_pVP->Handle());
+ VerifyGLState();
+
+ // if we have a pixel shader, attach it. If not, fixed pipeline
+ // will be used.
+ if (m_pFP->Handle())
+ {
+ glAttachShader(m_shaderProgram, m_pFP->Handle());
+ VerifyGLState();
+ }
+
+ // link the program
+ glLinkProgram(m_shaderProgram);
+ glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, params);
+ if (params[0]!=GL_TRUE)
+ {
+ GLchar log[LOG_SIZE];
+ CLog::Log(LOGERROR, "GL: Error linking shader");
+ glGetProgramInfoLog(m_shaderProgram, LOG_SIZE, NULL, log);
+ CLog::Log(LOGERROR, "{}", log);
+ goto error;
+ }
+ VerifyGLState();
+
+ m_validated = false;
+ m_ok = true;
+ OnCompiledAndLinked();
+ VerifyGLState();
+ return true;
+
+ error:
+ m_ok = false;
+ Free();
+ return false;
+}
+
+bool CGLSLShaderProgram::Enable()
+{
+ if (OK())
+ {
+ glUseProgram(m_shaderProgram);
+ if (OnEnabled())
+ {
+ if (!m_validated)
+ {
+ // validate the program
+ GLint params[4];
+ glValidateProgram(m_shaderProgram);
+ glGetProgramiv(m_shaderProgram, GL_VALIDATE_STATUS, params);
+ if (params[0]!=GL_TRUE)
+ {
+ GLchar log[LOG_SIZE];
+ CLog::Log(LOGERROR, "GL: Error validating shader");
+ glGetProgramInfoLog(m_shaderProgram, LOG_SIZE, NULL, log);
+ CLog::Log(LOGERROR, "{}", log);
+ }
+ m_validated = true;
+ }
+ VerifyGLState();
+ return true;
+ }
+ else
+ {
+ glUseProgram(0);
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+void CGLSLShaderProgram::Disable()
+{
+ if (OK())
+ {
+ glUseProgram(0);
+ OnDisabled();
+ }
+}
+
diff --git a/xbmc/guilib/Shader.h b/xbmc/guilib/Shader.h
new file mode 100644
index 0000000..d86ca2b
--- /dev/null
+++ b/xbmc/guilib/Shader.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "system_gl.h"
+
+namespace Shaders {
+
+ //////////////////////////////////////////////////////////////////////
+ // CShader - base class
+ //////////////////////////////////////////////////////////////////////
+ class CShader
+ {
+ public:
+ CShader() = default;
+ virtual ~CShader() = default;
+ virtual bool Compile() = 0;
+ virtual void Free() = 0;
+ virtual GLuint Handle() = 0;
+ virtual void SetSource(const std::string& src) { m_source = src; }
+ virtual bool LoadSource(const std::string& filename, const std::string& prefix = "");
+ virtual bool AppendSource(const std::string& filename);
+ virtual bool InsertSource(const std::string& filename, const std::string& loc);
+ bool OK() const { return m_compiled; }
+
+ std::string GetName() const { return m_filenames; }
+ std::string GetSourceWithLineNumbers() const;
+
+ protected:
+ std::string m_source;
+ std::string m_lastLog;
+ std::vector<std::string> m_attr;
+ bool m_compiled = false;
+
+ private:
+ std::string m_filenames;
+ };
+
+
+ //////////////////////////////////////////////////////////////////////
+ // CVertexShader - vertex shader class
+ //////////////////////////////////////////////////////////////////////
+ class CVertexShader : public CShader
+ {
+ public:
+ CVertexShader() = default;
+ ~CVertexShader() override { Free(); }
+ void Free() override {}
+ GLuint Handle() override { return m_vertexShader; }
+
+ protected:
+ GLuint m_vertexShader = 0;
+ };
+
+ class CGLSLVertexShader : public CVertexShader
+ {
+ public:
+ void Free() override;
+ bool Compile() override;
+ };
+
+
+ //////////////////////////////////////////////////////////////////////
+ // CPixelShader - abstract pixel shader class
+ //////////////////////////////////////////////////////////////////////
+ class CPixelShader : public CShader
+ {
+ public:
+ CPixelShader() = default;
+ ~CPixelShader() override { Free(); }
+ void Free() override {}
+ GLuint Handle() override { return m_pixelShader; }
+
+ protected:
+ GLuint m_pixelShader = 0;
+ };
+
+ class CGLSLPixelShader : public CPixelShader
+ {
+ public:
+ void Free() override;
+ bool Compile() override;
+ };
+
+ //////////////////////////////////////////////////////////////////////
+ // CShaderProgram - the complete shader consisting of both the vertex
+ // and pixel programs. (abstract)
+ //////////////////////////////////////////////////////////////////////
+ class CShaderProgram
+ {
+ public:
+ CShaderProgram() = default;
+
+ virtual ~CShaderProgram()
+ {
+ delete m_pFP;
+ delete m_pVP;
+ }
+
+ // enable the shader
+ virtual bool Enable() = 0;
+
+ // disable the shader
+ virtual void Disable() = 0;
+
+ // returns true if shader is compiled and linked
+ bool OK() const { return m_ok; }
+
+ // return the vertex shader object
+ CVertexShader* VertexShader() { return m_pVP; }
+
+ // return the pixel shader object
+ CPixelShader* PixelShader() { return m_pFP; }
+
+ // compile and link the shaders
+ virtual bool CompileAndLink() = 0;
+
+ // override to perform custom tasks on successful compilation
+ // and linkage. E.g. obtaining handles to shader attributes.
+ virtual void OnCompiledAndLinked() {}
+
+ // override to perform custom tasks before shader is enabled
+ // and after it is disabled. Return false in OnDisabled() to
+ // disable shader.
+ // E.g. setting attributes, disabling texture unites, etc
+ virtual bool OnEnabled() { return true; }
+ virtual void OnDisabled() { }
+
+ virtual GLuint ProgramHandle() { return m_shaderProgram; }
+
+ protected:
+ CVertexShader* m_pVP = nullptr;
+ CPixelShader* m_pFP = nullptr;
+ GLuint m_shaderProgram = 0;
+ bool m_ok = false;
+ };
+
+
+ class CGLSLShaderProgram : virtual public CShaderProgram
+ {
+ public:
+ CGLSLShaderProgram();
+ CGLSLShaderProgram(const std::string& vert
+ , const std::string& frag);
+ ~CGLSLShaderProgram() override;
+
+ // enable the shader
+ bool Enable() override;
+
+ // disable the shader
+ void Disable() override;
+
+ // compile and link the shaders
+ bool CompileAndLink() override;
+
+ protected:
+ void Free();
+
+ GLint m_lastProgram;
+ bool m_validated = false;
+ };
+
+
+} // close namespace
+
diff --git a/xbmc/guilib/StereoscopicsManager.cpp b/xbmc/guilib/StereoscopicsManager.cpp
new file mode 100644
index 0000000..3aa269b
--- /dev/null
+++ b/xbmc/guilib/StereoscopicsManager.cpp
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+/*!
+ * @file StereoscopicsManager.cpp
+ * @brief This class acts as container for stereoscopic related functions
+ */
+
+#include "StereoscopicsManager.h"
+
+#include "GUIComponent.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/DataCacheCore.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "dialogs/GUIDialogSelect.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/RenderSystem.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/RegExp.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <stdlib.h>
+
+struct StereoModeMap
+{
+ const char* name;
+ RENDER_STEREO_MODE mode;
+};
+
+static const struct StereoModeMap VideoModeToGuiModeMap[] =
+{
+ { "mono", RENDER_STEREO_MODE_OFF },
+ { "left_right", RENDER_STEREO_MODE_SPLIT_VERTICAL },
+ { "right_left", RENDER_STEREO_MODE_SPLIT_VERTICAL },
+ { "top_bottom", RENDER_STEREO_MODE_SPLIT_HORIZONTAL },
+ { "bottom_top", RENDER_STEREO_MODE_SPLIT_HORIZONTAL },
+ { "checkerboard_rl", RENDER_STEREO_MODE_CHECKERBOARD },
+ { "checkerboard_lr", RENDER_STEREO_MODE_CHECKERBOARD },
+ { "row_interleaved_rl", RENDER_STEREO_MODE_INTERLACED },
+ { "row_interleaved_lr", RENDER_STEREO_MODE_INTERLACED },
+ { "col_interleaved_rl", RENDER_STEREO_MODE_OFF }, // unsupported
+ { "col_interleaved_lr", RENDER_STEREO_MODE_OFF }, // unsupported
+ { "anaglyph_cyan_red", RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN },
+ { "anaglyph_green_magenta", RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA },
+ { "anaglyph_yellow_blue", RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE },
+ { "block_lr", RENDER_STEREO_MODE_OFF }, // unsupported
+ { "block_rl", RENDER_STEREO_MODE_OFF }, // unsupported
+ {}
+};
+
+static const struct StereoModeMap StringToGuiModeMap[] =
+{
+ { "off", RENDER_STEREO_MODE_OFF },
+ { "split_vertical", RENDER_STEREO_MODE_SPLIT_VERTICAL },
+ { "side_by_side", RENDER_STEREO_MODE_SPLIT_VERTICAL }, // alias
+ { "sbs", RENDER_STEREO_MODE_SPLIT_VERTICAL }, // alias
+ { "split_horizontal", RENDER_STEREO_MODE_SPLIT_HORIZONTAL },
+ { "over_under", RENDER_STEREO_MODE_SPLIT_HORIZONTAL }, // alias
+ { "tab", RENDER_STEREO_MODE_SPLIT_HORIZONTAL }, // alias
+ { "row_interleaved", RENDER_STEREO_MODE_INTERLACED },
+ { "interlaced", RENDER_STEREO_MODE_INTERLACED }, // alias
+ { "checkerboard", RENDER_STEREO_MODE_CHECKERBOARD },
+ { "anaglyph_cyan_red", RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN },
+ { "anaglyph_green_magenta", RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA },
+ { "anaglyph_yellow_blue", RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE },
+ { "hardware_based", RENDER_STEREO_MODE_HARDWAREBASED },
+ { "monoscopic", RENDER_STEREO_MODE_MONO },
+ {}
+};
+
+CStereoscopicsManager::CStereoscopicsManager()
+ : m_settings(CServiceBroker::GetSettingsComponent()->GetSettings())
+{
+ m_stereoModeSetByUser = RENDER_STEREO_MODE_UNDEFINED;
+ m_lastStereoModeSetByUser = RENDER_STEREO_MODE_UNDEFINED;
+
+ //! @todo Move this to Initialize() to avoid potential problems in ctor
+ std::set<std::string> settingSet{
+ CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE
+ };
+ m_settings->GetSettingsManager()->RegisterCallback(this, settingSet);
+}
+
+CStereoscopicsManager::~CStereoscopicsManager(void)
+{
+ m_settings->GetSettingsManager()->UnregisterCallback(this);
+}
+
+void CStereoscopicsManager::Initialize()
+{
+ // turn off stereo mode on XBMC startup
+ SetStereoMode(RENDER_STEREO_MODE_OFF);
+}
+
+RENDER_STEREO_MODE CStereoscopicsManager::GetStereoMode(void) const
+{
+ return static_cast<RENDER_STEREO_MODE>(m_settings->GetInt(CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE));
+}
+
+void CStereoscopicsManager::SetStereoModeByUser(const RENDER_STEREO_MODE &mode)
+{
+ // only update last user mode if desired mode is different from current
+ if (mode != m_stereoModeSetByUser)
+ m_lastStereoModeSetByUser = m_stereoModeSetByUser;
+
+ m_stereoModeSetByUser = mode;
+ SetStereoMode(mode);
+}
+
+void CStereoscopicsManager::SetStereoMode(const RENDER_STEREO_MODE &mode)
+{
+ RENDER_STEREO_MODE currentMode = GetStereoMode();
+ RENDER_STEREO_MODE applyMode = mode;
+
+ // resolve automatic mode before applying
+ if (mode == RENDER_STEREO_MODE_AUTO)
+ applyMode = GetStereoModeOfPlayingVideo();
+
+ if (applyMode != currentMode && applyMode >= RENDER_STEREO_MODE_OFF)
+ {
+ if (CServiceBroker::GetRenderSystem()->SupportsStereo(applyMode))
+ m_settings->SetInt(CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE, applyMode);
+ }
+}
+
+RENDER_STEREO_MODE CStereoscopicsManager::GetNextSupportedStereoMode(const RENDER_STEREO_MODE &currentMode, int step) const
+{
+ RENDER_STEREO_MODE mode = currentMode;
+
+ do
+ {
+ mode = static_cast<RENDER_STEREO_MODE>((mode + step) % RENDER_STEREO_MODE_COUNT);
+
+ if (CServiceBroker::GetRenderSystem()->SupportsStereo(mode))
+ break;
+ } while (mode != currentMode);
+
+ return mode;
+}
+
+std::string CStereoscopicsManager::DetectStereoModeByString(const std::string &needle) const
+{
+ std::string stereoMode;
+ const std::string& searchString(needle);
+ CRegExp re(true);
+
+ if (!re.RegComp(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_3d.c_str()))
+ {
+ CLog::Log(
+ LOGERROR, "{}: Invalid RegExp for matching 3d content:'{}'", __FUNCTION__,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_3d);
+ return stereoMode;
+ }
+
+ if (re.RegFind(searchString) == -1)
+ return stereoMode; // no match found for 3d content, assume mono mode
+
+ if (!re.RegComp(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_sbs.c_str()))
+ {
+ CLog::Log(
+ LOGERROR, "{}: Invalid RegExp for matching 3d SBS content:'{}'", __FUNCTION__,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_sbs);
+ return stereoMode;
+ }
+
+ if (re.RegFind(searchString) > -1)
+ {
+ stereoMode = "left_right";
+ return stereoMode;
+ }
+
+ if (!re.RegComp(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_tab.c_str()))
+ {
+ CLog::Log(
+ LOGERROR, "{}: Invalid RegExp for matching 3d TAB content:'{}'", __FUNCTION__,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_stereoscopicregex_tab);
+ return stereoMode;
+ }
+
+ if (re.RegFind(searchString) > -1)
+ stereoMode = "top_bottom";
+
+ return stereoMode;
+}
+
+RENDER_STEREO_MODE CStereoscopicsManager::GetStereoModeByUserChoice() const
+{
+ RENDER_STEREO_MODE mode = GetStereoMode();
+
+ // if no stereo mode is set already, suggest mode of current video by preselecting it
+ if (mode == RENDER_STEREO_MODE_OFF)
+ mode = GetStereoModeOfPlayingVideo();
+
+ CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
+ pDlgSelect->Reset();
+
+ // "Select stereoscopic 3D mode"
+ pDlgSelect->SetHeading(CVariant{g_localizeStrings.Get(36528)});
+
+ // prepare selectable stereo modes
+ std::vector<RENDER_STEREO_MODE> selectableModes;
+ for (int i = RENDER_STEREO_MODE_OFF; i < RENDER_STEREO_MODE_COUNT; i++)
+ {
+ RENDER_STEREO_MODE selectableMode = static_cast<RENDER_STEREO_MODE>(i);
+ if (CServiceBroker::GetRenderSystem()->SupportsStereo(selectableMode))
+ {
+ selectableModes.push_back(selectableMode);
+ std::string label = GetLabelForStereoMode((RENDER_STEREO_MODE) i);
+ pDlgSelect->Add( label );
+ if (mode == selectableMode)
+ pDlgSelect->SetSelected( label );
+ }
+
+ // inject AUTO pseudo mode after OFF
+ if (i == RENDER_STEREO_MODE_OFF)
+ {
+ selectableModes.push_back(RENDER_STEREO_MODE_AUTO);
+ pDlgSelect->Add(GetLabelForStereoMode(RENDER_STEREO_MODE_AUTO));
+ }
+ }
+
+ pDlgSelect->Open();
+
+ int iItem = pDlgSelect->GetSelectedItem();
+ if (iItem > -1 && pDlgSelect->IsConfirmed())
+ mode = selectableModes[iItem];
+ else
+ mode = GetStereoMode();
+
+ return mode;
+}
+
+RENDER_STEREO_MODE CStereoscopicsManager::GetStereoModeOfPlayingVideo(void) const
+{
+ RENDER_STEREO_MODE mode = RENDER_STEREO_MODE_OFF;
+ std::string playerMode = GetVideoStereoMode();
+
+ if (!playerMode.empty())
+ {
+ int convertedMode = ConvertVideoToGuiStereoMode(playerMode);
+ if (convertedMode > -1)
+ mode = static_cast<RENDER_STEREO_MODE>(convertedMode);
+ }
+
+ CLog::Log(LOGDEBUG, "StereoscopicsManager: autodetected stereo mode for movie mode {} is: {}",
+ playerMode, ConvertGuiStereoModeToString(mode));
+ return mode;
+}
+
+std::string CStereoscopicsManager::GetLabelForStereoMode(const RENDER_STEREO_MODE &mode) const
+{
+ int msgId;
+ switch(mode) {
+ case RENDER_STEREO_MODE_AUTO:
+ msgId = 36532;
+ break;
+ case RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE:
+ msgId = 36510;
+ break;
+ case RENDER_STEREO_MODE_INTERLACED:
+ msgId = 36507;
+ break;
+ case RENDER_STEREO_MODE_CHECKERBOARD:
+ msgId = 36511;
+ break;
+ case RENDER_STEREO_MODE_HARDWAREBASED:
+ msgId = 36508;
+ break;
+ case RENDER_STEREO_MODE_MONO:
+ msgId = 36509;
+ break;
+ default:
+ msgId = 36502 + mode;
+ }
+
+ return g_localizeStrings.Get(msgId);
+}
+
+RENDER_STEREO_MODE CStereoscopicsManager::GetPreferredPlaybackMode(void) const
+{
+ return static_cast<RENDER_STEREO_MODE>(m_settings->GetInt(CSettings::SETTING_VIDEOSCREEN_PREFEREDSTEREOSCOPICMODE));
+}
+
+int CStereoscopicsManager::ConvertVideoToGuiStereoMode(const std::string &mode)
+{
+ size_t i = 0;
+ while (VideoModeToGuiModeMap[i].name)
+ {
+ if (mode == VideoModeToGuiModeMap[i].name)
+ return VideoModeToGuiModeMap[i].mode;
+ i++;
+ }
+ return -1;
+}
+
+int CStereoscopicsManager::ConvertStringToGuiStereoMode(const std::string &mode)
+{
+ size_t i = 0;
+ while (StringToGuiModeMap[i].name)
+ {
+ if (mode == StringToGuiModeMap[i].name)
+ return StringToGuiModeMap[i].mode;
+ i++;
+ }
+ return ConvertVideoToGuiStereoMode(mode);
+}
+
+const char* CStereoscopicsManager::ConvertGuiStereoModeToString(const RENDER_STEREO_MODE &mode)
+{
+ size_t i = 0;
+ while (StringToGuiModeMap[i].name)
+ {
+ if (StringToGuiModeMap[i].mode == mode)
+ return StringToGuiModeMap[i].name;
+ i++;
+ }
+ return "";
+}
+
+std::string CStereoscopicsManager::NormalizeStereoMode(const std::string &mode)
+{
+ if (!mode.empty() && mode != "mono")
+ {
+ int guiMode = ConvertStringToGuiStereoMode(mode);
+
+ if (guiMode > -1)
+ return ConvertGuiStereoModeToString((RENDER_STEREO_MODE) guiMode);
+ else
+ return mode;
+ }
+
+ return "mono";
+}
+
+CAction CStereoscopicsManager::ConvertActionCommandToAction(const std::string &command, const std::string &parameter)
+{
+ std::string cmd = command;
+ std::string para = parameter;
+ StringUtils::ToLower(cmd);
+ StringUtils::ToLower(para);
+ if (cmd == "setstereomode")
+ {
+ int actionId = -1;
+ if (para == "next")
+ actionId = ACTION_STEREOMODE_NEXT;
+ else if (para == "previous")
+ actionId = ACTION_STEREOMODE_PREVIOUS;
+ else if (para == "toggle")
+ actionId = ACTION_STEREOMODE_TOGGLE;
+ else if (para == "select")
+ actionId = ACTION_STEREOMODE_SELECT;
+ else if (para == "tomono")
+ actionId = ACTION_STEREOMODE_TOMONO;
+
+ // already have a valid actionID return it
+ if (actionId > -1)
+ return CAction(actionId);
+
+ // still no valid action ID, check if parameter is a supported stereomode
+ if (ConvertStringToGuiStereoMode(para) > -1)
+ return CAction(ACTION_STEREOMODE_SET, para);
+ }
+ return CAction(ACTION_NONE);
+}
+
+void CStereoscopicsManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ if (setting == NULL)
+ return;
+
+ const std::string &settingId = setting->GetId();
+
+ if (settingId == CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE)
+ {
+ RENDER_STEREO_MODE mode = GetStereoMode();
+ CLog::Log(LOGDEBUG, "StereoscopicsManager: stereo mode setting changed to {}",
+ ConvertGuiStereoModeToString(mode));
+ ApplyStereoMode(mode);
+ }
+}
+
+bool CStereoscopicsManager::OnMessage(CGUIMessage &message)
+{
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_PLAYBACK_STOPPED:
+ case GUI_MSG_PLAYLISTPLAYER_STOPPED:
+ OnPlaybackStopped();
+ break;
+ }
+
+ return false;
+}
+
+bool CStereoscopicsManager::OnAction(const CAction &action)
+{
+ RENDER_STEREO_MODE mode = GetStereoMode();
+
+ if (action.GetID() == ACTION_STEREOMODE_NEXT)
+ {
+ SetStereoModeByUser(GetNextSupportedStereoMode(mode));
+ return true;
+ }
+ else if (action.GetID() == ACTION_STEREOMODE_PREVIOUS)
+ {
+ SetStereoModeByUser(GetNextSupportedStereoMode(mode, RENDER_STEREO_MODE_COUNT - 1));
+ return true;
+ }
+ else if (action.GetID() == ACTION_STEREOMODE_TOGGLE)
+ {
+ if (mode == RENDER_STEREO_MODE_OFF)
+ {
+ RENDER_STEREO_MODE targetMode = GetPreferredPlaybackMode();
+
+ // if user selected a specific mode before, make sure to
+ // switch back into that mode on toggle.
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_UNDEFINED)
+ {
+ // if user mode is set to OFF, he manually turned it off before. In this case use the last user applied mode
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_OFF)
+ targetMode = m_stereoModeSetByUser;
+ else if (m_lastStereoModeSetByUser != RENDER_STEREO_MODE_UNDEFINED && m_lastStereoModeSetByUser != RENDER_STEREO_MODE_OFF)
+ targetMode = m_lastStereoModeSetByUser;
+ }
+
+ SetStereoModeByUser(targetMode);
+ }
+ else
+ {
+ SetStereoModeByUser(RENDER_STEREO_MODE_OFF);
+ }
+ return true;
+ }
+ else if (action.GetID() == ACTION_STEREOMODE_SELECT)
+ {
+ SetStereoModeByUser(GetStereoModeByUserChoice());
+ return true;
+ }
+ else if (action.GetID() == ACTION_STEREOMODE_TOMONO)
+ {
+ if (mode == RENDER_STEREO_MODE_MONO)
+ {
+ RENDER_STEREO_MODE targetMode = GetPreferredPlaybackMode();
+
+ // if we have an old userdefined stereomode, use that one as toggle target
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_UNDEFINED)
+ {
+ // if user mode is set to OFF, he manually turned it off before. In this case use the last user applied mode
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_OFF && m_stereoModeSetByUser != mode)
+ targetMode = m_stereoModeSetByUser;
+ else if (m_lastStereoModeSetByUser != RENDER_STEREO_MODE_UNDEFINED && m_lastStereoModeSetByUser != RENDER_STEREO_MODE_OFF && m_lastStereoModeSetByUser != mode)
+ targetMode = m_lastStereoModeSetByUser;
+ }
+
+ SetStereoModeByUser(targetMode);
+ }
+ else
+ {
+ SetStereoModeByUser(RENDER_STEREO_MODE_MONO);
+ }
+ return true;
+ }
+ else if (action.GetID() == ACTION_STEREOMODE_SET)
+ {
+ int stereoMode = ConvertStringToGuiStereoMode(action.GetName());
+ if (stereoMode > -1)
+ SetStereoModeByUser(static_cast<RENDER_STEREO_MODE>(stereoMode));
+ return true;
+ }
+
+ return false;
+}
+
+void CStereoscopicsManager::ApplyStereoMode(const RENDER_STEREO_MODE &mode, bool notify)
+{
+ RENDER_STEREO_MODE currentMode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
+ CLog::Log(LOGDEBUG,
+ "StereoscopicsManager::ApplyStereoMode: trying to apply stereo mode. Current: {} | "
+ "Target: {}",
+ ConvertGuiStereoModeToString(currentMode), ConvertGuiStereoModeToString(mode));
+ if (currentMode != mode)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(mode);
+ CLog::Log(LOGDEBUG, "StereoscopicsManager: stereo mode changed to {}",
+ ConvertGuiStereoModeToString(mode));
+ if (notify)
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(36501), GetLabelForStereoMode(mode));
+ }
+}
+
+std::string CStereoscopicsManager::GetVideoStereoMode() const
+{
+ std::string playerMode;
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlaying())
+ playerMode = CServiceBroker::GetDataCacheCore().GetVideoStereoMode();
+
+ return playerMode;
+}
+
+bool CStereoscopicsManager::IsVideoStereoscopic() const
+{
+ std::string mode = GetVideoStereoMode();
+ return !mode.empty() && mode != "mono";
+}
+
+void CStereoscopicsManager::OnStreamChange()
+{
+ STEREOSCOPIC_PLAYBACK_MODE playbackMode = static_cast<STEREOSCOPIC_PLAYBACK_MODE>(m_settings->GetInt(CSettings::SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE));
+ RENDER_STEREO_MODE mode = GetStereoMode();
+
+ // early return if playback mode should be ignored and we're in no stereoscopic mode right now
+ if (playbackMode == STEREOSCOPIC_PLAYBACK_MODE_IGNORE && mode == RENDER_STEREO_MODE_OFF)
+ return;
+
+ if (!CStereoscopicsManager::IsVideoStereoscopic())
+ {
+ // exit stereo mode if started item is not stereoscopic
+ // and if user prefers to stop 3D playback when movie is finished
+ if (mode != RENDER_STEREO_MODE_OFF && m_settings->GetBool(CSettings::SETTING_VIDEOPLAYER_QUITSTEREOMODEONSTOP))
+ SetStereoMode(RENDER_STEREO_MODE_OFF);
+ return;
+ }
+
+ // if we're not in stereomode yet, restore previously selected stereo mode in case it was user selected
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_UNDEFINED)
+ {
+ SetStereoMode(m_stereoModeSetByUser);
+ return;
+ }
+
+ RENDER_STEREO_MODE preferred = GetPreferredPlaybackMode();
+ RENDER_STEREO_MODE playing = GetStereoModeOfPlayingVideo();
+
+ if (mode != RENDER_STEREO_MODE_OFF)
+ {
+ // don't change mode if user selected to not exit stereomode on playback stop
+ // users selecting this option usually have to manually switch their TV into 3D mode
+ // and would be annoyed by having to switch TV modes when next movies comes up
+ // @todo probably add a new setting for just this behavior
+ if (m_settings->GetBool(CSettings::SETTING_VIDEOPLAYER_QUITSTEREOMODEONSTOP) == false)
+ return;
+
+ // only change to new stereo mode if not yet in preferred stereo mode
+ if (mode == preferred || (preferred == RENDER_STEREO_MODE_AUTO && mode == playing))
+ return;
+ }
+
+ switch (playbackMode)
+ {
+ case STEREOSCOPIC_PLAYBACK_MODE_ASK: // Ask
+ {
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE);
+
+ CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
+ pDlgSelect->Reset();
+ pDlgSelect->SetHeading(CVariant{g_localizeStrings.Get(36527)});
+
+ int idx_playing = -1;
+
+ // add choices
+ int idx_preferred = pDlgSelect->Add(g_localizeStrings.Get(36524) // preferred
+ + " ("
+ + GetLabelForStereoMode(preferred)
+ + ")");
+
+ int idx_mono = pDlgSelect->Add(GetLabelForStereoMode(RENDER_STEREO_MODE_MONO)); // mono / 2d
+
+ if (playing != RENDER_STEREO_MODE_OFF && playing != preferred && preferred != RENDER_STEREO_MODE_AUTO && CServiceBroker::GetRenderSystem()->SupportsStereo(playing)) // same as movie
+ idx_playing = pDlgSelect->Add(g_localizeStrings.Get(36532)
+ + " ("
+ + GetLabelForStereoMode(playing)
+ + ")");
+
+ int idx_select = pDlgSelect->Add( g_localizeStrings.Get(36531) ); // other / select
+
+ pDlgSelect->Open();
+
+ if (pDlgSelect->IsConfirmed())
+ {
+ int iItem = pDlgSelect->GetSelectedItem();
+ if (iItem == idx_preferred) mode = preferred;
+ else if (iItem == idx_mono) mode = RENDER_STEREO_MODE_MONO;
+ else if (iItem == idx_playing) mode = RENDER_STEREO_MODE_AUTO;
+ else if (iItem == idx_select) mode = GetStereoModeByUserChoice();
+
+ SetStereoModeByUser(mode);
+ }
+
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_UNPAUSE);
+ }
+ break;
+ case STEREOSCOPIC_PLAYBACK_MODE_PREFERRED: // Stereoscopic
+ SetStereoMode(preferred);
+ break;
+ case 2: // Mono
+ SetStereoMode(RENDER_STEREO_MODE_MONO);
+ break;
+ default:
+ break;
+ }
+}
+
+void CStereoscopicsManager::OnPlaybackStopped(void)
+{
+ RENDER_STEREO_MODE mode = GetStereoMode();
+
+ if (m_settings->GetBool(CSettings::SETTING_VIDEOPLAYER_QUITSTEREOMODEONSTOP) && mode != RENDER_STEREO_MODE_OFF)
+ SetStereoMode(RENDER_STEREO_MODE_OFF);
+
+ // reset user modes on playback end to start over new on next playback and not end up in a probably unwanted mode
+ if (m_stereoModeSetByUser != RENDER_STEREO_MODE_OFF)
+ m_lastStereoModeSetByUser = m_stereoModeSetByUser;
+
+ m_stereoModeSetByUser = RENDER_STEREO_MODE_UNDEFINED;
+}
diff --git a/xbmc/guilib/StereoscopicsManager.h b/xbmc/guilib/StereoscopicsManager.h
new file mode 100644
index 0000000..8de93dc
--- /dev/null
+++ b/xbmc/guilib/StereoscopicsManager.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+ * @file StereoscopicsManager.cpp
+ * @brief This class acts as container for stereoscopic related functions
+ */
+
+#include "guilib/IMsgTargetCallback.h"
+#include "rendering/RenderSystemTypes.h"
+#include "settings/lib/ISettingCallback.h"
+
+#include <stdlib.h>
+
+class CAction;
+class CDataCacheCore;
+class CGUIWindowManager;
+class CSettings;
+
+enum STEREOSCOPIC_PLAYBACK_MODE
+{
+ STEREOSCOPIC_PLAYBACK_MODE_ASK,
+ STEREOSCOPIC_PLAYBACK_MODE_PREFERRED,
+ STEREOSCOPIC_PLAYBACK_MODE_MONO,
+
+ STEREOSCOPIC_PLAYBACK_MODE_IGNORE = 100,
+};
+
+class CStereoscopicsManager : public ISettingCallback,
+ public IMsgTargetCallback
+{
+public:
+ CStereoscopicsManager();
+
+ ~CStereoscopicsManager(void) override;
+
+ void Initialize();
+
+ RENDER_STEREO_MODE GetStereoMode(void) const;
+ std::string DetectStereoModeByString(const std::string &needle) const;
+ std::string GetLabelForStereoMode(const RENDER_STEREO_MODE &mode) const;
+
+ void SetStereoMode(const RENDER_STEREO_MODE &mode);
+
+ static const char* ConvertGuiStereoModeToString(const RENDER_STEREO_MODE &mode);
+ /**
+ * @brief Converts a stereoscopics related action/command from Builtins and JsonRPC into the according cAction ID.
+ * @param command The command/action
+ * @param parameter The parameter of the command
+ * @return The integer of the according cAction or -1 if not valid
+ */
+ static CAction ConvertActionCommandToAction(const std::string &command, const std::string &parameter);
+ static std::string NormalizeStereoMode(const std::string &mode);
+
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+ void OnStreamChange();
+ bool OnMessage(CGUIMessage &message) override;
+ /*!
+ * @brief Handle 3D specific cActions
+ * @param action The action to process
+ * @return True if action could be handled, false otherwise.
+ */
+ bool OnAction(const CAction &action);
+
+private:
+ RENDER_STEREO_MODE GetNextSupportedStereoMode(const RENDER_STEREO_MODE &currentMode, int step = 1) const;
+ RENDER_STEREO_MODE GetStereoModeByUserChoice() const;
+ RENDER_STEREO_MODE GetStereoModeOfPlayingVideo(void) const;
+ RENDER_STEREO_MODE GetPreferredPlaybackMode(void) const;
+ std::string GetVideoStereoMode() const;
+ bool IsVideoStereoscopic() const;
+
+ void SetStereoModeByUser(const RENDER_STEREO_MODE &mode);
+
+ void ApplyStereoMode(const RENDER_STEREO_MODE &mode, bool notify = true);
+ void OnPlaybackStopped(void);
+
+ /**
+ * @brief will convert a string representation into a GUI stereo mode
+ * @param mode The string to convert
+ * @return -1 if not found, otherwise the according int of the RENDER_STEREO_MODE enum
+ */
+ static int ConvertStringToGuiStereoMode(const std::string &mode);
+ static int ConvertVideoToGuiStereoMode(const std::string &mode);
+
+ // Construction parameters
+ std::shared_ptr<CSettings> m_settings;
+
+ // Stereoscopic parameters
+ RENDER_STEREO_MODE m_stereoModeSetByUser;
+ RENDER_STEREO_MODE m_lastStereoModeSetByUser;
+};
diff --git a/xbmc/guilib/Texture.cpp b/xbmc/guilib/Texture.cpp
new file mode 100644
index 0000000..4a2f80e
--- /dev/null
+++ b/xbmc/guilib/Texture.cpp
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Texture.h"
+
+#include "DDSImage.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "commons/ilog.h"
+#include "filesystem/File.h"
+#include "filesystem/ResourceFile.h"
+#include "filesystem/XbtFile.h"
+#include "guilib/iimage.h"
+#include "guilib/imagefactory.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#if defined(TARGET_DARWIN_EMBEDDED)
+#include <ImageIO/ImageIO.h>
+#include "filesystem/File.h"
+#endif
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/AndroidAppFile.h"
+#endif
+#include "rendering/RenderSystem.h"
+#include "utils/MemUtils.h"
+
+#include <algorithm>
+#include <cstring>
+#include <exception>
+#include <utility>
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CTexture::CTexture(unsigned int width, unsigned int height, unsigned int format)
+{
+ m_pixels = NULL;
+ m_loadedToGPU = false;
+ Allocate(width, height, format);
+}
+
+CTexture::~CTexture()
+{
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = NULL;
+}
+
+void CTexture::Allocate(unsigned int width, unsigned int height, unsigned int format)
+{
+ m_imageWidth = m_originalWidth = width;
+ m_imageHeight = m_originalHeight = height;
+ m_format = format;
+ m_orientation = 0;
+
+ m_textureWidth = m_imageWidth;
+ m_textureHeight = m_imageHeight;
+
+ if (m_format & XB_FMT_DXT_MASK)
+ {
+ while (GetPitch() < CServiceBroker::GetRenderSystem()->GetMinDXTPitch())
+ m_textureWidth += GetBlockSize();
+ }
+
+ if (!CServiceBroker::GetRenderSystem()->SupportsNPOT((m_format & XB_FMT_DXT_MASK) != 0))
+ {
+ m_textureWidth = PadPow2(m_textureWidth);
+ m_textureHeight = PadPow2(m_textureHeight);
+ }
+
+ if (m_format & XB_FMT_DXT_MASK)
+ {
+ // DXT textures must be a multiple of 4 in width and height
+ m_textureWidth = ((m_textureWidth + 3) / 4) * 4;
+ m_textureHeight = ((m_textureHeight + 3) / 4) * 4;
+ }
+ else
+ {
+ // align all textures so that they have an even width
+ // in some circumstances when we downsize a thumbnail
+ // which has an uneven number of pixels in width
+ // we crash in CPicture::ScaleImage in ffmpegs swscale
+ // because it tries to access beyond the source memory
+ // (happens on osx and ios)
+ // UPDATE: don't just update to be on an even width;
+ // ffmpegs swscale relies on a 16-byte stride on some systems
+ // so the textureWidth needs to be a multiple of 16. see ffmpeg
+ // swscale headers for more info.
+ m_textureWidth = ((m_textureWidth + 15) / 16) * 16;
+ }
+
+ // check for max texture size
+ #define CLAMP(x, y) { if (x > y) x = y; }
+ CLAMP(m_textureWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize());
+ CLAMP(m_textureHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize());
+ CLAMP(m_imageWidth, m_textureWidth);
+ CLAMP(m_imageHeight, m_textureHeight);
+
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = NULL;
+ if (GetPitch() * GetRows() > 0)
+ {
+ size_t size = GetPitch() * GetRows();
+ m_pixels = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(size, 32));
+
+ if (m_pixels == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - Could not allocate {} bytes. Out of memory.", __FUNCTION__, size);
+ }
+ }
+}
+
+void CTexture::Update(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ const unsigned char* pixels,
+ bool loadToGPU)
+{
+ if (pixels == NULL)
+ return;
+
+ if (format & XB_FMT_DXT_MASK)
+ return;
+
+ Allocate(width, height, format);
+
+ if (m_pixels == nullptr)
+ return;
+
+ unsigned int srcPitch = pitch ? pitch : GetPitch(width);
+ unsigned int srcRows = GetRows(height);
+ unsigned int dstPitch = GetPitch(m_textureWidth);
+ unsigned int dstRows = GetRows(m_textureHeight);
+
+ if (srcPitch == dstPitch)
+ memcpy(m_pixels, pixels, srcPitch * std::min(srcRows, dstRows));
+ else
+ {
+ const unsigned char *src = pixels;
+ unsigned char* dst = m_pixels;
+ for (unsigned int y = 0; y < srcRows && y < dstRows; y++)
+ {
+ memcpy(dst, src, std::min(srcPitch, dstPitch));
+ src += srcPitch;
+ dst += dstPitch;
+ }
+ }
+ ClampToEdge();
+
+ if (loadToGPU)
+ LoadToGPU();
+}
+
+void CTexture::ClampToEdge()
+{
+ if (m_pixels == nullptr)
+ return;
+
+ unsigned int imagePitch = GetPitch(m_imageWidth);
+ unsigned int imageRows = GetRows(m_imageHeight);
+ unsigned int texturePitch = GetPitch(m_textureWidth);
+ unsigned int textureRows = GetRows(m_textureHeight);
+ if (imagePitch < texturePitch)
+ {
+ unsigned int blockSize = GetBlockSize();
+ unsigned char *src = m_pixels + imagePitch - blockSize;
+ unsigned char *dst = m_pixels;
+ for (unsigned int y = 0; y < imageRows; y++)
+ {
+ for (unsigned int x = imagePitch; x < texturePitch; x += blockSize)
+ memcpy(dst + x, src, blockSize);
+ dst += texturePitch;
+ }
+ }
+
+ if (imageRows < textureRows)
+ {
+ unsigned char *dst = m_pixels + imageRows * texturePitch;
+ for (unsigned int y = imageRows; y < textureRows; y++)
+ {
+ memcpy(dst, dst - texturePitch, texturePitch);
+ dst += texturePitch;
+ }
+ }
+}
+
+std::unique_ptr<CTexture> CTexture::LoadFromFile(const std::string& texturePath,
+ unsigned int idealWidth,
+ unsigned int idealHeight,
+ bool requirePixels,
+ const std::string& strMimeType)
+{
+#if defined(TARGET_ANDROID)
+ CURL url(texturePath);
+ if (url.IsProtocol("androidapp"))
+ {
+ XFILE::CFileAndroidApp file;
+ if (file.Open(url))
+ {
+ unsigned char* inputBuff;
+ unsigned int width;
+ unsigned int height;
+ unsigned int inputBuffSize = file.ReadIcon(&inputBuff, &width, &height);
+ file.Close();
+ if (!inputBuffSize)
+ return NULL;
+
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ texture->LoadFromMemory(width, height, width*4, XB_FMT_RGBA8, true, inputBuff);
+ delete[] inputBuff;
+ return texture;
+ }
+ }
+#endif
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ if (texture->LoadFromFileInternal(texturePath, idealWidth, idealHeight, requirePixels, strMimeType))
+ return texture;
+ return {};
+}
+
+std::unique_ptr<CTexture> CTexture::LoadFromFileInMemory(unsigned char* buffer,
+ size_t bufferSize,
+ const std::string& mimeType,
+ unsigned int idealWidth,
+ unsigned int idealHeight)
+{
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ if (texture->LoadFromFileInMem(buffer, bufferSize, mimeType, idealWidth, idealHeight))
+ return texture;
+ return {};
+}
+
+bool CTexture::LoadFromFileInternal(const std::string& texturePath,
+ unsigned int maxWidth,
+ unsigned int maxHeight,
+ bool requirePixels,
+ const std::string& strMimeType)
+{
+ if (URIUtils::HasExtension(texturePath, ".dds"))
+ { // special case for DDS images
+ CDDSImage image;
+ if (image.ReadFile(texturePath))
+ {
+ Update(image.GetWidth(), image.GetHeight(), 0, image.GetFormat(), image.GetData(), false);
+ return true;
+ }
+ return false;
+ }
+
+ unsigned int width = maxWidth ? std::min(maxWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ unsigned int height = maxHeight ? std::min(maxHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+
+ // Read image into memory to use our vfs
+ XFILE::CFile file;
+ std::vector<uint8_t> buf;
+
+ if (file.LoadFile(texturePath, buf) <= 0)
+ return false;
+
+ CURL url(texturePath);
+ // make sure resource:// paths are properly resolved
+ if (url.IsProtocol("resource"))
+ {
+ std::string translatedPath;
+ if (XFILE::CResourceFile::TranslatePath(url, translatedPath))
+ url.Parse(translatedPath);
+ }
+
+ // handle xbt:// paths differently because it allows loading the texture directly from memory
+ if (url.IsProtocol("xbt"))
+ {
+ XFILE::CXbtFile xbtFile;
+ if (!xbtFile.Open(url))
+ return false;
+
+ return LoadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0,
+ xbtFile.GetImageFormat(), xbtFile.HasImageAlpha(), buf.data());
+ }
+
+ IImage* pImage;
+
+ if(strMimeType.empty())
+ pImage = ImageFactory::CreateLoader(texturePath);
+ else
+ pImage = ImageFactory::CreateLoaderFromMimeType(strMimeType);
+
+ if (!LoadIImage(pImage, buf.data(), buf.size(), width, height))
+ {
+ CLog::Log(LOGDEBUG, "{} - Load of {} failed.", __FUNCTION__, CURL::GetRedacted(texturePath));
+ delete pImage;
+ return false;
+ }
+ delete pImage;
+
+ return true;
+}
+
+bool CTexture::LoadFromFileInMem(unsigned char* buffer,
+ size_t size,
+ const std::string& mimeType,
+ unsigned int maxWidth,
+ unsigned int maxHeight)
+{
+ if (!buffer || !size)
+ return false;
+
+ unsigned int width = maxWidth ? std::min(maxWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ unsigned int height = maxHeight ? std::min(maxHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+
+ IImage* pImage = ImageFactory::CreateLoaderFromMimeType(mimeType);
+ if(!LoadIImage(pImage, buffer, size, width, height))
+ {
+ delete pImage;
+ return false;
+ }
+ delete pImage;
+ return true;
+}
+
+bool CTexture::LoadIImage(IImage* pImage,
+ unsigned char* buffer,
+ unsigned int bufSize,
+ unsigned int width,
+ unsigned int height)
+{
+ if(pImage != NULL && pImage->LoadImageFromMemory(buffer, bufSize, width, height))
+ {
+ if (pImage->Width() > 0 && pImage->Height() > 0)
+ {
+ Allocate(pImage->Width(), pImage->Height(), XB_FMT_A8R8G8B8);
+ if (m_pixels != nullptr && pImage->Decode(m_pixels, GetTextureWidth(), GetRows(), GetPitch(), XB_FMT_A8R8G8B8))
+ {
+ if (pImage->Orientation())
+ m_orientation = pImage->Orientation() - 1;
+ m_hasAlpha = pImage->hasAlpha();
+ m_originalWidth = pImage->originalWidth();
+ m_originalHeight = pImage->originalHeight();
+ m_imageWidth = pImage->Width();
+ m_imageHeight = pImage->Height();
+ ClampToEdge();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool CTexture::LoadFromMemory(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ bool hasAlpha,
+ const unsigned char* pixels)
+{
+ m_imageWidth = m_originalWidth = width;
+ m_imageHeight = m_originalHeight = height;
+ m_format = format;
+ m_hasAlpha = hasAlpha;
+ Update(width, height, pitch, format, pixels, false);
+ return true;
+}
+
+bool CTexture::LoadPaletted(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ const unsigned char* pixels,
+ const COLOR* palette)
+{
+ if (pixels == NULL || palette == NULL)
+ return false;
+
+ Allocate(width, height, format);
+
+ for (unsigned int y = 0; y < m_imageHeight; y++)
+ {
+ unsigned char *dest = m_pixels + y * GetPitch();
+ const unsigned char *src = pixels + y * pitch;
+ for (unsigned int x = 0; x < m_imageWidth; x++)
+ {
+ COLOR col = palette[*src++];
+ *dest++ = col.b;
+ *dest++ = col.g;
+ *dest++ = col.r;
+ *dest++ = col.x;
+ }
+ }
+ ClampToEdge();
+ return true;
+}
+
+unsigned int CTexture::PadPow2(unsigned int x)
+{
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return ++x;
+}
+
+bool CTexture::SwapBlueRed(unsigned char* pixels,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int elements,
+ unsigned int offset)
+{
+ if (!pixels) return false;
+ unsigned char *dst = pixels;
+ for (unsigned int y = 0; y < height; y++)
+ {
+ dst = pixels + (y * pitch);
+ for (unsigned int x = 0; x < pitch; x+=elements)
+ std::swap(dst[x+offset], dst[x+2+offset]);
+ }
+ return true;
+}
+
+unsigned int CTexture::GetPitch(unsigned int width) const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return ((width + 3) / 4) * 8;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return ((width + 3) / 4) * 16;
+ case XB_FMT_A8:
+ return width;
+ case XB_FMT_RGB8:
+ return (((width + 1)* 3 / 4) * 4);
+ case XB_FMT_RGBA8:
+ case XB_FMT_A8R8G8B8:
+ default:
+ return width*4;
+ }
+}
+
+unsigned int CTexture::GetRows(unsigned int height) const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return (height + 3) / 4;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return (height + 3) / 4;
+ default:
+ return height;
+ }
+}
+
+unsigned int CTexture::GetBlockSize() const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return 8;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return 16;
+ case XB_FMT_A8:
+ return 1;
+ default:
+ return 4;
+ }
+}
+
+bool CTexture::HasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+void CTexture::SetMipmapping()
+{
+ m_mipmapping = true;
+}
+
+bool CTexture::IsMipmapped() const
+{
+ return m_mipmapping;
+}
diff --git a/xbmc/guilib/Texture.h b/xbmc/guilib/Texture.h
new file mode 100644
index 0000000..343e996
--- /dev/null
+++ b/xbmc/guilib/Texture.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/TextureFormats.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+class IImage;
+
+
+#pragma pack(1)
+struct COLOR {unsigned char b,g,r,x;}; // Windows GDI expects 4bytes per color
+#pragma pack()
+
+enum class TEXTURE_SCALING
+{
+ LINEAR,
+ NEAREST,
+};
+
+/*!
+\ingroup textures
+\brief Base texture class, subclasses of which depend on the render spec (DX, GL etc.)
+*/
+class CTexture
+{
+
+public:
+ CTexture(unsigned int width = 0, unsigned int height = 0, unsigned int format = XB_FMT_A8R8G8B8);
+ virtual ~CTexture();
+
+ static std::unique_ptr<CTexture> CreateTexture(unsigned int width = 0,
+ unsigned int height = 0,
+ unsigned int format = XB_FMT_A8R8G8B8);
+
+ /*! \brief Load a texture from a file
+ Loads a texture from a file, restricting in size if needed based on maxHeight and maxWidth.
+ Note that these are the ideal size to load at - the returned texture may be smaller or larger than these.
+ \param texturePath the path of the texture to load.
+ \param idealWidth the ideal width of the texture (defaults to 0, no ideal width).
+ \param idealHeight the ideal height of the texture (defaults to 0, no ideal height).
+ \param strMimeType mimetype of the given texture if available (defaults to empty)
+ \return a CTexture std::unique_ptr to the created texture - nullptr if the texture failed to load.
+ */
+ static std::unique_ptr<CTexture> LoadFromFile(const std::string& texturePath,
+ unsigned int idealWidth = 0,
+ unsigned int idealHeight = 0,
+ bool requirePixels = false,
+ const std::string& strMimeType = "");
+
+ /*! \brief Load a texture from a file in memory
+ Loads a texture from a file in memory, restricting in size if needed based on maxHeight and maxWidth.
+ Note that these are the ideal size to load at - the returned texture may be smaller or larger than these.
+ \param buffer the memory buffer holding the file.
+ \param bufferSize the size of buffer.
+ \param mimeType the mime type of the file in buffer.
+ \param idealWidth the ideal width of the texture (defaults to 0, no ideal width).
+ \param idealHeight the ideal height of the texture (defaults to 0, no ideal height).
+ \return a CTexture std::unique_ptr to the created texture - nullptr if the texture failed to load.
+ */
+ static std::unique_ptr<CTexture> LoadFromFileInMemory(unsigned char* buffer,
+ size_t bufferSize,
+ const std::string& mimeType,
+ unsigned int idealWidth = 0,
+ unsigned int idealHeight = 0);
+
+ bool LoadFromMemory(unsigned int width, unsigned int height, unsigned int pitch, unsigned int format, bool hasAlpha, const unsigned char* pixels);
+ bool LoadPaletted(unsigned int width, unsigned int height, unsigned int pitch, unsigned int format, const unsigned char *pixels, const COLOR *palette);
+
+ bool HasAlpha() const;
+
+ void SetMipmapping();
+ bool IsMipmapped() const;
+ void SetScalingMethod(TEXTURE_SCALING scalingMethod) { m_scalingMethod = scalingMethod; }
+ TEXTURE_SCALING GetScalingMethod() const { return m_scalingMethod; }
+ void SetCacheMemory(bool bCacheMemory) { m_bCacheMemory = bCacheMemory; }
+ bool GetCacheMemory() const { return m_bCacheMemory; }
+
+ virtual void CreateTextureObject() = 0;
+ virtual void DestroyTextureObject() = 0;
+ virtual void LoadToGPU() = 0;
+ virtual void BindToUnit(unsigned int unit) = 0;
+
+ unsigned char* GetPixels() const { return m_pixels; }
+ unsigned int GetPitch() const { return GetPitch(m_textureWidth); }
+ unsigned int GetRows() const { return GetRows(m_textureHeight); }
+ unsigned int GetTextureWidth() const { return m_textureWidth; }
+ unsigned int GetTextureHeight() const { return m_textureHeight; }
+ unsigned int GetWidth() const { return m_imageWidth; }
+ unsigned int GetHeight() const { return m_imageHeight; }
+ /*! \brief return the original width of the image, before scaling/cropping */
+ unsigned int GetOriginalWidth() const { return m_originalWidth; }
+ /*! \brief return the original height of the image, before scaling/cropping */
+ unsigned int GetOriginalHeight() const { return m_originalHeight; }
+
+ int GetOrientation() const { return m_orientation; }
+ void SetOrientation(int orientation) { m_orientation = orientation; }
+
+ void Update(unsigned int width, unsigned int height, unsigned int pitch, unsigned int format, const unsigned char *pixels, bool loadToGPU);
+ void Allocate(unsigned int width, unsigned int height, unsigned int format);
+ void ClampToEdge();
+
+ static unsigned int PadPow2(unsigned int x);
+ static bool SwapBlueRed(unsigned char *pixels, unsigned int height, unsigned int pitch, unsigned int elements = 4, unsigned int offset=0);
+
+private:
+ // no copy constructor
+ CTexture(const CTexture& copy) = delete;
+
+protected:
+ bool LoadFromFileInMem(unsigned char* buffer, size_t size, const std::string& mimeType,
+ unsigned int maxWidth, unsigned int maxHeight);
+ bool LoadFromFileInternal(const std::string& texturePath, unsigned int maxWidth, unsigned int maxHeight, bool requirePixels, const std::string& strMimeType = "");
+ bool LoadIImage(IImage* pImage, unsigned char* buffer, unsigned int bufSize, unsigned int width, unsigned int height);
+ // helpers for computation of texture parameters for compressed textures
+ unsigned int GetPitch(unsigned int width) const;
+ unsigned int GetRows(unsigned int height) const;
+ unsigned int GetBlockSize() const;
+
+ unsigned int m_imageWidth;
+ unsigned int m_imageHeight;
+ unsigned int m_textureWidth;
+ unsigned int m_textureHeight;
+ unsigned int m_originalWidth; ///< original image width before scaling or cropping
+ unsigned int m_originalHeight; ///< original image height before scaling or cropping
+
+ unsigned char* m_pixels;
+ bool m_loadedToGPU;
+ unsigned int m_format;
+ int m_orientation;
+ bool m_hasAlpha = true ;
+ bool m_mipmapping = false ;
+ TEXTURE_SCALING m_scalingMethod = TEXTURE_SCALING::LINEAR;
+ bool m_bCacheMemory = false;
+};
diff --git a/xbmc/guilib/TextureBundle.cpp b/xbmc/guilib/TextureBundle.cpp
new file mode 100644
index 0000000..491ca69
--- /dev/null
+++ b/xbmc/guilib/TextureBundle.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureBundle.h"
+
+#include "guilib/TextureBundleXBT.h"
+
+class CTexture;
+
+CTextureBundle::CTextureBundle() : m_tbXBT{false}, m_useXBT{false}
+{
+}
+
+CTextureBundle::CTextureBundle(bool useXBT) : m_tbXBT{useXBT}, m_useXBT{useXBT}
+{
+}
+
+bool CTextureBundle::HasFile(const std::string& Filename)
+{
+ if (m_useXBT)
+ {
+ return m_tbXBT.HasFile(Filename);
+ }
+
+ if (m_tbXBT.HasFile(Filename))
+ {
+ m_useXBT = true;
+ return true;
+ }
+
+ return false;
+}
+
+std::vector<std::string> CTextureBundle::GetTexturesFromPath(const std::string& path)
+{
+ if (m_useXBT)
+ return m_tbXBT.GetTexturesFromPath(path);
+ else
+ return {};
+}
+
+bool CTextureBundle::LoadTexture(const std::string& filename,
+ std::unique_ptr<CTexture>& texture,
+ int& width,
+ int& height)
+{
+ if (m_useXBT)
+ return m_tbXBT.LoadTexture(filename, texture, width, height);
+ else
+ return false;
+}
+
+bool CTextureBundle::LoadAnim(const std::string& filename,
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>>& textures,
+ int& width,
+ int& height,
+ int& nLoops)
+{
+ if (m_useXBT)
+ return m_tbXBT.LoadAnim(filename, textures, width, height, nLoops);
+ else
+ return false;
+}
+
+void CTextureBundle::Close()
+{
+ m_tbXBT.CloseBundle();
+}
+
+void CTextureBundle::SetThemeBundle(bool themeBundle)
+{
+ m_tbXBT.SetThemeBundle(themeBundle);
+}
+
+std::string CTextureBundle::Normalize(std::string name)
+{
+ return CTextureBundleXBT::Normalize(std::move(name));
+}
diff --git a/xbmc/guilib/TextureBundle.h b/xbmc/guilib/TextureBundle.h
new file mode 100644
index 0000000..a736192
--- /dev/null
+++ b/xbmc/guilib/TextureBundle.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "TextureBundleXBT.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+class CTexture;
+
+class CTextureBundle
+{
+public:
+ CTextureBundle();
+ explicit CTextureBundle(bool useXBT);
+ ~CTextureBundle() = default;
+
+ void SetThemeBundle(bool themeBundle);
+ bool HasFile(const std::string& Filename);
+ std::vector<std::string> GetTexturesFromPath(const std::string& path);
+ static std::string Normalize(std::string name);
+
+ /*!
+ * \brief Load texture from bundle
+ *
+ * \param[in] filename name of the texture to load
+ * \param[out] texture holds the pointer to the texture after successful loading
+ * \param[out] width width of the loaded texture
+ * \param[out] height height of the loaded texture
+ * \return true if texture was loaded
+ *
+ * \todo With c++17 this should be changed to return a std::optional that's
+ * wrapping a struct containing the output values. Same for
+ * CTextureBundleXBT::LoadTexture.
+ */
+ bool LoadTexture(const std::string& filename,
+ std::unique_ptr<CTexture>& texture,
+ int& width,
+ int& height);
+
+ /*!
+ * \brief Load animation from bundle
+ *
+ * \param[in] filename name of the animation to load
+ * \param[out] texture vector of frames. Each frame is pair of a texture and
+ * the duration the frame
+ * \param[out] width width of the loaded textures
+ * \param[out] height height of the loaded textures
+ * \return true if animation was loaded
+ *
+ * \todo With c++17 this should be changed to return a std::optional that's
+ * wrapping a struct containing the output values. Same for
+ * CTextureBundleXBT::LoadAnim.
+ */
+ bool LoadAnim(const std::string& filename,
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>>& textures,
+ int& width,
+ int& height,
+ int& nLoops);
+ void Close();
+private:
+ CTextureBundleXBT m_tbXBT;
+
+ bool m_useXBT;
+};
+
+
diff --git a/xbmc/guilib/TextureBundleXBT.cpp b/xbmc/guilib/TextureBundleXBT.cpp
new file mode 100644
index 0000000..981ddad
--- /dev/null
+++ b/xbmc/guilib/TextureBundleXBT.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureBundleXBT.h"
+
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "URL.h"
+#include "XBTF.h"
+#include "XBTFReader.h"
+#include "commons/ilog.h"
+#include "filesystem/SpecialProtocol.h"
+#include "filesystem/XbtManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+
+#include <lzo/lzo1x.h>
+#include <lzo/lzoconf.h>
+
+
+#ifdef TARGET_WINDOWS_DESKTOP
+#ifdef NDEBUG
+#pragma comment(lib,"lzo2.lib")
+#else
+#pragma comment(lib, "lzo2d.lib")
+#endif
+#endif
+
+CTextureBundleXBT::CTextureBundleXBT()
+ : m_TimeStamp{0}
+ , m_themeBundle{false}
+{
+}
+
+CTextureBundleXBT::CTextureBundleXBT(bool themeBundle)
+ : m_TimeStamp{0}
+ , m_themeBundle{themeBundle}
+{
+}
+
+CTextureBundleXBT::~CTextureBundleXBT(void)
+{
+ CloseBundle();
+}
+
+void CTextureBundleXBT::CloseBundle()
+{
+ if (m_XBTFReader != nullptr && m_XBTFReader->IsOpen())
+ {
+ XFILE::CXbtManager::GetInstance().Release(CURL(m_path));
+ CLog::Log(LOGDEBUG, "{} - Closed {}bundle", __FUNCTION__, m_themeBundle ? "theme " : "");
+ }
+}
+
+bool CTextureBundleXBT::OpenBundle()
+{
+ // Find the correct texture file (skin or theme)
+ if (m_themeBundle)
+ {
+ // if we are the theme bundle, we only load if the user has chosen
+ // a valid theme (or the skin has a default one)
+ std::string theme = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
+ if (!theme.empty() && !StringUtils::EqualsNoCase(theme, "SKINDEFAULT"))
+ {
+ std::string themeXBT(URIUtils::ReplaceExtension(theme, ".xbt"));
+ m_path = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media", themeXBT);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ m_path = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media", "Textures.xbt");
+ }
+
+ m_path = CSpecialProtocol::TranslatePathConvertCase(m_path);
+
+ // Load the texture file
+ if (!XFILE::CXbtManager::GetInstance().GetReader(CURL(m_path), m_XBTFReader))
+ {
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "{} - Opened bundle {}", __FUNCTION__, m_path);
+
+ m_TimeStamp = m_XBTFReader->GetLastModificationTimestamp();
+
+ if (lzo_init() != LZO_E_OK)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool CTextureBundleXBT::HasFile(const std::string& Filename)
+{
+ if ((m_XBTFReader == nullptr || !m_XBTFReader->IsOpen()) && !OpenBundle())
+ return false;
+
+ if (m_XBTFReader->GetLastModificationTimestamp() > m_TimeStamp)
+ {
+ CLog::Log(LOGINFO, "Texture bundle has changed, reloading");
+ if (!OpenBundle())
+ return false;
+ }
+
+ std::string name = Normalize(Filename);
+ return m_XBTFReader->Exists(name);
+}
+
+std::vector<std::string> CTextureBundleXBT::GetTexturesFromPath(const std::string& path)
+{
+ if (path.size() > 1 && path[1] == ':')
+ return {};
+
+ if ((m_XBTFReader == nullptr || !m_XBTFReader->IsOpen()) && !OpenBundle())
+ return {};
+
+ std::string testPath = Normalize(path);
+ URIUtils::AddSlashAtEnd(testPath);
+
+ std::vector<std::string> textures;
+ std::vector<CXBTFFile> files = m_XBTFReader->GetFiles();
+ for (size_t i = 0; i < files.size(); i++)
+ {
+ std::string filePath = files[i].GetPath();
+ if (StringUtils::StartsWithNoCase(filePath, testPath))
+ textures.emplace_back(std::move(filePath));
+ }
+
+ return textures;
+}
+
+bool CTextureBundleXBT::LoadTexture(const std::string& filename,
+ std::unique_ptr<CTexture>& texture,
+ int& width,
+ int& height)
+{
+ std::string name = Normalize(filename);
+
+ CXBTFFile file;
+ if (!m_XBTFReader->Get(name, file))
+ return false;
+
+ if (file.GetFrames().empty())
+ return false;
+
+ const CXBTFFrame& frame = file.GetFrames().at(0);
+ if (!ConvertFrameToTexture(filename, frame, texture))
+ {
+ return false;
+ }
+
+ width = frame.GetWidth();
+ height = frame.GetHeight();
+
+ return true;
+}
+
+bool CTextureBundleXBT::LoadAnim(const std::string& filename,
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>>& textures,
+ int& width,
+ int& height,
+ int& nLoops)
+{
+ std::string name = Normalize(filename);
+
+ CXBTFFile file;
+ if (!m_XBTFReader->Get(name, file))
+ return false;
+
+ if (file.GetFrames().empty())
+ return false;
+
+ size_t nTextures = file.GetFrames().size();
+ textures.reserve(nTextures);
+
+ for (size_t i = 0; i < nTextures; i++)
+ {
+ CXBTFFrame& frame = file.GetFrames().at(i);
+
+ std::unique_ptr<CTexture> texture;
+ if (!ConvertFrameToTexture(filename, frame, texture))
+ return false;
+
+ textures.emplace_back(std::move(texture), frame.GetDuration());
+ }
+
+ width = file.GetFrames().at(0).GetWidth();
+ height = file.GetFrames().at(0).GetHeight();
+ nLoops = file.GetLoop();
+
+ return true;
+}
+
+bool CTextureBundleXBT::ConvertFrameToTexture(const std::string& name,
+ const CXBTFFrame& frame,
+ std::unique_ptr<CTexture>& texture)
+{
+ // found texture - allocate the necessary buffers
+ std::vector<unsigned char> buffer(static_cast<size_t>(frame.GetPackedSize()));
+
+ // load the compressed texture
+ if (!m_XBTFReader->Load(frame, buffer.data()))
+ {
+ CLog::Log(LOGERROR, "Error loading texture: {}", name);
+ return false;
+ }
+
+ // check if it's packed with lzo
+ if (frame.IsPacked())
+ { // unpack
+ std::vector<unsigned char> unpacked(static_cast<size_t>(frame.GetUnpackedSize()));
+ lzo_uint s = (lzo_uint)frame.GetUnpackedSize();
+ if (lzo1x_decompress_safe(buffer.data(), static_cast<lzo_uint>(buffer.size()), unpacked.data(),
+ &s, NULL) != LZO_E_OK ||
+ s != frame.GetUnpackedSize())
+ {
+ CLog::Log(LOGERROR, "Error loading texture: {}: Decompression error", name);
+ return false;
+ }
+ buffer = std::move(unpacked);
+ }
+
+ // create an xbmc texture
+ texture = CTexture::CreateTexture();
+ texture->LoadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, frame.GetFormat(),
+ frame.HasAlpha(), buffer.data());
+
+ return true;
+}
+
+void CTextureBundleXBT::SetThemeBundle(bool themeBundle)
+{
+ m_themeBundle = themeBundle;
+}
+
+// normalize to how it's stored within the bundle
+// lower case + using forward slash rather than back slash
+std::string CTextureBundleXBT::Normalize(std::string name)
+{
+ StringUtils::Trim(name);
+ StringUtils::ToLower(name);
+ StringUtils::Replace(name, '\\', '/');
+
+ return name;
+}
+
+std::vector<uint8_t> CTextureBundleXBT::UnpackFrame(const CXBTFReader& reader,
+ const CXBTFFrame& frame)
+{
+ // load the compressed texture
+ std::vector<uint8_t> packedBuffer(static_cast<size_t>(frame.GetPackedSize()));
+ if (!reader.Load(frame, packedBuffer.data()))
+ {
+ CLog::Log(LOGERROR, "CTextureBundleXBT: error loading frame");
+ return {};
+ }
+
+ // if the frame isn't packed there's nothing else to be done
+ if (!frame.IsPacked())
+ return packedBuffer;
+
+ // make sure lzo is initialized
+ if (lzo_init() != LZO_E_OK)
+ {
+ CLog::Log(LOGERROR, "CTextureBundleXBT: failed to initialize lzo");
+ return {};
+ }
+
+ lzo_uint size = static_cast<lzo_uint>(frame.GetUnpackedSize());
+ std::vector<uint8_t> unpackedBuffer(static_cast<size_t>(frame.GetUnpackedSize()));
+ if (lzo1x_decompress_safe(packedBuffer.data(), static_cast<lzo_uint>(packedBuffer.size()),
+ unpackedBuffer.data(), &size, nullptr) != LZO_E_OK ||
+ size != frame.GetUnpackedSize())
+ {
+ CLog::Log(LOGERROR,
+ "CTextureBundleXBT: failed to decompress frame with {} unpacked bytes to {} bytes",
+ frame.GetPackedSize(), frame.GetUnpackedSize());
+ return {};
+ }
+
+ return unpackedBuffer;
+}
diff --git a/xbmc/guilib/TextureBundleXBT.h b/xbmc/guilib/TextureBundleXBT.h
new file mode 100644
index 0000000..915b52b
--- /dev/null
+++ b/xbmc/guilib/TextureBundleXBT.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <ctime>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+class CTexture;
+class CXBTFReader;
+class CXBTFFrame;
+
+class CTextureBundleXBT
+{
+public:
+ CTextureBundleXBT();
+ explicit CTextureBundleXBT(bool themeBundle);
+ ~CTextureBundleXBT();
+
+ void SetThemeBundle(bool themeBundle);
+ bool HasFile(const std::string& Filename);
+ std::vector<std::string> GetTexturesFromPath(const std::string& path);
+ static std::string Normalize(std::string name);
+
+ /*!
+ * \brief See CTextureBundle::LoadTexture
+ */
+ bool LoadTexture(const std::string& filename,
+ std::unique_ptr<CTexture>& texture,
+ int& width,
+ int& height);
+
+ /*!
+ * \brief See CTextureBundle::LoadAnim
+ */
+ bool LoadAnim(const std::string& filename,
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>>& textures,
+ int& width,
+ int& height,
+ int& nLoops);
+
+ //! @todo Change return to std::optional<std::vector<uint8_t>>> when c++17 is allowed
+ static std::vector<uint8_t> UnpackFrame(const CXBTFReader& reader, const CXBTFFrame& frame);
+
+ void CloseBundle();
+
+private:
+ bool OpenBundle();
+ bool ConvertFrameToTexture(const std::string& name,
+ const CXBTFFrame& frame,
+ std::unique_ptr<CTexture>& texture);
+
+ time_t m_TimeStamp;
+
+ bool m_themeBundle;
+ std::string m_path;
+ std::shared_ptr<CXBTFReader> m_XBTFReader;
+};
+
+
diff --git a/xbmc/guilib/TextureDX.cpp b/xbmc/guilib/TextureDX.cpp
new file mode 100644
index 0000000..1690add
--- /dev/null
+++ b/xbmc/guilib/TextureDX.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureDX.h"
+
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+
+#include <memory>
+
+std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width,
+ unsigned int height,
+ unsigned int format)
+{
+ return std::make_unique<CDXTexture>(width, height, format);
+}
+
+CDXTexture::CDXTexture(unsigned int width, unsigned int height, unsigned int format)
+ : CTexture(width, height, format)
+{
+}
+
+CDXTexture::~CDXTexture()
+{
+ DestroyTextureObject();
+}
+
+void CDXTexture::CreateTextureObject()
+{
+ m_texture.Create(m_textureWidth, m_textureHeight, 1, D3D11_USAGE_DEFAULT, GetFormat());
+}
+
+DXGI_FORMAT CDXTexture::GetFormat()
+{
+ DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN; // DXGI_FORMAT_UNKNOWN
+
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ format = DXGI_FORMAT_BC1_UNORM; // D3DFMT_DXT1 -> DXGI_FORMAT_BC1_UNORM & DXGI_FORMAT_BC1_UNORM_SRGB
+ break;
+ case XB_FMT_DXT3:
+ format = DXGI_FORMAT_BC2_UNORM; // D3DFMT_DXT3 -> DXGI_FORMAT_BC2_UNORM & DXGI_FORMAT_BC2_UNORM_SRGB
+ break;
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ format = DXGI_FORMAT_BC3_UNORM; // XB_FMT_DXT5 -> DXGI_FORMAT_BC3_UNORM & DXGI_FORMAT_BC3_UNORM_SRGB
+ break;
+ case XB_FMT_RGB8:
+ case XB_FMT_A8R8G8B8:
+ format = DXGI_FORMAT_B8G8R8A8_UNORM; // D3DFMT_A8R8G8B8 -> DXGI_FORMAT_B8G8R8A8_UNORM | DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
+ break;
+ case XB_FMT_A8:
+ format = DXGI_FORMAT_R8_UNORM; // XB_FMT_A8 -> DXGI_FORMAT_A8_UNORM
+ break;
+ }
+
+ return format;
+}
+
+void CDXTexture::DestroyTextureObject()
+{
+ m_texture.Release();
+}
+
+void CDXTexture::LoadToGPU()
+{
+ if (!m_pixels)
+ {
+ // nothing to load - probably same image (no change)
+ return;
+ }
+
+ bool needUpdate = true;
+ D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
+ if (m_format == XB_FMT_RGB8)
+ usage = D3D11_USAGE_DYNAMIC; // fallback to dynamic to allow CPU write to texture
+
+ if (m_texture.Get() == nullptr)
+ {
+ // creates texture with initial data
+ if (m_format != XB_FMT_RGB8)
+ {
+ // this is faster way to create texture with initial data instead of create empty and then copy to it
+ m_texture.Create(m_textureWidth, m_textureHeight, IsMipmapped() ? 0 : 1, usage, GetFormat(), m_pixels, GetPitch());
+ if (m_texture.Get() != nullptr)
+ needUpdate = false;
+ }
+ else
+ m_texture.Create(m_textureWidth, m_textureHeight, IsMipmapped() ? 0 : 1, usage, GetFormat());
+
+ if (m_texture.Get() == nullptr)
+ {
+ CLog::Log(LOGDEBUG, "CDXTexture::CDXTexture: Error creating new texture for size {} x {}.",
+ m_textureWidth, m_textureHeight);
+ return;
+ }
+ }
+ else
+ {
+ // need to update texture, check usage first
+ D3D11_TEXTURE2D_DESC texDesc;
+ m_texture.GetDesc(&texDesc);
+ usage = texDesc.Usage;
+
+ // if usage is not dynamic re-create texture with dynamic usage for future updates
+ if (usage != D3D11_USAGE_DYNAMIC && usage != D3D11_USAGE_STAGING)
+ {
+ m_texture.Release();
+ usage = D3D11_USAGE_DYNAMIC;
+
+ m_texture.Create(m_textureWidth, m_textureHeight, IsMipmapped() ? 0 : 1, usage, GetFormat(), m_pixels, GetPitch());
+ if (m_texture.Get() == nullptr)
+ {
+ CLog::Log(LOGDEBUG, "CDXTexture::CDXTexture: Error creating new texture for size {} x {}.",
+ m_textureWidth, m_textureHeight);
+ return;
+ }
+
+ needUpdate = false;
+ }
+ }
+
+ if (needUpdate)
+ {
+ D3D11_MAP mapType = (usage == D3D11_USAGE_STAGING) ? D3D11_MAP_WRITE : D3D11_MAP_WRITE_DISCARD;
+ D3D11_MAPPED_SUBRESOURCE lr;
+ if (m_texture.LockRect(0, &lr, mapType))
+ {
+ unsigned char *dst = (unsigned char *)lr.pData;
+ unsigned char *src = m_pixels;
+ unsigned int dstPitch = lr.RowPitch;
+ unsigned int srcPitch = GetPitch();
+ unsigned int minPitch = std::min(srcPitch, dstPitch);
+
+ unsigned int rows = GetRows();
+ if (m_format == XB_FMT_RGB8)
+ {
+ for (unsigned int y = 0; y < rows; y++)
+ {
+ unsigned char *dst2 = dst;
+ unsigned char *src2 = src;
+ for (unsigned int x = 0; x < srcPitch / 3; x++, dst2 += 4, src2 += 3)
+ {
+ dst2[0] = src2[2];
+ dst2[1] = src2[1];
+ dst2[2] = src2[0];
+ dst2[3] = 0xff;
+ }
+ src += srcPitch;
+ dst += dstPitch;
+ }
+ }
+ else if (srcPitch == dstPitch)
+ {
+ memcpy(dst, src, srcPitch * rows);
+ }
+ else
+ {
+ for (unsigned int y = 0; y < rows; y++)
+ {
+ memcpy(dst, src, minPitch);
+ src += srcPitch;
+ dst += dstPitch;
+ }
+ }
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "failed to lock texture.");
+ }
+ m_texture.UnlockRect(0);
+ if (usage != D3D11_USAGE_STAGING && IsMipmapped())
+ m_texture.GenerateMipmaps();
+ }
+
+ if (!m_bCacheMemory)
+ {
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = nullptr;
+ }
+
+ m_loadedToGPU = true;
+}
+
+void CDXTexture::BindToUnit(unsigned int unit)
+{
+}
diff --git a/xbmc/guilib/TextureDX.h b/xbmc/guilib/TextureDX.h
new file mode 100644
index 0000000..0858780
--- /dev/null
+++ b/xbmc/guilib/TextureDX.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "D3DResource.h"
+#include "Texture.h"
+
+/************************************************************************/
+/* CDXTexture */
+/************************************************************************/
+class CDXTexture : public CTexture
+{
+public:
+ CDXTexture(unsigned int width = 0, unsigned int height = 0, unsigned int format = XB_FMT_UNKNOWN);
+ virtual ~CDXTexture();
+
+ void CreateTextureObject();
+ void DestroyTextureObject();
+ virtual void LoadToGPU();
+ void BindToUnit(unsigned int unit);
+
+ ID3D11Texture2D* GetTextureObject()
+ {
+ return m_texture.Get();
+ };
+
+ ID3D11ShaderResourceView* GetShaderResource()
+ {
+ return m_texture.GetShaderResource();
+ };
+
+private:
+ CD3DTexture m_texture;
+ DXGI_FORMAT GetFormat();
+};
diff --git a/xbmc/guilib/TextureFormats.h b/xbmc/guilib/TextureFormats.h
new file mode 100644
index 0000000..95fff02
--- /dev/null
+++ b/xbmc/guilib/TextureFormats.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#define XB_FMT_MASK 0xffff ///< mask for format info - other flags are outside this
+#define XB_FMT_DXT_MASK 15
+#define XB_FMT_UNKNOWN 0
+#define XB_FMT_DXT1 1
+#define XB_FMT_DXT3 2
+#define XB_FMT_DXT5 4
+#define XB_FMT_DXT5_YCoCg 8
+#define XB_FMT_A8R8G8B8 16 // texture.xbt byte order (matches BGRA8)
+#define XB_FMT_A8 32
+#define XB_FMT_RGBA8 64
+#define XB_FMT_RGB8 128
+#define XB_FMT_OPAQUE 65536
diff --git a/xbmc/guilib/TextureGL.cpp b/xbmc/guilib/TextureGL.cpp
new file mode 100644
index 0000000..ef01bdf
--- /dev/null
+++ b/xbmc/guilib/TextureGL.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureGL.h"
+
+#include "ServiceBroker.h"
+#include "guilib/TextureManager.h"
+#include "rendering/RenderSystem.h"
+#include "settings/AdvancedSettings.h"
+#include "utils/GLUtils.h"
+#include "utils/MemUtils.h"
+#include "utils/log.h"
+
+#include <memory>
+
+std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width,
+ unsigned int height,
+ unsigned int format)
+{
+ return std::make_unique<CGLTexture>(width, height, format);
+}
+
+CGLTexture::CGLTexture(unsigned int width, unsigned int height, unsigned int format)
+ : CTexture(width, height, format)
+{
+ unsigned int major, minor;
+ CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor);
+ if (major >= 3)
+ m_isOglVersion3orNewer = true;
+}
+
+CGLTexture::~CGLTexture()
+{
+ DestroyTextureObject();
+}
+
+void CGLTexture::CreateTextureObject()
+{
+ glGenTextures(1, (GLuint*) &m_texture);
+}
+
+void CGLTexture::DestroyTextureObject()
+{
+ if (m_texture)
+ CServiceBroker::GetGUI()->GetTextureManager().ReleaseHwTexture(m_texture);
+}
+
+void CGLTexture::LoadToGPU()
+{
+ if (!m_pixels)
+ {
+ // nothing to load - probably same image (no change)
+ return;
+ }
+ if (m_texture == 0)
+ {
+ // Have OpenGL generate a texture object handle for us
+ // this happens only one time - the first time the texture is loaded
+ CreateTextureObject();
+ }
+
+ // Bind the texture object
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+
+ GLenum filter = (m_scalingMethod == TEXTURE_SCALING::NEAREST ? GL_NEAREST : GL_LINEAR);
+
+ // Set the texture's stretching properties
+ if (IsMipmapped())
+ {
+ GLenum mipmapFilter = (m_scalingMethod == TEXTURE_SCALING::NEAREST ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmapFilter);
+
+#ifndef HAS_GLES
+ // Lower LOD bias equals more sharpness, but less smooth animation
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.5f);
+ if (!m_isOglVersion3orNewer)
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+#endif
+ }
+ else
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ unsigned int maxSize = CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ if (m_textureHeight > maxSize)
+ {
+ CLog::Log(LOGERROR,
+ "GL: Image height {} too big to fit into single texture unit, truncating to {}",
+ m_textureHeight, maxSize);
+ m_textureHeight = maxSize;
+ }
+ if (m_textureWidth > maxSize)
+ {
+ CLog::Log(LOGERROR,
+ "GL: Image width {} too big to fit into single texture unit, truncating to {}",
+ m_textureWidth, maxSize);
+#ifndef HAS_GLES
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, m_textureWidth);
+#endif
+ m_textureWidth = maxSize;
+ }
+
+#ifndef HAS_GLES
+ GLenum format = GL_BGRA;
+ GLint numcomponents = GL_RGBA;
+
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ break;
+ case XB_FMT_DXT3:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ break;
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ break;
+ case XB_FMT_RGB8:
+ format = GL_RGB;
+ numcomponents = GL_RGB;
+ break;
+ case XB_FMT_A8R8G8B8:
+ default:
+ break;
+ }
+
+ if ((m_format & XB_FMT_DXT_MASK) == 0)
+ {
+ glTexImage2D(GL_TEXTURE_2D, 0, numcomponents,
+ m_textureWidth, m_textureHeight, 0,
+ format, GL_UNSIGNED_BYTE, m_pixels);
+ }
+ else
+ {
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, format,
+ m_textureWidth, m_textureHeight, 0,
+ GetPitch() * GetRows(), m_pixels);
+ }
+
+ if (IsMipmapped() && m_isOglVersion3orNewer)
+ {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+#else // GLES version
+
+ // All incoming textures are BGRA, which GLES does not necessarily support.
+ // Some (most?) hardware supports BGRA textures via an extension.
+ // If not, we convert to RGBA first to avoid having to swizzle in shaders.
+ // Explicitly define GL_BGRA_EXT here in the case that it's not defined by
+ // system headers, and trust the extension list instead.
+#ifndef GL_BGRA_EXT
+#define GL_BGRA_EXT 0x80E1
+#endif
+
+ GLint internalformat;
+ GLenum pixelformat;
+
+ switch (m_format)
+ {
+ default:
+ case XB_FMT_RGBA8:
+ internalformat = pixelformat = GL_RGBA;
+ break;
+ case XB_FMT_RGB8:
+ internalformat = pixelformat = GL_RGB;
+ break;
+ case XB_FMT_A8R8G8B8:
+ if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_format_BGRA8888") ||
+ CServiceBroker::GetRenderSystem()->IsExtSupported("GL_IMG_texture_format_BGRA8888"))
+ {
+ internalformat = pixelformat = GL_BGRA_EXT;
+ }
+ else if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_APPLE_texture_format_BGRA8888"))
+ {
+ // Apple's implementation does not conform to spec. Instead, they require
+ // differing format/internalformat, more like GL.
+ internalformat = GL_RGBA;
+ pixelformat = GL_BGRA_EXT;
+ }
+ else
+ {
+ SwapBlueRed(m_pixels, m_textureHeight, GetPitch());
+ internalformat = pixelformat = GL_RGBA;
+ }
+ break;
+ }
+ glTexImage2D(GL_TEXTURE_2D, 0, internalformat, m_textureWidth, m_textureHeight, 0,
+ pixelformat, GL_UNSIGNED_BYTE, m_pixels);
+
+ if (IsMipmapped())
+ {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+
+#endif
+ VerifyGLState();
+
+ if (!m_bCacheMemory)
+ {
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = NULL;
+ }
+
+ m_loadedToGPU = true;
+}
+
+void CGLTexture::BindToUnit(unsigned int unit)
+{
+ glActiveTexture(GL_TEXTURE0 + unit);
+ glBindTexture(GL_TEXTURE_2D, m_texture);
+}
+
diff --git a/xbmc/guilib/TextureGL.h b/xbmc/guilib/TextureGL.h
new file mode 100644
index 0000000..51c7035
--- /dev/null
+++ b/xbmc/guilib/TextureGL.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "Texture.h"
+
+#include "system_gl.h"
+
+/************************************************************************/
+/* CGLTexture */
+/************************************************************************/
+class CGLTexture : public CTexture
+{
+public:
+ CGLTexture(unsigned int width = 0, unsigned int height = 0, unsigned int format = XB_FMT_A8R8G8B8);
+ ~CGLTexture() override;
+
+ void CreateTextureObject() override;
+ void DestroyTextureObject() override;
+ void LoadToGPU() override;
+ void BindToUnit(unsigned int unit) override;
+
+protected:
+ GLuint m_texture = 0;
+ bool m_isOglVersion3orNewer = false;
+};
+
diff --git a/xbmc/guilib/TextureManager.cpp b/xbmc/guilib/TextureManager.cpp
new file mode 100644
index 0000000..ddf2e77
--- /dev/null
+++ b/xbmc/guilib/TextureManager.cpp
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "TextureManager.h"
+
+#include "ServiceBroker.h"
+#include "Texture.h"
+#include "URL.h"
+#include "commons/ilog.h"
+#include "filesystem/Directory.h"
+#include "filesystem/File.h"
+#include "guilib/TextureBundle.h"
+#include "guilib/TextureFormats.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+#ifdef _DEBUG_TEXTURES
+#include "utils/TimeUtils.h"
+#endif
+#if defined(TARGET_DARWIN_IOS)
+#define WIN_SYSTEM_CLASS CWinSystemIOS
+#include "windowing/ios/WinSystemIOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
+#elif defined(TARGET_DARWIN_TVOS)
+#define WIN_SYSTEM_CLASS CWinSystemTVOS
+#include "windowing/tvos/WinSystemTVOS.h" // for g_Windowing in CGUITextureManager::FreeUnusedTextures
+#endif
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+#include "system_gl.h"
+#endif
+
+#include "FFmpegImage.h"
+
+#include <algorithm>
+#include <cassert>
+#include <exception>
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CTextureArray::CTextureArray(int width, int height, int loops, bool texCoordsArePixels)
+{
+ m_width = width;
+ m_height = height;
+ m_loops = loops;
+ m_orientation = 0;
+ m_texWidth = 0;
+ m_texHeight = 0;
+ m_texCoordsArePixels = false;
+}
+
+CTextureArray::CTextureArray()
+{
+ Reset();
+}
+
+CTextureArray::~CTextureArray() = default;
+
+unsigned int CTextureArray::size() const
+{
+ return m_textures.size();
+}
+
+
+void CTextureArray::Reset()
+{
+ m_textures.clear();
+ m_delays.clear();
+ m_width = 0;
+ m_height = 0;
+ m_loops = 0;
+ m_orientation = 0;
+ m_texWidth = 0;
+ m_texHeight = 0;
+ m_texCoordsArePixels = false;
+}
+
+void CTextureArray::Add(std::shared_ptr<CTexture> texture, int delay)
+{
+ if (!texture)
+ return;
+
+ m_texWidth = texture->GetTextureWidth();
+ m_texHeight = texture->GetTextureHeight();
+ m_texCoordsArePixels = false;
+
+ m_textures.emplace_back(std::move(texture));
+ m_delays.push_back(delay);
+}
+
+void CTextureArray::Set(std::shared_ptr<CTexture> texture, int width, int height)
+{
+ assert(!m_textures.size()); // don't try and set a texture if we already have one!
+ m_width = width;
+ m_height = height;
+ m_orientation = texture ? texture->GetOrientation() : 0;
+ Add(std::move(texture), 2);
+}
+
+void CTextureArray::Free()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ Reset();
+}
+
+
+/************************************************************************/
+/* */
+/************************************************************************/
+
+CTextureMap::CTextureMap()
+{
+ m_referenceCount = 0;
+ m_memUsage = 0;
+}
+
+CTextureMap::CTextureMap(const std::string& textureName, int width, int height, int loops)
+: m_texture(width, height, loops)
+, m_textureName(textureName)
+{
+ m_referenceCount = 0;
+ m_memUsage = 0;
+}
+
+CTextureMap::~CTextureMap()
+{
+ FreeTexture();
+}
+
+bool CTextureMap::Release()
+{
+ if (!m_texture.m_textures.size())
+ return true;
+ if (!m_referenceCount)
+ return true;
+
+ m_referenceCount--;
+ if (!m_referenceCount)
+ {
+ return true;
+ }
+ return false;
+}
+
+const std::string& CTextureMap::GetName() const
+{
+ return m_textureName;
+}
+
+const CTextureArray& CTextureMap::GetTexture()
+{
+ m_referenceCount++;
+ return m_texture;
+}
+
+void CTextureMap::Dump() const
+{
+ if (!m_referenceCount)
+ return; // nothing to see here
+
+ CLog::Log(LOGDEBUG, "{0}: texture:{1} has {2} frames {3} refcount", __FUNCTION__, m_textureName,
+ m_texture.m_textures.size(), m_referenceCount);
+}
+
+unsigned int CTextureMap::GetMemoryUsage() const
+{
+ return m_memUsage;
+}
+
+void CTextureMap::Flush()
+{
+ if (!m_referenceCount)
+ FreeTexture();
+}
+
+
+void CTextureMap::FreeTexture()
+{
+ m_texture.Free();
+}
+
+void CTextureMap::SetHeight(int height)
+{
+ m_texture.m_height = height;
+}
+
+void CTextureMap::SetWidth(int height)
+{
+ m_texture.m_width = height;
+}
+
+bool CTextureMap::IsEmpty() const
+{
+ return m_texture.m_textures.empty();
+}
+
+void CTextureMap::Add(std::unique_ptr<CTexture> texture, int delay)
+{
+ if (texture)
+ m_memUsage += sizeof(CTexture) + (texture->GetTextureWidth() * texture->GetTextureHeight() * 4);
+
+ m_texture.Add(std::move(texture), delay);
+}
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CGUITextureManager::CGUITextureManager(void)
+{
+ // we set the theme bundle to be the first bundle (thus prioritizing it)
+ m_TexBundle[0].SetThemeBundle(true);
+}
+
+CGUITextureManager::~CGUITextureManager(void)
+{
+ Cleanup();
+}
+
+/************************************************************************/
+/* */
+/************************************************************************/
+bool CGUITextureManager::CanLoad(const std::string &texturePath)
+{
+ if (texturePath.empty())
+ return false;
+
+ if (!CURL::IsFullPath(texturePath))
+ return true; // assume we have it
+
+ // we can't (or shouldn't) be loading from remote paths, so check these
+ return URIUtils::IsHD(texturePath);
+}
+
+bool CGUITextureManager::HasTexture(const std::string &textureName, std::string *path, int *bundle, int *size)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+
+ // default values
+ if (bundle) *bundle = -1;
+ if (size) *size = 0;
+ if (path) *path = textureName;
+
+ if (textureName.empty())
+ return false;
+
+ if (!CanLoad(textureName))
+ return false;
+
+ // Check our loaded and bundled textures - we store in bundles using \\.
+ std::string bundledName = CTextureBundle::Normalize(textureName);
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ CTextureMap *pMap = m_vecTextures[i];
+ if (pMap->GetName() == textureName)
+ {
+ if (size) *size = 1;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (m_TexBundle[i].HasFile(bundledName))
+ {
+ if (bundle) *bundle = i;
+ return true;
+ }
+ }
+
+ std::string fullPath = GetTexturePath(textureName);
+ if (path)
+ *path = fullPath;
+
+ return !fullPath.empty();
+}
+
+const CTextureArray& CGUITextureManager::Load(const std::string& strTextureName, bool checkBundleOnly /*= false */)
+{
+ std::string strPath;
+ static CTextureArray emptyTexture;
+ int bundle = -1;
+ int size = 0;
+
+ if (strTextureName.empty())
+ return emptyTexture;
+
+ if (!HasTexture(strTextureName, &strPath, &bundle, &size))
+ return emptyTexture;
+
+ if (size) // we found the texture
+ {
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ CTextureMap *pMap = m_vecTextures[i];
+ if (pMap->GetName() == strTextureName)
+ {
+ //CLog::Log(LOGDEBUG, "Total memusage {}", GetMemoryUsage());
+ return pMap->GetTexture();
+ }
+ }
+ // Whoops, not there.
+ return emptyTexture;
+ }
+
+ for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end(); ++i)
+ {
+ CTextureMap* pMap = i->first;
+
+ auto timestamp = i->second.time_since_epoch();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp);
+
+ if (pMap->GetName() == strTextureName && duration.count() > 0)
+ {
+ m_vecTextures.push_back(pMap);
+ m_unusedTextures.erase(i);
+ return pMap->GetTexture();
+ }
+ }
+
+ if (checkBundleOnly && bundle == -1)
+ return emptyTexture;
+
+ //Lock here, we will do stuff that could break rendering
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+#ifdef _DEBUG_TEXTURES
+ const auto start = std::chrono::steady_clock::now();
+#endif
+
+ if (bundle >= 0 && StringUtils::EndsWithNoCase(strPath, ".gif"))
+ {
+ CTextureMap* pMap = nullptr;
+ std::vector<std::pair<std::unique_ptr<CTexture>, int>> textures;
+ int nLoops = 0, width = 0, height = 0;
+ bool success = m_TexBundle[bundle].LoadAnim(strTextureName, textures, width, height, nLoops);
+ if (!success)
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
+ return emptyTexture;
+ }
+
+ unsigned int maxWidth = 0;
+ unsigned int maxHeight = 0;
+ pMap = new CTextureMap(strTextureName, width, height, nLoops);
+ for (auto& texture : textures)
+ {
+ maxWidth = std::max(maxWidth, texture.first->GetWidth());
+ maxHeight = std::max(maxHeight, texture.first->GetHeight());
+ pMap->Add(std::move(texture.first), texture.second);
+ }
+
+ pMap->SetWidth((int)maxWidth);
+ pMap->SetHeight((int)maxHeight);
+
+ m_vecTextures.push_back(pMap);
+ return pMap->GetTexture();
+ }
+ else if (StringUtils::EndsWithNoCase(strPath, ".gif") ||
+ StringUtils::EndsWithNoCase(strPath, ".apng"))
+ {
+ std::string mimeType;
+ if (StringUtils::EndsWithNoCase(strPath, ".gif"))
+ mimeType = "image/gif";
+ else if (StringUtils::EndsWithNoCase(strPath, ".apng"))
+ mimeType = "image/apng";
+
+ XFILE::CFile file;
+ std::vector<uint8_t> buf;
+ CFFmpegImage anim(mimeType);
+
+ if (file.LoadFile(strPath, buf) <= 0 || !anim.Initialize(buf.data(), buf.size()))
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load file: {}", CURL::GetRedacted(strPath));
+ file.Close();
+ return emptyTexture;
+ }
+
+ CTextureMap* pMap = new CTextureMap(strTextureName, 0, 0, 0);
+ unsigned int maxWidth = 0;
+ unsigned int maxHeight = 0;
+ uint64_t maxMemoryUsage = 91238400;// 1920*1080*4*11 bytes, i.e, a total of approx. 12 full hd frames
+
+ auto frame = anim.ReadFrame();
+ while (frame)
+ {
+ std::unique_ptr<CTexture> glTexture = CTexture::CreateTexture();
+ if (glTexture)
+ {
+ glTexture->LoadFromMemory(anim.Width(), anim.Height(), frame->GetPitch(), XB_FMT_A8R8G8B8, true, frame->m_pImage);
+ maxWidth = std::max(maxWidth, glTexture->GetWidth());
+ maxHeight = std::max(maxHeight, glTexture->GetHeight());
+ pMap->Add(std::move(glTexture), frame->m_delay);
+ }
+
+ if (pMap->GetMemoryUsage() <= maxMemoryUsage)
+ {
+ frame = anim.ReadFrame();
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Memory limit ({} bytes) exceeded, {} frames extracted from file : {}",
+ (maxMemoryUsage / 11) * 12, pMap->GetTexture().size(),
+ CURL::GetRedacted(strPath));
+ break;
+ }
+ }
+
+ pMap->SetWidth((int)maxWidth);
+ pMap->SetHeight((int)maxHeight);
+
+ file.Close();
+
+ m_vecTextures.push_back(pMap);
+ return pMap->GetTexture();
+ }
+
+ std::unique_ptr<CTexture> pTexture;
+ int width = 0, height = 0;
+ if (bundle >= 0)
+ {
+ if (!m_TexBundle[bundle].LoadTexture(strTextureName, pTexture, width, height))
+ {
+ CLog::Log(LOGERROR, "Texture manager unable to load bundled file: {}", strTextureName);
+ return emptyTexture;
+ }
+ }
+ else
+ {
+ pTexture = CTexture::LoadFromFile(strPath);
+ if (!pTexture)
+ return emptyTexture;
+ width = pTexture->GetWidth();
+ height = pTexture->GetHeight();
+ }
+
+ if (!pTexture) return emptyTexture;
+
+ CTextureMap* pMap = new CTextureMap(strTextureName, width, height, 0);
+ pMap->Add(std::move(pTexture), 100);
+ m_vecTextures.push_back(pMap);
+
+#ifdef _DEBUG_TEXTURES
+ const auto end = std::chrono::steady_clock::now();
+ const std::chrono::duration<double, std::milli> duration = end - start;
+ CLog::Log(LOGDEBUG, "Load {}: {:.3f} ms {}", strPath, duration.count(),
+ (bundle >= 0) ? "(bundled)" : "");
+#endif
+
+ return pMap->GetTexture();
+}
+
+
+void CGUITextureManager::ReleaseTexture(const std::string& strTextureName, bool immediately /*= false */)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ if (pMap->GetName() == strTextureName)
+ {
+ if (pMap->Release())
+ {
+ //CLog::Log(LOGINFO, " cleanup:{}", strTextureName);
+ // add to our textures to free
+ std::chrono::time_point<std::chrono::steady_clock> timestamp;
+
+ if (!immediately)
+ timestamp = std::chrono::steady_clock::now();
+
+ m_unusedTextures.emplace_back(pMap, timestamp);
+ i = m_vecTextures.erase(i);
+ }
+ return;
+ }
+ ++i;
+ }
+ CLog::Log(LOGWARNING, "{}: Unable to release texture {}", __FUNCTION__, strTextureName);
+}
+
+void CGUITextureManager::FreeUnusedTextures(unsigned int timeDelay)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ for (auto i = m_unusedTextures.begin(); i != m_unusedTextures.end();)
+ {
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - i->second);
+
+ if (duration.count() >= timeDelay)
+ {
+ delete i->first;
+ i = m_unusedTextures.erase(i);
+ }
+ else
+ ++i;
+ }
+
+#if defined(HAS_GL) || defined(HAS_GLES)
+ for (unsigned int i = 0; i < m_unusedHwTextures.size(); ++i)
+ {
+ // on ios/tvos the hw textures might be deleted from the os
+ // when XBMC is backgrounded (e.x. for backgrounded music playback)
+ // sanity check before delete in that case.
+#if defined(TARGET_DARWIN_EMBEDDED)
+ auto winSystem = dynamic_cast<WIN_SYSTEM_CLASS*>(CServiceBroker::GetWinSystem());
+ if (!winSystem->IsBackgrounded() || glIsTexture(m_unusedHwTextures[i]))
+#endif
+ glDeleteTextures(1, (GLuint*) &m_unusedHwTextures[i]);
+ }
+#endif
+ m_unusedHwTextures.clear();
+}
+
+void CGUITextureManager::ReleaseHwTexture(unsigned int texture)
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ m_unusedHwTextures.push_back(texture);
+}
+
+void CGUITextureManager::Cleanup()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ CLog::Log(LOGWARNING, "{}: Having to cleanup texture {}", __FUNCTION__, pMap->GetName());
+ delete pMap;
+ i = m_vecTextures.erase(i);
+ }
+ m_TexBundle[0].Close();
+ m_TexBundle[1].Close();
+ m_TexBundle[0] = CTextureBundle(true);
+ m_TexBundle[1] = CTextureBundle();
+ FreeUnusedTextures();
+}
+
+void CGUITextureManager::Dump() const
+{
+ CLog::Log(LOGDEBUG, "{0}: total texturemaps size: {1}", __FUNCTION__, m_vecTextures.size());
+
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ const CTextureMap* pMap = m_vecTextures[i];
+ if (!pMap->IsEmpty())
+ pMap->Dump();
+ }
+}
+
+void CGUITextureManager::Flush()
+{
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ ivecTextures i;
+ i = m_vecTextures.begin();
+ while (i != m_vecTextures.end())
+ {
+ CTextureMap* pMap = *i;
+ pMap->Flush();
+ if (pMap->IsEmpty() )
+ {
+ delete pMap;
+ i = m_vecTextures.erase(i);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+}
+
+unsigned int CGUITextureManager::GetMemoryUsage() const
+{
+ unsigned int memUsage = 0;
+ for (int i = 0; i < (int)m_vecTextures.size(); ++i)
+ {
+ memUsage += m_vecTextures[i]->GetMemoryUsage();
+ }
+ return memUsage;
+}
+
+void CGUITextureManager::SetTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ m_texturePaths.clear();
+ AddTexturePath(texturePath);
+}
+
+void CGUITextureManager::AddTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ if (!texturePath.empty())
+ m_texturePaths.push_back(texturePath);
+}
+
+void CGUITextureManager::RemoveTexturePath(const std::string &texturePath)
+{
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (std::vector<std::string>::iterator it = m_texturePaths.begin(); it != m_texturePaths.end(); ++it)
+ {
+ if (*it == texturePath)
+ {
+ m_texturePaths.erase(it);
+ return;
+ }
+ }
+}
+
+std::string CGUITextureManager::GetTexturePath(const std::string &textureName, bool directory /* = false */)
+{
+ if (CURL::IsFullPath(textureName))
+ return textureName;
+ else
+ { // texture doesn't include the full path, so check all fallbacks
+ std::unique_lock<CCriticalSection> lock(m_section);
+ for (const std::string& it : m_texturePaths)
+ {
+ std::string path = URIUtils::AddFileToFolder(it, "media", textureName);
+ if (directory)
+ {
+ if (XFILE::CDirectory::Exists(path))
+ return path;
+ }
+ else
+ {
+ if (XFILE::CFile::Exists(path))
+ return path;
+ }
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "[Warning] CGUITextureManager::GetTexturePath: could not find texture '{}'",
+ textureName);
+ return "";
+}
+
+std::vector<std::string> CGUITextureManager::GetBundledTexturesFromPath(
+ const std::string& texturePath)
+{
+ std::vector<std::string> items = m_TexBundle[0].GetTexturesFromPath(texturePath);
+ if (items.empty())
+ items = m_TexBundle[1].GetTexturesFromPath(texturePath);
+ return items;
+}
diff --git a/xbmc/guilib/TextureManager.h b/xbmc/guilib/TextureManager.h
new file mode 100644
index 0000000..ef3128e
--- /dev/null
+++ b/xbmc/guilib/TextureManager.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "GUIComponent.h"
+#include "TextureBundle.h"
+#include "threads/CriticalSection.h"
+
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+class CTexture;
+
+/************************************************************************/
+/* */
+/************************************************************************/
+class CTextureArray
+{
+public:
+ CTextureArray(int width, int height, int loops, bool texCoordsArePixels = false);
+ CTextureArray();
+
+ virtual ~CTextureArray();
+
+ void Reset();
+
+ void Add(std::shared_ptr<CTexture> texture, int delay);
+ void Set(std::shared_ptr<CTexture> texture, int width, int height);
+ void Free();
+ unsigned int size() const;
+
+ std::vector<std::shared_ptr<CTexture>> m_textures;
+ std::vector<int> m_delays;
+ int m_width;
+ int m_height;
+ int m_orientation;
+ int m_loops;
+ int m_texWidth;
+ int m_texHeight;
+ bool m_texCoordsArePixels;
+};
+
+/*!
+ \ingroup textures
+ \brief
+ */
+/************************************************************************/
+/* */
+/************************************************************************/
+class CTextureMap
+{
+public:
+ CTextureMap();
+ CTextureMap(const std::string& textureName, int width, int height, int loops);
+ virtual ~CTextureMap();
+
+ void Add(std::unique_ptr<CTexture> texture, int delay);
+ bool Release();
+
+ const std::string& GetName() const;
+ const CTextureArray& GetTexture();
+ void Dump() const;
+ uint32_t GetMemoryUsage() const;
+ void Flush();
+ bool IsEmpty() const;
+ void SetHeight(int height);
+ void SetWidth(int height);
+protected:
+ void FreeTexture();
+
+ CTextureArray m_texture;
+ std::string m_textureName;
+ unsigned int m_referenceCount;
+ uint32_t m_memUsage;
+};
+
+/*!
+ \ingroup textures
+ \brief
+ */
+/************************************************************************/
+/* */
+/************************************************************************/
+class CGUITextureManager
+{
+public:
+ CGUITextureManager(void);
+ virtual ~CGUITextureManager(void);
+
+ bool HasTexture(const std::string &textureName, std::string *path = NULL, int *bundle = NULL, int *size = NULL);
+ static bool CanLoad(const std::string &texturePath); ///< Returns true if the texture manager can load this texture
+ const CTextureArray& Load(const std::string& strTextureName, bool checkBundleOnly = false);
+ void ReleaseTexture(const std::string& strTextureName, bool immediately = false);
+ void Cleanup();
+ void Dump() const;
+ uint32_t GetMemoryUsage() const;
+ void Flush();
+ std::string GetTexturePath(const std::string& textureName, bool directory = false);
+ std::vector<std::string> GetBundledTexturesFromPath(const std::string& texturePath);
+
+ void AddTexturePath(const std::string &texturePath); ///< Add a new path to the paths to check when loading media
+ void SetTexturePath(const std::string &texturePath); ///< Set a single path as the path to check when loading media (clear then add)
+ void RemoveTexturePath(const std::string &texturePath); ///< Remove a path from the paths to check when loading media
+
+ void FreeUnusedTextures(unsigned int timeDelay = 0); ///< Free textures (called from app thread only)
+ void ReleaseHwTexture(unsigned int texture);
+protected:
+ std::vector<CTextureMap*> m_vecTextures;
+ std::list<std::pair<CTextureMap*, std::chrono::time_point<std::chrono::steady_clock>>>
+ m_unusedTextures;
+ std::vector<unsigned int> m_unusedHwTextures;
+ typedef std::vector<CTextureMap*>::iterator ivecTextures;
+ // we have 2 texture bundles (one for the base textures, one for the theme)
+ CTextureBundle m_TexBundle[2];
+
+ std::vector<std::string> m_texturePaths;
+ CCriticalSection m_section;
+};
+
diff --git a/xbmc/guilib/Tween.h b/xbmc/guilib/Tween.h
new file mode 100644
index 0000000..6aea302
--- /dev/null
+++ b/xbmc/guilib/Tween.h
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+///////////////////////////////////////////////////////////////////////
+// Tween.h
+// A couple of tweening classes implemented in C++.
+// ref: http://www.robertpenner.com/easing/
+//
+// Author: d4rk <d4rk@xbmc.org>
+///////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////
+// Current list of classes:
+//
+// LinearTweener
+// QuadTweener
+// CubicTweener
+// SineTweener
+// CircleTweener
+// BackTweener
+// BounceTweener
+// ElasticTweener
+//
+///////////////////////////////////////////////////////////////////////
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+enum TweenerType
+{
+ EASE_IN,
+ EASE_OUT,
+ EASE_INOUT
+};
+
+
+class Tweener
+{
+public:
+ explicit Tweener(TweenerType tweenerType = EASE_OUT) { m_tweenerType = tweenerType; }
+ virtual ~Tweener() = default;
+
+ void SetEasing(TweenerType type) { m_tweenerType = type; }
+ virtual float Tween(float time, float start, float change, float duration)=0;
+ virtual bool HasResumePoint() const { return m_tweenerType == EASE_INOUT; }
+protected:
+ TweenerType m_tweenerType;
+};
+
+
+class LinearTweener : public Tweener
+{
+public:
+ float Tween(float time, float start, float change, float duration) override
+ {
+ return change * time / duration + start;
+ }
+ bool HasResumePoint() const override { return false; }
+};
+
+
+class QuadTweener : public Tweener
+{
+public:
+ explicit QuadTweener(float a = 1.0f) { _a=a; }
+ float Tween(float time, float start, float change, float duration) override
+ {
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ time /= duration;
+ return change * time * (_a * time + 1 - _a) + start;
+ break;
+
+ case EASE_OUT:
+ time /= duration;
+ return -change * time * (_a * time - 1 - _a) + start;
+ break;
+
+ case EASE_INOUT:
+ time /= duration/2;
+ if (time < 1)
+ return (change) * time * (_a * time + 1 - _a) + start;
+ time--;
+ return (-change) * time * (_a * time - 1 - _a) + start;
+ break;
+ }
+ return change * time * time + start;
+ }
+private:
+ float _a;
+};
+
+
+class CubicTweener : public Tweener
+{
+public:
+ float Tween(float time, float start, float change, float duration) override
+ {
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ time /= duration;
+ return change * time * time * time + start;
+ break;
+
+ case EASE_OUT:
+ time /= duration;
+ time--;
+ return change * (time * time * time + 1) + start;
+ break;
+
+ case EASE_INOUT:
+ time /= duration/2;
+ if (time < 1)
+ return (change/2) * time * time * time + start;
+ time-=2;
+ return (change/2) * (time * time * time + 2) + start;
+ break;
+ }
+ return change * time * time + start;
+ }
+};
+
+class CircleTweener : public Tweener
+{
+public:
+ float Tween(float time, float start, float change, float duration) override
+ {
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ time /= duration;
+ return (-change) * (sqrt(1 - time * time) - 1) + start;
+ break;
+
+ case EASE_OUT:
+ time /= duration;
+ time--;
+ return change * sqrt(1 - time * time) + start;
+ break;
+
+ case EASE_INOUT:
+ time /= duration/2;
+ if (time < 1)
+ return (-change/2) * (sqrt(1 - time * time) - 1) + start;
+ time-=2;
+ return change/2 * (sqrt(1 - time * time) + 1) + start;
+ break;
+ }
+ return change * sqrt(1 - time * time) + start;
+ }
+};
+
+class BackTweener : public Tweener
+{
+public:
+ explicit BackTweener(float s=1.70158) { _s=s; }
+
+ float Tween(float time, float start, float change, float duration) override
+ {
+ float s = _s;
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ time /= duration;
+ return change * time * time * ((s + 1) * time - s) + start;
+ break;
+
+ case EASE_OUT:
+ time /= duration;
+ time--;
+ return change * (time * time * ((s + 1) * time + s) + 1) + start;
+ break;
+
+ case EASE_INOUT:
+ time /= duration/2;
+ s*=(1.525f);
+ if ((time ) < 1)
+ {
+ return (change/2) * (time * time * ((s + 1) * time - s)) + start;
+ }
+ time-=2;
+ return (change/2) * (time * time * ((s + 1) * time + s) + 2) + start;
+ break;
+ }
+ return change * ((time-1) * time * ((s + 1) * time + s) + 1) + start;
+ }
+private:
+ float _s;
+
+};
+
+
+class SineTweener : public Tweener
+{
+public:
+ float Tween(float time, float start, float change, float duration) override
+ {
+ time /= duration;
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ return change * (1 - cos(time * static_cast<float>(M_PI) / 2.0f)) + start;
+ break;
+
+ case EASE_OUT:
+ return change * sin(time * static_cast<float>(M_PI) / 2.0f) + start;
+ break;
+
+ case EASE_INOUT:
+ return change / 2 * (1 - cos(static_cast<float>(M_PI) * time)) + start;
+ break;
+ }
+ return (change / 2) * (1 - cos(static_cast<float>(M_PI) * time)) + start;
+ }
+};
+
+
+class BounceTweener : public Tweener
+{
+public:
+ float Tween(float time, float start, float change, float duration) override
+ {
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ return (change - easeOut(duration - time, 0, change, duration)) + start;
+ break;
+
+ case EASE_OUT:
+ return easeOut(time, start, change, duration);
+ break;
+
+ case EASE_INOUT:
+ if (time < duration/2)
+ return (change - easeOut (duration - (time * 2), 0, change, duration) + start) * .5f + start;
+ else
+ return (easeOut (time * 2 - duration, 0, change, duration) * .5f + change * .5f) + start;
+ break;
+ }
+
+ return easeOut(time, start, change, duration);
+ }
+protected:
+ static float easeOut(float time, float start, float change, float duration)
+ {
+ time /= duration;
+ if (time < (1 / 2.75f))
+ {
+ return change * (7.5625f * time * time) + start;
+ }
+ else if (time < (2 / 2.75f))
+ {
+ time -= (1.5f/2.75f);
+ return change * (7.5625f * time * time + .75f) + start;
+ }
+ else if (time < (2.5f / 2.75f))
+ {
+ time -= (2.25f/2.75f);
+ return change * (7.5625f * time * time + .9375f) + start;
+ }
+ else
+ {
+ time -= (2.625f/2.75f);
+ return change * (7.5625f * time * time + .984375f) + start;
+ }
+ }
+};
+
+
+class ElasticTweener : public Tweener
+{
+public:
+ ElasticTweener(float a=0.0, float p=0.0) { _a=a; _p=p; }
+
+ float Tween(float time, float start, float change, float duration) override
+ {
+ switch (m_tweenerType)
+ {
+ case EASE_IN:
+ return easeIn(time, start, change, duration);
+ break;
+
+ case EASE_OUT:
+ return easeOut(time, start, change, duration);
+ break;
+
+ case EASE_INOUT:
+ return easeInOut(time, start, change, duration);
+ break;
+ }
+ return easeOut(time, start, change, duration);
+ }
+protected:
+ float _a;
+ float _p;
+
+ float easeIn(float time, float start, float change, float duration) const
+ {
+ float s=0;
+ float a=_a;
+ float p=_p;
+
+ if (time==0)
+ return start;
+ time /= duration;
+ if (time==1)
+ return start + change;
+ if (!p)
+ p=duration*.3f;
+ if (!a || a < fabs(change))
+ {
+ a = change;
+ s = p / 4.0f;
+ }
+ else
+ {
+ s = p / (2 * static_cast<float>(M_PI)) * asin(change / a);
+ }
+ time--;
+ return -(a * pow(2.0f, 10 * time) *
+ sin((time * duration - s) * (2 * static_cast<float>(M_PI)) / p)) +
+ start;
+ }
+
+ float easeOut(float time, float start, float change, float duration) const
+ {
+ float s=0;
+ float a=_a;
+ float p=_p;
+
+ if (time==0)
+ return start;
+ time /= duration;
+ if (time==1)
+ return start + change;
+ if (!p)
+ p=duration*.3f;
+ if (!a || a < fabs(change))
+ {
+ a = change;
+ s = p / 4.0f;
+ }
+ else
+ {
+ s = p / (2 * static_cast<float>(M_PI)) * asin(change / a);
+ }
+ return (a * pow(2.0f, -10 * time) *
+ sin((time * duration - s) * (2 * static_cast<float>(M_PI)) / p)) +
+ change + start;
+ }
+
+ float easeInOut(float time, float start, float change, float duration) const
+ {
+ float s=0;
+ float a=_a;
+ float p=_p;
+
+ if (time==0)
+ return start;
+ time /= duration/2;
+ if (time==2)
+ return start + change;
+ if (!p)
+ p=duration*.3f*1.5f;
+ if (!a || a < fabs(change))
+ {
+ a = change;
+ s = p / 4.0f;
+ }
+ else
+ {
+ s = p / (2 * static_cast<float>(M_PI)) * asin(change / a);
+ }
+
+ if (time < 1)
+ {
+ time--;
+ return -.5f * (a * pow(2.0f, 10 * (time)) *
+ sin((time * duration - s) * (2 * static_cast<float>(M_PI)) / p)) +
+ start;
+ }
+ time--;
+ return a * pow(2.0f, -10 * (time)) *
+ sin((time * duration - s) * (2 * static_cast<float>(M_PI)) / p) * .5f +
+ change + start;
+ }
+};
+
diff --git a/xbmc/guilib/VisibleEffect.cpp b/xbmc/guilib/VisibleEffect.cpp
new file mode 100644
index 0000000..17b2daa
--- /dev/null
+++ b/xbmc/guilib/VisibleEffect.cpp
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "VisibleEffect.h"
+
+#include "GUIColorManager.h"
+#include "GUIControlFactory.h"
+#include "GUIInfoManager.h"
+#include "Tween.h"
+#include "addons/Skin.h" // for the effect time adjustments
+#include "guilib/GUIComponent.h"
+#include "utils/ColorUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XMLUtils.h"
+#include "utils/log.h"
+
+#include <utility>
+
+CAnimEffect::CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect)
+{
+ m_effect = effect;
+ // defaults
+ m_delay = m_length = 0;
+ m_pTweener.reset();
+ // time and delay
+
+ float temp;
+ if (TIXML_SUCCESS == node->QueryFloatAttribute("time", &temp)) m_length = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
+ if (TIXML_SUCCESS == node->QueryFloatAttribute("delay", &temp)) m_delay = (unsigned int)(temp * g_SkinInfo->GetEffectsSlowdown());
+
+ m_pTweener = GetTweener(node);
+}
+
+CAnimEffect::CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect)
+{
+ m_delay = delay;
+ m_length = length;
+ m_effect = effect;
+ m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
+}
+
+CAnimEffect::~CAnimEffect() = default;
+
+CAnimEffect::CAnimEffect(const CAnimEffect &src)
+{
+ m_pTweener.reset();
+ *this = src;
+}
+
+CAnimEffect& CAnimEffect::operator=(const CAnimEffect &src)
+{
+ if (&src == this) return *this;
+
+ m_matrix = src.m_matrix;
+ m_effect = src.m_effect;
+ m_length = src.m_length;
+ m_delay = src.m_delay;
+
+ m_pTweener = src.m_pTweener;
+ return *this;
+}
+
+void CAnimEffect::Calculate(unsigned int time, const CPoint &center)
+{
+ assert(m_delay + m_length);
+ // calculate offset and tweening
+ float offset = 0.0f; // delayed forward, or finished reverse
+ if (time >= m_delay && time < m_delay + m_length)
+ offset = (float)(time - m_delay) / m_length;
+ else if (time >= m_delay + m_length)
+ offset = 1.0f;
+ if (m_pTweener)
+ offset = m_pTweener->Tween(offset, 0.0f, 1.0f, 1.0f);
+ // and apply the effect
+ ApplyEffect(offset, center);
+}
+
+void CAnimEffect::ApplyState(ANIMATION_STATE state, const CPoint &center)
+{
+ float offset = (state == ANIM_STATE_APPLIED) ? 1.0f : 0.0f;
+ ApplyEffect(offset, center);
+}
+
+std::shared_ptr<Tweener> CAnimEffect::GetTweener(const TiXmlElement *pAnimationNode)
+{
+ std::shared_ptr<Tweener> m_pTweener;
+ const char *tween = pAnimationNode->Attribute("tween");
+ if (tween)
+ {
+ if (StringUtils::CompareNoCase(tween, "linear") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
+ else if (StringUtils::CompareNoCase(tween, "quadratic") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new QuadTweener());
+ else if (StringUtils::CompareNoCase(tween, "cubic") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new CubicTweener());
+ else if (StringUtils::CompareNoCase(tween, "sine") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new SineTweener());
+ else if (StringUtils::CompareNoCase(tween, "back") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new BackTweener());
+ else if (StringUtils::CompareNoCase(tween, "circle") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new CircleTweener());
+ else if (StringUtils::CompareNoCase(tween, "bounce") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new BounceTweener());
+ else if (StringUtils::CompareNoCase(tween, "elastic") == 0)
+ m_pTweener = std::shared_ptr<Tweener>(new ElasticTweener());
+
+ const char *easing = pAnimationNode->Attribute("easing");
+ if (m_pTweener && easing)
+ {
+ if (StringUtils::CompareNoCase(easing, "in") == 0)
+ m_pTweener->SetEasing(EASE_IN);
+ else if (StringUtils::CompareNoCase(easing, "out") == 0)
+ m_pTweener->SetEasing(EASE_OUT);
+ else if (StringUtils::CompareNoCase(easing, "inout") == 0)
+ m_pTweener->SetEasing(EASE_INOUT);
+ }
+ }
+
+ float accel = 0;
+ pAnimationNode->QueryFloatAttribute("acceleration", &accel);
+
+ if (!m_pTweener)
+ { // no tweener is specified - use a linear tweener
+ // or quadratic if we have acceleration
+ if (accel)
+ {
+ m_pTweener = std::shared_ptr<Tweener>(new QuadTweener(accel));
+ m_pTweener->SetEasing(EASE_IN);
+ }
+ else
+ m_pTweener = std::shared_ptr<Tweener>(new LinearTweener());
+ }
+
+ return m_pTweener;
+}
+
+CFadeEffect::CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_TYPE effect)
+ : CAnimEffect(node, effect)
+{
+ if (reverseDefaults)
+ { // out effect defaults
+ m_startAlpha = 100.0f;
+ m_endAlpha = 0;
+ }
+ else
+ { // in effect defaults
+ m_startAlpha = 0;
+ m_endAlpha = 100.0f;
+ }
+
+ m_startColor.alpha = m_startColor.red = m_startColor.green = m_startColor.blue = 1.0f;
+ m_endColor.alpha = m_endColor.red = m_endColor.green = m_endColor.blue = 1.0f;
+
+ if (effect == EFFECT_TYPE_FADE)
+ {
+ node->QueryFloatAttribute("start", &m_startAlpha);
+ node->QueryFloatAttribute("end", &m_endAlpha);
+ if (m_startAlpha > 100.0f)
+ m_startAlpha = 100.0f;
+ if (m_endAlpha > 100.0f)
+ m_endAlpha = 100.0f;
+ if (m_startAlpha < 0)
+ m_startAlpha = 0;
+ if (m_endAlpha < 0)
+ m_endAlpha = 0;
+ m_startColor.alpha = m_startAlpha * 0.01f;
+ m_endColor.alpha = m_endAlpha * 0.01f;
+ }
+ else if (effect == EFFECT_TYPE_FADE_DIFFUSE)
+ {
+ const char* start = node->Attribute("start");
+ const char* end = node->Attribute("end");
+ if (start)
+ m_startColor = UTILS::COLOR::ConvertToFloats(
+ CServiceBroker::GetGUI()->GetColorManager().GetColor(start));
+ if (end)
+ m_endColor =
+ UTILS::COLOR::ConvertToFloats(CServiceBroker::GetGUI()->GetColorManager().GetColor(end));
+ }
+}
+
+CFadeEffect::CFadeEffect(float start, float end, unsigned int delay, unsigned int length) : CAnimEffect(delay, length, EFFECT_TYPE_FADE)
+{
+ m_startAlpha = start;
+ m_endAlpha = end;
+}
+
+CFadeEffect::CFadeEffect(UTILS::COLOR::Color start,
+ UTILS::COLOR::Color end,
+ unsigned int delay,
+ unsigned int length)
+ : CAnimEffect(delay, length, EFFECT_TYPE_FADE_DIFFUSE)
+{
+ m_startAlpha = m_endAlpha = 1.0f;
+ m_startColor = UTILS::COLOR::ConvertToFloats(start);
+ m_endColor = UTILS::COLOR::ConvertToFloats(end);
+}
+
+void CFadeEffect::ApplyEffect(float offset, const CPoint &center)
+{
+ if (m_effect == EFFECT_TYPE_FADE)
+ {
+ m_matrix.SetFader(((m_endAlpha - m_startAlpha) * offset + m_startAlpha) * 0.01f);
+ }
+ else if (m_effect == EFFECT_TYPE_FADE_DIFFUSE)
+ {
+ m_matrix.SetFader(((m_endColor.alpha - m_startColor.alpha) * offset + m_startColor.alpha),
+ ((m_endColor.red - m_startColor.red) * offset + m_startColor.red),
+ ((m_endColor.green - m_startColor.green) * offset + m_startColor.green),
+ ((m_endColor.blue - m_startColor.blue) * offset + m_startColor.blue));
+ }
+}
+
+CSlideEffect::CSlideEffect(const TiXmlElement *node) : CAnimEffect(node, EFFECT_TYPE_SLIDE)
+{
+ m_startX = m_endX = 0;
+ m_startY = m_endY = 0;
+ const char *startPos = node->Attribute("start");
+ if (startPos)
+ {
+ std::vector<std::string> commaSeparated = StringUtils::Split(startPos, ",");
+ if (commaSeparated.size() > 1)
+ m_startY = (float)atof(commaSeparated[1].c_str());
+ if (!commaSeparated.empty())
+ m_startX = (float)atof(commaSeparated[0].c_str());
+ }
+ const char *endPos = node->Attribute("end");
+ if (endPos)
+ {
+ std::vector<std::string> commaSeparated = StringUtils::Split(endPos, ",");
+ if (commaSeparated.size() > 1)
+ m_endY = (float)atof(commaSeparated[1].c_str());
+ if (!commaSeparated.empty())
+ m_endX = (float)atof(commaSeparated[0].c_str());
+ }
+}
+
+void CSlideEffect::ApplyEffect(float offset, const CPoint &center)
+{
+ m_matrix.SetTranslation((m_endX - m_startX)*offset + m_startX, (m_endY - m_startY)*offset + m_startY, 0);
+}
+
+CRotateEffect::CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect) : CAnimEffect(node, effect)
+{
+ m_startAngle = m_endAngle = 0;
+ m_autoCenter = false;
+ node->QueryFloatAttribute("start", &m_startAngle);
+ node->QueryFloatAttribute("end", &m_endAngle);
+
+ // convert to a negative to account for our reversed Y axis (Needed for X and Z ???)
+ m_startAngle *= -1;
+ m_endAngle *= -1;
+
+ const char *centerPos = node->Attribute("center");
+ if (centerPos)
+ {
+ if (StringUtils::CompareNoCase(centerPos, "auto") == 0)
+ m_autoCenter = true;
+ else
+ {
+ std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ",");
+ if (commaSeparated.size() > 1)
+ m_center.y = (float)atof(commaSeparated[1].c_str());
+ if (!commaSeparated.empty())
+ m_center.x = (float)atof(commaSeparated[0].c_str());
+ }
+ }
+}
+
+void CRotateEffect::ApplyEffect(float offset, const CPoint &center)
+{
+ static const float degree_to_radian = 0.01745329252f;
+ if (m_autoCenter)
+ m_center = center;
+ if (m_effect == EFFECT_TYPE_ROTATE_X)
+ m_matrix.SetXRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
+ else if (m_effect == EFFECT_TYPE_ROTATE_Y)
+ m_matrix.SetYRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, 1.0f);
+ else if (m_effect == EFFECT_TYPE_ROTATE_Z) // note coordinate aspect ratio is not generally square in the XY plane, so correct for it.
+ m_matrix.SetZRotation(((m_endAngle - m_startAngle)*offset + m_startAngle) * degree_to_radian, m_center.x, m_center.y, CServiceBroker::GetWinSystem()->GetGfxContext().GetScalingPixelRatio());
+}
+
+CZoomEffect::CZoomEffect(const TiXmlElement *node, const CRect &rect) : CAnimEffect(node, EFFECT_TYPE_ZOOM), m_center(CPoint(0,0))
+{
+ // effect defaults
+ m_startX = m_startY = 100;
+ m_endX = m_endY = 100;
+ m_autoCenter = false;
+
+ float startPosX = rect.x1;
+ float startPosY = rect.y1;
+ float endPosX = rect.x1;
+ float endPosY = rect.y1;
+
+ float width = std::max(rect.Width(), 0.001f);
+ float height = std::max(rect.Height(),0.001f);
+
+ const char *start = node->Attribute("start");
+ if (start)
+ {
+ std::vector<std::string> params = StringUtils::Split(start, ",");
+ if (params.size() == 1)
+ {
+ m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ m_startY = m_startX;
+ }
+ else if (params.size() == 2)
+ {
+ m_startX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ m_startY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
+ }
+ else if (params.size() == 4)
+ { // format is start="x,y,width,height"
+ // use width and height from our rect to calculate our sizing
+ startPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ startPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
+ m_startX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width());
+ m_startY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height());
+ m_startX *= 100.0f / width;
+ m_startY *= 100.0f / height;
+ }
+ }
+ const char *end = node->Attribute("end");
+ if (end)
+ {
+ std::vector<std::string> params = StringUtils::Split(end, ",");
+ if (params.size() == 1)
+ {
+ m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ m_endY = m_endX;
+ }
+ else if (params.size() == 2)
+ {
+ m_endX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ m_endY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
+ }
+ else if (params.size() == 4)
+ { // format is start="x,y,width,height"
+ // use width and height from our rect to calculate our sizing
+ endPosX = CGUIControlFactory::ParsePosition(params[0].c_str(), rect.Width());
+ endPosY = CGUIControlFactory::ParsePosition(params[1].c_str(), rect.Height());
+ m_endX = CGUIControlFactory::ParsePosition(params[2].c_str(), rect.Width());
+ m_endY = CGUIControlFactory::ParsePosition(params[3].c_str(), rect.Height());
+ m_endX *= 100.0f / width;
+ m_endY *= 100.0f / height;
+ }
+ }
+ const char *centerPos = node->Attribute("center");
+ if (centerPos)
+ {
+ if (StringUtils::CompareNoCase(centerPos, "auto") == 0)
+ m_autoCenter = true;
+ else
+ {
+ std::vector<std::string> commaSeparated = StringUtils::Split(centerPos, ",");
+ if (commaSeparated.size() > 1)
+ m_center.y = CGUIControlFactory::ParsePosition(commaSeparated[1].c_str(), rect.Height());
+ if (!commaSeparated.empty())
+ m_center.x = CGUIControlFactory::ParsePosition(commaSeparated[0].c_str(), rect.Width());
+ }
+ }
+ else
+ { // no center specified
+ // calculate the center position...
+ if (m_startX)
+ {
+ float scale = m_endX / m_startX;
+ if (scale != 1)
+ m_center.x = (endPosX - scale*startPosX) / (1 - scale);
+ }
+ if (m_startY)
+ {
+ float scale = m_endY / m_startY;
+ if (scale != 1)
+ m_center.y = (endPosY - scale*startPosY) / (1 - scale);
+ }
+ }
+}
+
+void CZoomEffect::ApplyEffect(float offset, const CPoint &center)
+{
+ if (m_autoCenter)
+ m_center = center;
+ float scaleX = ((m_endX - m_startX)*offset + m_startX) * 0.01f;
+ float scaleY = ((m_endY - m_startY)*offset + m_startY) * 0.01f;
+ m_matrix.SetScaler(scaleX, scaleY, m_center.x, m_center.y);
+}
+
+CAnimation::CAnimation()
+{
+ m_type = ANIM_TYPE_NONE;
+ m_reversible = true;
+ m_repeatAnim = ANIM_REPEAT_NONE;
+ m_currentState = ANIM_STATE_NONE;
+ m_currentProcess = ANIM_PROCESS_NONE;
+ m_queuedProcess = ANIM_PROCESS_NONE;
+ m_lastCondition = false;
+ m_length = 0;
+ m_delay = 0;
+ m_start = 0;
+ m_amount = 0;
+}
+
+CAnimation::CAnimation(const CAnimation &src)
+{
+ *this = src;
+}
+
+CAnimation::~CAnimation()
+{
+ for (unsigned int i = 0; i < m_effects.size(); i++)
+ delete m_effects[i];
+ m_effects.clear();
+}
+
+CAnimation &CAnimation::operator =(const CAnimation &src)
+{
+ if (this == &src) return *this; // same
+ m_type = src.m_type;
+ m_reversible = src.m_reversible;
+ m_condition = src.m_condition;
+ m_repeatAnim = src.m_repeatAnim;
+ m_lastCondition = src.m_lastCondition;
+ m_queuedProcess = src.m_queuedProcess;
+ m_currentProcess = src.m_currentProcess;
+ m_currentState = src.m_currentState;
+ m_start = src.m_start;
+ m_length = src.m_length;
+ m_delay = src.m_delay;
+ m_amount = src.m_amount;
+ // clear all our effects
+ for (unsigned int i = 0; i < m_effects.size(); i++)
+ delete m_effects[i];
+ m_effects.clear();
+ // and assign the others across
+ for (unsigned int i = 0; i < src.m_effects.size(); i++)
+ {
+ CAnimEffect *newEffect = NULL;
+ if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE)
+ newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i]));
+ else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE)
+ newEffect = new CFadeEffect(*static_cast<CFadeEffect*>(src.m_effects[i]));
+ else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ZOOM)
+ newEffect = new CZoomEffect(*static_cast<CZoomEffect*>(src.m_effects[i]));
+ else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_SLIDE)
+ newEffect = new CSlideEffect(*static_cast<CSlideEffect*>(src.m_effects[i]));
+ else if (src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_X ||
+ src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Y ||
+ src.m_effects[i]->GetType() == CAnimEffect::EFFECT_TYPE_ROTATE_Z)
+ newEffect = new CRotateEffect(*static_cast<CRotateEffect*>(src.m_effects[i]));
+ if (newEffect)
+ m_effects.push_back(newEffect);
+ }
+ return *this;
+}
+
+void CAnimation::Animate(unsigned int time, bool startAnim)
+{
+ // First start any queued animations
+ if (m_queuedProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (m_currentProcess == ANIM_PROCESS_REVERSE)
+ m_start = time - m_amount; // reverse direction of animation
+ else
+ m_start = time;
+ m_currentProcess = ANIM_PROCESS_NORMAL;
+ }
+ else if (m_queuedProcess == ANIM_PROCESS_REVERSE)
+ {
+ if (m_currentProcess == ANIM_PROCESS_NORMAL)
+ m_start = time - (m_length - m_amount); // reverse direction of animation
+ else if (m_currentProcess == ANIM_PROCESS_NONE)
+ m_start = time;
+ m_currentProcess = ANIM_PROCESS_REVERSE;
+ }
+ // reset the queued state once we've rendered to ensure allocation has occurred
+ m_queuedProcess = ANIM_PROCESS_NONE;
+
+ // Update our animation process
+ if (m_currentProcess == ANIM_PROCESS_NORMAL)
+ {
+ if (time - m_start < m_delay)
+ {
+ m_amount = 0;
+ m_currentState = ANIM_STATE_DELAYED;
+ }
+ else if (time - m_start < m_length + m_delay)
+ {
+ m_amount = time - m_start - m_delay;
+ m_currentState = ANIM_STATE_IN_PROCESS;
+ }
+ else
+ {
+ m_amount = m_length;
+ if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
+ { // pulsed anims auto-reverse
+ m_currentProcess = ANIM_PROCESS_REVERSE;
+ m_start = time;
+ }
+ else if (m_repeatAnim == ANIM_REPEAT_LOOP && m_lastCondition)
+ { // looped anims start over
+ m_amount = 0;
+ m_start = time;
+ }
+ else
+ m_currentState = ANIM_STATE_APPLIED;
+ }
+ }
+ else if (m_currentProcess == ANIM_PROCESS_REVERSE)
+ {
+ if (time - m_start < m_length)
+ {
+ m_amount = m_length - (time - m_start);
+ m_currentState = ANIM_STATE_IN_PROCESS;
+ }
+ else
+ {
+ m_amount = 0;
+ if (m_repeatAnim == ANIM_REPEAT_PULSE && m_lastCondition)
+ { // pulsed anims auto-reverse
+ m_currentProcess = ANIM_PROCESS_NORMAL;
+ m_start = time;
+ }
+ else
+ m_currentState = ANIM_STATE_APPLIED;
+ }
+ }
+}
+
+void CAnimation::ResetAnimation()
+{
+ m_queuedProcess = ANIM_PROCESS_NONE;
+ m_currentProcess = ANIM_PROCESS_NONE;
+ m_currentState = ANIM_STATE_NONE;
+}
+
+void CAnimation::ApplyAnimation()
+{
+ m_queuedProcess = ANIM_PROCESS_NONE;
+ if (m_repeatAnim == ANIM_REPEAT_PULSE)
+ { // pulsed anims auto-reverse
+ m_amount = m_length;
+ m_currentProcess = ANIM_PROCESS_REVERSE;
+ m_currentState = ANIM_STATE_IN_PROCESS;
+ }
+ else if (m_repeatAnim == ANIM_REPEAT_LOOP)
+ { // looped anims start over
+ m_amount = 0;
+ m_currentProcess = ANIM_PROCESS_NORMAL;
+ m_currentState = ANIM_STATE_IN_PROCESS;
+ }
+ else
+ { // set normal process, so that Calculate() knows that we're finishing for zero length effects
+ // it will be reset in RenderAnimation()
+ m_currentProcess = ANIM_PROCESS_NORMAL;
+ m_currentState = ANIM_STATE_APPLIED;
+ m_amount = m_length;
+ }
+ Calculate(CPoint());
+}
+
+void CAnimation::Calculate(const CPoint &center)
+{
+ for (unsigned int i = 0; i < m_effects.size(); i++)
+ {
+ CAnimEffect *effect = m_effects[i];
+ if (effect->GetLength())
+ effect->Calculate(m_delay + m_amount, center);
+ else
+ { // effect has length zero, so either apply complete
+ if (m_currentProcess == ANIM_PROCESS_NORMAL)
+ effect->ApplyState(ANIM_STATE_APPLIED, center);
+ else
+ effect->ApplyState(ANIM_STATE_NONE, center);
+ }
+ }
+}
+
+void CAnimation::RenderAnimation(TransformMatrix &matrix, const CPoint &center)
+{
+ if (m_currentProcess != ANIM_PROCESS_NONE)
+ Calculate(center);
+ // If we have finished an animation, reset the animation state
+ // We do this here (rather than in Animate()) as we need the
+ // currentProcess information in the UpdateStates() function of the
+ // window and control classes.
+ if (m_currentState == ANIM_STATE_APPLIED)
+ {
+ m_currentProcess = ANIM_PROCESS_NONE;
+ m_queuedProcess = ANIM_PROCESS_NONE;
+ }
+ if (m_currentState != ANIM_STATE_NONE)
+ {
+ for (unsigned int i = 0; i < m_effects.size(); i++)
+ matrix *= m_effects[i]->GetTransform();
+ }
+}
+
+void CAnimation::QueueAnimation(ANIMATION_PROCESS process)
+{
+ m_queuedProcess = process;
+}
+
+CAnimation CAnimation::CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type)
+{
+ CAnimation anim;
+ anim.m_type = type;
+ anim.m_delay = delay;
+ anim.m_length = length;
+ anim.m_effects.push_back(new CFadeEffect(start, end, delay, length));
+ return anim;
+}
+
+bool CAnimation::CheckCondition()
+{
+ return !m_condition || m_condition->Get(INFO::DEFAULT_CONTEXT);
+}
+
+void CAnimation::UpdateCondition(const CGUIListItem *item)
+{
+ if (!m_condition)
+ return;
+ bool condition = m_condition->Get(INFO::DEFAULT_CONTEXT, item);
+ if (condition && !m_lastCondition)
+ QueueAnimation(ANIM_PROCESS_NORMAL);
+ else if (!condition && m_lastCondition)
+ {
+ if (m_reversible)
+ QueueAnimation(ANIM_PROCESS_REVERSE);
+ else
+ ResetAnimation();
+ }
+ m_lastCondition = condition;
+}
+
+void CAnimation::SetInitialCondition()
+{
+ m_lastCondition = m_condition ? m_condition->Get(INFO::DEFAULT_CONTEXT) : false;
+ if (m_lastCondition)
+ ApplyAnimation();
+ else
+ ResetAnimation();
+}
+
+void CAnimation::Create(const TiXmlElement *node, const CRect &rect, int context)
+{
+ if (!node || !node->FirstChild())
+ return;
+
+ // conditions and reversibility
+ const char *condition = node->Attribute("condition");
+ if (condition)
+ m_condition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context);
+ const char *reverse = node->Attribute("reversible");
+ if (reverse && StringUtils::CompareNoCase(reverse, "false") == 0)
+ m_reversible = false;
+
+ const TiXmlElement *effect = node->FirstChildElement("effect");
+
+ std::string type = node->FirstChild()->Value();
+ m_type = ANIM_TYPE_CONDITIONAL;
+ if (effect) // new layout
+ type = XMLUtils::GetAttribute(node, "type");
+
+ if (StringUtils::StartsWithNoCase(type, "visible")) m_type = ANIM_TYPE_VISIBLE;
+ else if (StringUtils::EqualsNoCase(type, "hidden")) m_type = ANIM_TYPE_HIDDEN;
+ else if (StringUtils::EqualsNoCase(type, "focus")) m_type = ANIM_TYPE_FOCUS;
+ else if (StringUtils::EqualsNoCase(type, "unfocus")) m_type = ANIM_TYPE_UNFOCUS;
+ else if (StringUtils::EqualsNoCase(type, "windowopen")) m_type = ANIM_TYPE_WINDOW_OPEN;
+ else if (StringUtils::EqualsNoCase(type, "windowclose")) m_type = ANIM_TYPE_WINDOW_CLOSE;
+ // sanity check
+ if (m_type == ANIM_TYPE_CONDITIONAL)
+ {
+ if (!m_condition)
+ {
+ CLog::Log(LOGERROR, "Control has invalid animation type (no condition or no type)");
+ return;
+ }
+
+ // pulsed or loop animations
+ const char *pulse = node->Attribute("pulse");
+ if (pulse && StringUtils::CompareNoCase(pulse, "true") == 0)
+ m_repeatAnim = ANIM_REPEAT_PULSE;
+ const char *loop = node->Attribute("loop");
+ if (loop && StringUtils::CompareNoCase(loop, "true") == 0)
+ m_repeatAnim = ANIM_REPEAT_LOOP;
+ }
+
+ if (!effect)
+ { // old layout:
+ // <animation effect="fade" start="0" end="100" delay="10" time="2000" condition="blahdiblah" reversible="false">focus</animation>
+ std::string type = XMLUtils::GetAttribute(node, "effect");
+ AddEffect(type, node, rect);
+ }
+ while (effect)
+ { // new layout:
+ // <animation type="focus" condition="blahdiblah" reversible="false">
+ // <effect type="fade" start="0" end="100" delay="10" time="2000" />
+ // ...
+ // </animation>
+ std::string type = XMLUtils::GetAttribute(effect, "type");
+ AddEffect(type, effect, rect);
+ effect = effect->NextSiblingElement("effect");
+ }
+ // compute the minimum delay and maximum length
+ m_delay = 0xffffffff;
+ unsigned int total = 0;
+ for (const auto& i : m_effects)
+ {
+ m_delay = std::min(m_delay, i->GetDelay());
+ total = std::max(total, i->GetLength());
+ }
+ m_length = total - m_delay;
+}
+
+void CAnimation::AddEffect(const std::string &type, const TiXmlElement *node, const CRect &rect)
+{
+ CAnimEffect *effect = NULL;
+ if (StringUtils::EqualsNoCase(type, "fade"))
+ effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE);
+ else if (StringUtils::EqualsNoCase(type, "fadediffuse"))
+ effect = new CFadeEffect(node, m_type < 0, CAnimEffect::EFFECT_TYPE_FADE_DIFFUSE);
+ else if (StringUtils::EqualsNoCase(type, "slide"))
+ effect = new CSlideEffect(node);
+ else if (StringUtils::EqualsNoCase(type, "rotate"))
+ effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Z);
+ else if (StringUtils::EqualsNoCase(type, "rotatey"))
+ effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_Y);
+ else if (StringUtils::EqualsNoCase(type, "rotatex"))
+ effect = new CRotateEffect(node, CAnimEffect::EFFECT_TYPE_ROTATE_X);
+ else if (StringUtils::EqualsNoCase(type, "zoom"))
+ effect = new CZoomEffect(node, rect);
+
+ if (effect)
+ m_effects.push_back(effect);
+}
+
+CScroller::CScroller(unsigned int duration /* = 200 */, std::shared_ptr<Tweener> tweener /* = NULL */)
+{
+ m_scrollValue = 0;
+ m_delta = 0;
+ m_startTime = 0;
+ m_startPosition = 0;
+ m_hasResumePoint = false;
+ m_duration = duration > 0 ? duration : 1;
+ m_pTweener = std::move(tweener);
+}
+
+CScroller::CScroller(const CScroller& right)
+{
+ m_pTweener.reset();
+ *this = right;
+}
+
+CScroller& CScroller::operator=(const CScroller &right)
+{
+ if (&right == this) return *this;
+
+ m_scrollValue = right.m_scrollValue;
+ m_delta = right.m_delta;
+ m_startTime = right.m_startTime;
+ m_startPosition = right.m_startPosition;
+ m_hasResumePoint = right.m_hasResumePoint;
+ m_duration = right.m_duration;
+ m_pTweener = right.m_pTweener;
+ return *this;
+}
+
+CScroller::~CScroller() = default;
+
+void CScroller::ScrollTo(float endPos)
+{
+ float delta = endPos - m_scrollValue;
+ // if there is scrolling running in same direction - set resume point
+ m_hasResumePoint = m_delta != 0 && delta * m_delta > 0 && m_pTweener ? m_pTweener->HasResumePoint() : false;
+
+ m_delta = delta;
+ m_startPosition = m_scrollValue;
+ m_startTime = 0;
+}
+
+float CScroller::Tween(float progress)
+{
+ if (m_pTweener)
+ {
+ if (m_hasResumePoint) // tweener with in_and_out easing
+ {
+ // time linear transformation (y = a*x + b): 0 -> resumePoint and 1 -> 1
+ // resumePoint = a * 0 + b and 1 = a * 1 + b
+ // a = 1 - resumePoint , b = resumePoint
+ // our resume point is 0.5
+ // a = 0.5 , b = 0.5
+ progress = 0.5f * progress + 0.5f;
+ // tweener value linear transformation (y = a*x + b): resumePointValue -> 0 and 1 -> 1
+ // 0 = a * resumePointValue and 1 = a * 1 + b
+ // a = 1 / ( 1 - resumePointValue) , b = -resumePointValue / (1 - resumePointValue)
+ // we assume resumePointValue = Tween(resumePoint) = Tween(0.5) = 0.5
+ // (this is true for tweener with in_and_out easing - it's rotationally symmetric about (0.5,0.5) continuous function)
+ // a = 2 , b = -1
+ return (2 * m_pTweener->Tween(progress, 0, 1, 1) - 1);
+ }
+ else
+ return m_pTweener->Tween(progress, 0, 1, 1);
+ }
+ else
+ return progress;
+}
+
+bool CScroller::Update(unsigned int time)
+{
+ if (!m_startTime)
+ m_startTime = time;
+ if (m_delta != 0)
+ {
+ if (time - m_startTime >= m_duration) // we are finished
+ {
+ m_scrollValue = m_startPosition + m_delta;
+ m_startTime = 0;
+ m_hasResumePoint = false;
+ m_delta = 0;
+ m_startPosition = 0;
+ }
+ else
+ m_scrollValue = m_startPosition + Tween((float)(time - m_startTime) / m_duration) * m_delta;
+ return true;
+ }
+ else
+ return false;
+}
diff --git a/xbmc/guilib/VisibleEffect.h b/xbmc/guilib/VisibleEffect.h
new file mode 100644
index 0000000..1cc4a09
--- /dev/null
+++ b/xbmc/guilib/VisibleEffect.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+enum ANIMATION_PROCESS { ANIM_PROCESS_NONE = 0, ANIM_PROCESS_NORMAL, ANIM_PROCESS_REVERSE };
+enum ANIMATION_STATE { ANIM_STATE_NONE = 0, ANIM_STATE_DELAYED, ANIM_STATE_IN_PROCESS, ANIM_STATE_APPLIED };
+
+// forward definitions
+
+class TiXmlElement;
+class Tweener;
+class CGUIListItem;
+
+#include "interfaces/info/InfoBool.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h" // for CPoint, CRect
+#include "utils/TransformMatrix.h" // needed for the TransformMatrix member
+
+#include <memory>
+#include <string>
+#include <vector>
+
+enum ANIMATION_TYPE
+{
+ ANIM_TYPE_UNFOCUS = -3,
+ ANIM_TYPE_HIDDEN,
+ ANIM_TYPE_WINDOW_CLOSE,
+ ANIM_TYPE_NONE,
+ ANIM_TYPE_WINDOW_OPEN,
+ ANIM_TYPE_VISIBLE,
+ ANIM_TYPE_FOCUS,
+ ANIM_TYPE_CONDITIONAL // for animations triggered by a condition change
+};
+
+class CAnimEffect
+{
+public:
+ enum EFFECT_TYPE
+ {
+ EFFECT_TYPE_NONE = 0,
+ EFFECT_TYPE_FADE,
+ EFFECT_TYPE_FADE_DIFFUSE,
+ EFFECT_TYPE_SLIDE,
+ EFFECT_TYPE_ROTATE_X,
+ EFFECT_TYPE_ROTATE_Y,
+ EFFECT_TYPE_ROTATE_Z,
+ EFFECT_TYPE_ZOOM
+ };
+
+ CAnimEffect(const TiXmlElement *node, EFFECT_TYPE effect);
+ CAnimEffect(unsigned int delay, unsigned int length, EFFECT_TYPE effect);
+ CAnimEffect(const CAnimEffect &src);
+
+ virtual ~CAnimEffect();
+ CAnimEffect& operator=(const CAnimEffect &src);
+
+ void Calculate(unsigned int time, const CPoint &center);
+ void ApplyState(ANIMATION_STATE state, const CPoint &center);
+
+ unsigned int GetDelay() const { return m_delay; }
+ unsigned int GetLength() const { return m_delay + m_length; }
+ const TransformMatrix& GetTransform() const { return m_matrix; }
+ EFFECT_TYPE GetType() const { return m_effect; }
+
+ static std::shared_ptr<Tweener> GetTweener(const TiXmlElement *pAnimationNode);
+protected:
+ TransformMatrix m_matrix;
+ EFFECT_TYPE m_effect;
+
+private:
+ virtual void ApplyEffect(float offset, const CPoint &center)=0;
+
+ // timing variables
+ unsigned int m_length;
+ unsigned int m_delay;
+
+ std::shared_ptr<Tweener> m_pTweener;
+};
+
+class CFadeEffect : public CAnimEffect
+{
+public:
+ CFadeEffect(const TiXmlElement* node, bool reverseDefaults, EFFECT_TYPE effect);
+ CFadeEffect(float start, float end, unsigned int delay, unsigned int length);
+ CFadeEffect(UTILS::COLOR::Color start,
+ UTILS::COLOR::Color end,
+ unsigned int delay,
+ unsigned int length);
+ ~CFadeEffect() override = default;
+private:
+ void ApplyEffect(float offset, const CPoint &center) override;
+
+ float m_startAlpha;
+ float m_endAlpha;
+ UTILS::COLOR::ColorFloats m_startColor;
+ UTILS::COLOR::ColorFloats m_endColor;
+};
+
+class CSlideEffect : public CAnimEffect
+{
+public:
+ explicit CSlideEffect(const TiXmlElement *node);
+ ~CSlideEffect() override = default;
+private:
+ void ApplyEffect(float offset, const CPoint &center) override;
+
+ float m_startX;
+ float m_startY;
+ float m_endX;
+ float m_endY;
+};
+
+class CRotateEffect : public CAnimEffect
+{
+public:
+ CRotateEffect(const TiXmlElement *node, EFFECT_TYPE effect);
+ ~CRotateEffect() override = default;
+private:
+ void ApplyEffect(float offset, const CPoint &center) override;
+
+ float m_startAngle;
+ float m_endAngle;
+
+ bool m_autoCenter;
+ CPoint m_center;
+};
+
+class CZoomEffect : public CAnimEffect
+{
+public:
+ CZoomEffect(const TiXmlElement *node, const CRect &rect);
+ ~CZoomEffect() override = default;
+private:
+ void ApplyEffect(float offset, const CPoint &center) override;
+
+ float m_startX;
+ float m_startY;
+ float m_endX;
+ float m_endY;
+
+ bool m_autoCenter;
+ CPoint m_center;
+};
+
+class CAnimation
+{
+public:
+ CAnimation();
+ CAnimation(const CAnimation &src);
+
+ virtual ~CAnimation();
+
+ CAnimation& operator=(const CAnimation &src);
+
+ static CAnimation CreateFader(float start, float end, unsigned int delay, unsigned int length, ANIMATION_TYPE type = ANIM_TYPE_NONE);
+
+ void Create(const TiXmlElement *node, const CRect &rect, int context);
+
+ void Animate(unsigned int time, bool startAnim);
+ void ResetAnimation();
+ void ApplyAnimation();
+ inline void RenderAnimation(TransformMatrix &matrix)
+ {
+ RenderAnimation(matrix, CPoint());
+ }
+ void RenderAnimation(TransformMatrix &matrix, const CPoint &center);
+ void QueueAnimation(ANIMATION_PROCESS process);
+
+ inline bool IsReversible() const { return m_reversible; }
+ inline ANIMATION_TYPE GetType() const { return m_type; }
+ inline ANIMATION_STATE GetState() const { return m_currentState; }
+ inline ANIMATION_PROCESS GetProcess() const { return m_currentProcess; }
+ inline ANIMATION_PROCESS GetQueuedProcess() const { return m_queuedProcess; }
+
+ bool CheckCondition();
+ void UpdateCondition(const CGUIListItem *item = NULL);
+ void SetInitialCondition();
+
+private:
+ void Calculate(const CPoint &point);
+ void AddEffect(const std::string &type, const TiXmlElement *node, const CRect &rect);
+
+ enum ANIM_REPEAT { ANIM_REPEAT_NONE = 0, ANIM_REPEAT_PULSE, ANIM_REPEAT_LOOP };
+
+ // type of animation
+ ANIMATION_TYPE m_type;
+ bool m_reversible;
+ INFO::InfoPtr m_condition;
+
+ // conditional anims can repeat
+ ANIM_REPEAT m_repeatAnim;
+ bool m_lastCondition;
+
+ // state of animation
+ ANIMATION_PROCESS m_queuedProcess;
+ ANIMATION_PROCESS m_currentProcess;
+ ANIMATION_STATE m_currentState;
+
+ // timing of animation
+ unsigned int m_start;
+ unsigned int m_length;
+ unsigned int m_delay;
+ unsigned int m_amount;
+
+ std::vector<CAnimEffect *> m_effects;
+};
+
+/**
+ * Class used to handle scrolling, allow using tweeners.
+ * Usage:
+ * start scrolling using ScrollTo() method / stop scrolling using Stop() method
+ * update scroll value each frame with current time using Update() method
+ * get/set scroll value using GetValue()/SetValue()
+ */
+class CScroller
+{
+public:
+ CScroller(unsigned int duration = 200, std::shared_ptr<Tweener> tweener = std::shared_ptr<Tweener>());
+ CScroller(const CScroller& right);
+ CScroller& operator=(const CScroller &src);
+ ~CScroller();
+
+ /**
+ * Set target value scroller will be scrolling to
+ * @param endPos target
+ */
+ void ScrollTo(float endPos);
+
+ /**
+ * Immediately stop scrolling
+ */
+ void Stop() { m_delta = 0; }
+ /**
+ * Update the scroller to where it would be at the given time point, calculating a new Value.
+ * @param time time point
+ * @return True if we are scrolling at given time point
+ */
+ bool Update(unsigned int time);
+
+ /**
+ * Value of scroll
+ */
+ float GetValue() const { return m_scrollValue; }
+ void SetValue(float scrollValue) { m_scrollValue = scrollValue; }
+
+ bool IsScrolling() const { return m_delta != 0; }
+ bool IsScrollingUp() const { return m_delta < 0; }
+ bool IsScrollingDown() const { return m_delta > 0; }
+
+ unsigned int GetDuration() const { return m_duration; }
+
+private:
+ float Tween(float progress);
+
+ float m_scrollValue;
+ float m_delta; //!< Brief distance that we have to travel during scroll
+ float m_startPosition; //!< Brief starting position of scroll
+ bool m_hasResumePoint; //!< Brief check if we should tween from middle of the tween
+ unsigned int m_startTime; //!< Brief starting time of scroll
+
+ unsigned int m_duration; //!< Brief duration of scroll
+ std::shared_ptr<Tweener> m_pTweener;
+};
diff --git a/xbmc/guilib/WindowIDs.dox b/xbmc/guilib/WindowIDs.dox
new file mode 100644
index 0000000..68fdc7d
--- /dev/null
+++ b/xbmc/guilib/WindowIDs.dox
@@ -0,0 +1,145 @@
+/*!
+
+\page window_ids WindowIDs
+
+This page shows the window names, the window definition, the window ID and the specific XML file in use.
+
+@note Window names are case-insensitive
+
+| Name | Definition | WindowID | XML Filename | Revision history |
+|:------------------------|:-------------------------------------|:----------|:------------------------------------|:-----------------------------------|
+| Home | WINDOW_HOME | 10000 | Home.xml | |
+| Programs | WINDOW_PROGRAMS | 10001 | MyPrograms.xml | |
+| Pictures | WINDOW_PICTURES | 10002 | MyPics.xml | |
+| FileManager | WINDOW_FILES | 10003 | FileManager.xml | |
+| Settings | WINDOW_SETTINGS_MENU | 10004 | Settings.xml | |
+| SystemInfo | WINDOW_SYSTEM_INFORMATION | 10007 | SettingsSystemInfo.xml | |
+| ScreenCalibration | WINDOW_SCREEN_CALIBRATION | 10011 | SettingsScreenCalibration.xml | |
+| SystemSettings | WINDOW_SETTINGS_SYSTEM | 10016 | SettingsCategory.xml | |
+| ServiceSettings | WINDOW_SETTINGS_SERVICE | 10018 | SettingsCategory.xml | |
+| PVRSettings | WINDOW_SETTINGS_MYPVR | 10021 | SettingsCategory.xml | |
+| GameSettings | WINDOW_SETTINGS_MYGAMES | 10022 | SettingsCategory.xml | |
+| Videos | WINDOW_VIDEO_NAV | 10025 | MyVideoNav.xml | |
+| VideoPlaylist | WINDOW_VIDEO_PLAYLIST | 10028 | MyPlaylist.xml | |
+| LoginScreen | WINDOW_LOGIN_SCREEN | 10029 | LoginScreen.xml | |
+| PlayerSettings | WINDOW_SETTINGS_PLAYER | 10030 | SettingsCategory.xml | |
+| MediaSettings | WINDOW_SETTINGS_MEDIA | 10031 | SettingsCategory.xml | |
+| InterfaceSettings | WINDOW_SETTINGS_INTERFACE | 10032 | SettingsCategory.xml | |
+| Profiles | WINDOW_SETTINGS_PROFILES | 10034 | SettingsProfile.xml | |
+| SkinSettings | WINDOW_SKIN_SETTINGS | 10035 | SkinSettings.xml | |
+| AddonBrowser | WINDOW_ADDON_BROWSER | 10040 | AddonBrowser.xml | |
+| EventLog | WINDOW_EVENT_LOG | 10050 | EventLog.xml | |
+| Pointer | WINDOW_DIALOG_POINTER | 10099 | Pointer.xml | |
+| YesNoDialog | WINDOW_DIALOG_YES_NO | 10100 | DialogConfirm.xml | |
+| ProgressDialog | WINDOW_DIALOG_PROGRESS | 10101 | DialogConfirm.xml | |
+| VirtualKeyboard | WINDOW_DIALOG_KEYBOARD | 10103 | DialogKeyboard.xml | |
+| VolumeBar | WINDOW_DIALOG_VOLUME_BAR | 10104 | DialogVolumeBar.xml | |
+| SubMenu | WINDOW_DIALOG_SUB_MENU | 10105 | DialogSubMenu.xml | |
+| ContextMenu | WINDOW_DIALOG_CONTEXT_MENU | 10106 | DialogContextMenu.xml | |
+| Notification | WINDOW_DIALOG_KAI_TOAST | 10107 | DialogNotification.xml | |
+| NumericInput | WINDOW_DIALOG_NUMERIC | 10109 | DialogNumeric.xml | |
+| GamepadInput | WINDOW_DIALOG_GAMEPAD | 10110 | DialogSelect.xml | |
+| ShutdownMenu | WINDOW_DIALOG_BUTTON_MENU | 10111 | DialogButtonMenu.xml | |
+| PlayerControls | WINDOW_DIALOG_PLAYER_CONTROLS | 10114 | PlayerControls.xml | |
+| SeekBar | WINDOW_DIALOG_SEEK_BAR | 10115 | DialogSeekBar.xml | |
+| PlayerProcessInfo | WINDOW_DIALOG_PLAYER_PROCESS_INFO | 10116 | DialogPlayerProcessInfo.xml | |
+| MusicOSD | WINDOW_DIALOG_MUSIC_OSD | 10120 | MusicOSD.xml | |
+| | WINDOW_DIALOG_VIS_SETTINGS | 10121 | | |
+| VisualisationPresetList | WINDOW_DIALOG_VIS_PRESET_LIST | 10122 | DialogSelect.xml | |
+| OSDVideoSettings | WINDOW_DIALOG_VIDEO_OSD_SETTINGS | 10123 | DialogSettings.xml | |
+| OSDAudioSettings | WINDOW_DIALOG_AUDIO_OSD_SETTINGS | 10124 | DialogSettings.xml | |
+| VideoBookmarks | WINDOW_DIALOG_VIDEO_BOOKMARKS | 10125 | VideoOSDBookmarks.xml | |
+| FileBrowser | WINDOW_DIALOG_FILE_BROWSER | 10126 | FileBrowser.xml | |
+| NetworkSetup | WINDOW_DIALOG_NETWORK_SETUP | 10128 | DialogSettings.xml | |
+| MediaSource | WINDOW_DIALOG_MEDIA_SOURCE | 10129 | DialogMediaSource.xml | |
+| ProfileSettings | WINDOW_DIALOG_PROFILE_SETTINGS | 10130 | DialogSettings.xml | |
+| LockSettings | WINDOW_DIALOG_LOCK_SETTINGS | 10131 | DialogSettings.xml | |
+| ContentSettings | WINDOW_DIALOG_CONTENT_SETTINGS | 10132 | DialogSettings.xml | |
+| LibexportSettings | WINDOW_DIALOG_LIBEXPORT_SETTINGS | 10133 | DialogSettings.xml | |
+| Favourites | WINDOW_DIALOG_FAVOURITES | 10134 | DialogFavourites.xml | @deprecated Dialog **Favourites** is deprecated and is scheduled for removal in v21.<p></p> @skinning_v20 Deprecated. Please use **FavouritesBrowser** window instead. <p></p> |
+| SongInformation | WINDOW_DIALOG_SONG_INFO | 10135 | DialogMusicInfo.xml | |
+| SmartPlaylistEditor | WINDOW_DIALOG_SMART_PLAYLIST_EDITOR | 10136 | SmartPlaylistEditor.xml | |
+| SmartPlaylistRule | WINDOW_DIALOG_SMART_PLAYLIST_RULE | 10137 | SmartPlaylistRule.xml | |
+| BusyDialog | WINDOW_DIALOG_BUSY | 10138 | DialogBusy.xml | |
+| PictureInfo | WINDOW_DIALOG_PICTURE_INFO | 10139 | DialogPictureInfo.xml | |
+| AddonSettings | WINDOW_DIALOG_ADDON_SETTINGS | 10140 | DialogAddonSettings.xml | |
+| FullscreenInfo | WINDOW_DIALOG_FULLSCREEN_INFO | 10142 | DialogFullScreenInfo.xml | |
+| SliderDialog | WINDOW_DIALOG_SLIDER | 10145 | DialogSlider.xml | |
+| AddonInformation | WINDOW_DIALOG_ADDON_INFO | 10146 | DialogAddonInfo.xml | |
+| TextViewer | WINDOW_DIALOG_TEXT_VIEWER | 10147 | DialogTextViewer.xml | |
+| | WINDOW_DIALOG_PLAY_EJECT | 10148 | DialogConfirm.xml | |
+| | WINDOW_DIALOG_PERIPHERALS | 10149 | DialogSelect.xml | |
+| PeripheralSettings | WINDOW_DIALOG_PERIPHERAL_SETTINGS | 10150 | DialogSettings.xml | |
+| ExtendedProgressDialog | WINDOW_DIALOG_EXT_PROGRESS | 10151 | DialogExtendedProgressBar.xml | |
+| MediaFilter | WINDOW_DIALOG_MEDIA_FILTER | 10152 | DialogSettings.xml | |
+| SubtitleSearch | WINDOW_DIALOG_SUBTITLES | 10153 | DialogSubtitles.xml | |
+| | WINDOW_DIALOG_KEYBOARD_TOUCH | 10156 | | |
+| OSDCMSSettings | WINDOW_DIALOG_CMS_OSD_SETTINGS | 10157 | DialogSettings.xml | |
+| InfoproviderSettings | WINDOW_DIALOG_INFOPROVIDER_SETTINGS | 10158 | DialogSettings.xml | |
+| OSDSubtitleSettings | WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS | 10159 | DialogSettings.xml | |
+| BusyDialogNoCancel | WINDOW_DIALOG_BUSY_NOCANCEL | 10160 | DialogBusy.xml | |
+| MusicPlaylist | WINDOW_MUSIC_PLAYLIST | 10500 | MyPlaylist.xml | |
+| Music | WINDOW_MUSIC_NAV | 10502 | MyMusicNav.xml | |
+| MusicPlaylistEditor | WINDOW_MUSIC_PLAYLIST_EDITOR | 10503 | MyMusicPlaylistEditor.xml | |
+| Teletext | WINDOW_DIALOG_OSD_TELETEXT | 10550 | | |
+| PVRGuideInfo | WINDOW_DIALOG_PVR_GUIDE_INFO | 10600 | DialogPVRInfo.xml | |
+| PVRRecordingInfo | WINDOW_DIALOG_PVR_RECORDING_INFO | 10601 | DialogPVRInfo.xml | |
+| PVRTimerSetting | WINDOW_DIALOG_PVR_TIMER_SETTING | 10602 | DialogSettings.xml | |
+| PVRGroupManager | WINDOW_DIALOG_PVR_GROUP_MANAGER | 10603 | DialogPVRGroupManager.xml | |
+| PVRChannelManager | WINDOW_DIALOG_PVR_CHANNEL_MANAGER | 10604 | DialogPVRChannelManager.xml | |
+| PVRGuideSearch | WINDOW_DIALOG_PVR_GUIDE_SEARCH | 10605 | DialogPVRGuideSearch.xml | |
+| PVRChannelScan | WINDOW_DIALOG_PVR_CHANNEL_SCAN | 10606 | none (unused) | |
+| PVRUpdateProgress | WINDOW_DIALOG_PVR_UPDATE_PROGRESS | 10607 | none (unused) | |
+| PVROSDChannels | WINDOW_DIALOG_PVR_OSD_CHANNELS | 10608 | DialogPVRChannelsOSD.xml | |
+| PVRChannelGuide | WINDOW_DIALOG_PVR_CHANNEL_GUIDE | 10609 | DialogPVRChannelGuide.xml | |
+| PVRRadioRDSInfo | WINDOW_DIALOG_PVR_RADIO_RDS_INFO | 10610 | DialogPVRRadioRDSInfo.xml | |
+| PVRRecordingSettings | WINDOW_DIALOG_PVR_RECORDING_SETTING | 10611 | DialogSettings.xml | |
+| | WINDOW_DIALOG_PVR_CLIENT_PRIORITIES | 10612 | DialogSettings.xml | |
+| TVChannels | WINDOW_TV_CHANNELS | 10700 | MyPVRChannels.xml | |
+| TVRecordings | WINDOW_TV_RECORDINGS | 10701 | MyPVRRecordings.xml | |
+| TVGuide | WINDOW_TV_GUIDE | 10702 | MyPVRGuide.xml | |
+| TVTimers | WINDOW_TV_TIMERS | 10703 | MyPVRTimers.xml | |
+| TVSearch | WINDOW_TV_SEARCH | 10704 | MyPVRSearch.xml | |
+| RadioChannels | WINDOW_RADIO_CHANNELS | 10705 | MyPVRChannels.xml | |
+| RadioRecordings | WINDOW_RADIO_RECORDINGS | 10706 | MyPVRRecordings.xml | |
+| RadioGuide | WINDOW_RADIO_GUIDE | 10707 | MyPVRGuide.xml | |
+| RadioTimers | WINDOW_RADIO_TIMERS | 10708 | MyPVRTimers.xml | |
+| RadioSearch | WINDOW_RADIO_SEARCH | 10709 | MyPVRSearch.xml | |
+| TVTimerRules | WINDOW_TV_TIMER_RULES | 10710 | MyPVRTimers.xml | |
+| RadioTimerRules | WINDOW_RADIO_TIMER_RULES | 10711 | MyPVRTimers.xml | |
+| FullscreenLiveTV | WINDOW_FULLSCREEN_LIVETV | 10800 | None (shortcut to fullscreenvideo) | |
+| FullscreenRadio | WINDOW_FULLSCREEN_RADIO | 10801 | None (shortcut to visualisation) | |
+| FullscreenLivetvPreview | WINDOW_FULLSCREEN_LIVETV_PREVIEW | 10802 | None (shortcut to fullscreenlivetv) | |
+| FullscreenRadioPreview | WINDOW_FULLSCREEN_RADIO_PREVIEW | 10803 | None (shortcut to fullscreenradio) | |
+| FullscreenLivetvInput | WINDOW_FULLSCREEN_LIVETV_INPUT | 10804 | None (shortcut to fullscreenlivetv) | |
+| FullscreenRadioInput | WINDOW_FULLSCREEN_RADIO_INPUT | 10805 | None (shortcut to fullscreenradio) | |
+| GameControllers | WINDOW_DIALOG_GAME_CONTROLLERS | 10820 | DialogGameControllers.xml | |
+| Games | WINDOW_GAMES | 10821 | MyGames.xml | |
+| GameOSD | WINDOW_DIALOG_GAME_OSD | 10822 | GameOSD.xml | |
+| GameVideoFilter | WINDOW_DIALOG_GAME_VIDEO_FILTER | 10823 | DialogSelect.xml | |
+| GameStretchMode | WINDOW_DIALOG_GAME_STRETCH_MODE | 10824 | DialogSelect.xml | |
+| GameVolume | WINDOW_DIALOG_GAME_VOLUME | 10825 | DialogVolumeBar.xml | |
+| GameAdvancedSettings | WINDOW_DIALOG_GAME_ADVANCED_SETTINGS | 10826 | DialogAddonSettings.xml | |
+| GameVideoRotation | WINDOW_DIALOG_GAME_VIDEO_ROTATION | 10827 | DialogSelect.xml | |
+| Custom Skin Windows | - | - | custom*.xml | |
+| SelectDialog | WINDOW_DIALOG_SELECT | 12000 | DialogSelect.xml | |
+| MusicInformation | WINDOW_DIALOG_MUSIC_INFO | 12001 | DialogMusicInfo.xml | |
+| OKDialog | WINDOW_DIALOG_OK | 12002 | DialogConfirm.xml | |
+| MovieInformation | WINDOW_DIALOG_VIDEO_INFO | 12003 | DialogVideoInfo.xml | |
+| FullscreenVideo | WINDOW_FULLSCREEN_VIDEO | 12005 | VideoFullScreen.xml | |
+| Visualisation | WINDOW_VISUALISATION | 12006 | MusicVisualisation.xml | |
+| Slideshow | WINDOW_SLIDESHOW | 12007 | SlideShow.xml | |
+| DialogColorPicker | WINDOW_DIALOG_COLOR_PICKER | 12008 | DialogColorPicker.xml | |
+| Weather | WINDOW_WEATHER | 12600 | MyWeather.xml | |
+| Screensaver | WINDOW_SCREENSAVER | 12900 | none | |
+| VideoOSD | WINDOW_DIALOG_VIDEO_OSD | 12901 | VideoOSD.xml | |
+| VideoMenu | WINDOW_VIDEO_MENU | 12902 | none | |
+| VideoTimeSeek | WINDOW_VIDEO_TIME_SEEK | 12905 | none | |
+| FullscreenGame | WINDOW_FULLSCREEN_GAME | 12906 | none | |
+| Splash | WINDOW_SPLASH | 12997 | none | |
+| StartWindow | WINDOW_START | 12998 | shortcut to the current startwindow | |
+| Startup | WINDOW_STARTUP_ANIM | 12999 | Startup.xml | |
+| FavouritesBrowser | WINDOW_FAVOURITES | 10060 | MyFavourites.xml | @skinning_v20 **New window** FavouritesBrowser, replaces the old Favourites dialog.<p></p> |
+
+
+*/
diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h
new file mode 100644
index 0000000..02ff8eb
--- /dev/null
+++ b/xbmc/guilib/WindowIDs.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// Window ID defines to make the code a bit more readable
+#define WINDOW_INVALID 9999 // do not change. value is used to avoid include in headers.
+#define WINDOW_HOME 10000
+#define WINDOW_PROGRAMS 10001
+#define WINDOW_PICTURES 10002
+#define WINDOW_FILES 10003
+#define WINDOW_SETTINGS_MENU 10004
+#define WINDOW_SYSTEM_INFORMATION 10007
+#define WINDOW_SCREEN_CALIBRATION 10011
+
+#define WINDOW_SETTINGS_START 10016
+#define WINDOW_SETTINGS_SYSTEM 10016
+#define WINDOW_SETTINGS_SERVICE 10018
+
+#define WINDOW_SETTINGS_MYPVR 10021
+#define WINDOW_SETTINGS_MYGAMES 10022
+
+#define WINDOW_VIDEO_NAV 10025
+#define WINDOW_VIDEO_PLAYLIST 10028
+
+#define WINDOW_LOGIN_SCREEN 10029
+
+#define WINDOW_SETTINGS_PLAYER 10030
+#define WINDOW_SETTINGS_MEDIA 10031
+#define WINDOW_SETTINGS_INTERFACE 10032
+
+#define WINDOW_SETTINGS_PROFILES 10034
+#define WINDOW_SKIN_SETTINGS 10035
+
+#define WINDOW_ADDON_BROWSER 10040
+
+#define WINDOW_EVENT_LOG 10050
+
+#define WINDOW_FAVOURITES 10060
+
+#define WINDOW_SCREENSAVER_DIM 97
+#define WINDOW_DEBUG_INFO 98
+#define WINDOW_DIALOG_POINTER 10099
+#define WINDOW_DIALOG_YES_NO 10100
+#define WINDOW_DIALOG_PROGRESS 10101
+#define WINDOW_DIALOG_KEYBOARD 10103
+#define WINDOW_DIALOG_VOLUME_BAR 10104
+#define WINDOW_DIALOG_SUB_MENU 10105
+#define WINDOW_DIALOG_CONTEXT_MENU 10106
+#define WINDOW_DIALOG_KAI_TOAST 10107
+#define WINDOW_DIALOG_NUMERIC 10109
+#define WINDOW_DIALOG_GAMEPAD 10110
+#define WINDOW_DIALOG_BUTTON_MENU 10111
+#define WINDOW_DIALOG_PLAYER_CONTROLS 10114
+#define WINDOW_DIALOG_SEEK_BAR 10115
+#define WINDOW_DIALOG_PLAYER_PROCESS_INFO 10116
+#define WINDOW_DIALOG_MUSIC_OSD 10120
+#define WINDOW_DIALOG_VIS_SETTINGS 10121
+#define WINDOW_DIALOG_VIS_PRESET_LIST 10122
+#define WINDOW_DIALOG_VIDEO_OSD_SETTINGS 10123
+#define WINDOW_DIALOG_AUDIO_OSD_SETTINGS 10124
+#define WINDOW_DIALOG_VIDEO_BOOKMARKS 10125
+#define WINDOW_DIALOG_FILE_BROWSER 10126
+#define WINDOW_DIALOG_NETWORK_SETUP 10128
+#define WINDOW_DIALOG_MEDIA_SOURCE 10129
+#define WINDOW_DIALOG_PROFILE_SETTINGS 10130
+#define WINDOW_DIALOG_LOCK_SETTINGS 10131
+#define WINDOW_DIALOG_CONTENT_SETTINGS 10132
+#define WINDOW_DIALOG_LIBEXPORT_SETTINGS 10133
+#define WINDOW_DIALOG_FAVOURITES 10134
+#define WINDOW_DIALOG_SONG_INFO 10135
+#define WINDOW_DIALOG_SMART_PLAYLIST_EDITOR 10136
+#define WINDOW_DIALOG_SMART_PLAYLIST_RULE 10137
+#define WINDOW_DIALOG_BUSY 10138
+#define WINDOW_DIALOG_PICTURE_INFO 10139
+#define WINDOW_DIALOG_ADDON_SETTINGS 10140
+#define WINDOW_DIALOG_FULLSCREEN_INFO 10142
+#define WINDOW_DIALOG_SLIDER 10145
+#define WINDOW_DIALOG_ADDON_INFO 10146
+#define WINDOW_DIALOG_TEXT_VIEWER 10147
+#ifdef HAS_DVD_DRIVE
+#define WINDOW_DIALOG_PLAY_EJECT 10148
+#endif
+#define WINDOW_DIALOG_PERIPHERALS 10149
+#define WINDOW_DIALOG_PERIPHERAL_SETTINGS 10150
+#define WINDOW_DIALOG_EXT_PROGRESS 10151
+#define WINDOW_DIALOG_MEDIA_FILTER 10152
+#define WINDOW_DIALOG_SUBTITLES 10153
+#define WINDOW_DIALOG_KEYBOARD_TOUCH 10156
+#define WINDOW_DIALOG_CMS_OSD_SETTINGS 10157
+#define WINDOW_DIALOG_INFOPROVIDER_SETTINGS 10158
+#define WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS 10159
+#define WINDOW_DIALOG_BUSY_NOCANCEL 10160
+
+#define WINDOW_MUSIC_PLAYLIST 10500
+#define WINDOW_MUSIC_NAV 10502
+#define WINDOW_MUSIC_PLAYLIST_EDITOR 10503
+
+#define WINDOW_DIALOG_OSD_TELETEXT 10550
+
+// PVR related Window and Dialog ID's
+
+#define WINDOW_DIALOG_PVR_ID_START 10600
+#define WINDOW_DIALOG_PVR_GUIDE_INFO (WINDOW_DIALOG_PVR_ID_START)
+#define WINDOW_DIALOG_PVR_RECORDING_INFO (WINDOW_DIALOG_PVR_ID_START+1)
+#define WINDOW_DIALOG_PVR_TIMER_SETTING (WINDOW_DIALOG_PVR_ID_START+2)
+#define WINDOW_DIALOG_PVR_GROUP_MANAGER (WINDOW_DIALOG_PVR_ID_START+3)
+#define WINDOW_DIALOG_PVR_CHANNEL_MANAGER (WINDOW_DIALOG_PVR_ID_START+4)
+#define WINDOW_DIALOG_PVR_GUIDE_SEARCH (WINDOW_DIALOG_PVR_ID_START+5)
+#define WINDOW_DIALOG_PVR_CHANNEL_SCAN (WINDOW_DIALOG_PVR_ID_START+6)
+#define WINDOW_DIALOG_PVR_UPDATE_PROGRESS (WINDOW_DIALOG_PVR_ID_START+7)
+#define WINDOW_DIALOG_PVR_OSD_CHANNELS (WINDOW_DIALOG_PVR_ID_START+8)
+#define WINDOW_DIALOG_PVR_CHANNEL_GUIDE (WINDOW_DIALOG_PVR_ID_START+9)
+#define WINDOW_DIALOG_PVR_RADIO_RDS_INFO (WINDOW_DIALOG_PVR_ID_START+10)
+#define WINDOW_DIALOG_PVR_RECORDING_SETTING (WINDOW_DIALOG_PVR_ID_START+11)
+#define WINDOW_DIALOG_PVR_CLIENT_PRIORITIES (WINDOW_DIALOG_PVR_ID_START+12)
+#define WINDOW_DIALOG_PVR_GUIDE_CONTROLS (WINDOW_DIALOG_PVR_ID_START+13)
+#define WINDOW_DIALOG_PVR_ID_END WINDOW_DIALOG_PVR_GUIDE_CONTROLS
+
+#define WINDOW_PVR_ID_START 10700
+#define WINDOW_TV_CHANNELS (WINDOW_PVR_ID_START)
+#define WINDOW_TV_RECORDINGS (WINDOW_PVR_ID_START+1)
+#define WINDOW_TV_GUIDE (WINDOW_PVR_ID_START+2)
+#define WINDOW_TV_TIMERS (WINDOW_PVR_ID_START+3)
+#define WINDOW_TV_SEARCH (WINDOW_PVR_ID_START+4)
+#define WINDOW_RADIO_CHANNELS (WINDOW_PVR_ID_START+5)
+#define WINDOW_RADIO_RECORDINGS (WINDOW_PVR_ID_START+6)
+#define WINDOW_RADIO_GUIDE (WINDOW_PVR_ID_START+7)
+#define WINDOW_RADIO_TIMERS (WINDOW_PVR_ID_START+8)
+#define WINDOW_RADIO_SEARCH (WINDOW_PVR_ID_START+9)
+#define WINDOW_TV_TIMER_RULES (WINDOW_PVR_ID_START+10)
+#define WINDOW_RADIO_TIMER_RULES (WINDOW_PVR_ID_START+11)
+#define WINDOW_PVR_ID_END WINDOW_RADIO_TIMER_RULES
+
+// virtual windows for PVR specific keymap bindings in fullscreen playback
+#define WINDOW_FULLSCREEN_LIVETV 10800
+#define WINDOW_FULLSCREEN_RADIO 10801
+#define WINDOW_FULLSCREEN_LIVETV_PREVIEW 10802
+#define WINDOW_FULLSCREEN_RADIO_PREVIEW 10803
+#define WINDOW_FULLSCREEN_LIVETV_INPUT 10804
+#define WINDOW_FULLSCREEN_RADIO_INPUT 10805
+
+#define WINDOW_DIALOG_GAME_CONTROLLERS 10820
+#define WINDOW_GAMES 10821
+#define WINDOW_DIALOG_GAME_OSD 10822
+#define WINDOW_DIALOG_GAME_VIDEO_FILTER 10823
+#define WINDOW_DIALOG_GAME_STRETCH_MODE 10824
+#define WINDOW_DIALOG_GAME_VOLUME 10825
+#define WINDOW_DIALOG_GAME_ADVANCED_SETTINGS 10826
+#define WINDOW_DIALOG_GAME_VIDEO_ROTATION 10827
+#define WINDOW_DIALOG_GAME_PORTS 10828
+#define WINDOW_DIALOG_IN_GAME_SAVES 10829
+#define WINDOW_DIALOG_GAME_SAVES 10830
+
+//#define WINDOW_VIRTUAL_KEYBOARD 11000
+// WINDOW_ID's from 11100 to 11199 reserved for Skins
+
+#define WINDOW_DIALOG_SELECT 12000
+#define WINDOW_DIALOG_MUSIC_INFO 12001
+#define WINDOW_DIALOG_OK 12002
+#define WINDOW_DIALOG_VIDEO_INFO 12003
+#define WINDOW_FULLSCREEN_VIDEO 12005
+#define WINDOW_VISUALISATION 12006
+#define WINDOW_SLIDESHOW 12007
+#define WINDOW_DIALOG_COLOR_PICKER 12008
+#define WINDOW_WEATHER 12600
+#define WINDOW_SCREENSAVER 12900
+#define WINDOW_DIALOG_VIDEO_OSD 12901
+
+#define WINDOW_VIDEO_MENU 12902
+#define WINDOW_VIDEO_TIME_SEEK 12905 // virtual window for time seeking during fullscreen video
+
+#define WINDOW_FULLSCREEN_GAME 12906
+
+#define WINDOW_SPLASH 12997 // splash window
+#define WINDOW_START 12998 // first window to load
+#define WINDOW_STARTUP_ANIM 12999 // for startup animations
+
+// WINDOW_ID's from 13000 to 13099 reserved for Python
+
+#define WINDOW_PYTHON_START 13000
+#define WINDOW_PYTHON_END 13099
+
+// WINDOW_ID's from 14000 to 14099 reserved for Addons
+
+#define WINDOW_ADDON_START 14000
+#define WINDOW_ADDON_END 14099
+
diff --git a/xbmc/guilib/XBTF.cpp b/xbmc/guilib/XBTF.cpp
new file mode 100644
index 0000000..aa095cc
--- /dev/null
+++ b/xbmc/guilib/XBTF.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "XBTF.h"
+
+#include <cstring>
+#include <utility>
+
+CXBTFFrame::CXBTFFrame()
+{
+ m_width = 0;
+ m_height = 0;
+ m_packedSize = 0;
+ m_unpackedSize = 0;
+ m_offset = 0;
+ m_format = XB_FMT_UNKNOWN;
+ m_duration = 0;
+}
+
+uint32_t CXBTFFrame::GetWidth() const
+{
+ return m_width;
+}
+
+void CXBTFFrame::SetWidth(uint32_t width)
+{
+ m_width = width;
+}
+
+uint32_t CXBTFFrame::GetHeight() const
+{
+ return m_height;
+}
+
+void CXBTFFrame::SetHeight(uint32_t height)
+{
+ m_height = height;
+}
+
+uint64_t CXBTFFrame::GetPackedSize() const
+{
+ return m_packedSize;
+}
+
+void CXBTFFrame::SetPackedSize(uint64_t size)
+{
+ m_packedSize = size;
+}
+
+bool CXBTFFrame::IsPacked() const
+{
+ return m_unpackedSize != m_packedSize;
+}
+
+bool CXBTFFrame::HasAlpha() const
+{
+ return (m_format & XB_FMT_OPAQUE) == 0;
+}
+
+uint64_t CXBTFFrame::GetUnpackedSize() const
+{
+ return m_unpackedSize;
+}
+
+void CXBTFFrame::SetUnpackedSize(uint64_t size)
+{
+ m_unpackedSize = size;
+}
+
+void CXBTFFrame::SetFormat(uint32_t format)
+{
+ m_format = format;
+}
+
+uint32_t CXBTFFrame::GetFormat(bool raw) const
+{
+ return raw ? m_format : (m_format & XB_FMT_MASK);
+}
+
+uint64_t CXBTFFrame::GetOffset() const
+{
+ return m_offset;
+}
+
+void CXBTFFrame::SetOffset(uint64_t offset)
+{
+ m_offset = offset;
+}
+
+uint32_t CXBTFFrame::GetDuration() const
+{
+ return m_duration;
+}
+
+void CXBTFFrame::SetDuration(uint32_t duration)
+{
+ m_duration = duration;
+}
+
+uint64_t CXBTFFrame::GetHeaderSize() const
+{
+ uint64_t result =
+ sizeof(m_width) +
+ sizeof(m_height) +
+ sizeof(m_format) +
+ sizeof(m_packedSize) +
+ sizeof(m_unpackedSize) +
+ sizeof(m_offset) +
+ sizeof(m_duration);
+
+ return result;
+}
+
+CXBTFFile::CXBTFFile()
+ : m_path(),
+ m_frames()
+{ }
+
+const std::string& CXBTFFile::GetPath() const
+{
+ return m_path;
+}
+
+void CXBTFFile::SetPath(const std::string& path)
+{
+ m_path = path;
+}
+
+uint32_t CXBTFFile::GetLoop() const
+{
+ return m_loop;
+}
+
+void CXBTFFile::SetLoop(uint32_t loop)
+{
+ m_loop = loop;
+}
+
+const std::vector<CXBTFFrame>& CXBTFFile::GetFrames() const
+{
+ return m_frames;
+}
+
+std::vector<CXBTFFrame>& CXBTFFile::GetFrames()
+{
+ return m_frames;
+}
+
+uint64_t CXBTFFile::GetPackedSize() const
+{
+ uint64_t size = 0;
+ for (const auto& frame : m_frames)
+ size += frame.GetPackedSize();
+
+ return size;
+}
+
+uint64_t CXBTFFile::GetUnpackedSize() const
+{
+ uint64_t size = 0;
+ for (const auto& frame : m_frames)
+ size += frame.GetUnpackedSize();
+
+ return size;
+}
+
+uint64_t CXBTFFile::GetHeaderSize() const
+{
+ uint64_t result =
+ MaximumPathLength +
+ sizeof(m_loop) +
+ sizeof(uint32_t); /* Number of frames */
+
+ for (const auto& frame : m_frames)
+ result += frame.GetHeaderSize();
+
+ return result;
+}
+
+uint64_t CXBTFBase::GetHeaderSize() const
+{
+ uint64_t result = XBTF_MAGIC.size() + XBTF_VERSION.size() +
+ sizeof(uint32_t) /* number of files */;
+
+ for (const auto& file : m_files)
+ result += file.second.GetHeaderSize();
+
+ return result;
+}
+
+bool CXBTFBase::Exists(const std::string& name) const
+{
+ CXBTFFile dummy;
+ return Get(name, dummy);
+}
+
+bool CXBTFBase::Get(const std::string& name, CXBTFFile& file) const
+{
+ const auto& iter = m_files.find(name);
+ if (iter == m_files.end())
+ return false;
+
+ file = iter->second;
+ return true;
+}
+
+std::vector<CXBTFFile> CXBTFBase::GetFiles() const
+{
+ std::vector<CXBTFFile> files;
+ files.reserve(m_files.size());
+
+ for (const auto& file : m_files)
+ files.push_back(file.second);
+
+ return files;
+}
+
+void CXBTFBase::AddFile(const CXBTFFile& file)
+{
+ m_files.insert(std::make_pair(file.GetPath(), file));
+}
+
+void CXBTFBase::UpdateFile(const CXBTFFile& file)
+{
+ auto&& it = m_files.find(file.GetPath());
+ if (it == m_files.end())
+ return;
+
+ it->second = file;
+}
diff --git a/xbmc/guilib/XBTF.h b/xbmc/guilib/XBTF.h
new file mode 100644
index 0000000..3829439
--- /dev/null
+++ b/xbmc/guilib/XBTF.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <ctime>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <stdint.h>
+
+static const std::string XBTF_MAGIC = "XBTF";
+static const std::string XBTF_VERSION = "2";
+
+#include "TextureFormats.h"
+
+class CXBTFFrame
+{
+public:
+ CXBTFFrame();
+
+ uint32_t GetWidth() const;
+ void SetWidth(uint32_t width);
+
+ uint32_t GetFormat(bool raw = false) const;
+ void SetFormat(uint32_t format);
+
+ uint32_t GetHeight() const;
+ void SetHeight(uint32_t height);
+
+ uint64_t GetUnpackedSize() const;
+ void SetUnpackedSize(uint64_t size);
+
+ uint64_t GetPackedSize() const;
+ void SetPackedSize(uint64_t size);
+
+ uint64_t GetOffset() const;
+ void SetOffset(uint64_t offset);
+
+ uint64_t GetHeaderSize() const;
+
+ uint32_t GetDuration() const;
+ void SetDuration(uint32_t duration);
+
+ bool IsPacked() const;
+ bool HasAlpha() const;
+
+private:
+ uint32_t m_width;
+ uint32_t m_height;
+ uint32_t m_format;
+ uint64_t m_packedSize;
+ uint64_t m_unpackedSize;
+ uint64_t m_offset;
+ uint32_t m_duration;
+};
+
+class CXBTFFile
+{
+public:
+ CXBTFFile();
+
+ const std::string& GetPath() const;
+ void SetPath(const std::string& path);
+
+ uint32_t GetLoop() const;
+ void SetLoop(uint32_t loop);
+
+ const std::vector<CXBTFFrame>& GetFrames() const;
+ std::vector<CXBTFFrame>& GetFrames();
+
+ uint64_t GetPackedSize() const;
+ uint64_t GetUnpackedSize() const;
+ uint64_t GetHeaderSize() const;
+
+ static const size_t MaximumPathLength = 256;
+
+private:
+ std::string m_path;
+ uint32_t m_loop = 0;
+ std::vector<CXBTFFrame> m_frames;
+};
+
+class CXBTFBase
+{
+public:
+ virtual ~CXBTFBase() = default;
+
+ uint64_t GetHeaderSize() const;
+
+ bool Exists(const std::string& name) const;
+ bool Get(const std::string& name, CXBTFFile& file) const;
+ std::vector<CXBTFFile> GetFiles() const;
+ void AddFile(const CXBTFFile& file);
+ void UpdateFile(const CXBTFFile& file);
+
+protected:
+ CXBTFBase() = default;
+
+ std::map<std::string, CXBTFFile> m_files;
+};
diff --git a/xbmc/guilib/XBTFReader.cpp b/xbmc/guilib/XBTFReader.cpp
new file mode 100644
index 0000000..cca8e83
--- /dev/null
+++ b/xbmc/guilib/XBTFReader.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "XBTFReader.h"
+#include "guilib/XBTF.h"
+#include "utils/EndianSwap.h"
+
+#ifdef TARGET_WINDOWS
+#include "filesystem/SpecialProtocol.h"
+#include "utils/CharsetConverter.h"
+#include "platform/win32/PlatformDefs.h"
+#endif
+
+static bool ReadString(FILE* file, char* str, size_t max_length)
+{
+ if (file == nullptr || str == nullptr || max_length <= 0)
+ return false;
+
+ return (fread(str, max_length, 1, file) == 1);
+}
+
+static bool ReadUInt32(FILE* file, uint32_t& value)
+{
+ if (file == nullptr)
+ return false;
+
+ if (fread(&value, sizeof(uint32_t), 1, file) != 1)
+ return false;
+
+ value = Endian_SwapLE32(value);
+ return true;
+}
+
+static bool ReadUInt64(FILE* file, uint64_t& value)
+{
+ if (file == nullptr)
+ return false;
+
+ if (fread(&value, sizeof(uint64_t), 1, file) != 1)
+ return false;
+
+ value = Endian_SwapLE64(value);
+ return true;
+}
+
+CXBTFReader::CXBTFReader()
+ : CXBTFBase(),
+ m_path()
+{ }
+
+CXBTFReader::~CXBTFReader()
+{
+ Close();
+}
+
+bool CXBTFReader::Open(const std::string& path)
+{
+ if (path.empty())
+ return false;
+
+ m_path = path;
+
+#ifdef TARGET_WINDOWS
+ std::wstring strPathW;
+ g_charsetConverter.utf8ToW(CSpecialProtocol::TranslatePath(m_path), strPathW, false);
+ m_file = _wfopen(strPathW.c_str(), L"rb");
+#else
+ m_file = fopen(m_path.c_str(), "rb");
+#endif
+ if (m_file == nullptr)
+ return false;
+
+ // read the magic word
+ char magic[4];
+ if (!ReadString(m_file, magic, sizeof(magic)))
+ return false;
+
+ if (strncmp(XBTF_MAGIC.c_str(), magic, sizeof(magic)) != 0)
+ return false;
+
+ // read the version
+ char version[1];
+ if (!ReadString(m_file, version, sizeof(version)))
+ return false;
+
+ if (strncmp(XBTF_VERSION.c_str(), version, sizeof(version)) != 0)
+ return false;
+
+ unsigned int nofFiles;
+ if (!ReadUInt32(m_file, nofFiles))
+ return false;
+
+ for (uint32_t i = 0; i < nofFiles; i++)
+ {
+ CXBTFFile xbtfFile;
+ uint32_t u32;
+ uint64_t u64;
+
+ // one extra char to null terminate the string
+ char path[CXBTFFile::MaximumPathLength + 1] = {};
+ if (!ReadString(m_file, path, sizeof(path) - 1))
+ return false;
+ xbtfFile.SetPath(path);
+
+ if (!ReadUInt32(m_file, u32))
+ return false;
+ xbtfFile.SetLoop(u32);
+
+ unsigned int nofFrames;
+ if (!ReadUInt32(m_file, nofFrames))
+ return false;
+
+ for (uint32_t j = 0; j < nofFrames; j++)
+ {
+ CXBTFFrame frame;
+
+ if (!ReadUInt32(m_file, u32))
+ return false;
+ frame.SetWidth(u32);
+
+ if (!ReadUInt32(m_file, u32))
+ return false;
+ frame.SetHeight(u32);
+
+ if (!ReadUInt32(m_file, u32))
+ return false;
+ frame.SetFormat(u32);
+
+ if (!ReadUInt64(m_file, u64))
+ return false;
+ frame.SetPackedSize(u64);
+
+ if (!ReadUInt64(m_file, u64))
+ return false;
+ frame.SetUnpackedSize(u64);
+
+ if (!ReadUInt32(m_file, u32))
+ return false;
+ frame.SetDuration(u32);
+
+ if (!ReadUInt64(m_file, u64))
+ return false;
+ frame.SetOffset(u64);
+
+ xbtfFile.GetFrames().push_back(frame);
+ }
+
+ AddFile(xbtfFile);
+ }
+
+ // Sanity check
+ uint64_t pos = static_cast<uint64_t>(ftell(m_file));
+ if (pos != GetHeaderSize())
+ return false;
+
+ return true;
+}
+
+bool CXBTFReader::IsOpen() const
+{
+ return m_file != nullptr;
+}
+
+void CXBTFReader::Close()
+{
+ if (m_file != nullptr)
+ {
+ fclose(m_file);
+ m_file = nullptr;
+ }
+
+ m_path.clear();
+ m_files.clear();
+}
+
+time_t CXBTFReader::GetLastModificationTimestamp() const
+{
+ if (m_file == nullptr)
+ return 0;
+
+ struct stat fileStat;
+ if (fstat(fileno(m_file), &fileStat) == -1)
+ return 0;
+
+ return fileStat.st_mtime;
+}
+
+bool CXBTFReader::Load(const CXBTFFrame& frame, unsigned char* buffer) const
+{
+ if (m_file == nullptr)
+ return false;
+
+#if defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)
+ if (fseeko(m_file, static_cast<off_t>(frame.GetOffset()), SEEK_SET) == -1)
+#elif defined(TARGET_ANDROID)
+ if (fseek(m_file, static_cast<long>(frame.GetOffset()), SEEK_SET) == -1) // No fseeko64 before N
+#else
+ if (fseeko64(m_file, static_cast<off_t>(frame.GetOffset()), SEEK_SET) == -1)
+#endif
+ return false;
+
+ if (fread(buffer, 1, static_cast<size_t>(frame.GetPackedSize()), m_file) != frame.GetPackedSize())
+ return false;
+
+ return true;
+}
diff --git a/xbmc/guilib/XBTFReader.h b/xbmc/guilib/XBTFReader.h
new file mode 100644
index 0000000..cfe9976
--- /dev/null
+++ b/xbmc/guilib/XBTFReader.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBTF.h"
+
+#include <memory>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+class CXBTFReader : public CXBTFBase
+{
+public:
+ CXBTFReader();
+ ~CXBTFReader() override;
+
+ bool Open(const std::string& path);
+ bool IsOpen() const;
+ void Close();
+
+ time_t GetLastModificationTimestamp() const;
+
+ bool Load(const CXBTFFrame& frame, unsigned char* buffer) const;
+
+private:
+ std::string m_path;
+ FILE* m_file = nullptr;
+};
+
+typedef std::shared_ptr<CXBTFReader> CXBTFReaderPtr;
diff --git a/xbmc/guilib/_Controls.dox b/xbmc/guilib/_Controls.dox
new file mode 100644
index 0000000..c9947c3
--- /dev/null
+++ b/xbmc/guilib/_Controls.dox
@@ -0,0 +1,40 @@
+/*!
+
+\page skin_controls Controls
+\brief Controls are the substance of your skin. They define everything from
+buttons, to text labels, to visualization placement. This portion of the
+manual will explain each and every control in detail.
+
+- \subpage Addon_Rendering_control - Control where rendering becomes performed from add-on
+- \subpage skin_Button_control - a standard push button control.
+- \subpage Color_button_control - used for creating push buttons in Kodi with a box for color preview.
+- \subpage EPGGrid_control - used to display the EPG guide in the PVR section.
+- \subpage skin_Edit_control - used as an input control for the osd keyboard and other input fields.
+- \subpage Fade_Label_Control - used to show multiple pieces of text in the same position, by fading from one to the other.
+- \subpage Fixed_List_Container - used for a list of items with a fixed focus. Same as the \ref Wrap_List_Container "Wrap List Container" except it doesn't wrap.
+- \subpage Game_Control - used to display the currently playing game, with optional effects, whilst in the GUI.
+- \subpage Group_Control - used to group controls together.
+- \subpage Group_List_Control - special case of the group control that forms a scrolling list of controls.
+- \subpage Image_Control - used to show an image.
+- \subpage Label_Control - used to show some lines of text.
+- \subpage List_Container - used for a scrolling lists of items. Replaces the list control.
+- \subpage Mover_Control - used in the calibration screens.
+- \subpage MultiImage_Control - used to show a slideshow of images.
+- \subpage Panel_Container - used for a scrolling panel of items. Replaces the thumbnail panel.
+- \subpage Progress_Control - Used to show the progress of a particular operation.
+- \subpage RSS_feed_Control - used to display scrolling RSS feeds.
+- \subpage Ranges_Control - Used to show multiple range blocks.
+- \subpage Radio_button_control - a radio button control (as used for on/off settings).
+- \subpage Resize_Control - used to set the pixel ratio in Video Calibration.
+- \subpage Scroll_Bar_Control - used for a implementing a scroll bar.
+- \subpage Settings_Slider_Control - used for a slider control in the settings menus.
+- \subpage Settings_Spin_Control - used for cycling up/down controls in the settings menus.
+- \subpage Slider_Control - used for a volume slider.
+- \subpage Spin_Control - used for cycling up/down controls.
+- \subpage Text_Box - used to show a multi-page piece of text.
+- \subpage skin_Toggle_button_control - a toggle on/off button that can take 2 different states.
+- \subpage Video_Control - used to display the currently playing video whilst in the GUI.
+- \subpage Visualisation_Control - used to display a visualisation while music is playing.
+- \subpage Wrap_List_Container - used for a wrapping list of items with fixed focus.
+
+*/
diff --git a/xbmc/guilib/gui3d.h b/xbmc/guilib/gui3d.h
new file mode 100644
index 0000000..f747336
--- /dev/null
+++ b/xbmc/guilib/gui3d.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PlatformDefs.h" // for TARGET_WINDOWS types
+
+#define GAMMA_RAMP_FLAG D3DSGR_CALIBRATE
+
+#define D3DFMT_LIN_A8R8G8B8 D3DFMT_A8R8G8B8
+#define D3DFMT_LIN_X8R8G8B8 D3DFMT_X8R8G8B8
+#define D3DFMT_LIN_L8 D3DFMT_L8
+#define D3DFMT_LIN_D16 D3DFMT_D16
+#define D3DFMT_LIN_A8 D3DFMT_A8
+
+#define D3DPIXELSHADERDEF DWORD
+
+struct D3DTexture
+{
+ DWORD Common;
+ DWORD Data;
+ DWORD Lock;
+
+ DWORD Format; // Format information about the texture.
+ DWORD Size; // Size of a non power-of-2 texture, must be zero otherwise
+};
+
+#define D3DCOMMON_TYPE_MASK 0x0070000
+#define D3DCOMMON_TYPE_TEXTURE 0x0040000
+
+struct D3DPalette
+{
+ DWORD Common;
+ DWORD Data;
+ DWORD Lock;
+};
+
+typedef D3DPalette* LPDIRECT3DPALETTE8;
+
diff --git a/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp
new file mode 100644
index 0000000..6630fa1
--- /dev/null
+++ b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/AddonsGUIInfo.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+bool CAddonsGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CAddonsGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ const std::shared_ptr<const ADDON::IAddon> addonInfo = item->GetAddonInfo();
+ if (addonInfo)
+ {
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_ADDON_NAME:
+ value = addonInfo->Name();
+ return true;
+ case LISTITEM_ADDON_VERSION:
+ value = addonInfo->Version().asString();
+ return true;
+ case LISTITEM_ADDON_CREATOR:
+ value = addonInfo->Author();
+ return true;
+ case LISTITEM_ADDON_SUMMARY:
+ value = addonInfo->Summary();
+ return true;
+ case LISTITEM_ADDON_DESCRIPTION:
+ value = addonInfo->Description();
+ return true;
+ case LISTITEM_ADDON_DISCLAIMER:
+ value = addonInfo->Disclaimer();
+ return true;
+ case LISTITEM_ADDON_NEWS:
+ value = addonInfo->ChangeLog();
+ return true;
+ case LISTITEM_ADDON_BROKEN:
+ {
+ // Fallback for old GUI info
+ if (addonInfo->LifecycleState() == ADDON::AddonLifecycleState::BROKEN)
+ value = addonInfo->LifecycleStateDescription();
+ else
+ value = "";
+ return true;
+ }
+ case LISTITEM_ADDON_LIFECYCLE_TYPE:
+ {
+ const ADDON::AddonLifecycleState state = addonInfo->LifecycleState();
+ switch (state)
+ {
+ case ADDON::AddonLifecycleState::BROKEN:
+ value = g_localizeStrings.Get(24171); // "Broken"
+ break;
+ case ADDON::AddonLifecycleState::DEPRECATED:
+ value = g_localizeStrings.Get(24170); // "Deprecated";
+ break;
+ case ADDON::AddonLifecycleState::NORMAL:
+ default:
+ value = g_localizeStrings.Get(24169); // "Normal";
+ break;
+ }
+ return true;
+ }
+ case LISTITEM_ADDON_LIFECYCLE_DESC:
+ value = addonInfo->LifecycleStateDescription();
+ return true;
+ case LISTITEM_ADDON_TYPE:
+ value = ADDON::CAddonInfo::TranslateType(addonInfo->Type(), true);
+ return true;
+ case LISTITEM_ADDON_INSTALL_DATE:
+ value = addonInfo->InstallDate().GetAsLocalizedDateTime();
+ return true;
+ case LISTITEM_ADDON_LAST_UPDATED:
+ if (addonInfo->LastUpdated().IsValid())
+ {
+ value = addonInfo->LastUpdated().GetAsLocalizedDateTime();
+ return true;
+ }
+ break;
+ case LISTITEM_ADDON_LAST_USED:
+ if (addonInfo->LastUsed().IsValid())
+ {
+ value = addonInfo->LastUsed().GetAsLocalizedDateTime();
+ return true;
+ }
+ break;
+ case LISTITEM_ADDON_ORIGIN:
+ {
+ if (item->GetAddonInfo()->Origin() == ADDON::ORIGIN_SYSTEM)
+ {
+ value = g_localizeStrings.Get(24992);
+ return true;
+ }
+ if (!item->GetAddonInfo()->OriginName().empty())
+ {
+ value = item->GetAddonInfo()->OriginName();
+ return true;
+ }
+ else if (!item->GetAddonInfo()->Origin().empty())
+ {
+ value = item->GetAddonInfo()->Origin();
+ return true;
+ }
+ value = g_localizeStrings.Get(25014);
+ return true;
+ }
+ case LISTITEM_ADDON_SIZE:
+ {
+ uint64_t packageSize = item->GetAddonInfo()->PackageSize();
+ if (packageSize > 0)
+ {
+ value = StringUtils::FormatFileSize(packageSize);
+ return true;
+ }
+ break;
+ }
+ }
+ }
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ADDON_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case ADDON_SETTING_STRING:
+ {
+ ADDON::AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(info.GetData3(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ return false;
+ }
+ value = addon->GetSetting(info.GetData5());
+ return true;
+ }
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_ADDON_TITLE:
+ case SYSTEM_ADDON_ICON:
+ case SYSTEM_ADDON_VERSION:
+ {
+ // This logic does not check/care whether an addon has been disabled/marked as broken,
+ // it simply retrieves it's name or icon that means if an addon is placed on the home screen it
+ // will stay there even if it's disabled/marked as broken. This might need to be changed/fixed
+ // in the future.
+ ADDON::AddonPtr addon;
+ if (!info.GetData3().empty())
+ {
+ bool success = CServiceBroker::GetAddonMgr().GetAddon(info.GetData3(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES);
+ if (!success || !addon)
+ break;
+
+ if (info.m_info == SYSTEM_ADDON_TITLE)
+ {
+ value = addon->Name();
+ return true;
+ }
+ if (info.m_info == SYSTEM_ADDON_ICON)
+ {
+ value = addon->Icon();
+ return true;
+ }
+ if (info.m_info == SYSTEM_ADDON_VERSION)
+ {
+ value = addon->Version().asString();
+ return true;
+ }
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CAddonsGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ADDON_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case ADDON_SETTING_INT:
+ {
+ ADDON::AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(info.GetData3(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ return false;
+ }
+ return addon->GetSettingInt(info.GetData5(), value);
+ }
+ }
+ return false;
+}
+
+bool CAddonsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ADDON_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case ADDON_SETTING_BOOL:
+ {
+ ADDON::AddonPtr addon;
+ if (!CServiceBroker::GetAddonMgr().GetAddon(info.GetData3(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ return false;
+ }
+ return addon->GetSettingBool(info.GetData5(), value);
+ }
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_HAS_ADDON:
+ {
+ ADDON::AddonPtr addon;
+ value = CServiceBroker::GetAddonMgr().IsAddonInstalled(info.GetData3());
+ return true;
+ }
+ case SYSTEM_ADDON_IS_ENABLED:
+ {
+ value = false;
+ ADDON::AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().GetAddon(info.GetData3(), addon,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ value = !CServiceBroker::GetAddonMgr().IsAddonDisabled(info.GetData3());
+ return true;
+ }
+ case LISTITEM_ISAUTOUPDATEABLE:
+ {
+ value = true;
+ const CFileItem* item = static_cast<const CFileItem*>(gitem);
+ if (item->GetAddonInfo())
+ value = CServiceBroker::GetAddonMgr().IsAutoUpdateable(item->GetAddonInfo()->ID()) ||
+ !CServiceBroker::GetAddonMgr().IsAddonInstalled(item->GetAddonInfo()->ID(),
+ item->GetAddonInfo()->Origin());
+
+ //! @Todo: store origin of not-autoupdateable installed addons in table 'update_rules'
+ // of the addon database. this is needed to pin ambiguous addon-id's that are
+ // available from multiple origins accordingly.
+ //
+ // after this is done the above call should be changed to
+ //
+ // value = CServiceBroker::GetAddonMgr().IsAutoUpdateable(item->GetAddonInfo()->ID(),
+ // item->GetAddonInfo()->Origin());
+
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/AddonsGUIInfo.h b/xbmc/guilib/guiinfo/AddonsGUIInfo.h
new file mode 100644
index 0000000..e06ef51
--- /dev/null
+++ b/xbmc/guilib/guiinfo/AddonsGUIInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CAddonsGUIInfo : public CGUIInfoProvider
+{
+public:
+ CAddonsGUIInfo() = default;
+ ~CAddonsGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/CMakeLists.txt b/xbmc/guilib/guiinfo/CMakeLists.txt
new file mode 100644
index 0000000..9203511
--- /dev/null
+++ b/xbmc/guilib/guiinfo/CMakeLists.txt
@@ -0,0 +1,42 @@
+set(SOURCES GUIInfo.cpp
+ GUIInfoHelper.cpp
+ GUIInfoProviders.cpp
+ GUIInfoLabel.cpp
+ GUIInfoBool.cpp
+ GUIInfoColor.cpp
+ AddonsGUIInfo.cpp
+ GamesGUIInfo.cpp
+ GUIControlsGUIInfo.cpp
+ LibraryGUIInfo.cpp
+ MusicGUIInfo.cpp
+ PicturesGUIInfo.cpp
+ PlayerGUIInfo.cpp
+ SkinGUIInfo.cpp
+ SystemGUIInfo.cpp
+ VideoGUIInfo.cpp
+ VisualisationGUIInfo.cpp
+ WeatherGUIInfo.cpp)
+
+set(HEADERS GUIInfo.h
+ GUIInfoHelper.h
+ GUIInfoLabels.h
+ GUIInfoProvider.h
+ GUIInfoProviders.h
+ GUIInfoLabel.h
+ GUIInfoBool.h
+ GUIInfoColor.h
+ IGUIInfoProvider.h
+ AddonsGUIInfo.h
+ GamesGUIInfo.h
+ GUIControlsGUIInfo.h
+ LibraryGUIInfo.h
+ MusicGUIInfo.h
+ PicturesGUIInfo.h
+ PlayerGUIInfo.h
+ SkinGUIInfo.h
+ SystemGUIInfo.h
+ VideoGUIInfo.h
+ VisualisationGUIInfo.h
+ WeatherGUIInfo.h)
+
+core_add_library(guilib_guiinfo)
diff --git a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp
new file mode 100644
index 0000000..a5ee133
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GUIControlsGUIInfo.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "dialogs/GUIDialogKeyboardGeneric.h"
+#include "dialogs/GUIDialogNumeric.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIControlGroupList.h"
+#include "guilib/GUITextBox.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/IGUIContainer.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoHelper.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "music/dialogs/GUIDialogMusicInfo.h"
+#include "music/dialogs/GUIDialogSongInfo.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoInfoTag.h"
+#include "video/dialogs/GUIDialogVideoInfo.h"
+#include "view/GUIViewState.h"
+#include "windows/GUIMediaWindow.h"
+
+using namespace KODI::GUILIB;
+using namespace KODI::GUILIB::GUIINFO;
+
+void CGUIControlsGUIInfo::SetContainerMoving(int id, bool next, bool scrolling)
+{
+ // magnitude 2 indicates a scroll, sign indicates direction
+ m_containerMoves[id] = (next ? 1 : -1) * (scrolling ? 2 : 1);
+}
+
+void CGUIControlsGUIInfo::ResetContainerMovingCache()
+{
+ m_containerMoves.clear();
+}
+
+bool CGUIControlsGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CGUIControlsGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // CONTAINER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case CONTAINER_FOLDERPATH:
+ case CONTAINER_FOLDERNAME:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ if (info.m_info == CONTAINER_FOLDERNAME)
+ value = window->CurrentDirectory().GetLabel();
+ else
+ value = CURL(window->CurrentDirectory().GetPath()).GetWithoutUserDetails();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_PLUGINNAME:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CURL url(window->CurrentDirectory().GetPath());
+ if (url.IsProtocol("plugin"))
+ {
+ value = URIUtils::GetFileName(url.GetHostName());
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTAINER_VIEWCOUNT:
+ case CONTAINER_VIEWMODE:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CGUIControl *control = window->GetControl(window->GetViewContainerID());
+ if (control && control->IsContainer())
+ {
+ if (info.m_info == CONTAINER_VIEWMODE)
+ {
+ value = static_cast<const IGUIContainer*>(control)->GetLabel();
+ return true;
+ }
+ else if (info.m_info == CONTAINER_VIEWCOUNT)
+ {
+ value = std::to_string(window->GetViewCount());
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ case CONTAINER_SORT_METHOD:
+ case CONTAINER_SORT_ORDER:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CGUIViewState *viewState = window->GetViewState();
+ if (viewState)
+ {
+ if (info.m_info == CONTAINER_SORT_METHOD)
+ {
+ value = g_localizeStrings.Get(viewState->GetSortMethodLabel());
+ return true;
+ }
+ else if (info.m_info == CONTAINER_SORT_ORDER)
+ {
+ value = g_localizeStrings.Get(viewState->GetSortOrderLabel());
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ case CONTAINER_PROPERTY:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty(info.GetData3()).asString();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_ART:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetArt(info.GetData3());
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_CONTENT:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetContent();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_SHOWPLOT:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("showplot").asString();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_SHOWTITLE:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("showtitle").asString();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_PLUGINCATEGORY:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("plugincategory").asString();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_TOTALTIME:
+ case CONTAINER_TOTALWATCHED:
+ case CONTAINER_TOTALUNWATCHED:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ int count = 0;
+ const CFileItemList& items = window->CurrentDirectory();
+ for (const auto& item : items)
+ {
+ // Iterate through container and count watched, unwatched and total duration.
+ if (info.m_info == CONTAINER_TOTALWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() > 0)
+ count += 1;
+ else if (info.m_info == CONTAINER_TOTALUNWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() == 0)
+ count += 1;
+ else if (info.m_info == CONTAINER_TOTALTIME && item->HasMusicInfoTag())
+ count += item->GetMusicInfoTag()->GetDuration();
+ else if (info.m_info == CONTAINER_TOTALTIME && item->HasVideoInfoTag())
+ count += item->GetVideoInfoTag()->m_streamDetails.GetVideoDuration();
+ }
+ if (info.m_info == CONTAINER_TOTALTIME && count > 0)
+ {
+ value = StringUtils::SecondsToTimeString(count);
+ return true;
+ }
+ else if (info.m_info == CONTAINER_TOTALWATCHED || info.m_info == CONTAINER_TOTALUNWATCHED)
+ {
+ value = std::to_string(count);
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTAINER_NUM_PAGES:
+ case CONTAINER_CURRENT_PAGE:
+ case CONTAINER_NUM_ITEMS:
+ case CONTAINER_POSITION:
+ case CONTAINER_ROW:
+ case CONTAINER_COLUMN:
+ case CONTAINER_CURRENT_ITEM:
+ case CONTAINER_NUM_ALL_ITEMS:
+ case CONTAINER_NUM_NONFOLDER_ITEMS:
+ {
+ const CGUIControl *control = nullptr;
+ if (info.GetData1())
+ { // container specified
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ control = window->GetControl(info.GetData1());
+ }
+ else
+ { // no container specified - assume a mediawindow
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ control = window->GetControl(window->GetViewContainerID());
+ }
+ if (control)
+ {
+ if (control->IsContainer())
+ value = static_cast<const IGUIContainer*>(control)->GetLabel(info.m_info);
+ else if (control->GetControlType() == CGUIControl::GUICONTROL_GROUPLIST)
+ value = static_cast<const CGUIControlGroupList*>(control)->GetLabel(info.m_info);
+ else if (control->GetControlType() == CGUIControl::GUICONTROL_TEXTBOX)
+ value = static_cast<const CGUITextBox*>(control)->GetLabel(info.m_info);
+ return true;
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // CONTROL_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case CONTROL_GET_LABEL:
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ const CGUIControl *control = window->GetControl(info.GetData1());
+ if (control)
+ {
+ int data2 = info.GetData2();
+ if (data2)
+ value = control->GetDescriptionByIndex(data2);
+ else
+ value = control->GetDescription();
+ return true;
+ }
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // WINDOW_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case WINDOW_PROPERTY:
+ {
+ CGUIWindow *window = nullptr;
+ if (info.GetData1())
+ { // window specified
+ window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(info.GetData1());
+ }
+ else
+ { // no window specified - assume active
+ window = GUIINFO::GetWindow(contextWindow);
+ }
+ if (window)
+ {
+ value = window->GetProperty(info.GetData3()).asString();
+ return true;
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_CURRENT_WINDOW:
+ value = g_localizeStrings.Get(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog());
+ return true;
+ case SYSTEM_STARTUP_WINDOW:
+ value = std::to_string(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CSettings::SETTING_LOOKANDFEEL_STARTUPWINDOW));
+ return true;
+ case SYSTEM_CURRENT_CONTROL:
+ case SYSTEM_CURRENT_CONTROL_ID:
+ {
+ CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog());
+ if (window)
+ {
+ CGUIControl *control = window->GetFocusedControl();
+ if (control)
+ {
+ if (info.m_info == SYSTEM_CURRENT_CONTROL_ID)
+ value = std::to_string(control->GetID());
+ else if (info.m_info == SYSTEM_CURRENT_CONTROL)
+ value = control->GetDescription();
+ return true;
+ }
+ }
+ break;
+ }
+ case SYSTEM_PROGRESS_BAR:
+ {
+ CGUIDialogProgress *bar = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (bar && bar->IsDialogRunning())
+ value = std::to_string(bar->GetPercentage());
+ return true;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // FANART_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case FANART_COLOR1:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("fanart_color1").asString();
+ return true;
+ }
+ break;
+ }
+ case FANART_COLOR2:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("fanart_color2").asString();
+ return true;
+ }
+ break;
+ }
+ case FANART_COLOR3:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("fanart_color3").asString();
+ return true;
+ }
+ break;
+ }
+ case FANART_IMAGE:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetArt("fanart");
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CGUIControlsGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_PROGRESS_BAR:
+ {
+ CGUIDialogProgress *bar = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (bar && bar->IsDialogRunning())
+ value = bar->GetPercentage();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CGUIControlsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // CONTAINER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case CONTAINER_HASFILES:
+ case CONTAINER_HASFOLDERS:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CFileItemList& items = window->CurrentDirectory();
+ for (const auto& item : items)
+ {
+ if ((!item->m_bIsFolder && info.m_info == CONTAINER_HASFILES) ||
+ (item->m_bIsFolder && !item->IsParentFolder() && info.m_info == CONTAINER_HASFOLDERS))
+ {
+ value = true;
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ case CONTAINER_STACKED:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().GetProperty("isstacked").asBoolean();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_HAS_THUMB:
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CurrentDirectory().HasArt("thumb");
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_CAN_FILTER:
+ {
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = !window->CanFilterAdvanced();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_CAN_FILTERADVANCED:
+ {
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->CanFilterAdvanced();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_FILTERED:
+ {
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = window->IsFiltered();
+ return true;
+ }
+ break;
+ }
+ case CONTAINER_SORT_METHOD:
+ {
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CGUIViewState *viewState = window->GetViewState();
+ if (viewState)
+ {
+ value = (static_cast<int>(viewState->GetSortMethod().sortBy) == info.GetData2());
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTAINER_SORT_DIRECTION:
+ {
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ const CGUIViewState *viewState = window->GetViewState();
+ if (viewState)
+ {
+ value = (static_cast<unsigned int>(viewState->GetSortOrder()) == info.GetData1());
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTAINER_CONTENT:
+ {
+ std::string content;
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ if (window->GetID() == WINDOW_DIALOG_MUSIC_INFO)
+ content = static_cast<CGUIDialogMusicInfo*>(window)->GetContent();
+ else if (window->GetID() == WINDOW_DIALOG_SONG_INFO)
+ content = static_cast<CGUIDialogSongInfo*>(window)->GetContent();
+ else if (window->GetID() == WINDOW_DIALOG_VIDEO_INFO)
+ content = static_cast<CGUIDialogVideoInfo*>(window)->CurrentDirectory().GetContent();
+ }
+ if (content.empty())
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ content = window->CurrentDirectory().GetContent();
+ }
+ value = StringUtils::EqualsNoCase(info.GetData3(), content);
+ return true;
+ }
+ case CONTAINER_ROW:
+ case CONTAINER_COLUMN:
+ case CONTAINER_POSITION:
+ case CONTAINER_HAS_NEXT:
+ case CONTAINER_HAS_PREVIOUS:
+ case CONTAINER_SCROLLING:
+ case CONTAINER_SUBITEM:
+ case CONTAINER_ISUPDATING:
+ case CONTAINER_HAS_PARENT_ITEM:
+ {
+ if (info.GetData1())
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ const CGUIControl *control = window->GetControl(info.GetData1());
+ if (control)
+ {
+ value = control->GetCondition(info.m_info, info.GetData2());
+ return true;
+ }
+ }
+ }
+ else
+ {
+ const CGUIControl *activeContainer = GUIINFO::GetActiveContainer(0, contextWindow);
+ if (activeContainer)
+ {
+ value = activeContainer->GetCondition(info.m_info, info.GetData2());
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTAINER_HAS_FOCUS:
+ { // grab our container
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ const CGUIControl *control = window->GetControl(info.GetData1());
+ if (control && control->IsContainer())
+ {
+ const CFileItemPtr item = std::static_pointer_cast<CFileItem>(static_cast<const IGUIContainer*>(control)->GetListItem(0));
+ if (item && item->m_iprogramCount == info.GetData2()) // programcount used to store item id
+ {
+ value = true;
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ case CONTAINER_SCROLL_PREVIOUS:
+ case CONTAINER_MOVE_PREVIOUS:
+ case CONTAINER_MOVE_NEXT:
+ case CONTAINER_SCROLL_NEXT:
+ {
+ int containerId = -1;
+ if (info.GetData1())
+ {
+ containerId = info.GetData1();
+ }
+ else
+ {
+ // no parameters, so we assume it's just requested for a media window. It therefore
+ // can only happen if the list has focus.
+ CGUIMediaWindow *window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ containerId = window->GetViewContainerID();
+ }
+ if (containerId != -1)
+ {
+ const std::map<int,int>::const_iterator it = m_containerMoves.find(containerId);
+ if (it != m_containerMoves.end())
+ {
+ if (info.m_info == CONTAINER_SCROLL_PREVIOUS)
+ value = it->second <= -2;
+ else if (info.m_info == CONTAINER_MOVE_PREVIOUS)
+ value = it->second <= -1;
+ else if (info.m_info == CONTAINER_MOVE_NEXT)
+ value = it->second >= 1;
+ else if (info.m_info == CONTAINER_SCROLL_NEXT)
+ value = it->second >= 2;
+ return true;
+ }
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // CONTROL_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case CONTROL_IS_VISIBLE:
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ // Note: This'll only work for unique id's
+ const CGUIControl *control = window->GetControl(info.GetData1());
+ if (control)
+ {
+ value = control->IsVisible();
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTROL_IS_ENABLED:
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ // Note: This'll only work for unique id's
+ const CGUIControl *control = window->GetControl(info.GetData1());
+ if (control)
+ {
+ value = !control->IsDisabled();
+ return true;
+ }
+ }
+ break;
+ }
+ case CONTROL_HAS_FOCUS:
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ value = (window->GetFocusedControlID() == static_cast<int>(info.GetData1()));
+ return true;
+ }
+ break;
+ }
+ case CONTROL_GROUP_HAS_FOCUS:
+ {
+ CGUIWindow *window = GUIINFO::GetWindow(contextWindow);
+ if (window)
+ {
+ value = window->ControlGroupHasFocus(info.GetData1(), info.GetData2());
+ return true;
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // WINDOW_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case WINDOW_IS_MEDIA:
+ { // note: This doesn't return true for dialogs (content, favourites, login, videoinfo)
+ CGUIWindowManager& windowMgr = CServiceBroker::GetGUI()->GetWindowManager();
+ CGUIWindow *window = windowMgr.GetWindow(windowMgr.GetActiveWindow());
+ if (window)
+ {
+ value = window->IsMediaWindow();
+ return true;
+ }
+ break;
+ }
+ case WINDOW_IS:
+ {
+ if (info.GetData1())
+ {
+ CGUIWindowManager& windowMgr = CServiceBroker::GetGUI()->GetWindowManager();
+ CGUIWindow *window = windowMgr.GetWindow(contextWindow);
+ if (!window)
+ {
+ // try topmost dialog
+ window = windowMgr.GetWindow(windowMgr.GetTopmostModalDialog());
+ if (!window)
+ {
+ // try active window
+ window = windowMgr.GetWindow(windowMgr.GetActiveWindow());
+ }
+ }
+ if (window)
+ {
+ value = (window && window->GetID() == static_cast<int>(info.GetData1()));
+ return true;
+ }
+ }
+ break;
+ }
+ case WINDOW_IS_VISIBLE:
+ {
+ if (info.GetData1())
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsWindowVisible(info.GetData1());
+ else
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsWindowVisible(info.GetData3());
+ return true;
+ }
+ case WINDOW_IS_ACTIVE:
+ {
+ if (info.GetData1())
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(info.GetData1());
+ else
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(info.GetData3());
+ return true;
+ }
+ case WINDOW_IS_DIALOG_TOPMOST:
+ {
+ if (info.GetData1())
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsDialogTopmost(info.GetData1());
+ else
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsDialogTopmost(info.GetData3());
+ return true;
+ }
+ case WINDOW_IS_MODAL_DIALOG_TOPMOST:
+ {
+ if (info.GetData1())
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsModalDialogTopmost(info.GetData1());
+ else
+ value = CServiceBroker::GetGUI()->GetWindowManager().IsModalDialogTopmost(info.GetData3());
+ return true;
+ }
+ case WINDOW_NEXT:
+ {
+ if (info.GetData1())
+ {
+ value = (static_cast<int>(info.GetData1()) == m_nextWindowID);
+ return true;
+ }
+ else
+ {
+ CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(m_nextWindowID);
+ if (window && StringUtils::EqualsNoCase(URIUtils::GetFileName(window->GetProperty("xmlfile").asString()), info.GetData3()))
+ {
+ value = true;
+ return true;
+ }
+ }
+ break;
+ }
+ case WINDOW_PREVIOUS:
+ {
+ if (info.GetData1())
+ {
+ value = (static_cast<int>(info.GetData1()) == m_prevWindowID);
+ return true;
+ }
+ else
+ {
+ CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(m_prevWindowID);
+ if (window && StringUtils::EqualsNoCase(URIUtils::GetFileName(window->GetProperty("xmlfile").asString()), info.GetData3()))
+ {
+ value = true;
+ return true;
+ }
+ }
+ break;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_HAS_ACTIVE_MODAL_DIALOG:
+ value = CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true);
+ return true;
+ case SYSTEM_HAS_VISIBLE_MODAL_DIALOG:
+ value = CServiceBroker::GetGUI()->GetWindowManager().HasVisibleModalDialog();
+ return true;
+ case SYSTEM_HAS_INPUT_HIDDEN:
+ {
+ CGUIDialogNumeric *pNumeric = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC);
+ CGUIDialogKeyboardGeneric *pKeyboard = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKeyboardGeneric>(WINDOW_DIALOG_KEYBOARD);
+
+ if (pNumeric && pNumeric->IsActive())
+ value = pNumeric->IsInputHidden();
+ else if (pKeyboard && pKeyboard->IsActive())
+ value = pKeyboard->IsInputHidden();
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.h b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.h
new file mode 100644
index 0000000..f6b8b68
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/WindowIDs.h"
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+#include <map>
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CGUIControlsGUIInfo : public CGUIInfoProvider
+{
+public:
+ ~CGUIControlsGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+ void SetNextWindow(int windowID) { m_nextWindowID = windowID; }
+ void SetPreviousWindow(int windowID) { m_prevWindowID = windowID; }
+
+ /*! \brief containers call this to specify that the focus is changing
+ \param id control id
+ \param next true if we're moving to the next item, false if previous
+ \param scrolling true if the container is scrolling, false if the movement requires no scroll
+ */
+ void SetContainerMoving(int id, bool next, bool scrolling);
+ void ResetContainerMovingCache();
+
+private:
+ int m_nextWindowID = WINDOW_INVALID;
+ int m_prevWindowID = WINDOW_INVALID;
+
+ std::map<int, int> m_containerMoves; // direction of list moving
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfo.cpp b/xbmc/guilib/guiinfo/GUIInfo.cpp
new file mode 100644
index 0000000..5542a78
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfo.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIInfo.h"
+
+#include <assert.h>
+
+using namespace KODI::GUILIB::GUIINFO;
+
+void CGUIInfo::SetInfoFlag(uint32_t flag)
+{
+ assert(flag >= (1 << 24));
+ m_data1 |= flag;
+}
+
+uint32_t CGUIInfo::GetInfoFlag() const
+{
+ // we strip out the bottom 24 bits, where we keep data
+ // and return the flag only
+ return m_data1 & 0xff000000;
+}
+
+uint32_t CGUIInfo::GetData1() const
+{
+ // we strip out the top 8 bits, where we keep flags
+ // and return the unflagged data
+ return m_data1 & ((1 << 24) -1);
+}
diff --git a/xbmc/guilib/guiinfo/GUIInfo.h b/xbmc/guilib/guiinfo/GUIInfo.h
new file mode 100644
index 0000000..c5bffe8
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfo.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+// class to hold multiple integer data
+// for storage referenced from a single integer
+class CGUIInfo
+{
+public:
+ CGUIInfo(int info, uint32_t data1, int data2, uint32_t flag, const std::string& data3, int data4)
+ : m_info(info),
+ m_data1(data1),
+ m_data2(data2),
+ m_data3(data3),
+ m_data4(data4)
+ {
+ if (flag)
+ SetInfoFlag(flag);
+ }
+
+ explicit CGUIInfo(int info, uint32_t data1 = 0, int data2 = 0, uint32_t flag = 0)
+ : m_info(info),
+ m_data1(data1),
+ m_data2(data2),
+ m_data4(0)
+ {
+ if (flag)
+ SetInfoFlag(flag);
+ }
+
+ CGUIInfo(int info, uint32_t data1, int data2, const std::string& data3)
+ : m_info(info), m_data1(data1), m_data2(data2), m_data3(data3), m_data4(0)
+ {
+ }
+
+ CGUIInfo(int info, uint32_t data1, const std::string& data3)
+ : m_info(info),
+ m_data1(data1),
+ m_data2(0),
+ m_data3(data3),
+ m_data4(0)
+ {
+ }
+
+ CGUIInfo(int info, const std::string& data3)
+ : m_info(info),
+ m_data1(0),
+ m_data2(0),
+ m_data3(data3),
+ m_data4(0)
+ {
+ }
+
+ CGUIInfo(int info, const std::string& data3, int data2)
+ : m_info(info),
+ m_data1(0),
+ m_data2(data2),
+ m_data3(data3),
+ m_data4(0)
+ {
+ }
+
+ CGUIInfo(int info, const std::string& data3, const std::string& data5)
+ : m_info(info), m_data1(0), m_data3(data3), m_data4(0), m_data5(data5)
+ {
+ }
+
+ bool operator ==(const CGUIInfo &right) const
+ {
+ return (m_info == right.m_info && m_data1 == right.m_data1 && m_data2 == right.m_data2 &&
+ m_data3 == right.m_data3 && m_data4 == right.m_data4 && m_data5 == right.m_data5);
+ }
+
+ uint32_t GetInfoFlag() const;
+ uint32_t GetData1() const;
+ int GetData2() const { return m_data2; }
+ const std::string& GetData3() const { return m_data3; }
+ int GetData4() const { return m_data4; }
+ const std::string& GetData5() const { return m_data5; }
+
+ int m_info;
+private:
+ void SetInfoFlag(uint32_t flag);
+
+ uint32_t m_data1;
+ int m_data2;
+ std::string m_data3;
+ int m_data4;
+ std::string m_data5;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoBool.cpp b/xbmc/guilib/guiinfo/GUIInfoBool.cpp
new file mode 100644
index 0000000..2f6b4f9
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoBool.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GUIInfoBool.h"
+
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+CGUIInfoBool::CGUIInfoBool(bool value)
+{
+ m_value = value;
+}
+
+CGUIInfoBool::~CGUIInfoBool() = default;
+
+void CGUIInfoBool::Parse(const std::string &expression, int context)
+{
+ if (expression == "true")
+ m_value = true;
+ else if (expression == "false")
+ m_value = false;
+ else
+ {
+ m_info = CServiceBroker::GetGUI()->GetInfoManager().Register(expression, context);
+ Update(context);
+ }
+}
+
+void CGUIInfoBool::Update(int contextWindow, const CGUIListItem* item /*= nullptr*/)
+{
+ if (m_info)
+ m_value = m_info->Get(contextWindow, item);
+}
diff --git a/xbmc/guilib/guiinfo/GUIInfoBool.h b/xbmc/guilib/guiinfo/GUIInfoBool.h
new file mode 100644
index 0000000..1913684
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoBool.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIInfoBool.h
+\brief
+*/
+
+#include "interfaces/info/InfoBool.h"
+
+#include <string>
+
+class CGUIListItem;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfoBool
+{
+public:
+ explicit CGUIInfoBool(bool value = false);
+ ~CGUIInfoBool();
+
+ operator bool() const { return m_value; }
+
+ void Update(int contextWindow, const CGUIListItem* item = nullptr);
+ void Parse(const std::string &expression, int context);
+private:
+ INFO::InfoPtr m_info;
+ bool m_value;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoColor.cpp b/xbmc/guilib/guiinfo/GUIInfoColor.cpp
new file mode 100644
index 0000000..c20326d
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoColor.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GUIInfoColor.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "addons/Skin.h"
+#include "guilib/GUIColorManager.h"
+#include "guilib/GUIComponent.h"
+#include "utils/StringUtils.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+bool CGUIInfoColor::Update(const CGUIListItem* item /* = nullptr */)
+{
+ if (!m_info)
+ return false; // no infolabel
+
+ // Expand the infolabel, and then convert it to a color
+ std::string infoLabel;
+ if (item && item->IsFileItem())
+ infoLabel = CServiceBroker::GetGUI()->GetInfoManager().GetItemLabel(
+ static_cast<const CFileItem*>(item), 0, m_info);
+ else
+ infoLabel = CServiceBroker::GetGUI()->GetInfoManager().GetLabel(m_info, INFO::DEFAULT_CONTEXT);
+
+ UTILS::COLOR::Color color =
+ !infoLabel.empty() ? CServiceBroker::GetGUI()->GetColorManager().GetColor(infoLabel) : 0;
+ if (m_color != color)
+ {
+ m_color = color;
+ return true;
+ }
+ else
+ return false;
+}
+
+void CGUIInfoColor::Parse(const std::string &label, int context)
+{
+ if (label.empty())
+ return;
+
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+
+ // Check for the standard $INFO[] block layout, and strip it if present
+ std::string label2 = label;
+ if (StringUtils::StartsWithNoCase(label, "$var["))
+ {
+ label2 = label.substr(5, label.length() - 6);
+ m_info = infoMgr.TranslateSkinVariableString(label2, context);
+ if (!m_info)
+ m_info = infoMgr.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(label2, context));
+ return;
+ }
+
+ if (StringUtils::StartsWithNoCase(label, "$info["))
+ label2 = label.substr(6, label.length()-7);
+
+ m_info = infoMgr.TranslateString(label2);
+ if (!m_info)
+ m_color = CServiceBroker::GetGUI()->GetColorManager().GetColor(label);
+}
diff --git a/xbmc/guilib/guiinfo/GUIInfoColor.h b/xbmc/guilib/guiinfo/GUIInfoColor.h
new file mode 100644
index 0000000..a711047
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoColor.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIInfoColor.h
+\brief
+*/
+
+#include "guilib/GUIListItem.h"
+#include "utils/ColorUtils.h"
+
+#include <string>
+
+class CGUIListItem;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfoColor
+{
+public:
+ constexpr CGUIInfoColor(UTILS::COLOR::Color color = 0) : m_color(color) {}
+
+ constexpr operator UTILS::COLOR::Color() const { return m_color; }
+
+ bool Update(const CGUIListItem* item = nullptr);
+ void Parse(const std::string &label, int context);
+
+ /*!
+ * @brief Check if the infocolor has an info condition bound to its color definition (or otherwise, if it's constant color)
+ * @return true if the color depends on an info condition, false otherwise
+ */
+ bool HasInfo() const { return m_info != 0; }
+
+private:
+ int m_info = 0;
+ UTILS::COLOR::Color m_color;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoHelper.cpp b/xbmc/guilib/guiinfo/GUIInfoHelper.cpp
new file mode 100644
index 0000000..f025b5e
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoHelper.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIInfoHelper.h"
+
+#include "FileItem.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindow.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/IGUIContainer.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "playlists/PlayList.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "windows/GUIMediaWindow.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+// conditions for window retrieval
+static const int WINDOW_CONDITION_HAS_LIST_ITEMS = 1;
+static const int WINDOW_CONDITION_IS_MEDIA_WINDOW = 2;
+
+std::string GetPlaylistLabel(int item, PLAYLIST::Id playlistId /* = TYPE_NONE */)
+{
+ PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer();
+
+ if (playlistId == PLAYLIST::TYPE_NONE)
+ playlistId = player.GetCurrentPlaylist();
+
+ switch (item)
+ {
+ case PLAYLIST_LENGTH:
+ {
+ return std::to_string(player.GetPlaylist(playlistId).size());
+ }
+ case PLAYLIST_POSITION:
+ {
+ int currentSong = player.GetCurrentSong();
+ if (currentSong > -1)
+ return std::to_string(currentSong + 1);
+ break;
+ }
+ case PLAYLIST_RANDOM:
+ {
+ if (player.IsShuffled(playlistId))
+ return g_localizeStrings.Get(16041); // 16041: On
+ else
+ return g_localizeStrings.Get(591); // 591: Off
+ }
+ case PLAYLIST_REPEAT:
+ {
+ PLAYLIST::RepeatState state = player.GetRepeat(playlistId);
+ if (state == PLAYLIST::RepeatState::ONE)
+ return g_localizeStrings.Get(592); // 592: One
+ else if (state == PLAYLIST::RepeatState::ALL)
+ return g_localizeStrings.Get(593); // 593: All
+ else
+ return g_localizeStrings.Get(594); // 594: Off
+ }
+ }
+ return std::string();
+}
+
+namespace
+{
+
+bool CheckWindowCondition(CGUIWindow *window, int condition)
+{
+ // check if it satisfies our condition
+ if (!window)
+ return false;
+ if ((condition & WINDOW_CONDITION_HAS_LIST_ITEMS) && !window->HasListItems())
+ return false;
+ if ((condition & WINDOW_CONDITION_IS_MEDIA_WINDOW) && !window->IsMediaWindow())
+ return false;
+ return true;
+}
+
+CGUIWindow* GetWindowWithCondition(int contextWindow, int condition)
+{
+ const CGUIWindowManager& windowMgr = CServiceBroker::GetGUI()->GetWindowManager();
+
+ CGUIWindow *window = windowMgr.GetWindow(contextWindow);
+ if (CheckWindowCondition(window, condition))
+ return window;
+
+ // try topmost dialog
+ window = windowMgr.GetWindow(windowMgr.GetTopmostModalDialog());
+ if (CheckWindowCondition(window, condition))
+ return window;
+
+ // try active window
+ window = windowMgr.GetWindow(windowMgr.GetActiveWindow());
+ if (CheckWindowCondition(window, condition))
+ return window;
+
+ return nullptr;
+}
+
+} // unnamed namespace
+
+CGUIWindow* GetWindow(int contextWindow)
+{
+ return GetWindowWithCondition(contextWindow, 0);
+}
+
+CFileItemPtr GetCurrentListItemFromWindow(int contextWindow)
+{
+ CGUIWindow* window = GetWindowWithCondition(contextWindow, WINDOW_CONDITION_HAS_LIST_ITEMS);
+ if (window)
+ return window->GetCurrentListItem();
+
+ return CFileItemPtr();
+}
+
+CGUIMediaWindow* GetMediaWindow(int contextWindow)
+{
+ CGUIWindow* window = GetWindowWithCondition(contextWindow, WINDOW_CONDITION_IS_MEDIA_WINDOW);
+ if (window)
+ return static_cast<CGUIMediaWindow*>(window);
+
+ return nullptr;
+}
+
+CGUIControl* GetActiveContainer(int containerId, int contextWindow)
+{
+ CGUIWindow *window = GetWindow(contextWindow);
+ if (!window)
+ return nullptr;
+
+ CGUIControl *control = nullptr;
+ if (!containerId) // No container specified, so we lookup the current view container
+ {
+ if (window->IsMediaWindow())
+ containerId = static_cast<CGUIMediaWindow*>(window)->GetViewContainerID();
+ else
+ control = window->GetFocusedControl();
+ }
+
+ if (!control)
+ control = window->GetControl(containerId);
+
+ if (control && control->IsContainer())
+ return control;
+
+ return nullptr;
+}
+
+CGUIListItemPtr GetCurrentListItem(int contextWindow, int containerId /* = 0 */, int itemOffset /* = 0 */, unsigned int itemFlags /* = 0 */)
+{
+ CGUIListItemPtr item;
+
+ if (containerId == 0 &&
+ itemOffset == 0 &&
+ !(itemFlags & INFOFLAG_LISTITEM_CONTAINER) &&
+ !(itemFlags & INFOFLAG_LISTITEM_ABSOLUTE) &&
+ !(itemFlags & INFOFLAG_LISTITEM_POSITION))
+ item = GetCurrentListItemFromWindow(contextWindow);
+
+ if (!item)
+ {
+ CGUIControl* activeContainer = GetActiveContainer(containerId, contextWindow);
+ if (activeContainer)
+ item = static_cast<IGUIContainer *>(activeContainer)->GetListItem(itemOffset, itemFlags);
+ }
+
+ return item;
+}
+
+std::string GetFileInfoLabelValueFromPath(int info, const std::string& filenameAndPath)
+{
+ std::string value = filenameAndPath;
+
+ if (info == PLAYER_PATH)
+ {
+ // do this twice since we want the path outside the archive if this is to be of use.
+ if (URIUtils::IsInArchive(value))
+ value = URIUtils::GetParentPath(value);
+
+ value = URIUtils::GetParentPath(value);
+ }
+ else if (info == PLAYER_FILENAME)
+ {
+ value = URIUtils::GetFileName(value);
+ }
+
+ return value;
+}
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoHelper.h b/xbmc/guilib/guiinfo/GUIInfoHelper.h
new file mode 100644
index 0000000..ce5191e
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoHelper.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "playlists/PlayListTypes.h"
+
+#include <memory>
+#include <string>
+
+class CFileItem;
+
+class CGUIListItem;
+typedef std::shared_ptr<CGUIListItem> CGUIListItemPtr;
+
+class CGUIControl;
+class CGUIMediaWindow;
+class CGUIWindow;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+std::string GetPlaylistLabel(int item, PLAYLIST::Id playlistid = PLAYLIST::TYPE_NONE);
+
+CGUIWindow* GetWindow(int contextWindow);
+CGUIControl* GetActiveContainer(int containerId, int contextWindow);
+CGUIMediaWindow* GetMediaWindow(int contextWindow);
+CGUIListItemPtr GetCurrentListItem(int contextWindow, int containerId = 0, int itemOffset = 0, unsigned int itemFlags = 0);
+
+std::string GetFileInfoLabelValueFromPath(int info, const std::string& filenameAndPath);
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.cpp b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp
new file mode 100644
index 0000000..4a6ce13
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GUIInfoLabel.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "addons/Skin.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIListItem.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+CGUIInfoLabel::CGUIInfoLabel(const std::string &label, const std::string &fallback /*= ""*/, int context /*= 0*/)
+{
+ SetLabel(label, fallback, context);
+}
+
+int CGUIInfoLabel::GetIntValue(int contextWindow) const
+{
+ const std::string label = GetLabel(contextWindow);
+ if (!label.empty())
+ return strtol(label.c_str(), NULL, 10);
+
+ return 0;
+}
+
+void CGUIInfoLabel::SetLabel(const std::string &label, const std::string &fallback, int context /*= 0*/)
+{
+ m_fallback = fallback;
+ Parse(label, context);
+}
+
+const std::string &CGUIInfoLabel::GetLabel(int contextWindow, bool preferImage, std::string *fallback /*= NULL*/) const
+{
+ bool needsUpdate = m_dirty;
+ if (!m_info.empty())
+ {
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ for (const auto &portion : m_info)
+ {
+ if (portion.m_info)
+ {
+ std::string infoLabel;
+ if (preferImage)
+ infoLabel = infoMgr.GetImage(portion.m_info, contextWindow, fallback);
+ if (infoLabel.empty())
+ infoLabel = infoMgr.GetLabel(portion.m_info, contextWindow, fallback);
+ needsUpdate |= portion.NeedsUpdate(infoLabel);
+ }
+ }
+ }
+ else
+ needsUpdate = !m_label.empty();
+
+ return CacheLabel(needsUpdate);
+}
+
+const std::string &CGUIInfoLabel::GetItemLabel(const CGUIListItem *item, bool preferImages, std::string *fallback /*= NULL*/) const
+{
+ bool needsUpdate = m_dirty;
+ if (item->IsFileItem() && !m_info.empty())
+ {
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+ for (const auto &portion : m_info)
+ {
+ if (portion.m_info)
+ {
+ std::string infoLabel;
+ if (preferImages)
+ infoLabel = infoMgr.GetItemImage(item, 0, portion.m_info, fallback);
+ else
+ infoLabel = infoMgr.GetItemLabel(static_cast<const CFileItem *>(item), 0, portion.m_info, fallback);
+ needsUpdate |= portion.NeedsUpdate(infoLabel);
+ }
+ }
+ }
+ else
+ needsUpdate = !m_label.empty();
+
+ return CacheLabel(needsUpdate);
+}
+
+const std::string &CGUIInfoLabel::CacheLabel(bool rebuild) const
+{
+ if (rebuild)
+ {
+ m_label.clear();
+ for (const auto &portion : m_info)
+ m_label += portion.Get();
+ m_dirty = false;
+ }
+ if (m_label.empty()) // empty label, use the fallback
+ return m_fallback;
+ return m_label;
+}
+
+bool CGUIInfoLabel::IsEmpty() const
+{
+ return m_info.empty();
+}
+
+bool CGUIInfoLabel::IsConstant() const
+{
+ return m_info.empty() || (m_info.size() == 1 && m_info[0].m_info == 0);
+}
+
+bool CGUIInfoLabel::ReplaceSpecialKeywordReferences(const std::string &strInput, const std::string &strKeyword, const StringReplacerFunc &func, std::string &strOutput)
+{
+ // replace all $strKeyword[value] with resolved strings
+ const std::string dollarStrPrefix = "$" + strKeyword + "[";
+
+ size_t index = 0;
+ size_t startPos;
+
+ while ((startPos = strInput.find(dollarStrPrefix, index)) != std::string::npos)
+ {
+ size_t valuePos = startPos + dollarStrPrefix.size();
+ size_t endPos = StringUtils::FindEndBracket(strInput, '[', ']', valuePos);
+ if (endPos != std::string::npos)
+ {
+ if (index == 0) // first occurrence?
+ strOutput.clear();
+ strOutput.append(strInput, index, startPos - index); // append part from the left side
+ strOutput += func(strInput.substr(valuePos, endPos - valuePos)); // resolve and append value part
+ index = endPos + 1;
+ }
+ else
+ {
+ // if closing bracket is missing, report error and leave incomplete reference in
+ CLog::Log(LOGERROR, "Error parsing value - missing ']' in \"{}\"", strInput);
+ break;
+ }
+ }
+
+ if (index) // if we replaced anything
+ {
+ strOutput.append(strInput, index, std::string::npos); // append leftover from the right side
+ return true;
+ }
+
+ return false;
+}
+
+bool CGUIInfoLabel::ReplaceSpecialKeywordReferences(std::string &work, const std::string &strKeyword, const StringReplacerFunc &func)
+{
+ std::string output;
+ if (ReplaceSpecialKeywordReferences(work, strKeyword, func, output))
+ {
+ work = std::move(output);
+ return true;
+ }
+ return false;
+}
+
+std::string LocalizeReplacer(const std::string &str)
+{
+ std::string replace = g_localizeStringsTemp.Get(atoi(str.c_str()));
+ if (replace.empty())
+ replace = g_localizeStrings.Get(atoi(str.c_str()));
+ return replace;
+}
+
+std::string AddonReplacer(const std::string &str)
+{
+ // assumes "addon.id #####"
+ size_t length = str.find(' ');
+ const std::string addonid = str.substr(0, length);
+ int stringid = atoi(str.substr(length + 1).c_str());
+ return g_localizeStrings.GetAddonString(addonid, stringid);
+}
+
+std::string NumberReplacer(const std::string &str)
+{
+ return str;
+}
+
+std::string CGUIInfoLabel::ReplaceLocalize(const std::string &label)
+{
+ std::string work(label);
+ ReplaceSpecialKeywordReferences(work, "LOCALIZE", LocalizeReplacer);
+ ReplaceSpecialKeywordReferences(work, "NUMBER", NumberReplacer);
+ return work;
+}
+
+std::string CGUIInfoLabel::ReplaceAddonStrings(std::string &&label)
+{
+ ReplaceSpecialKeywordReferences(label, "ADDON", AddonReplacer);
+ return std::move(label);
+}
+
+enum EINFOFORMAT { NONE = 0, FORMATINFO, FORMATESCINFO, FORMATVAR, FORMATESCVAR };
+
+typedef struct
+{
+ const char *str;
+ EINFOFORMAT val;
+} infoformat;
+
+const static infoformat infoformatmap[] = {{ "$INFO[", FORMATINFO},
+ { "$ESCINFO[", FORMATESCINFO},
+ { "$VAR[", FORMATVAR},
+ { "$ESCVAR[", FORMATESCVAR}};
+
+void CGUIInfoLabel::Parse(const std::string &label, int context)
+{
+ m_info.clear();
+ m_dirty = true;
+ // Step 1: Replace all $LOCALIZE[number] with the real string
+ std::string work = ReplaceLocalize(label);
+ // Step 2: Replace all $ADDON[id number] with the real string
+ work = ReplaceAddonStrings(std::move(work));
+ // Step 3: Find all $INFO[info,prefix,postfix] blocks
+ EINFOFORMAT format;
+ do
+ {
+ format = NONE;
+ size_t pos1 = work.size();
+ size_t pos2;
+ size_t len = 0;
+ for (const infoformat& infoformat : infoformatmap)
+ {
+ pos2 = work.find(infoformat.str);
+ if (pos2 != std::string::npos && pos2 < pos1)
+ {
+ pos1 = pos2;
+ len = strlen(infoformat.str);
+ format = infoformat.val;
+ }
+ }
+
+ if (format != NONE)
+ {
+ if (pos1 > 0)
+ m_info.emplace_back(0, work.substr(0, pos1), "");
+
+ pos2 = StringUtils::FindEndBracket(work, '[', ']', pos1 + len);
+ if (pos2 != std::string::npos)
+ {
+ // decipher the block
+ std::vector<std::string> params = StringUtils::Split(work.substr(pos1 + len, pos2 - pos1 - len), ",");
+ if (!params.empty())
+ {
+ CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
+
+ int info;
+ if (format == FORMATVAR || format == FORMATESCVAR)
+ {
+ info = infoMgr.TranslateSkinVariableString(params[0], context);
+ if (info == 0)
+ info = infoMgr.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(params[0], context));
+ if (info == 0) // skinner didn't define this conditional label!
+ CLog::Log(LOGWARNING, "Label Formatting: $VAR[{}] is not defined", params[0]);
+ }
+ else
+ info = infoMgr.TranslateString(params[0]);
+ std::string prefix, postfix;
+ if (params.size() > 1)
+ prefix = params[1];
+ if (params.size() > 2)
+ postfix = params[2];
+ m_info.emplace_back(info, prefix, postfix, format == FORMATESCINFO || format == FORMATESCVAR);
+ }
+ // and delete it from our work string
+ work.erase(0, pos2 + 1);
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "Error parsing label - missing ']' in \"{}\"", label);
+ return;
+ }
+ }
+ }
+ while (format != NONE);
+
+ if (!work.empty())
+ m_info.emplace_back(0, work, "");
+}
+
+CGUIInfoLabel::CInfoPortion::CInfoPortion(int info, const std::string &prefix, const std::string &postfix, bool escaped /*= false */):
+ m_prefix(prefix),
+ m_postfix(postfix)
+{
+ m_info = info;
+ m_escaped = escaped;
+ // filter our prefix and postfix for comma's
+ StringUtils::Replace(m_prefix, "$COMMA", ",");
+ StringUtils::Replace(m_postfix, "$COMMA", ",");
+ StringUtils::Replace(m_prefix, "$LBRACKET", "["); StringUtils::Replace(m_prefix, "$RBRACKET", "]");
+ StringUtils::Replace(m_postfix, "$LBRACKET", "["); StringUtils::Replace(m_postfix, "$RBRACKET", "]");
+}
+
+bool CGUIInfoLabel::CInfoPortion::NeedsUpdate(const std::string &label) const
+{
+ if (m_label != label)
+ {
+ m_label = label;
+ return true;
+ }
+ return false;
+}
+
+std::string CGUIInfoLabel::CInfoPortion::Get() const
+{
+ if (!m_info)
+ return m_prefix;
+ else if (m_label.empty())
+ return "";
+ std::string label = m_prefix + m_label + m_postfix;
+ if (m_escaped) // escape all quotes and backslashes, then quote
+ {
+ StringUtils::Replace(label, "\\", "\\\\");
+ StringUtils::Replace(label, "\"", "\\\"");
+ return "\"" + label + "\"";
+ }
+ return label;
+}
+
+std::string CGUIInfoLabel::GetLabel(const std::string &label, int contextWindow /*= 0*/, bool preferImage /*= false */)
+{ // translate the label
+ const CGUIInfoLabel info(label, "", contextWindow);
+ return info.GetLabel(contextWindow, preferImage);
+}
+
+std::string CGUIInfoLabel::GetItemLabel(const std::string &label, const CGUIListItem *item, bool preferImage /*= false */)
+{ // translate the label
+ const CGUIInfoLabel info(label);
+ return info.GetItemLabel(item, preferImage);
+}
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.h b/xbmc/guilib/guiinfo/GUIInfoLabel.h
new file mode 100644
index 0000000..c9ae1a8
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoLabel.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+/*!
+\file GUIInfoLabel.h
+\brief
+*/
+
+#include "interfaces/info/Info.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+class CGUIListItem;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfoLabel
+{
+public:
+ CGUIInfoLabel() = default;
+ CGUIInfoLabel(const std::string& label,
+ const std::string& fallback = "",
+ int context = INFO::DEFAULT_CONTEXT);
+
+ void SetLabel(const std::string& label,
+ const std::string& fallback,
+ int context = INFO::DEFAULT_CONTEXT);
+
+ /*!
+ \brief Gets a label (or image) for a given window context from the info manager.
+ \param contextWindow the context in which to evaluate the expression.
+ \param preferImage caller is specifically wanting an image rather than a label. Defaults to false.
+ \param fallback if non-NULL, is set to an alternate value to use should the actual value be not appropriate. Defaults to NULL.
+ \return label (or image).
+ */
+ const std::string &GetLabel(int contextWindow, bool preferImage = false, std::string *fallback = NULL) const;
+
+ /*!
+ \brief Gets the label and returns it as an int value
+ \param contextWindow the context in which to evaluate the expression.
+ \return int value.
+ \sa GetLabel
+ */
+ int GetIntValue(int contextWindow) const;
+
+ /*!
+ \brief Gets a label (or image) for a given listitem from the info manager.
+ \param item listitem in question.
+ \param preferImage caller is specifically wanting an image rather than a label. Defaults to false.
+ \param fallback if non-NULL, is set to an alternate value to use should the actual value be not appropriate. Defaults to NULL.
+ \return label (or image).
+ */
+ const std::string &GetItemLabel(const CGUIListItem *item, bool preferImage = false, std::string *fallback = NULL) const;
+
+ bool IsConstant() const;
+ bool IsEmpty() const;
+
+ const std::string& GetFallback() const { return m_fallback; }
+
+ static std::string GetLabel(const std::string& label,
+ int contextWindow,
+ bool preferImage = false);
+ static std::string GetItemLabel(const std::string &label, const CGUIListItem *item, bool preferImage = false);
+
+ /*!
+ \brief Replaces instances of $LOCALIZE[number] with the appropriate localized string
+ \param label text to replace
+ \return text with any localized strings filled in.
+ */
+ static std::string ReplaceLocalize(const std::string &label);
+
+ /*!
+ \brief Replaces instances of $ADDON[id number] with the appropriate localized addon string
+ \param label text to replace
+ \return text with any localized strings filled in.
+ */
+ static std::string ReplaceAddonStrings(std::string &&label);
+
+ typedef std::function<std::string(const std::string&)> StringReplacerFunc;
+
+ /*!
+ \brief Replaces instances of $strKeyword[value] with the appropriate resolved string
+ \param strInput text to replace
+ \param strKeyword keyword to look for
+ \param func function that does the actual replacement of each bracketed value found
+ \param strOutput the output string
+ \return whether anything has been replaced.
+ */
+ static bool ReplaceSpecialKeywordReferences(const std::string &strInput, const std::string &strKeyword, const StringReplacerFunc &func, std::string &strOutput);
+
+ /*!
+ \brief Replaces instances of $strKeyword[value] with the appropriate resolved string in-place
+ \param work text to replace in-place
+ \param strKeyword keyword to look for
+ \param func function that does the actual replacement of each bracketed value found
+ \return whether anything has been replaced.
+ */
+ static bool ReplaceSpecialKeywordReferences(std::string &work, const std::string &strKeyword, const StringReplacerFunc &func);
+
+private:
+ void Parse(const std::string &label, int context);
+
+ /*! \brief return (and cache) built label from info portions.
+ \param rebuild whether we need to rebuild the label
+ \sa GetLabel, GetItemLabel
+ */
+ const std::string &CacheLabel(bool rebuild) const;
+
+ class CInfoPortion
+ {
+ public:
+ CInfoPortion(int info, const std::string &prefix, const std::string &postfix, bool escaped = false);
+ bool NeedsUpdate(const std::string &label) const;
+ std::string Get() const;
+ int m_info;
+ private:
+ bool m_escaped;
+ mutable std::string m_label;
+ std::string m_prefix;
+ std::string m_postfix;
+ };
+
+ mutable bool m_dirty = false;
+ mutable std::string m_label;
+ std::string m_fallback;
+ std::vector<CInfoPortion> m_info;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
+
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h
new file mode 100644
index 0000000..3e2b163
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h
@@ -0,0 +1,985 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+// clang-format off
+#define PLAYER_HAS_MEDIA 1
+#define PLAYER_HAS_AUDIO 2
+#define PLAYER_HAS_VIDEO 3
+#define PLAYER_PLAYING 4
+#define PLAYER_PAUSED 5
+#define PLAYER_REWINDING 6
+#define PLAYER_REWINDING_2x 7
+#define PLAYER_REWINDING_4x 8
+#define PLAYER_REWINDING_8x 9
+#define PLAYER_REWINDING_16x 10
+#define PLAYER_REWINDING_32x 11
+#define PLAYER_FORWARDING 12
+#define PLAYER_FORWARDING_2x 13
+#define PLAYER_FORWARDING_4x 14
+#define PLAYER_FORWARDING_8x 15
+#define PLAYER_FORWARDING_16x 16
+#define PLAYER_FORWARDING_32x 17
+#define PLAYER_CACHING 20
+// unused id 21
+#define PLAYER_PROGRESS 22
+#define PLAYER_SEEKBAR 23
+#define PLAYER_SEEKTIME 24
+#define PLAYER_SEEKING 25
+#define PLAYER_SHOWTIME 26
+#define PLAYER_TIME 27
+#define PLAYER_TIME_REMAINING 28
+#define PLAYER_DURATION 29
+#define PLAYER_HASPERFORMEDSEEK 30
+#define PLAYER_SHOWINFO 31
+#define PLAYER_VOLUME 32
+#define PLAYER_MUTED 33
+#define PLAYER_HASDURATION 34
+#define PLAYER_CHAPTER 35
+#define PLAYER_CHAPTERCOUNT 36
+#define PLAYER_TIME_SPEED 37
+#define PLAYER_FINISH_TIME 38
+#define PLAYER_CACHELEVEL 39
+#define PLAYER_CHAPTERNAME 41
+#define PLAYER_SUBTITLE_DELAY 42
+#define PLAYER_AUDIO_DELAY 43
+#define PLAYER_PASSTHROUGH 44
+// unused 45
+// unused 46
+#define PLAYER_SEEKOFFSET 47
+#define PLAYER_PROGRESS_CACHE 48
+#define PLAYER_ITEM_ART 49
+#define PLAYER_CAN_PAUSE 50
+#define PLAYER_CAN_SEEK 51
+#define PLAYER_START_TIME 52
+// unused 53
+#define PLAYER_ISINTERNETSTREAM 54
+// unused 55
+#define PLAYER_SEEKSTEPSIZE 56
+#define PLAYER_IS_CHANNEL_PREVIEW_ACTIVE 57
+#define PLAYER_SUPPORTS_TEMPO 58
+#define PLAYER_IS_TEMPO 59
+#define PLAYER_PLAYSPEED 60
+#define PLAYER_SEEKNUMERIC 61
+#define PLAYER_HAS_GAME 62
+#define PLAYER_HAS_PROGRAMS 63
+#define PLAYER_HAS_RESOLUTIONS 64
+#define PLAYER_FRAMEADVANCE 65
+#define PLAYER_ICON 66
+#define PLAYER_CUTLIST 67
+#define PLAYER_CHAPTERS 68
+#define PLAYER_EDITLIST 69
+#define PLAYER_CUTS 70
+#define PLAYER_SCENE_MARKERS 71
+#define PLAYER_HAS_SCENE_MARKERS 72
+// Keep player infolabels that work with offset and position together
+#define PLAYER_PATH 81
+#define PLAYER_FILEPATH 82
+#define PLAYER_TITLE 83
+#define PLAYER_FILENAME 84
+
+// Range of player infolabels that work with offset and position
+#define PLAYER_OFFSET_POSITION_FIRST PLAYER_PATH
+#define PLAYER_OFFSET_POSITION_LAST PLAYER_FILENAME
+
+#define WEATHER_CONDITIONS_TEXT 100
+#define WEATHER_TEMPERATURE 101
+#define WEATHER_LOCATION 102
+#define WEATHER_IS_FETCHED 103
+#define WEATHER_FANART_CODE 104
+#define WEATHER_PLUGIN 105
+#define WEATHER_CONDITIONS_ICON 106
+
+#define SYSTEM_TEMPERATURE_UNITS 107
+#define SYSTEM_PROGRESS_BAR 108
+#define SYSTEM_LANGUAGE 109
+#define SYSTEM_TIME 110
+#define SYSTEM_DATE 111
+#define SYSTEM_CPU_TEMPERATURE 112
+#define SYSTEM_GPU_TEMPERATURE 113
+#define SYSTEM_FAN_SPEED 114
+#define SYSTEM_FREE_SPACE_C 115
+// #define SYSTEM_FREE_SPACE_D 116 //116 is reserved for space on D
+#define SYSTEM_FREE_SPACE_E 117
+#define SYSTEM_FREE_SPACE_F 118
+#define SYSTEM_FREE_SPACE_G 119
+#define SYSTEM_BUILD_VERSION 120
+#define SYSTEM_BUILD_DATE 121
+#define SYSTEM_ETHERNET_LINK_ACTIVE 122
+#define SYSTEM_FPS 123
+#define SYSTEM_ALWAYS_TRUE 125 // useful for <visible fade="10" start="hidden">true</visible>, to fade in a control
+#define SYSTEM_ALWAYS_FALSE 126 // used for <visible fade="10">false</visible>, to fade out a control (ie not particularly useful!)
+#define SYSTEM_MEDIA_DVD 127
+#define SYSTEM_DVDREADY 128
+#define SYSTEM_HAS_ALARM 129
+#define SYSTEM_SUPPORTS_CPU_USAGE 130
+#define SYSTEM_SCREEN_MODE 132
+#define SYSTEM_SCREEN_WIDTH 133
+#define SYSTEM_SCREEN_HEIGHT 134
+#define SYSTEM_CURRENT_WINDOW 135
+#define SYSTEM_CURRENT_CONTROL 136
+#define SYSTEM_CURRENT_CONTROL_ID 137
+#define SYSTEM_DVD_LABEL 138
+#define SYSTEM_HASLOCKS 140
+#define SYSTEM_ISMASTER 141
+#define SYSTEM_TRAYOPEN 142
+#define SYSTEM_SHOW_EXIT_BUTTON 143
+#define SYSTEM_ALARM_POS 144
+#define SYSTEM_LOGGEDON 145
+#define SYSTEM_PROFILENAME 146
+#define SYSTEM_PROFILETHUMB 147
+#define SYSTEM_HAS_LOGINSCREEN 148
+#define SYSTEM_HAS_ACTIVE_MODAL_DIALOG 149
+#define SYSTEM_HDD_SMART 150
+#define SYSTEM_HDD_TEMPERATURE 151
+#define SYSTEM_HDD_MODEL 152
+#define SYSTEM_HDD_SERIAL 153
+#define SYSTEM_HDD_FIRMWARE 154
+#define SYSTEM_HAS_VISIBLE_MODAL_DIALOG 155
+#define SYSTEM_HDD_PASSWORD 156
+#define SYSTEM_HDD_LOCKSTATE 157
+#define SYSTEM_HDD_LOCKKEY 158
+#define SYSTEM_INTERNET_STATE 159
+#define SYSTEM_HAS_INPUT_HIDDEN 160
+#define SYSTEM_HAS_PVR_ADDON 161
+#define SYSTEM_ALARM_LESS_OR_EQUAL 180
+#define SYSTEM_PROFILECOUNT 181
+#define SYSTEM_ISFULLSCREEN 182
+#define SYSTEM_ISSTANDALONE 183
+#define SYSTEM_IDLE_SHUTDOWN_INHIBITED 184
+#define SYSTEM_HAS_SHUTDOWN 185
+#define SYSTEM_HAS_PVR 186
+#define SYSTEM_STARTUP_WINDOW 187
+#define SYSTEM_STEREOSCOPIC_MODE 188
+#define SYSTEM_BUILD_VERSION_SHORT 189
+
+#define NETWORK_IP_ADDRESS 190
+#define NETWORK_MAC_ADDRESS 191
+#define NETWORK_IS_DHCP 192
+#define NETWORK_LINK_STATE 193
+#define NETWORK_SUBNET_MASK 194
+#define NETWORK_GATEWAY_ADDRESS 195
+#define NETWORK_DNS1_ADDRESS 196
+#define NETWORK_DNS2_ADDRESS 197
+#define NETWORK_DHCP_ADDRESS 198
+
+// Keep musicplayer infolabels that work with offset and position together
+#define MUSICPLAYER_TITLE 200
+#define MUSICPLAYER_ALBUM 201
+#define MUSICPLAYER_ARTIST 202
+#define MUSICPLAYER_GENRE 203
+#define MUSICPLAYER_YEAR 204
+#define MUSICPLAYER_DURATION 205
+#define MUSICPLAYER_TRACK_NUMBER 208
+#define MUSICPLAYER_COVER 210
+#define MUSICPLAYER_BITRATE 211
+#define MUSICPLAYER_PLAYLISTLEN 212
+#define MUSICPLAYER_PLAYLISTPOS 213
+#define MUSICPLAYER_CHANNELS 214
+#define MUSICPLAYER_BITSPERSAMPLE 215
+#define MUSICPLAYER_SAMPLERATE 216
+#define MUSICPLAYER_CODEC 217
+#define MUSICPLAYER_DISC_NUMBER 218
+#define MUSICPLAYER_RATING 219
+#define MUSICPLAYER_COMMENT 220
+#define MUSICPLAYER_LYRICS 221
+#define MUSICPLAYER_ALBUM_ARTIST 222
+#define MUSICPLAYER_PLAYCOUNT 223
+#define MUSICPLAYER_LASTPLAYED 224
+#define MUSICPLAYER_USER_RATING 225
+#define MUSICPLAYER_RATING_AND_VOTES 226
+#define MUSICPLAYER_VOTES 227
+#define MUSICPLAYER_MOOD 228
+#define MUSICPLAYER_CONTRIBUTORS 229
+#define MUSICPLAYER_CONTRIBUTOR_AND_ROLE 230
+#define MUSICPLAYER_DBID 231
+#define MUSICPLAYER_DISC_TITLE 232
+#define MUSICPLAYER_RELEASEDATE 233
+#define MUSICPLAYER_ORIGINALDATE 234
+#define MUSICPLAYER_BPM 235
+
+// Range of musicplayer infolabels that work with offset and position
+#define MUSICPLAYER_OFFSET_POSITION_FIRST MUSICPLAYER_TITLE
+#define MUSICPLAYER_OFFSET_POSITION_LAST MUSICPLAYER_BPM
+
+#define MUSICPLAYER_PROPERTY 236
+#define MUSICPLAYER_CHANNEL_NAME 237
+#define MUSICPLAYER_CHANNEL_GROUP 238
+#define MUSICPLAYER_CHANNEL_NUMBER 239
+#define MUSICPLAYER_TOTALDISCS 240
+#define MUSICPLAYER_STATIONNAME 241
+
+// Musicplayer infobools
+#define MUSICPLAYER_HASPREVIOUS 242
+#define MUSICPLAYER_HASNEXT 243
+#define MUSICPLAYER_EXISTS 244
+#define MUSICPLAYER_PLAYLISTPLAYING 245
+#define MUSICPLAYER_CONTENT 246
+#define MUSICPLAYER_ISMULTIDISC 247
+
+// Videoplayer infolabels
+#define VIDEOPLAYER_HDR_TYPE 249
+// Keep videoplayer infolabels that work with offset and position together
+#define VIDEOPLAYER_TITLE 250
+#define VIDEOPLAYER_GENRE 251
+#define VIDEOPLAYER_DIRECTOR 252
+#define VIDEOPLAYER_YEAR 253
+#define VIDEOPLAYER_COVER 254
+#define VIDEOPLAYER_ORIGINALTITLE 255
+#define VIDEOPLAYER_PLOT 256
+#define VIDEOPLAYER_PLOT_OUTLINE 257
+#define VIDEOPLAYER_EPISODE 258
+#define VIDEOPLAYER_SEASON 259
+#define VIDEOPLAYER_RATING 260
+#define VIDEOPLAYER_TVSHOW 261
+#define VIDEOPLAYER_PREMIERED 262
+#define VIDEOPLAYER_STUDIO 263
+#define VIDEOPLAYER_MPAA 264
+#define VIDEOPLAYER_ARTIST 265
+#define VIDEOPLAYER_ALBUM 266
+#define VIDEOPLAYER_WRITER 267
+#define VIDEOPLAYER_TAGLINE 268
+#define VIDEOPLAYER_TOP250 269
+#define VIDEOPLAYER_RATING_AND_VOTES 270
+#define VIDEOPLAYER_TRAILER 271
+#define VIDEOPLAYER_COUNTRY 272
+#define VIDEOPLAYER_PLAYCOUNT 273
+#define VIDEOPLAYER_LASTPLAYED 274
+#define VIDEOPLAYER_VOTES 275
+#define VIDEOPLAYER_IMDBNUMBER 276
+#define VIDEOPLAYER_USER_RATING 277
+#define VIDEOPLAYER_DBID 278
+#define VIDEOPLAYER_TVSHOWDBID 279
+#define VIDEOPLAYER_ART 280
+
+// Range of videoplayer infolabels that work with offset and position
+#define VIDEOPLAYER_OFFSET_POSITION_FIRST VIDEOPLAYER_TITLE
+#define VIDEOPLAYER_OFFSET_POSITION_LAST VIDEOPLAYER_ART
+
+#define VIDEOPLAYER_AUDIO_BITRATE 281
+#define VIDEOPLAYER_VIDEO_BITRATE 282
+#define VIDEOPLAYER_VIDEO_CODEC 283
+#define VIDEOPLAYER_VIDEO_RESOLUTION 284
+#define VIDEOPLAYER_AUDIO_CODEC 285
+#define VIDEOPLAYER_AUDIO_CHANNELS 286
+#define VIDEOPLAYER_VIDEO_ASPECT 287
+#define VIDEOPLAYER_SUBTITLES_LANG 288
+#define VIDEOPLAYER_AUDIO_LANG 290
+#define VIDEOPLAYER_STEREOSCOPIC_MODE 291
+#define VIDEOPLAYER_CAST 292
+#define VIDEOPLAYER_CAST_AND_ROLE 293
+#define VIDEOPLAYER_UNIQUEID 294
+#define VIDEOPLAYER_AUDIOSTREAMCOUNT 295
+
+// Videoplayer infobools
+#define VIDEOPLAYER_HASSUBTITLES 300
+#define VIDEOPLAYER_SUBTITLESENABLED 301
+#define VIDEOPLAYER_USING_OVERLAYS 302
+#define VIDEOPLAYER_ISFULLSCREEN 303
+#define VIDEOPLAYER_HASMENU 304
+#define VIDEOPLAYER_PLAYLISTLEN 305
+#define VIDEOPLAYER_PLAYLISTPOS 306
+#define VIDEOPLAYER_CONTENT 307
+#define VIDEOPLAYER_HAS_INFO 308
+#define VIDEOPLAYER_HASTELETEXT 309
+#define VIDEOPLAYER_IS_STEREOSCOPIC 310
+
+// PVR infolabels
+#define VIDEOPLAYER_EVENT 313
+#define VIDEOPLAYER_EPISODENAME 314
+#define VIDEOPLAYER_STARTTIME 315
+#define VIDEOPLAYER_ENDTIME 316
+#define VIDEOPLAYER_NEXT_TITLE 317
+#define VIDEOPLAYER_NEXT_GENRE 318
+#define VIDEOPLAYER_NEXT_PLOT 319
+#define VIDEOPLAYER_NEXT_PLOT_OUTLINE 320
+#define VIDEOPLAYER_NEXT_STARTTIME 321
+#define VIDEOPLAYER_NEXT_ENDTIME 322
+#define VIDEOPLAYER_NEXT_DURATION 323
+#define VIDEOPLAYER_CHANNEL_NAME 324
+#define VIDEOPLAYER_CHANNEL_GROUP 325
+#define VIDEOPLAYER_PARENTAL_RATING 326
+#define VIDEOPLAYER_CHANNEL_NUMBER 327
+
+// PVR infobools
+#define VIDEOPLAYER_HAS_EPG 328
+#define VIDEOPLAYER_CAN_RESUME_LIVE_TV 329
+
+#define RETROPLAYER_VIDEO_FILTER 330
+#define RETROPLAYER_STRETCH_MODE 331
+#define RETROPLAYER_VIDEO_ROTATION 332
+
+#define CONTAINER_HAS_PARENT_ITEM 341
+#define CONTAINER_CAN_FILTER 342
+#define CONTAINER_CAN_FILTERADVANCED 343
+#define CONTAINER_FILTERED 344
+
+#define CONTAINER_SCROLL_PREVIOUS 345
+#define CONTAINER_MOVE_PREVIOUS 346
+// unused 347
+#define CONTAINER_MOVE_NEXT 348
+#define CONTAINER_SCROLL_NEXT 349
+#define CONTAINER_ISUPDATING 350
+#define CONTAINER_HASFILES 351
+#define CONTAINER_HASFOLDERS 352
+#define CONTAINER_STACKED 353
+#define CONTAINER_FOLDERNAME 354
+#define CONTAINER_SCROLLING 355
+#define CONTAINER_PLUGINNAME 356
+#define CONTAINER_PROPERTY 357
+#define CONTAINER_SORT_DIRECTION 358
+#define CONTAINER_NUM_ITEMS 359
+#define CONTAINER_FOLDERPATH 360
+#define CONTAINER_CONTENT 361
+#define CONTAINER_HAS_THUMB 362
+#define CONTAINER_SORT_METHOD 363
+#define CONTAINER_CURRENT_ITEM 364
+#define CONTAINER_ART 365
+#define CONTAINER_HAS_FOCUS 366
+#define CONTAINER_ROW 367
+#define CONTAINER_COLUMN 368
+#define CONTAINER_POSITION 369
+#define CONTAINER_VIEWMODE 370
+#define CONTAINER_HAS_NEXT 371
+#define CONTAINER_HAS_PREVIOUS 372
+#define CONTAINER_SUBITEM 373
+#define CONTAINER_NUM_PAGES 374
+#define CONTAINER_CURRENT_PAGE 375
+#define CONTAINER_SHOWPLOT 376
+#define CONTAINER_TOTALTIME 377
+#define CONTAINER_SORT_ORDER 378
+#define CONTAINER_TOTALWATCHED 379
+#define CONTAINER_TOTALUNWATCHED 380
+#define CONTAINER_VIEWCOUNT 381
+#define CONTAINER_SHOWTITLE 382
+#define CONTAINER_PLUGINCATEGORY 383
+#define CONTAINER_NUM_ALL_ITEMS 384
+#define CONTAINER_NUM_NONFOLDER_ITEMS 385
+
+#define MUSICPM_ENABLED 390
+#define MUSICPM_SONGSPLAYED 391
+#define MUSICPM_MATCHINGSONGS 392
+#define MUSICPM_MATCHINGSONGSPICKED 393
+#define MUSICPM_MATCHINGSONGSLEFT 394
+#define MUSICPM_RELAXEDSONGSPICKED 395
+#define MUSICPM_RANDOMSONGSPICKED 396
+
+#define PLAYLIST_LENGTH 400
+#define PLAYLIST_POSITION 401
+#define PLAYLIST_RANDOM 402
+#define PLAYLIST_REPEAT 403
+#define PLAYLIST_ISRANDOM 404
+#define PLAYLIST_ISREPEAT 405
+#define PLAYLIST_ISREPEATONE 406
+
+#define VISUALISATION_LOCKED 410
+#define VISUALISATION_PRESET 411
+#define VISUALISATION_NAME 412
+#define VISUALISATION_ENABLED 413
+#define VISUALISATION_HAS_PRESETS 414
+
+#define STRING_IS_EMPTY 420
+#define STRING_IS_EQUAL 421
+#define STRING_STARTS_WITH 422
+#define STRING_ENDS_WITH 423
+#define STRING_CONTAINS 424
+
+#define INTEGER_IS_EQUAL 450
+#define INTEGER_GREATER_THAN 451
+#define INTEGER_GREATER_OR_EQUAL 452
+#define INTEGER_LESS_THAN 453
+#define INTEGER_LESS_OR_EQUAL 454
+#define INTEGER_EVEN 455
+#define INTEGER_ODD 456
+#define INTEGER_VALUEOF 457
+
+#define SKIN_BOOL 600
+#define SKIN_STRING 601
+#define SKIN_STRING_IS_EQUAL 602
+#define SKIN_THEME 604
+#define SKIN_COLOUR_THEME 605
+#define SKIN_HAS_THEME 606
+#define SKIN_ASPECT_RATIO 607
+#define SKIN_FONT 608
+#define SKIN_INTEGER 609
+#define SKIN_TIMER_IS_RUNNING 610
+#define SKIN_TIMER_ELAPSEDSECS 611
+
+#define SYSTEM_IS_SCREENSAVER_INHIBITED 641
+#define SYSTEM_ADDON_UPDATE_COUNT 642
+#define SYSTEM_PRIVACY_POLICY 643
+#define SYSTEM_TOTAL_MEMORY 644
+#define SYSTEM_CPU_USAGE 645
+#define SYSTEM_USED_MEMORY_PERCENT 646
+#define SYSTEM_USED_MEMORY 647
+#define SYSTEM_FREE_MEMORY 648
+#define SYSTEM_FREE_MEMORY_PERCENT 649
+#define SYSTEM_UPTIME 654
+#define SYSTEM_TOTALUPTIME 655
+#define SYSTEM_CPUFREQUENCY 656
+#define SYSTEM_SCREEN_RESOLUTION 659
+#define SYSTEM_VIDEO_ENCODER_INFO 660
+#define SYSTEM_OS_VERSION_INFO 667
+#define SYSTEM_FREE_SPACE 679
+#define SYSTEM_USED_SPACE 680
+#define SYSTEM_TOTAL_SPACE 681
+#define SYSTEM_USED_SPACE_PERCENT 682
+#define SYSTEM_FREE_SPACE_PERCENT 683
+#define SYSTEM_ADDON_IS_ENABLED 703
+#define SYSTEM_GET_BOOL 704
+#define SYSTEM_GET_CORE_USAGE 705
+#define SYSTEM_HAS_CORE_ID 706
+#define SYSTEM_RENDER_VENDOR 707
+#define SYSTEM_RENDER_RENDERER 708
+#define SYSTEM_RENDER_VERSION 709
+#define SYSTEM_SETTING 710
+#define SYSTEM_HAS_ADDON 711
+#define SYSTEM_ADDON_TITLE 712
+#define SYSTEM_ADDON_ICON 713
+#define SYSTEM_BATTERY_LEVEL 714
+#define SYSTEM_IDLE_TIME 715
+#define SYSTEM_FRIENDLY_NAME 716
+#define SYSTEM_SCREENSAVER_ACTIVE 717
+#define SYSTEM_ADDON_VERSION 718
+#define SYSTEM_DPMS_ACTIVE 719
+
+#define LIBRARY_HAS_MUSIC 720
+#define LIBRARY_HAS_VIDEO 721
+#define LIBRARY_HAS_MOVIES 722
+#define LIBRARY_HAS_MOVIE_SETS 723
+#define LIBRARY_HAS_TVSHOWS 724
+#define LIBRARY_HAS_MUSICVIDEOS 725
+#define LIBRARY_HAS_SINGLES 726
+#define LIBRARY_HAS_COMPILATIONS 727
+#define LIBRARY_IS_SCANNING 728
+#define LIBRARY_IS_SCANNING_VIDEO 729
+#define LIBRARY_IS_SCANNING_MUSIC 730
+#define LIBRARY_HAS_ROLE 735
+#define LIBRARY_HAS_BOXSETS 736
+#define LIBRARY_HAS_NODE 737
+
+#define SYSTEM_PLATFORM_LINUX 741
+#define SYSTEM_PLATFORM_WINDOWS 742
+#define SYSTEM_PLATFORM_DARWIN 743
+#define SYSTEM_PLATFORM_DARWIN_OSX 744
+#define SYSTEM_PLATFORM_DARWIN_IOS 745
+#define SYSTEM_PLATFORM_UWP 746
+#define SYSTEM_PLATFORM_ANDROID 747
+#define SYSTEM_PLATFORM_WINDOWING 748
+#define SYSTEM_PLATFORM_WIN10 749
+
+#define SYSTEM_CAN_POWERDOWN 750
+#define SYSTEM_CAN_SUSPEND 751
+#define SYSTEM_CAN_HIBERNATE 752
+#define SYSTEM_CAN_REBOOT 753
+#define SYSTEM_MEDIA_AUDIO_CD 754
+
+#define SYSTEM_PLATFORM_DARWIN_TVOS 755
+#define SYSTEM_SUPPORTED_HDR_TYPES 756
+
+#define SLIDESHOW_ISPAUSED 800
+#define SLIDESHOW_ISRANDOM 801
+#define SLIDESHOW_ISACTIVE 802
+#define SLIDESHOW_ISVIDEO 803
+
+#define SLIDESHOW_LABELS_START 900
+#define SLIDESHOW_FILE_NAME (SLIDESHOW_LABELS_START)
+#define SLIDESHOW_FILE_PATH (SLIDESHOW_LABELS_START + 1)
+#define SLIDESHOW_FILE_SIZE (SLIDESHOW_LABELS_START + 2)
+#define SLIDESHOW_FILE_DATE (SLIDESHOW_LABELS_START + 3)
+#define SLIDESHOW_INDEX (SLIDESHOW_LABELS_START + 4)
+#define SLIDESHOW_RESOLUTION (SLIDESHOW_LABELS_START + 5)
+#define SLIDESHOW_COMMENT (SLIDESHOW_LABELS_START + 6)
+#define SLIDESHOW_COLOUR (SLIDESHOW_LABELS_START + 7)
+#define SLIDESHOW_PROCESS (SLIDESHOW_LABELS_START + 8)
+
+#define SLIDESHOW_EXIF_LONG_DATE (SLIDESHOW_LABELS_START + 17)
+#define SLIDESHOW_EXIF_LONG_DATE_TIME (SLIDESHOW_LABELS_START + 18)
+#define SLIDESHOW_EXIF_DATE (SLIDESHOW_LABELS_START + 19) /* Implementation only to just get localized date */
+#define SLIDESHOW_EXIF_DATE_TIME (SLIDESHOW_LABELS_START + 20)
+#define SLIDESHOW_EXIF_DESCRIPTION (SLIDESHOW_LABELS_START + 21)
+#define SLIDESHOW_EXIF_CAMERA_MAKE (SLIDESHOW_LABELS_START + 22)
+#define SLIDESHOW_EXIF_CAMERA_MODEL (SLIDESHOW_LABELS_START + 23)
+#define SLIDESHOW_EXIF_COMMENT (SLIDESHOW_LABELS_START + 24)
+#define SLIDESHOW_EXIF_SOFTWARE (SLIDESHOW_LABELS_START + 25)
+#define SLIDESHOW_EXIF_APERTURE (SLIDESHOW_LABELS_START + 26)
+#define SLIDESHOW_EXIF_FOCAL_LENGTH (SLIDESHOW_LABELS_START + 27)
+#define SLIDESHOW_EXIF_FOCUS_DIST (SLIDESHOW_LABELS_START + 28)
+#define SLIDESHOW_EXIF_EXPOSURE (SLIDESHOW_LABELS_START + 29)
+#define SLIDESHOW_EXIF_EXPOSURE_TIME (SLIDESHOW_LABELS_START + 30)
+#define SLIDESHOW_EXIF_EXPOSURE_BIAS (SLIDESHOW_LABELS_START + 31)
+#define SLIDESHOW_EXIF_EXPOSURE_MODE (SLIDESHOW_LABELS_START + 32)
+#define SLIDESHOW_EXIF_FLASH_USED (SLIDESHOW_LABELS_START + 33)
+#define SLIDESHOW_EXIF_WHITE_BALANCE (SLIDESHOW_LABELS_START + 34)
+#define SLIDESHOW_EXIF_LIGHT_SOURCE (SLIDESHOW_LABELS_START + 35)
+#define SLIDESHOW_EXIF_METERING_MODE (SLIDESHOW_LABELS_START + 36)
+#define SLIDESHOW_EXIF_ISO_EQUIV (SLIDESHOW_LABELS_START + 37)
+#define SLIDESHOW_EXIF_DIGITAL_ZOOM (SLIDESHOW_LABELS_START + 38)
+#define SLIDESHOW_EXIF_CCD_WIDTH (SLIDESHOW_LABELS_START + 39)
+#define SLIDESHOW_EXIF_GPS_LATITUDE (SLIDESHOW_LABELS_START + 40)
+#define SLIDESHOW_EXIF_GPS_LONGITUDE (SLIDESHOW_LABELS_START + 41)
+#define SLIDESHOW_EXIF_GPS_ALTITUDE (SLIDESHOW_LABELS_START + 42)
+#define SLIDESHOW_EXIF_ORIENTATION (SLIDESHOW_LABELS_START + 43)
+#define SLIDESHOW_EXIF_XPCOMMENT (SLIDESHOW_LABELS_START + 44)
+
+#define SLIDESHOW_IPTC_SUBLOCATION (SLIDESHOW_LABELS_START + 57)
+#define SLIDESHOW_IPTC_IMAGETYPE (SLIDESHOW_LABELS_START + 58)
+#define SLIDESHOW_IPTC_TIMECREATED (SLIDESHOW_LABELS_START + 59)
+#define SLIDESHOW_IPTC_SUP_CATEGORIES (SLIDESHOW_LABELS_START + 60)
+#define SLIDESHOW_IPTC_KEYWORDS (SLIDESHOW_LABELS_START + 61)
+#define SLIDESHOW_IPTC_CAPTION (SLIDESHOW_LABELS_START + 62)
+#define SLIDESHOW_IPTC_AUTHOR (SLIDESHOW_LABELS_START + 63)
+#define SLIDESHOW_IPTC_HEADLINE (SLIDESHOW_LABELS_START + 64)
+#define SLIDESHOW_IPTC_SPEC_INSTR (SLIDESHOW_LABELS_START + 65)
+#define SLIDESHOW_IPTC_CATEGORY (SLIDESHOW_LABELS_START + 66)
+#define SLIDESHOW_IPTC_BYLINE (SLIDESHOW_LABELS_START + 67)
+#define SLIDESHOW_IPTC_BYLINE_TITLE (SLIDESHOW_LABELS_START + 68)
+#define SLIDESHOW_IPTC_CREDIT (SLIDESHOW_LABELS_START + 69)
+#define SLIDESHOW_IPTC_SOURCE (SLIDESHOW_LABELS_START + 70)
+#define SLIDESHOW_IPTC_COPYRIGHT_NOTICE (SLIDESHOW_LABELS_START + 71)
+#define SLIDESHOW_IPTC_OBJECT_NAME (SLIDESHOW_LABELS_START + 72)
+#define SLIDESHOW_IPTC_CITY (SLIDESHOW_LABELS_START + 73)
+#define SLIDESHOW_IPTC_STATE (SLIDESHOW_LABELS_START + 74)
+#define SLIDESHOW_IPTC_COUNTRY (SLIDESHOW_LABELS_START + 75)
+#define SLIDESHOW_IPTC_TX_REFERENCE (SLIDESHOW_LABELS_START + 76)
+#define SLIDESHOW_IPTC_DATE (SLIDESHOW_LABELS_START + 77)
+#define SLIDESHOW_IPTC_URGENCY (SLIDESHOW_LABELS_START + 78)
+#define SLIDESHOW_IPTC_COUNTRY_CODE (SLIDESHOW_LABELS_START + 79)
+#define SLIDESHOW_IPTC_REF_SERVICE (SLIDESHOW_LABELS_START + 80)
+#define SLIDESHOW_LABELS_END SLIDESHOW_IPTC_REF_SERVICE
+
+#define FANART_COLOR1 1000
+#define FANART_COLOR2 1001
+#define FANART_COLOR3 1002
+#define FANART_IMAGE 1003
+
+#define SYSTEM_PROFILEAUTOLOGIN 1004
+
+#define SYSTEM_HAS_CMS 1006
+#define SYSTEM_BUILD_VERSION_CODE 1007
+#define SYSTEM_BUILD_VERSION_GIT 1008
+
+#define PVR_CONDITIONS_START 1100
+#define PVR_IS_RECORDING (PVR_CONDITIONS_START)
+#define PVR_HAS_TIMER (PVR_CONDITIONS_START + 1)
+#define PVR_HAS_NONRECORDING_TIMER (PVR_CONDITIONS_START + 2)
+#define PVR_IS_PLAYING_TV (PVR_CONDITIONS_START + 3)
+#define PVR_IS_PLAYING_RADIO (PVR_CONDITIONS_START + 4)
+#define PVR_IS_PLAYING_RECORDING (PVR_CONDITIONS_START + 5)
+#define PVR_ACTUAL_STREAM_ENCRYPTED (PVR_CONDITIONS_START + 6)
+#define PVR_HAS_TV_CHANNELS (PVR_CONDITIONS_START + 7)
+#define PVR_HAS_RADIO_CHANNELS (PVR_CONDITIONS_START + 8)
+#define PVR_IS_TIMESHIFTING (PVR_CONDITIONS_START + 9)
+#define PVR_IS_RECORDING_TV (PVR_CONDITIONS_START + 10)
+#define PVR_HAS_TV_TIMER (PVR_CONDITIONS_START + 11)
+#define PVR_HAS_NONRECORDING_TV_TIMER (PVR_CONDITIONS_START + 12)
+#define PVR_IS_RECORDING_RADIO (PVR_CONDITIONS_START + 13)
+#define PVR_HAS_RADIO_TIMER (PVR_CONDITIONS_START + 14)
+#define PVR_HAS_NONRECORDING_RADIO_TIMER (PVR_CONDITIONS_START + 15)
+#define PVR_IS_PLAYING_EPGTAG (PVR_CONDITIONS_START + 16)
+#define PVR_CAN_RECORD_PLAYING_CHANNEL (PVR_CONDITIONS_START + 17)
+#define PVR_IS_RECORDING_PLAYING_CHANNEL (PVR_CONDITIONS_START + 18)
+#define PVR_IS_PLAYING_ACTIVE_RECORDING (PVR_CONDITIONS_START + 19)
+#define PVR_CONDITIONS_END PVR_IS_PLAYING_ACTIVE_RECORDING
+
+#define PVR_STRINGS_START 1200
+#define PVR_NEXT_RECORDING_CHANNEL (PVR_STRINGS_START)
+#define PVR_NEXT_RECORDING_CHAN_ICO (PVR_STRINGS_START + 1)
+#define PVR_NEXT_RECORDING_DATETIME (PVR_STRINGS_START + 2)
+#define PVR_NEXT_RECORDING_TITLE (PVR_STRINGS_START + 3)
+#define PVR_NOW_RECORDING_CHANNEL (PVR_STRINGS_START + 4)
+#define PVR_NOW_RECORDING_CHAN_ICO (PVR_STRINGS_START + 5)
+#define PVR_NOW_RECORDING_DATETIME (PVR_STRINGS_START + 6)
+#define PVR_NOW_RECORDING_TITLE (PVR_STRINGS_START + 7)
+#define PVR_BACKEND_NAME (PVR_STRINGS_START + 8)
+#define PVR_BACKEND_VERSION (PVR_STRINGS_START + 9)
+#define PVR_BACKEND_HOST (PVR_STRINGS_START + 10)
+#define PVR_BACKEND_DISKSPACE (PVR_STRINGS_START + 11)
+#define PVR_BACKEND_CHANNELS (PVR_STRINGS_START + 12)
+#define PVR_BACKEND_TIMERS (PVR_STRINGS_START + 13)
+#define PVR_BACKEND_RECORDINGS (PVR_STRINGS_START + 14)
+#define PVR_BACKEND_DELETED_RECORDINGS (PVR_STRINGS_START + 15)
+#define PVR_BACKEND_NUMBER (PVR_STRINGS_START + 16)
+#define PVR_TOTAL_DISKSPACE (PVR_STRINGS_START + 17)
+#define PVR_NEXT_TIMER (PVR_STRINGS_START + 18)
+#define PVR_EPG_EVENT_DURATION (PVR_STRINGS_START + 19)
+#define PVR_EPG_EVENT_ELAPSED_TIME (PVR_STRINGS_START + 20)
+#define PVR_EPG_EVENT_PROGRESS (PVR_STRINGS_START + 21)
+#define PVR_ACTUAL_STREAM_CLIENT (PVR_STRINGS_START + 22)
+#define PVR_ACTUAL_STREAM_DEVICE (PVR_STRINGS_START + 23)
+#define PVR_ACTUAL_STREAM_STATUS (PVR_STRINGS_START + 24)
+#define PVR_ACTUAL_STREAM_SIG (PVR_STRINGS_START + 25)
+#define PVR_ACTUAL_STREAM_SNR (PVR_STRINGS_START + 26)
+#define PVR_ACTUAL_STREAM_SIG_PROGR (PVR_STRINGS_START + 27)
+#define PVR_ACTUAL_STREAM_SNR_PROGR (PVR_STRINGS_START + 28)
+#define PVR_ACTUAL_STREAM_BER (PVR_STRINGS_START + 29)
+#define PVR_ACTUAL_STREAM_UNC (PVR_STRINGS_START + 30)
+#define PVR_ACTUAL_STREAM_CRYPTION (PVR_STRINGS_START + 34)
+#define PVR_ACTUAL_STREAM_SERVICE (PVR_STRINGS_START + 35)
+#define PVR_ACTUAL_STREAM_MUX (PVR_STRINGS_START + 36)
+#define PVR_ACTUAL_STREAM_PROVIDER (PVR_STRINGS_START + 37)
+#define PVR_BACKEND_DISKSPACE_PROGR (PVR_STRINGS_START + 38)
+#define PVR_TIMESHIFT_START_TIME (PVR_STRINGS_START + 39)
+#define PVR_TIMESHIFT_END_TIME (PVR_STRINGS_START + 40)
+#define PVR_TIMESHIFT_PLAY_TIME (PVR_STRINGS_START + 41)
+#define PVR_TIMESHIFT_PROGRESS (PVR_STRINGS_START + 42)
+#define PVR_TV_NOW_RECORDING_TITLE (PVR_STRINGS_START + 43)
+#define PVR_TV_NOW_RECORDING_CHANNEL (PVR_STRINGS_START + 44)
+#define PVR_TV_NOW_RECORDING_CHAN_ICO (PVR_STRINGS_START + 45)
+#define PVR_TV_NOW_RECORDING_DATETIME (PVR_STRINGS_START + 46)
+#define PVR_TV_NEXT_RECORDING_TITLE (PVR_STRINGS_START + 47)
+#define PVR_TV_NEXT_RECORDING_CHANNEL (PVR_STRINGS_START + 48)
+#define PVR_TV_NEXT_RECORDING_CHAN_ICO (PVR_STRINGS_START + 49)
+#define PVR_TV_NEXT_RECORDING_DATETIME (PVR_STRINGS_START + 50)
+#define PVR_RADIO_NOW_RECORDING_TITLE (PVR_STRINGS_START + 51)
+#define PVR_RADIO_NOW_RECORDING_CHANNEL (PVR_STRINGS_START + 52)
+#define PVR_RADIO_NOW_RECORDING_CHAN_ICO (PVR_STRINGS_START + 53)
+#define PVR_RADIO_NOW_RECORDING_DATETIME (PVR_STRINGS_START + 54)
+#define PVR_RADIO_NEXT_RECORDING_TITLE (PVR_STRINGS_START + 55)
+#define PVR_RADIO_NEXT_RECORDING_CHANNEL (PVR_STRINGS_START + 56)
+#define PVR_RADIO_NEXT_RECORDING_CHAN_ICO (PVR_STRINGS_START + 57)
+#define PVR_RADIO_NEXT_RECORDING_DATETIME (PVR_STRINGS_START + 58)
+#define PVR_CHANNEL_NUMBER_INPUT (PVR_STRINGS_START + 59)
+#define PVR_EPG_EVENT_REMAINING_TIME (PVR_STRINGS_START + 60)
+#define PVR_EPG_EVENT_FINISH_TIME (PVR_STRINGS_START + 61)
+#define PVR_TIMESHIFT_OFFSET (PVR_STRINGS_START + 62)
+#define PVR_EPG_EVENT_SEEK_TIME (PVR_STRINGS_START + 63)
+#define PVR_TIMESHIFT_PROGRESS_PLAY_POS (PVR_STRINGS_START + 64)
+#define PVR_TIMESHIFT_PROGRESS_DURATION (PVR_STRINGS_START + 65)
+#define PVR_TIMESHIFT_PROGRESS_EPG_START (PVR_STRINGS_START + 66)
+#define PVR_TIMESHIFT_PROGRESS_EPG_END (PVR_STRINGS_START + 67)
+#define PVR_TIMESHIFT_PROGRESS_BUFFER_START (PVR_STRINGS_START + 68)
+#define PVR_TIMESHIFT_PROGRESS_BUFFER_END (PVR_STRINGS_START + 69)
+#define PVR_TIMESHIFT_PROGRESS_START_TIME (PVR_STRINGS_START + 70)
+#define PVR_TIMESHIFT_PROGRESS_END_TIME (PVR_STRINGS_START + 71)
+#define PVR_EPG_EVENT_ICON (PVR_STRINGS_START + 72)
+#define PVR_TIMESHIFT_SEEKBAR (PVR_STRINGS_START + 73)
+#define PVR_BACKEND_PROVIDERS (PVR_STRINGS_START + 74)
+#define PVR_BACKEND_CHANNEL_GROUPS (PVR_STRINGS_START + 75)
+#define PVR_STRINGS_END PVR_BACKEND_CHANNEL_GROUPS
+
+#define RDS_DATA_START 1400
+#define RDS_HAS_RDS (RDS_DATA_START)
+#define RDS_HAS_RADIOTEXT (RDS_DATA_START + 1)
+#define RDS_HAS_RADIOTEXT_PLUS (RDS_DATA_START + 2)
+#define RDS_GET_RADIOTEXT_LINE (RDS_DATA_START + 3)
+#define RDS_TITLE (RDS_DATA_START + 4)
+#define RDS_BAND (RDS_DATA_START + 5)
+#define RDS_ARTIST (RDS_DATA_START + 6)
+#define RDS_COMPOSER (RDS_DATA_START + 7)
+#define RDS_CONDUCTOR (RDS_DATA_START + 8)
+#define RDS_ALBUM (RDS_DATA_START + 9)
+#define RDS_ALBUM_TRACKNUMBER (RDS_DATA_START + 10)
+#define RDS_GET_RADIO_STYLE (RDS_DATA_START + 11)
+#define RDS_COMMENT (RDS_DATA_START + 12)
+#define RDS_INFO_NEWS (RDS_DATA_START + 13)
+#define RDS_INFO_NEWS_LOCAL (RDS_DATA_START + 14)
+#define RDS_INFO_STOCK (RDS_DATA_START + 15)
+#define RDS_INFO_STOCK_SIZE (RDS_DATA_START + 16)
+#define RDS_INFO_SPORT (RDS_DATA_START + 17)
+#define RDS_INFO_SPORT_SIZE (RDS_DATA_START + 18)
+#define RDS_INFO_LOTTERY (RDS_DATA_START + 19)
+#define RDS_INFO_LOTTERY_SIZE (RDS_DATA_START + 20)
+#define RDS_INFO_WEATHER (RDS_DATA_START + 21)
+#define RDS_INFO_WEATHER_SIZE (RDS_DATA_START + 22)
+#define RDS_INFO_CINEMA (RDS_DATA_START + 23)
+#define RDS_INFO_CINEMA_SIZE (RDS_DATA_START + 24)
+#define RDS_INFO_HOROSCOPE (RDS_DATA_START + 25)
+#define RDS_INFO_HOROSCOPE_SIZE (RDS_DATA_START + 26)
+#define RDS_INFO_OTHER (RDS_DATA_START + 27)
+#define RDS_INFO_OTHER_SIZE (RDS_DATA_START + 28)
+#define RDS_PROG_STATION (RDS_DATA_START + 29)
+#define RDS_PROG_NOW (RDS_DATA_START + 30)
+#define RDS_PROG_NEXT (RDS_DATA_START + 31)
+#define RDS_PROG_HOST (RDS_DATA_START + 32)
+#define RDS_PROG_EDIT_STAFF (RDS_DATA_START + 33)
+#define RDS_PROG_HOMEPAGE (RDS_DATA_START + 34)
+#define RDS_PROG_STYLE (RDS_DATA_START + 35)
+#define RDS_PHONE_HOTLINE (RDS_DATA_START + 36)
+#define RDS_PHONE_STUDIO (RDS_DATA_START + 37)
+#define RDS_SMS_STUDIO (RDS_DATA_START + 38)
+#define RDS_EMAIL_HOTLINE (RDS_DATA_START + 39)
+#define RDS_EMAIL_STUDIO (RDS_DATA_START + 40)
+#define RDS_HAS_HOTLINE_DATA (RDS_DATA_START + 41)
+#define RDS_HAS_STUDIO_DATA (RDS_DATA_START + 42)
+#define RDS_AUDIO_LANG (RDS_DATA_START + 43)
+#define RDS_CHANNEL_COUNTRY (RDS_DATA_START + 44)
+#define RDS_DATA_END RDS_CHANNEL_COUNTRY
+
+#define PLAYER_PROCESS 1500
+#define PLAYER_PROCESS_VIDEODECODER (PLAYER_PROCESS)
+#define PLAYER_PROCESS_DEINTMETHOD (PLAYER_PROCESS + 1)
+#define PLAYER_PROCESS_PIXELFORMAT (PLAYER_PROCESS + 2)
+#define PLAYER_PROCESS_VIDEOWIDTH (PLAYER_PROCESS + 3)
+#define PLAYER_PROCESS_VIDEOHEIGHT (PLAYER_PROCESS + 4)
+#define PLAYER_PROCESS_VIDEOFPS (PLAYER_PROCESS + 5)
+#define PLAYER_PROCESS_VIDEODAR (PLAYER_PROCESS + 6)
+#define PLAYER_PROCESS_VIDEOHWDECODER (PLAYER_PROCESS + 7)
+#define PLAYER_PROCESS_AUDIODECODER (PLAYER_PROCESS + 8)
+#define PLAYER_PROCESS_AUDIOCHANNELS (PLAYER_PROCESS + 9)
+#define PLAYER_PROCESS_AUDIOSAMPLERATE (PLAYER_PROCESS + 10)
+#define PLAYER_PROCESS_AUDIOBITSPERSAMPLE (PLAYER_PROCESS + 11)
+#define PLAYER_PROCESS_VIDEOSCANTYPE (PLAYER_PROCESS + 12)
+
+#define ADDON_INFOS 1600
+#define ADDON_SETTING_STRING (ADDON_INFOS)
+#define ADDON_SETTING_BOOL (ADDON_INFOS + 1)
+#define ADDON_SETTING_INT (ADDON_INFOS + 2)
+
+#define WINDOW_PROPERTY 9993
+#define WINDOW_IS_VISIBLE 9995
+#define WINDOW_NEXT 9996
+#define WINDOW_PREVIOUS 9997
+#define WINDOW_IS_MEDIA 9998
+#define WINDOW_IS_ACTIVE 9999
+#define WINDOW_IS 10000
+#define WINDOW_IS_DIALOG_TOPMOST 10001
+#define WINDOW_IS_MODAL_DIALOG_TOPMOST 10002
+
+#define CONTROL_GET_LABEL 29996
+#define CONTROL_IS_ENABLED 29997
+#define CONTROL_IS_VISIBLE 29998
+#define CONTROL_GROUP_HAS_FOCUS 29999
+#define CONTROL_HAS_FOCUS 30000
+
+#define LISTITEM_START 35000
+#define LISTITEM_THUMB (LISTITEM_START)
+#define LISTITEM_LABEL (LISTITEM_START + 1)
+#define LISTITEM_TITLE (LISTITEM_START + 2)
+#define LISTITEM_TRACKNUMBER (LISTITEM_START + 3)
+#define LISTITEM_ARTIST (LISTITEM_START + 4)
+#define LISTITEM_ALBUM (LISTITEM_START + 5)
+#define LISTITEM_YEAR (LISTITEM_START + 6)
+#define LISTITEM_GENRE (LISTITEM_START + 7)
+#define LISTITEM_ICON (LISTITEM_START + 8)
+#define LISTITEM_DIRECTOR (LISTITEM_START + 9)
+#define LISTITEM_OVERLAY (LISTITEM_START + 10)
+#define LISTITEM_LABEL2 (LISTITEM_START + 11)
+#define LISTITEM_FILENAME (LISTITEM_START + 12)
+#define LISTITEM_DATE (LISTITEM_START + 13)
+#define LISTITEM_SIZE (LISTITEM_START + 14)
+#define LISTITEM_RATING (LISTITEM_START + 15)
+#define LISTITEM_PROGRAM_COUNT (LISTITEM_START + 16)
+#define LISTITEM_DURATION (LISTITEM_START + 17)
+#define LISTITEM_ISPLAYING (LISTITEM_START + 18)
+#define LISTITEM_ISSELECTED (LISTITEM_START + 19)
+#define LISTITEM_PLOT (LISTITEM_START + 20)
+#define LISTITEM_PLOT_OUTLINE (LISTITEM_START + 21)
+#define LISTITEM_EPISODE (LISTITEM_START + 22)
+#define LISTITEM_SEASON (LISTITEM_START + 23)
+#define LISTITEM_TVSHOW (LISTITEM_START + 24)
+#define LISTITEM_PREMIERED (LISTITEM_START + 25)
+#define LISTITEM_COMMENT (LISTITEM_START + 26)
+#define LISTITEM_ACTUAL_ICON (LISTITEM_START + 27)
+#define LISTITEM_PATH (LISTITEM_START + 28)
+#define LISTITEM_PICTURE_PATH (LISTITEM_START + 29)
+
+#define LISTITEM_PICTURE_START (LISTITEM_START + 30)
+#define LISTITEM_PICTURE_RESOLUTION (LISTITEM_PICTURE_START) // => SLIDESHOW_RESOLUTION
+#define LISTITEM_PICTURE_LONGDATE (LISTITEM_START + 31) // => SLIDESHOW_EXIF_LONG_DATE
+#define LISTITEM_PICTURE_LONGDATETIME (LISTITEM_START + 32) // => SLIDESHOW_EXIF_LONG_DATE_TIME
+#define LISTITEM_PICTURE_DATE (LISTITEM_START + 33) // => SLIDESHOW_EXIF_DATE
+#define LISTITEM_PICTURE_DATETIME (LISTITEM_START + 34) // => SLIDESHOW_EXIF_DATE_TIME
+#define LISTITEM_PICTURE_COMMENT (LISTITEM_START + 35) // => SLIDESHOW_COMMENT
+#define LISTITEM_PICTURE_CAPTION (LISTITEM_START + 36) // => SLIDESHOW_IPTC_CAPTION
+#define LISTITEM_PICTURE_DESC (LISTITEM_START + 37) // => SLIDESHOW_EXIF_DESCRIPTION
+#define LISTITEM_PICTURE_KEYWORDS (LISTITEM_START + 38) // => SLIDESHOW_IPTC_KEYWORDS
+#define LISTITEM_PICTURE_CAM_MAKE (LISTITEM_START + 39) // => SLIDESHOW_EXIF_CAMERA_MAKE
+#define LISTITEM_PICTURE_CAM_MODEL (LISTITEM_START + 40) // => SLIDESHOW_EXIF_CAMERA_MODEL
+#define LISTITEM_PICTURE_APERTURE (LISTITEM_START + 41) // => SLIDESHOW_EXIF_APERTURE
+#define LISTITEM_PICTURE_FOCAL_LEN (LISTITEM_START + 42) // => SLIDESHOW_EXIF_FOCAL_LENGTH
+#define LISTITEM_PICTURE_FOCUS_DIST (LISTITEM_START + 43) // => SLIDESHOW_EXIF_FOCUS_DIST
+#define LISTITEM_PICTURE_EXP_MODE (LISTITEM_START + 44) // => SLIDESHOW_EXIF_EXPOSURE_MODE
+#define LISTITEM_PICTURE_EXP_TIME (LISTITEM_START + 45) // => SLIDESHOW_EXIF_EXPOSURE_TIME
+#define LISTITEM_PICTURE_ISO (LISTITEM_START + 46) // => SLIDESHOW_EXIF_ISO_EQUIV
+#define LISTITEM_PICTURE_AUTHOR (LISTITEM_START + 47) // => SLIDESHOW_IPTC_AUTHOR
+#define LISTITEM_PICTURE_BYLINE (LISTITEM_START + 48) // => SLIDESHOW_IPTC_BYLINE
+#define LISTITEM_PICTURE_BYLINE_TITLE (LISTITEM_START + 49) // => SLIDESHOW_IPTC_BYLINE_TITLE
+#define LISTITEM_PICTURE_CATEGORY (LISTITEM_START + 50) // => SLIDESHOW_IPTC_CATEGORY
+#define LISTITEM_PICTURE_CCD_WIDTH (LISTITEM_START + 51) // => SLIDESHOW_EXIF_CCD_WIDTH
+#define LISTITEM_PICTURE_CITY (LISTITEM_START + 52) // => SLIDESHOW_IPTC_CITY
+#define LISTITEM_PICTURE_URGENCY (LISTITEM_START + 53) // => SLIDESHOW_IPTC_URGENCY
+#define LISTITEM_PICTURE_COPYRIGHT_NOTICE (LISTITEM_START + 54) // => SLIDESHOW_IPTC_COPYRIGHT_NOTICE
+#define LISTITEM_PICTURE_COUNTRY (LISTITEM_START + 55) // => SLIDESHOW_IPTC_COUNTRY
+#define LISTITEM_PICTURE_COUNTRY_CODE (LISTITEM_START + 56) // => SLIDESHOW_IPTC_COUNTRY_CODE
+#define LISTITEM_PICTURE_CREDIT (LISTITEM_START + 57) // => SLIDESHOW_IPTC_CREDIT
+#define LISTITEM_PICTURE_IPTCDATE (LISTITEM_START + 58) // => SLIDESHOW_IPTC_DATE
+#define LISTITEM_PICTURE_DIGITAL_ZOOM (LISTITEM_START + 59) // => SLIDESHOW_EXIF_DIGITAL_ZOOM
+#define LISTITEM_PICTURE_EXPOSURE (LISTITEM_START + 60) // => SLIDESHOW_EXIF_EXPOSURE
+#define LISTITEM_PICTURE_EXPOSURE_BIAS (LISTITEM_START + 61) // => SLIDESHOW_EXIF_EXPOSURE_BIAS
+#define LISTITEM_PICTURE_FLASH_USED (LISTITEM_START + 62) // => SLIDESHOW_EXIF_FLASH_USED
+#define LISTITEM_PICTURE_HEADLINE (LISTITEM_START + 63) // => SLIDESHOW_IPTC_HEADLINE
+#define LISTITEM_PICTURE_COLOUR (LISTITEM_START + 64) // => SLIDESHOW_COLOUR
+#define LISTITEM_PICTURE_LIGHT_SOURCE (LISTITEM_START + 65) // => SLIDESHOW_EXIF_LIGHT_SOURCE
+#define LISTITEM_PICTURE_METERING_MODE (LISTITEM_START + 66) // => SLIDESHOW_EXIF_METERING_MODE
+#define LISTITEM_PICTURE_OBJECT_NAME (LISTITEM_START + 67) // => SLIDESHOW_IPTC_OBJECT_NAME
+#define LISTITEM_PICTURE_ORIENTATION (LISTITEM_START + 68) // => SLIDESHOW_EXIF_ORIENTATION
+#define LISTITEM_PICTURE_PROCESS (LISTITEM_START + 69) // => SLIDESHOW_PROCESS
+#define LISTITEM_PICTURE_REF_SERVICE (LISTITEM_START + 70) // => SLIDESHOW_IPTC_REF_SERVICE
+#define LISTITEM_PICTURE_SOURCE (LISTITEM_START + 71) // => SLIDESHOW_IPTC_SOURCE
+#define LISTITEM_PICTURE_SPEC_INSTR (LISTITEM_START + 72) // => SLIDESHOW_IPTC_SPEC_INSTR
+#define LISTITEM_PICTURE_STATE (LISTITEM_START + 73) // => SLIDESHOW_IPTC_STATE
+#define LISTITEM_PICTURE_SUP_CATEGORIES (LISTITEM_START + 74) // => SLIDESHOW_IPTC_SUP_CATEGORIES
+#define LISTITEM_PICTURE_TX_REFERENCE (LISTITEM_START + 75) // => SLIDESHOW_IPTC_TX_REFERENCE
+#define LISTITEM_PICTURE_WHITE_BALANCE (LISTITEM_START + 76) // => SLIDESHOW_EXIF_WHITE_BALANCE
+#define LISTITEM_PICTURE_IMAGETYPE (LISTITEM_START + 77) // => SLIDESHOW_IPTC_IMAGETYPE
+#define LISTITEM_PICTURE_SUBLOCATION (LISTITEM_START + 78) // => SLIDESHOW_IPTC_SUBLOCATION
+#define LISTITEM_PICTURE_TIMECREATED (LISTITEM_START + 79) // => SLIDESHOW_IPTC_TIMECREATED
+#define LISTITEM_PICTURE_GPS_LAT (LISTITEM_START + 80) // => SLIDESHOW_EXIF_GPS_LATITUDE
+#define LISTITEM_PICTURE_GPS_LON (LISTITEM_START + 81) // => SLIDESHOW_EXIF_GPS_LONGITUDE
+#define LISTITEM_PICTURE_GPS_ALT (LISTITEM_START + 82) // => SLIDESHOW_EXIF_GPS_ALTITUDE
+#define LISTITEM_PICTURE_END (LISTITEM_PICTURE_GPS_ALT)
+
+#define LISTITEM_STUDIO (LISTITEM_START + 83)
+#define LISTITEM_MPAA (LISTITEM_START + 84)
+#define LISTITEM_CAST (LISTITEM_START + 85)
+#define LISTITEM_CAST_AND_ROLE (LISTITEM_START + 86)
+#define LISTITEM_WRITER (LISTITEM_START + 87)
+#define LISTITEM_TAGLINE (LISTITEM_START + 88)
+#define LISTITEM_TOP250 (LISTITEM_START + 89)
+#define LISTITEM_RATING_AND_VOTES (LISTITEM_START + 90)
+#define LISTITEM_TRAILER (LISTITEM_START + 91)
+#define LISTITEM_APPEARANCES (LISTITEM_START + 92)
+#define LISTITEM_FILENAME_AND_PATH (LISTITEM_START + 93)
+#define LISTITEM_SORT_LETTER (LISTITEM_START + 94)
+#define LISTITEM_ALBUM_ARTIST (LISTITEM_START + 95)
+#define LISTITEM_FOLDERNAME (LISTITEM_START + 96)
+#define LISTITEM_VIDEO_CODEC (LISTITEM_START + 97)
+#define LISTITEM_VIDEO_RESOLUTION (LISTITEM_START + 98)
+#define LISTITEM_VIDEO_ASPECT (LISTITEM_START + 99)
+#define LISTITEM_AUDIO_CODEC (LISTITEM_START + 100)
+#define LISTITEM_AUDIO_CHANNELS (LISTITEM_START + 101)
+#define LISTITEM_AUDIO_LANGUAGE (LISTITEM_START + 102)
+#define LISTITEM_SUBTITLE_LANGUAGE (LISTITEM_START + 103)
+#define LISTITEM_IS_FOLDER (LISTITEM_START + 104)
+#define LISTITEM_ORIGINALTITLE (LISTITEM_START + 105)
+#define LISTITEM_COUNTRY (LISTITEM_START + 106)
+#define LISTITEM_PLAYCOUNT (LISTITEM_START + 107)
+#define LISTITEM_LASTPLAYED (LISTITEM_START + 108)
+#define LISTITEM_FOLDERPATH (LISTITEM_START + 109)
+#define LISTITEM_DISC_NUMBER (LISTITEM_START + 110)
+#define LISTITEM_FILE_EXTENSION (LISTITEM_START + 111)
+#define LISTITEM_IS_RESUMABLE (LISTITEM_START + 112)
+#define LISTITEM_PERCENT_PLAYED (LISTITEM_START + 113)
+#define LISTITEM_DATE_ADDED (LISTITEM_START + 114)
+#define LISTITEM_DBTYPE (LISTITEM_START + 115)
+#define LISTITEM_DBID (LISTITEM_START + 116)
+#define LISTITEM_ART (LISTITEM_START + 117)
+#define LISTITEM_STARTTIME (LISTITEM_START + 118)
+#define LISTITEM_ENDTIME (LISTITEM_START + 119)
+#define LISTITEM_STARTDATE (LISTITEM_START + 120)
+#define LISTITEM_ENDDATE (LISTITEM_START + 121)
+#define LISTITEM_NEXT_TITLE (LISTITEM_START + 122)
+#define LISTITEM_NEXT_GENRE (LISTITEM_START + 123)
+#define LISTITEM_NEXT_PLOT (LISTITEM_START + 124)
+#define LISTITEM_NEXT_PLOT_OUTLINE (LISTITEM_START + 125)
+#define LISTITEM_NEXT_STARTTIME (LISTITEM_START + 126)
+#define LISTITEM_NEXT_ENDTIME (LISTITEM_START + 127)
+#define LISTITEM_NEXT_STARTDATE (LISTITEM_START + 128)
+#define LISTITEM_NEXT_ENDDATE (LISTITEM_START + 129)
+#define LISTITEM_NEXT_DURATION (LISTITEM_START + 130)
+#define LISTITEM_CHANNEL_NAME (LISTITEM_START + 131)
+#define LISTITEM_CHANNEL_GROUP (LISTITEM_START + 132)
+#define LISTITEM_HASTIMER (LISTITEM_START + 133)
+#define LISTITEM_ISRECORDING (LISTITEM_START + 134)
+#define LISTITEM_ISENCRYPTED (LISTITEM_START + 135)
+#define LISTITEM_PARENTAL_RATING (LISTITEM_START + 136)
+#define LISTITEM_PROGRESS (LISTITEM_START + 137)
+#define LISTITEM_HAS_EPG (LISTITEM_START + 138)
+#define LISTITEM_VOTES (LISTITEM_START + 139)
+#define LISTITEM_STEREOSCOPIC_MODE (LISTITEM_START + 140)
+#define LISTITEM_IS_STEREOSCOPIC (LISTITEM_START + 141)
+#define LISTITEM_INPROGRESS (LISTITEM_START + 142)
+#define LISTITEM_HASRECORDING (LISTITEM_START + 143)
+#define LISTITEM_HASREMINDER (LISTITEM_START + 144)
+#define LISTITEM_CHANNEL_NUMBER (LISTITEM_START + 145)
+#define LISTITEM_IMDBNUMBER (LISTITEM_START + 146)
+#define LISTITEM_EPISODENAME (LISTITEM_START + 147)
+#define LISTITEM_IS_COLLECTION (LISTITEM_START + 148)
+#define LISTITEM_HASTIMERSCHEDULE (LISTITEM_START + 149)
+#define LISTITEM_TIMERTYPE (LISTITEM_START + 150)
+#define LISTITEM_EPG_EVENT_TITLE (LISTITEM_START + 151)
+#define LISTITEM_DATETIME (LISTITEM_START + 152)
+#define LISTITEM_USER_RATING (LISTITEM_START + 153)
+#define LISTITEM_TAG (LISTITEM_START + 154)
+#define LISTITEM_SET (LISTITEM_START + 155)
+#define LISTITEM_SETID (LISTITEM_START + 156)
+#define LISTITEM_IS_PARENTFOLDER (LISTITEM_START + 157)
+#define LISTITEM_MOOD (LISTITEM_START + 158)
+#define LISTITEM_CONTRIBUTORS (LISTITEM_START + 159)
+#define LISTITEM_CONTRIBUTOR_AND_ROLE (LISTITEM_START + 160)
+#define LISTITEM_TIMERISACTIVE (LISTITEM_START + 161)
+#define LISTITEM_TIMERHASCONFLICT (LISTITEM_START + 162)
+#define LISTITEM_TIMERHASERROR (LISTITEM_START + 163)
+
+#define LISTITEM_ADDON_NAME (LISTITEM_START + 164)
+#define LISTITEM_ADDON_VERSION (LISTITEM_START + 165)
+#define LISTITEM_ADDON_CREATOR (LISTITEM_START + 166)
+#define LISTITEM_ADDON_SUMMARY (LISTITEM_START + 167)
+#define LISTITEM_ADDON_DESCRIPTION (LISTITEM_START + 168)
+#define LISTITEM_ADDON_DISCLAIMER (LISTITEM_START + 169)
+#define LISTITEM_ADDON_BROKEN (LISTITEM_START + 170)
+#define LISTITEM_ADDON_LIFECYCLE_TYPE (LISTITEM_START + 171)
+#define LISTITEM_ADDON_LIFECYCLE_DESC (LISTITEM_START + 172)
+#define LISTITEM_ADDON_TYPE (LISTITEM_START + 173)
+#define LISTITEM_ADDON_INSTALL_DATE (LISTITEM_START + 174)
+#define LISTITEM_ADDON_LAST_UPDATED (LISTITEM_START + 175)
+#define LISTITEM_ADDON_LAST_USED (LISTITEM_START + 176)
+#define LISTITEM_STATUS (LISTITEM_START + 177)
+#define LISTITEM_ENDTIME_RESUME (LISTITEM_START + 178)
+#define LISTITEM_ADDON_ORIGIN (LISTITEM_START + 179)
+#define LISTITEM_ADDON_NEWS (LISTITEM_START + 180)
+#define LISTITEM_ADDON_SIZE (LISTITEM_START + 181)
+#define LISTITEM_EXPIRATION_DATE (LISTITEM_START + 182)
+#define LISTITEM_EXPIRATION_TIME (LISTITEM_START + 183)
+#define LISTITEM_PROPERTY (LISTITEM_START + 184)
+#define LISTITEM_EPG_EVENT_ICON (LISTITEM_START + 185)
+#define LISTITEM_HASREMINDERRULE (LISTITEM_START + 186)
+#define LISTITEM_HASARCHIVE (LISTITEM_START + 187)
+#define LISTITEM_ISPLAYABLE (LISTITEM_START + 188)
+#define LISTITEM_FILENAME_NO_EXTENSION (LISTITEM_START + 189)
+#define LISTITEM_CURRENTITEM (LISTITEM_START + 190)
+#define LISTITEM_IS_NEW (LISTITEM_START + 191)
+#define LISTITEM_DISC_TITLE (LISTITEM_START + 192)
+#define LISTITEM_IS_BOXSET (LISTITEM_START + 193)
+#define LISTITEM_TOTALDISCS (LISTITEM_START + 194)
+#define LISTITEM_RELEASEDATE (LISTITEM_START + 195)
+#define LISTITEM_ORIGINALDATE (LISTITEM_START + 196)
+#define LISTITEM_BPM (LISTITEM_START + 197)
+#define LISTITEM_UNIQUEID (LISTITEM_START + 198)
+#define LISTITEM_BITRATE (LISTITEM_START + 199)
+#define LISTITEM_SAMPLERATE (LISTITEM_START + 200)
+#define LISTITEM_MUSICCHANNELS (LISTITEM_START + 201)
+#define LISTITEM_IS_PREMIERE (LISTITEM_START + 202)
+#define LISTITEM_IS_FINALE (LISTITEM_START + 203)
+#define LISTITEM_IS_LIVE (LISTITEM_START + 204)
+#define LISTITEM_TVSHOWDBID (LISTITEM_START + 205)
+#define LISTITEM_ALBUMSTATUS (LISTITEM_START + 206)
+#define LISTITEM_ISAUTOUPDATEABLE (LISTITEM_START + 207)
+#define LISTITEM_VIDEO_HDR_TYPE (LISTITEM_START + 208)
+
+#define LISTITEM_END (LISTITEM_START + 2500)
+
+#define CONDITIONAL_LABEL_START (LISTITEM_END + 1) // 37501
+#define CONDITIONAL_LABEL_END 39999
+
+// the multiple information vector
+#define MULTI_INFO_START 40000
+#define MULTI_INFO_END 99999
+#define COMBINED_VALUES_START 100000
+
+// listitem info Flags
+// Stored in the top 8 bits of GUIInfo::m_data1
+// therefore we only have room for 8 flags
+#define INFOFLAG_LISTITEM_WRAP (static_cast<uint32_t>(1 << 25)) // Wrap ListItem lookups
+#define INFOFLAG_LISTITEM_POSITION (static_cast<uint32_t>(1 << 26)) // ListItem lookups based on cursor position
+#define INFOFLAG_LISTITEM_ABSOLUTE (static_cast<uint32_t>(1 << 27)) // Absolute ListItem lookups
+#define INFOFLAG_LISTITEM_NOWRAP (static_cast<uint32_t>(1 << 28)) // Do not wrap ListItem lookups
+#define INFOFLAG_LISTITEM_CONTAINER (static_cast<uint32_t>(1 << 29)) // Lookup the item in given container
+// clang-format on
diff --git a/xbmc/guilib/guiinfo/GUIInfoProvider.h b/xbmc/guilib/guiinfo/GUIInfoProvider.h
new file mode 100644
index 0000000..a03159b
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoProvider.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/VideoPlayer/Interface/StreamInfo.h"
+#include "guilib/guiinfo/IGUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CGUIInfoProvider : public IGUIInfoProvider
+{
+public:
+ CGUIInfoProvider() = default;
+ ~CGUIInfoProvider() override = default;
+
+ bool GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback) override
+ {
+ return false;
+ }
+
+ void UpdateAVInfo(const AudioStreamInfo& audioInfo, const VideoStreamInfo& videoInfo, const SubtitleStreamInfo& subtitleInfo) override
+ { m_audioInfo = audioInfo, m_videoInfo = videoInfo, m_subtitleInfo = subtitleInfo; }
+
+protected:
+ VideoStreamInfo m_videoInfo;
+ AudioStreamInfo m_audioInfo;
+ SubtitleStreamInfo m_subtitleInfo;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GUIInfoProviders.cpp b/xbmc/guilib/guiinfo/GUIInfoProviders.cpp
new file mode 100644
index 0000000..274318e
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoProviders.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GUIInfoProviders.h"
+
+#include "guilib/guiinfo/IGUIInfoProvider.h"
+
+#include <algorithm>
+
+using namespace KODI::GUILIB::GUIINFO;
+
+CGUIInfoProviders::CGUIInfoProviders()
+{
+ RegisterProvider(&m_guiControlsGUIInfo);
+ RegisterProvider(&m_videoGUIInfo); // Note: video info provider must be registered before music info provider,
+ // because of music videos having both a video info tag and a music info tag
+ // and video info tag always has to be evaluated first.
+ RegisterProvider(&m_musicGUIInfo);
+ RegisterProvider(&m_picturesGUIInfo);
+ RegisterProvider(&m_playerGUIInfo);
+ RegisterProvider(&m_libraryGUIInfo);
+ RegisterProvider(&m_addonsGUIInfo);
+ RegisterProvider(&m_weatherGUIInfo);
+ RegisterProvider(&m_gamesGUIInfo);
+ RegisterProvider(&m_systemGUIInfo);
+ RegisterProvider(&m_visualisationGUIInfo);
+ RegisterProvider(&m_skinGUIInfo);
+}
+
+CGUIInfoProviders::~CGUIInfoProviders()
+{
+ UnregisterProvider(&m_skinGUIInfo);
+ UnregisterProvider(&m_visualisationGUIInfo);
+ UnregisterProvider(&m_systemGUIInfo);
+ UnregisterProvider(&m_gamesGUIInfo);
+ UnregisterProvider(&m_weatherGUIInfo);
+ UnregisterProvider(&m_addonsGUIInfo);
+ UnregisterProvider(&m_libraryGUIInfo);
+ UnregisterProvider(&m_playerGUIInfo);
+ UnregisterProvider(&m_picturesGUIInfo);
+ UnregisterProvider(&m_musicGUIInfo);
+ UnregisterProvider(&m_videoGUIInfo);
+ UnregisterProvider(&m_guiControlsGUIInfo);
+}
+
+void CGUIInfoProviders::RegisterProvider(IGUIInfoProvider *provider, bool bAppend /* = true */)
+{
+ auto it = std::find(m_providers.begin(), m_providers.end(), provider);
+ if (it == m_providers.end())
+ {
+ if (bAppend)
+ m_providers.emplace_back(provider);
+ else
+ m_providers.insert(m_providers.begin(), provider);
+ }
+}
+
+void CGUIInfoProviders::UnregisterProvider(IGUIInfoProvider *provider)
+{
+ auto it = std::find(m_providers.begin(), m_providers.end(), provider);
+ if (it != m_providers.end())
+ m_providers.erase(it);
+}
+
+bool CGUIInfoProviders::InitCurrentItem(CFileItem *item)
+{
+ bool bReturn = false;
+
+ for (const auto& provider : m_providers)
+ {
+ bReturn = provider->InitCurrentItem(item);
+ }
+ return bReturn;
+}
+
+bool CGUIInfoProviders::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ for (const auto& provider : m_providers)
+ {
+ if (provider->GetLabel(value, item, contextWindow, info, fallback))
+ return true;
+ }
+ for (const auto& provider : m_providers)
+ {
+ if (provider->GetFallbackLabel(value, item, contextWindow, info, fallback))
+ return true;
+ }
+ return false;
+}
+
+bool CGUIInfoProviders::GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const
+{
+ for (const auto& provider : m_providers)
+ {
+ if (provider->GetInt(value, item, contextWindow, info))
+ return true;
+ }
+ return false;
+}
+
+bool CGUIInfoProviders::GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const
+{
+ for (const auto& provider : m_providers)
+ {
+ if (provider->GetBool(value, item, contextWindow, info))
+ return true;
+ }
+ return false;
+}
+
+void CGUIInfoProviders::UpdateAVInfo(const AudioStreamInfo& audioInfo, const VideoStreamInfo& videoInfo, const SubtitleStreamInfo& subtitleInfo)
+{
+ for (const auto& provider : m_providers)
+ {
+ provider->UpdateAVInfo(audioInfo, videoInfo, subtitleInfo);
+ }
+}
diff --git a/xbmc/guilib/guiinfo/GUIInfoProviders.h b/xbmc/guilib/guiinfo/GUIInfoProviders.h
new file mode 100644
index 0000000..1aed710
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GUIInfoProviders.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/AddonsGUIInfo.h"
+#include "guilib/guiinfo/GUIControlsGUIInfo.h"
+#include "guilib/guiinfo/GamesGUIInfo.h"
+#include "guilib/guiinfo/LibraryGUIInfo.h"
+#include "guilib/guiinfo/MusicGUIInfo.h"
+#include "guilib/guiinfo/PicturesGUIInfo.h"
+#include "guilib/guiinfo/PlayerGUIInfo.h"
+#include "guilib/guiinfo/SkinGUIInfo.h"
+#include "guilib/guiinfo/SystemGUIInfo.h"
+#include "guilib/guiinfo/VideoGUIInfo.h"
+#include "guilib/guiinfo/VisualisationGUIInfo.h"
+#include "guilib/guiinfo/WeatherGUIInfo.h"
+
+#include <string>
+#include <vector>
+
+class CFileItem;
+class CGUIListItem;
+
+struct AudioStreamInfo;
+struct VideoStreamInfo;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+class IGUIInfoProvider;
+
+class CGUIInfoProviders
+{
+public:
+ CGUIInfoProviders();
+ virtual ~CGUIInfoProviders();
+
+ /*!
+ * @brief Register a guiinfo provider.
+ * @param provider The provider to register.
+ * @param bAppend True to append to the list of providers, false to insert before the first provider
+ */
+ void RegisterProvider(IGUIInfoProvider *provider, bool bAppend = true);
+
+ /*!
+ * @brief Unregister a guiinfo provider.
+ * @param provider The provider to unregister.
+ */
+ void UnregisterProvider(IGUIInfoProvider *provider);
+
+ /*!
+ * @brief Init a new current guiinfo manager item. Gets called whenever the active guiinfo manager item changes.
+ * @param item The new item.
+ * @return True if the item was inited by one of the providers, false otherwise.
+ */
+ bool InitCurrentItem(CFileItem *item);
+
+ /*!
+ * @brief Get a GUIInfoManager label string from one of the registered providers.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @param fallback A fallback value. Can be nullptr.
+ * @return True if the value was filled successfully by one of the providers, false otherwise.
+ */
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const;
+
+ /*!
+ * @brief Get a GUIInfoManager integer value from one of the registered providers.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @return True if the value was filled successfully by one of the providers, false otherwise.
+ */
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const;
+
+ /*!
+ * @brief Get a GUIInfoManager bool value from one of the registered providers.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @return True if the value was filled successfully by one of the providers, false otherwise.
+ */
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const;
+
+ /*!
+ * @brief Set new audio/video/subtitle stream info data at all registered providers.
+ * @param audioInfo New audio stream info.
+ * @param videoInfo New video stream info.
+ * @param subtitleInfo New subtitle stream info.
+ */
+ void UpdateAVInfo(const AudioStreamInfo& audioInfo, const VideoStreamInfo& videoInfo, const SubtitleStreamInfo& subtitleInfo);
+
+ /*!
+ * @brief Get the player guiinfo provider.
+ * @return The player guiinfo provider.
+ */
+ CPlayerGUIInfo& GetPlayerInfoProvider() { return m_playerGUIInfo; }
+
+ /*!
+ * @brief Get the system guiinfo provider.
+ * @return The system guiinfo provider.
+ */
+ CSystemGUIInfo& GetSystemInfoProvider() { return m_systemGUIInfo; }
+
+ /*!
+ * @brief Get the pictures guiinfo provider.
+ * @return The pictures guiinfo provider.
+ */
+ CPicturesGUIInfo& GetPicturesInfoProvider() { return m_picturesGUIInfo; }
+
+ /*!
+ * @brief Get the gui controls guiinfo provider.
+ * @return The gui controls guiinfo provider.
+ */
+ CGUIControlsGUIInfo& GetGUIControlsInfoProvider() { return m_guiControlsGUIInfo; }
+
+ /*!
+ * @brief Get the library guiinfo provider.
+ * @return The library guiinfo provider.
+ */
+ CLibraryGUIInfo& GetLibraryInfoProvider() { return m_libraryGUIInfo; }
+
+private:
+ std::vector<IGUIInfoProvider *> m_providers;
+
+ CAddonsGUIInfo m_addonsGUIInfo;
+ CGamesGUIInfo m_gamesGUIInfo;
+ CGUIControlsGUIInfo m_guiControlsGUIInfo;
+ CLibraryGUIInfo m_libraryGUIInfo;
+ CMusicGUIInfo m_musicGUIInfo;
+ CPicturesGUIInfo m_picturesGUIInfo;
+ CPlayerGUIInfo m_playerGUIInfo;
+ CSkinGUIInfo m_skinGUIInfo;
+ CSystemGUIInfo m_systemGUIInfo;
+ CVideoGUIInfo m_videoGUIInfo;
+ CVisualisationGUIInfo m_visualisationGUIInfo;
+ CWeatherGUIInfo m_weatherGUIInfo;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/GamesGUIInfo.cpp b/xbmc/guilib/guiinfo/GamesGUIInfo.cpp
new file mode 100644
index 0000000..5428c3a
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GamesGUIInfo.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/GamesGUIInfo.h"
+
+#include "FileItem.h"
+#include "Util.h"
+#include "cores/RetroPlayer/RetroPlayerUtils.h"
+#include "games/tags/GameInfoTag.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "settings/MediaSettings.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+using namespace KODI::GAME;
+using namespace KODI::RETRO;
+
+//! @todo Savestates were removed from v18
+//#define FILEITEM_PROPERTY_SAVESTATE_DURATION "duration"
+
+bool CGamesGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ if (item && item->IsGame())
+ {
+ CLog::Log(LOGDEBUG, "CGamesGUIInfo::InitCurrentItem({})", item->GetPath());
+
+ item->LoadGameTag();
+ CGameInfoTag* tag = item->GetGameInfoTag(); // creates item if not yet set, so no nullptr checks needed
+
+ if (tag->GetTitle().empty())
+ {
+ // No title in tag, show filename only
+ tag->SetTitle(CUtil::GetTitleFromPath(item->GetPath()));
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CGamesGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // RETROPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case RETROPLAYER_VIDEO_FILTER:
+ {
+ value = CMediaSettings::GetInstance().GetCurrentGameSettings().VideoFilter();
+ return true;
+ }
+ case RETROPLAYER_STRETCH_MODE:
+ {
+ STRETCHMODE stretchMode = CMediaSettings::GetInstance().GetCurrentGameSettings().StretchMode();
+ value = CRetroPlayerUtils::StretchModeToIdentifier(stretchMode);
+ return true;
+ }
+ case RETROPLAYER_VIDEO_ROTATION:
+ {
+ const unsigned int rotationDegCCW = CMediaSettings::GetInstance().GetCurrentGameSettings().RotationDegCCW();
+ value = std::to_string(rotationDegCCW);
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool CGamesGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CGamesGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/GamesGUIInfo.h b/xbmc/guilib/guiinfo/GamesGUIInfo.h
new file mode 100644
index 0000000..5ad78d9
--- /dev/null
+++ b/xbmc/guilib/guiinfo/GamesGUIInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CGamesGUIInfo : public CGUIInfoProvider
+{
+public:
+ CGamesGUIInfo() = default;
+ ~CGamesGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/IGUIInfoProvider.h b/xbmc/guilib/guiinfo/IGUIInfoProvider.h
new file mode 100644
index 0000000..4560b08
--- /dev/null
+++ b/xbmc/guilib/guiinfo/IGUIInfoProvider.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class CFileItem;
+class CGUIListItem;
+
+struct AudioStreamInfo;
+struct VideoStreamInfo;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class IGUIInfoProvider
+{
+public:
+ virtual ~IGUIInfoProvider() = default;
+
+ /*!
+ * @brief Init a new current guiinfo manager item. Gets called whenever the active guiinfo manager item changes.
+ * @param item The new item.
+ * @return True if the item was inited by the provider, false otherwise.
+ */
+ virtual bool InitCurrentItem(CFileItem *item) = 0;
+
+ /*!
+ * @brief Get a GUIInfoManager label string.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @param fallback A fallback value. Can be nullptr.
+ * @return True if the value was filled successfully, false otherwise.
+ */
+ virtual bool GetLabel(std::string &value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const = 0;
+
+ /*!
+ * @brief Get a GUIInfoManager label fallback string. Will be called if none of the registered
+ * provider's GetLabel() implementation has returned success.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @param fallback A fallback value. Can be nullptr.
+ * @return True if the value was filled successfully, false otherwise.
+ */
+ virtual bool GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback) = 0;
+
+ /*!
+ * @brief Get a GUIInfoManager integer value.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @return True if the value was filled successfully, false otherwise.
+ */
+ virtual bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const = 0;
+
+ /*!
+ * @brief Get a GUIInfoManager bool value.
+ * @param value Will be filled with the requested value.
+ * @param item The item to get the value for. Can be nullptr.
+ * @param contextWindow The context window. Can be 0.
+ * @param info The GUI info (label id + additional data).
+ * @return True if the value was filled successfully, false otherwise.
+ */
+ virtual bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const = 0;
+
+ /*!
+ * @brief Set new audio/video stream info data.
+ * @param audioInfo New audio stream info.
+ * @param videoInfo New video stream info.
+ */
+ virtual void UpdateAVInfo(const AudioStreamInfo& audioInfo, const VideoStreamInfo& videoInfo, const SubtitleStreamInfo& subtitleInfo) = 0;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp
new file mode 100644
index 0000000..fc0f471
--- /dev/null
+++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/LibraryGUIInfo.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "music/MusicDatabase.h"
+#include "music/MusicLibraryQueue.h"
+#include "profiles/ProfileManager.h"
+#include "settings/SettingsComponent.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoDatabase.h"
+#include "video/VideoLibraryQueue.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+CLibraryGUIInfo::CLibraryGUIInfo()
+{
+ ResetLibraryBools();
+}
+
+bool CLibraryGUIInfo::GetLibraryBool(int condition) const
+{
+ bool value = false;
+ GetBool(value, nullptr, 0, CGUIInfo(condition));
+ return value;
+}
+
+void CLibraryGUIInfo::SetLibraryBool(int condition, bool value)
+{
+ switch (condition)
+ {
+ case LIBRARY_HAS_MUSIC:
+ m_libraryHasMusic = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_MOVIES:
+ m_libraryHasMovies = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_MOVIE_SETS:
+ m_libraryHasMovieSets = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_TVSHOWS:
+ m_libraryHasTVShows = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_MUSICVIDEOS:
+ m_libraryHasMusicVideos = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_SINGLES:
+ m_libraryHasSingles = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_COMPILATIONS:
+ m_libraryHasCompilations = value ? 1 : 0;
+ break;
+ case LIBRARY_HAS_BOXSETS:
+ m_libraryHasBoxsets = value ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void CLibraryGUIInfo::ResetLibraryBools()
+{
+ m_libraryHasMusic = -1;
+ m_libraryHasMovies = -1;
+ m_libraryHasTVShows = -1;
+ m_libraryHasMusicVideos = -1;
+ m_libraryHasMovieSets = -1;
+ m_libraryHasSingles = -1;
+ m_libraryHasCompilations = -1;
+ m_libraryHasBoxsets = -1;
+ m_libraryRoleCounts.clear();
+}
+
+bool CLibraryGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CLibraryGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ return false;
+}
+
+bool CLibraryGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CLibraryGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LIBRARY_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LIBRARY_HAS_MUSIC:
+ {
+ if (m_libraryHasMusic < 0)
+ { // query
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasMusic = (db.GetSongsCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasMusic > 0;
+ return true;
+ }
+ case LIBRARY_HAS_MOVIES:
+ {
+ if (m_libraryHasMovies < 0)
+ {
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasMovies = db.HasContent(VideoDbContentType::MOVIES) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasMovies > 0;
+ return true;
+ }
+ case LIBRARY_HAS_MOVIE_SETS:
+ {
+ if (m_libraryHasMovieSets < 0)
+ {
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasMovieSets = db.HasSets() ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasMovieSets > 0;
+ return true;
+ }
+ case LIBRARY_HAS_TVSHOWS:
+ {
+ if (m_libraryHasTVShows < 0)
+ {
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasTVShows = db.HasContent(VideoDbContentType::TVSHOWS) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasTVShows > 0;
+ return true;
+ }
+ case LIBRARY_HAS_MUSICVIDEOS:
+ {
+ if (m_libraryHasMusicVideos < 0)
+ {
+ CVideoDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasMusicVideos = db.HasContent(VideoDbContentType::MUSICVIDEOS) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasMusicVideos > 0;
+ return true;
+ }
+ case LIBRARY_HAS_SINGLES:
+ {
+ if (m_libraryHasSingles < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasSingles = (db.GetSinglesCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasSingles > 0;
+ return true;
+ }
+ case LIBRARY_HAS_COMPILATIONS:
+ {
+ if (m_libraryHasCompilations < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasCompilations = (db.GetCompilationAlbumsCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasCompilations > 0;
+ return true;
+ }
+ case LIBRARY_HAS_BOXSETS:
+ {
+ if (m_libraryHasBoxsets < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ m_libraryHasBoxsets = (db.GetBoxsetsCount() > 0) ? 1 : 0;
+ db.Close();
+ }
+ }
+ value = m_libraryHasBoxsets > 0;
+ return true;
+ }
+ case LIBRARY_HAS_VIDEO:
+ {
+ return (GetBool(value, gitem, contextWindow, CGUIInfo(LIBRARY_HAS_MOVIES)) ||
+ GetBool(value, gitem, contextWindow, CGUIInfo(LIBRARY_HAS_TVSHOWS)) ||
+ GetBool(value, gitem, contextWindow, CGUIInfo(LIBRARY_HAS_MUSICVIDEOS)));
+ }
+ case LIBRARY_HAS_ROLE:
+ {
+ std::string strRole = info.GetData3();
+ // Find value for role if already stored
+ int artistcount = -1;
+ for (const auto &role : m_libraryRoleCounts)
+ {
+ if (StringUtils::EqualsNoCase(strRole, role.first))
+ {
+ artistcount = role.second;
+ break;
+ }
+ }
+ // Otherwise get from DB and store
+ if (artistcount < 0)
+ {
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ artistcount = db.GetArtistCountForRole(strRole);
+ db.Close();
+ m_libraryRoleCounts.emplace_back(std::make_pair(strRole, artistcount));
+ }
+ }
+ value = artistcount > 0;
+ return true;
+ }
+ case LIBRARY_HAS_NODE:
+ {
+ const CURL url(info.GetData3());
+ const std::shared_ptr<CProfileManager> profileManager =
+ CServiceBroker::GetSettingsComponent()->GetProfileManager();
+ CFileItemList items;
+
+ std::string libDir = profileManager->GetLibraryFolder();
+ XFILE::CDirectory::GetDirectory(libDir, items, "", XFILE::DIR_FLAG_NO_FILE_DIRS);
+ if (items.Size() == 0)
+ libDir = "special://xbmc/system/library/";
+
+ std::string nodePath = URIUtils::AddFileToFolder(libDir, url.GetHostName() + "/");
+ nodePath = URIUtils::AddFileToFolder(nodePath, url.GetFileName());
+ value = CFileUtils::Exists(nodePath);
+ return true;
+ }
+ case LIBRARY_IS_SCANNING:
+ {
+ value = (CMusicLibraryQueue::GetInstance().IsScanningLibrary() ||
+ CVideoLibraryQueue::GetInstance().IsScanningLibrary());
+ return true;
+ }
+ case LIBRARY_IS_SCANNING_VIDEO:
+ {
+ value = CVideoLibraryQueue::GetInstance().IsScanningLibrary();
+ return true;
+ }
+ case LIBRARY_IS_SCANNING_MUSIC:
+ {
+ value = CMusicLibraryQueue::GetInstance().IsScanningLibrary();
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/LibraryGUIInfo.h b/xbmc/guilib/guiinfo/LibraryGUIInfo.h
new file mode 100644
index 0000000..a9d47fc
--- /dev/null
+++ b/xbmc/guilib/guiinfo/LibraryGUIInfo.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CLibraryGUIInfo : public CGUIInfoProvider
+{
+public:
+ CLibraryGUIInfo();
+ ~CLibraryGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+ bool GetLibraryBool(int condition) const;
+ void SetLibraryBool(int condition, bool value);
+ void ResetLibraryBools();
+
+private:
+ mutable int m_libraryHasMusic;
+ mutable int m_libraryHasMovies;
+ mutable int m_libraryHasTVShows;
+ mutable int m_libraryHasMusicVideos;
+ mutable int m_libraryHasMovieSets;
+ mutable int m_libraryHasSingles;
+ mutable int m_libraryHasCompilations;
+ mutable int m_libraryHasBoxsets;
+
+ //Count of artists in music library contributing to song by role e.g. composers, conductors etc.
+ //For checking visibility of custom nodes for a role.
+ mutable std::vector<std::pair<std::string, int>> m_libraryRoleCounts;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp
new file mode 100644
index 0000000..f58a9ae
--- /dev/null
+++ b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/MusicGUIInfo.h"
+
+#include "FileItem.h"
+#include "PartyModeManager.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoHelper.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "music/MusicInfoLoader.h"
+#include "music/MusicThumbLoader.h"
+#include "music/tags/MusicInfoTag.h"
+#include "playlists/PlayList.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace KODI::GUILIB;
+using namespace KODI::GUILIB::GUIINFO;
+using namespace MUSIC_INFO;
+
+bool CMusicGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (item && (item->IsAudio() || (item->IsInternetStream() && appPlayer->IsPlayingAudio())))
+ {
+ CLog::Log(LOGDEBUG, "CMusicGUIInfo::InitCurrentItem({})", item->GetPath());
+
+ item->LoadMusicTag();
+
+ CMusicInfoTag* tag = item->GetMusicInfoTag(); // creates item if not yet set, so no nullptr checks needed
+ tag->SetLoaded(true);
+
+ // find a thumb for this file.
+ if (item->IsInternetStream() && !item->IsMusicDb())
+ {
+ if (!g_application.m_strPlayListFile.empty())
+ {
+ CLog::Log(LOGDEBUG, "Streaming media detected... using {} to find a thumb",
+ g_application.m_strPlayListFile);
+ CFileItem streamingItem(g_application.m_strPlayListFile,false);
+
+ CMusicThumbLoader loader;
+ loader.FillThumb(streamingItem);
+ if (streamingItem.HasArt("thumb"))
+ item->SetArt("thumb", streamingItem.GetArt("thumb"));
+ }
+ }
+ else
+ {
+ CMusicThumbLoader loader;
+ loader.LoadItem(item);
+ }
+
+ CMusicInfoLoader::LoadAdditionalTagInfo(item);
+ return true;
+ }
+ return false;
+}
+
+bool CMusicGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ // For musicplayer "offset" and "position" info labels check playlist
+ if (info.GetData1() && ((info.m_info >= MUSICPLAYER_OFFSET_POSITION_FIRST &&
+ info.m_info <= MUSICPLAYER_OFFSET_POSITION_LAST) ||
+ (info.m_info >= PLAYER_OFFSET_POSITION_FIRST && info.m_info <= PLAYER_OFFSET_POSITION_LAST)))
+ return GetPlaylistInfo(value, info);
+
+ const CMusicInfoTag* tag = item->GetMusicInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_* / MUSICPLAYER_* / LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_PATH:
+ case PLAYER_FILENAME:
+ case PLAYER_FILEPATH:
+ value = tag->GetURL();
+ if (value.empty())
+ value = item->GetPath();
+ value = GUIINFO::GetFileInfoLabelValueFromPath(info.m_info, value);
+ return true;
+ case PLAYER_TITLE:
+ value = tag->GetTitle();
+ return !value.empty();
+ case MUSICPLAYER_TITLE:
+ value = tag->GetTitle();
+ return !value.empty();
+ case LISTITEM_TITLE:
+ value = tag->GetTitle();
+ return true;
+ case MUSICPLAYER_PLAYCOUNT:
+ case LISTITEM_PLAYCOUNT:
+ if (tag->GetPlayCount() > 0)
+ {
+ value = std::to_string(tag->GetPlayCount());
+ return true;
+ }
+ break;
+ case MUSICPLAYER_LASTPLAYED:
+ case LISTITEM_LASTPLAYED:
+ {
+ const CDateTime& dateTime = tag->GetLastPlayed();
+ if (dateTime.IsValid())
+ {
+ value = dateTime.GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_TRACK_NUMBER:
+ case LISTITEM_TRACKNUMBER:
+ if (tag->Loaded() && tag->GetTrackNumber() > 0)
+ {
+ value = StringUtils::Format("{:02}", tag->GetTrackNumber());
+ return true;
+ }
+ break;
+ case MUSICPLAYER_DISC_NUMBER:
+ case LISTITEM_DISC_NUMBER:
+ if (tag->GetDiscNumber() > 0)
+ {
+ value = std::to_string(tag->GetDiscNumber());
+ return true;
+ }
+ break;
+ case MUSICPLAYER_TOTALDISCS:
+ case LISTITEM_TOTALDISCS:
+ value = std::to_string(tag->GetTotalDiscs());
+ return true;
+ case MUSICPLAYER_DISC_TITLE:
+ case LISTITEM_DISC_TITLE:
+ value = tag->GetDiscSubtitle();
+ return true;
+ case MUSICPLAYER_ARTIST:
+ case LISTITEM_ARTIST:
+ value = tag->GetArtistString();
+ return true;
+ case MUSICPLAYER_ALBUM_ARTIST:
+ case LISTITEM_ALBUM_ARTIST:
+ value = tag->GetAlbumArtistString();
+ return true;
+ case MUSICPLAYER_CONTRIBUTORS:
+ case LISTITEM_CONTRIBUTORS:
+ if (tag->HasContributors())
+ {
+ value = tag->GetContributorsText();
+ return true;
+ }
+ break;
+ case MUSICPLAYER_CONTRIBUTOR_AND_ROLE:
+ case LISTITEM_CONTRIBUTOR_AND_ROLE:
+ if (tag->HasContributors())
+ {
+ value = tag->GetContributorsAndRolesText();
+ return true;
+ }
+ break;
+ case MUSICPLAYER_ALBUM:
+ case LISTITEM_ALBUM:
+ value = tag->GetAlbum();
+ return true;
+ case MUSICPLAYER_YEAR:
+ case LISTITEM_YEAR:
+ value = tag->GetYearString();
+ return true;
+ case MUSICPLAYER_GENRE:
+ case LISTITEM_GENRE:
+ value = StringUtils::Join(tag->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
+ return true;
+ case MUSICPLAYER_LYRICS:
+ value = tag->GetLyrics();
+ return true;
+ case MUSICPLAYER_RATING:
+ case LISTITEM_RATING:
+ {
+ float rating = tag->GetRating();
+ if (rating > 0.f)
+ {
+ value = StringUtils::FormatNumber(rating);
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_RATING_AND_VOTES:
+ case LISTITEM_RATING_AND_VOTES:
+ {
+ float rating = tag->GetRating();
+ if (rating > 0.f)
+ {
+ int votes = tag->GetVotes();
+ if (votes <= 0)
+ value = StringUtils::FormatNumber(rating);
+ else
+ value =
+ StringUtils::Format(g_localizeStrings.Get(20350), StringUtils::FormatNumber(rating),
+ StringUtils::FormatNumber(votes));
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_USER_RATING:
+ case LISTITEM_USER_RATING:
+ if (tag->GetUserrating() > 0)
+ {
+ value = std::to_string(tag->GetUserrating());
+ return true;
+ }
+ break;
+ case MUSICPLAYER_COMMENT:
+ case LISTITEM_COMMENT:
+ value = tag->GetComment();
+ return true;
+ case MUSICPLAYER_MOOD:
+ case LISTITEM_MOOD:
+ value = tag->GetMood();
+ return true;
+ case LISTITEM_DBTYPE:
+ value = tag->GetType();
+ return true;
+ case MUSICPLAYER_DBID:
+ case LISTITEM_DBID:
+ {
+ int dbId = tag->GetDatabaseId();
+ if (dbId > -1)
+ {
+ value = std::to_string(dbId);
+ return true;
+ }
+ break;
+ }
+ case PLAYER_DURATION:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (!appPlayer->IsPlayingAudio())
+ break;
+ }
+ [[fallthrough]];
+ case MUSICPLAYER_DURATION:
+ case LISTITEM_DURATION:
+ {
+ int iDuration = tag->GetDuration();
+ if (iDuration > 0)
+ {
+ value = StringUtils::SecondsToTimeString(iDuration,
+ static_cast<TIME_FORMAT>(info.m_info == LISTITEM_DURATION
+ ? info.GetData4()
+ : info.GetData1()));
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_BPM:
+ case LISTITEM_BPM:
+ if (tag->GetBPM() > 0)
+ {
+ value = std::to_string(tag->GetBPM());
+ return true;
+ }
+ break;
+ case MUSICPLAYER_STATIONNAME:
+ // This property can be used for example by addons to enforce/override the station name.
+ value = item->GetProperty("StationName").asString();
+ if (value.empty())
+ value = tag->GetStationName();
+ return true;
+
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_PROPERTY:
+ if (StringUtils::StartsWithNoCase(info.GetData3(), "Role."))
+ {
+ // "Role.xxxx" properties are held in music tag
+ std::string property = info.GetData3();
+ property.erase(0, 5); //Remove Role.
+ value = tag->GetArtistStringForRole(property);
+ return true;
+ }
+ break;
+ case LISTITEM_VOTES:
+ value = StringUtils::FormatNumber(tag->GetVotes());
+ return true;
+ case MUSICPLAYER_ORIGINALDATE:
+ case LISTITEM_ORIGINALDATE:
+ {
+ value = tag->GetOriginalDate();
+ if (!CServiceBroker::GetSettingsComponent()
+ ->GetAdvancedSettings()
+ ->m_bMusicLibraryUseISODates)
+ value = StringUtils::ISODateToLocalizedDate(value);
+ return true;
+ }
+ case MUSICPLAYER_RELEASEDATE:
+ case LISTITEM_RELEASEDATE:
+ {
+ value = tag->GetReleaseDate();
+ if (!CServiceBroker::GetSettingsComponent()
+ ->GetAdvancedSettings()
+ ->m_bMusicLibraryUseISODates)
+ value = StringUtils::ISODateToLocalizedDate(value);
+ return true;
+ }
+ break;
+ case LISTITEM_BITRATE:
+ {
+ int BitRate = tag->GetBitRate();
+ if (BitRate > 0)
+ {
+ value = std::to_string(BitRate);
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_SAMPLERATE:
+ {
+ int sampleRate = tag->GetSampleRate();
+ if (sampleRate > 0)
+ {
+ value = StringUtils::Format("{:.5}", static_cast<double>(sampleRate) / 1000.0);
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_MUSICCHANNELS:
+ {
+ int channels = tag->GetNoOfChannels();
+ if (channels > 0)
+ {
+ value = std::to_string(channels);
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_ALBUMSTATUS:
+ value = tag->GetAlbumReleaseStatus();
+ return true;
+ case LISTITEM_FILENAME:
+ case LISTITEM_FILE_EXTENSION:
+ if (item->IsMusicDb())
+ value = URIUtils::GetFileName(tag->GetURL());
+ else if (item->HasVideoInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ value = URIUtils::GetFileName(item->GetPath());
+
+ if (info.m_info == LISTITEM_FILE_EXTENSION)
+ {
+ std::string strExtension = URIUtils::GetExtension(value);
+ value = StringUtils::TrimLeft(strExtension, ".");
+ }
+ return true;
+ case LISTITEM_FOLDERNAME:
+ case LISTITEM_PATH:
+ if (item->IsMusicDb())
+ value = URIUtils::GetDirectory(tag->GetURL());
+ else if (item->HasVideoInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ URIUtils::GetParentPath(item->GetPath(), value);
+
+ value = CURL(value).GetWithoutUserDetails();
+
+ if (info.m_info == LISTITEM_FOLDERNAME)
+ {
+ URIUtils::RemoveSlashAtEnd(value);
+ value = URIUtils::GetFileName(value);
+ }
+ return true;
+ case LISTITEM_FILENAME_AND_PATH:
+ if (item->IsMusicDb())
+ value = tag->GetURL();
+ else if (item->HasVideoInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ value = item->GetPath();
+
+ value = CURL(value).GetWithoutUserDetails();
+ return true;
+ case LISTITEM_DATE_ADDED:
+ if (tag->GetDateAdded().IsValid())
+ {
+ value = tag->GetDateAdded().GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ }
+ }
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // MUSICPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case MUSICPLAYER_PROPERTY:
+ if (StringUtils::StartsWithNoCase(info.GetData3(), "Role.") && item->HasMusicInfoTag())
+ {
+ // "Role.xxxx" properties are held in music tag
+ std::string property = info.GetData3();
+ property.erase(0, 5); //Remove Role.
+ value = item->GetMusicInfoTag()->GetArtistStringForRole(property);
+ return true;
+ }
+ value = item->GetProperty(info.GetData3()).asString();
+ return true;
+ case MUSICPLAYER_PLAYLISTLEN:
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC)
+ {
+ value = GUIINFO::GetPlaylistLabel(PLAYLIST_LENGTH);
+ return true;
+ }
+ break;
+ case MUSICPLAYER_PLAYLISTPOS:
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC)
+ {
+ value = GUIINFO::GetPlaylistLabel(PLAYLIST_POSITION);
+ return true;
+ }
+ break;
+ case MUSICPLAYER_COVER:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingAudio())
+ {
+ if (fallback)
+ *fallback = "DefaultAlbumCover.png";
+ value = item->HasArt("thumb") ? item->GetArt("thumb") : "DefaultAlbumCover.png";
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_BITRATE:
+ {
+ int iBitrate = m_audioInfo.bitrate;
+ if (iBitrate > 0)
+ {
+ value = std::to_string(std::lrint(static_cast<double>(iBitrate) / 1000.0));
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_CHANNELS:
+ {
+ int iChannels = m_audioInfo.channels;
+ if (iChannels > 0)
+ {
+ value = std::to_string(iChannels);
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_BITSPERSAMPLE:
+ {
+ int iBPS = m_audioInfo.bitspersample;
+ if (iBPS > 0)
+ {
+ value = std::to_string(iBPS);
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_SAMPLERATE:
+ {
+ int iSamplerate = m_audioInfo.samplerate;
+ if (iSamplerate > 0)
+ {
+ value = StringUtils::Format("{:.5}", static_cast<double>(iSamplerate) / 1000.0);
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_CODEC:
+ value = m_audioInfo.codecName;
+ return true;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // MUSICPM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ if (GetPartyModeLabel(value, info))
+ return true;
+
+ return false;
+}
+
+bool CMusicGUIInfo::GetPartyModeLabel(std::string& value, const CGUIInfo &info) const
+{
+ int iSongs = -1;
+
+ switch (info.m_info)
+ {
+ case MUSICPM_SONGSPLAYED:
+ iSongs = g_partyModeManager.GetSongsPlayed();
+ break;
+ case MUSICPM_MATCHINGSONGS:
+ iSongs = g_partyModeManager.GetMatchingSongs();
+ break;
+ case MUSICPM_MATCHINGSONGSPICKED:
+ iSongs = g_partyModeManager.GetMatchingSongsPicked();
+ break;
+ case MUSICPM_MATCHINGSONGSLEFT:
+ iSongs = g_partyModeManager.GetMatchingSongsLeft();
+ break;
+ case MUSICPM_RELAXEDSONGSPICKED:
+ iSongs = g_partyModeManager.GetRelaxedSongs();
+ break;
+ case MUSICPM_RANDOMSONGSPICKED:
+ iSongs = g_partyModeManager.GetRandomSongs();
+ break;
+ }
+
+ if (iSongs >= 0)
+ {
+ value = std::to_string(iSongs);
+ return true;
+ }
+
+ return false;
+}
+
+bool CMusicGUIInfo::GetPlaylistInfo(std::string& value, const CGUIInfo &info) const
+{
+ const PLAYLIST::CPlayList& playlist =
+ CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC);
+ if (playlist.size() < 1)
+ return false;
+
+ int index = info.GetData2();
+ if (info.GetData1() == 1)
+ { // relative index (requires current playlist is TYPE_MUSIC)
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != PLAYLIST::TYPE_MUSIC)
+ return false;
+
+ index = CServiceBroker::GetPlaylistPlayer().GetNextSong(index);
+ }
+
+ if (index < 0 || index >= playlist.size())
+ return false;
+
+ const CFileItemPtr playlistItem = playlist[index];
+ if (playlistItem->HasMusicInfoTag() && !playlistItem->GetMusicInfoTag()->Loaded())
+ {
+ playlistItem->LoadMusicTag();
+ playlistItem->GetMusicInfoTag()->SetLoaded();
+ }
+ // try to set a thumbnail
+ if (!playlistItem->HasArt("thumb"))
+ {
+ CMusicThumbLoader loader;
+ loader.LoadItem(playlistItem.get());
+ // still no thumb? then just the set the default cover
+ if (!playlistItem->HasArt("thumb"))
+ playlistItem->SetArt("thumb", "DefaultAlbumCover.png");
+ }
+ if (info.m_info == MUSICPLAYER_PLAYLISTPOS)
+ {
+ value = std::to_string(index + 1);
+ return true;
+ }
+ else if (info.m_info == MUSICPLAYER_COVER)
+ {
+ value = playlistItem->GetArt("thumb");
+ return true;
+ }
+
+ return GetLabel(value, playlistItem.get(), 0, CGUIInfo(info.m_info), nullptr);
+}
+
+bool CMusicGUIInfo::GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback)
+{
+ // No fallback for musicplayer "offset" and "position" info labels
+ if (info.GetData1() && ((info.m_info >= MUSICPLAYER_OFFSET_POSITION_FIRST &&
+ info.m_info <= MUSICPLAYER_OFFSET_POSITION_LAST) ||
+ (info.m_info >= PLAYER_OFFSET_POSITION_FIRST && info.m_info <= PLAYER_OFFSET_POSITION_LAST)))
+ return false;
+
+ const CMusicInfoTag* tag = item->GetMusicInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // MUSICPLAYER_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case MUSICPLAYER_TITLE:
+ value = item->GetLabel();
+ if (value.empty())
+ value = CUtil::GetTitleFromPath(item->GetPath());
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+bool CMusicGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CMusicGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ const CFileItem* item = static_cast<const CFileItem*>(gitem);
+ const CMusicInfoTag* tag = item->GetMusicInfoTag();
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // MUSICPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case MUSICPLAYER_CONTENT:
+ value = StringUtils::EqualsNoCase(info.GetData3(), "files");
+ return value; // if no match for this provider, other providers shall be asked.
+ case MUSICPLAYER_HASPREVIOUS:
+ // requires current playlist be TYPE_MUSIC
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC)
+ {
+ value = (CServiceBroker::GetPlaylistPlayer().GetCurrentSong() > 0); // not first song
+ return true;
+ }
+ break;
+ case MUSICPLAYER_HASNEXT:
+ // requires current playlist be TYPE_MUSIC
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC)
+ {
+ value = (CServiceBroker::GetPlaylistPlayer().GetCurrentSong() <
+ (CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC).size() -
+ 1)); // not last song
+ return true;
+ }
+ break;
+ case MUSICPLAYER_PLAYLISTPLAYING:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingAudio() &&
+ CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC)
+ {
+ value = true;
+ return true;
+ }
+ break;
+ }
+ case MUSICPLAYER_EXISTS:
+ {
+ int index = info.GetData2();
+ if (info.GetData1() == 1)
+ { // relative index
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != PLAYLIST::TYPE_MUSIC)
+ {
+ value = false;
+ return true;
+ }
+ index += CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
+ }
+ value =
+ (index >= 0 &&
+ index < CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_MUSIC).size());
+ return true;
+ }
+ case MUSICPLAYER_ISMULTIDISC:
+ if (tag)
+ {
+ value = (item->GetMusicInfoTag()->GetTotalDiscs() > 1);
+ return true;
+ }
+ break;
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // MUSICPM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case MUSICPM_ENABLED:
+ value = g_partyModeManager.IsEnabled();
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_IS_BOXSET:
+ if (tag)
+ {
+ value = tag->GetBoxset() == true;
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.h b/xbmc/guilib/guiinfo/MusicGUIInfo.h
new file mode 100644
index 0000000..4132ca6
--- /dev/null
+++ b/xbmc/guilib/guiinfo/MusicGUIInfo.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CMusicGUIInfo : public CGUIInfoProvider
+{
+public:
+ CMusicGUIInfo() = default;
+ ~CMusicGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback) override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+private:
+ bool GetPartyModeLabel(std::string& value, const CGUIInfo &info) const;
+ bool GetPlaylistInfo(std::string& value, const CGUIInfo &info) const;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp b/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp
new file mode 100644
index 0000000..6115517
--- /dev/null
+++ b/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/PicturesGUIInfo.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "pictures/GUIWindowSlideShow.h"
+#include "pictures/PictureInfoTag.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+#include <map>
+
+using namespace KODI::GUILIB::GUIINFO;
+
+static const std::map<int, int> listitem2slideshow_map =
+{
+ { LISTITEM_PICTURE_RESOLUTION , SLIDESHOW_RESOLUTION },
+ { LISTITEM_PICTURE_LONGDATE , SLIDESHOW_EXIF_LONG_DATE },
+ { LISTITEM_PICTURE_LONGDATETIME , SLIDESHOW_EXIF_LONG_DATE_TIME },
+ { LISTITEM_PICTURE_DATE , SLIDESHOW_EXIF_DATE },
+ { LISTITEM_PICTURE_DATETIME , SLIDESHOW_EXIF_DATE_TIME },
+ { LISTITEM_PICTURE_COMMENT , SLIDESHOW_COMMENT },
+ { LISTITEM_PICTURE_CAPTION , SLIDESHOW_IPTC_CAPTION },
+ { LISTITEM_PICTURE_DESC , SLIDESHOW_EXIF_DESCRIPTION },
+ { LISTITEM_PICTURE_KEYWORDS , SLIDESHOW_IPTC_KEYWORDS },
+ { LISTITEM_PICTURE_CAM_MAKE , SLIDESHOW_EXIF_CAMERA_MAKE },
+ { LISTITEM_PICTURE_CAM_MODEL , SLIDESHOW_EXIF_CAMERA_MODEL },
+ { LISTITEM_PICTURE_APERTURE , SLIDESHOW_EXIF_APERTURE },
+ { LISTITEM_PICTURE_FOCAL_LEN , SLIDESHOW_EXIF_FOCAL_LENGTH },
+ { LISTITEM_PICTURE_FOCUS_DIST , SLIDESHOW_EXIF_FOCUS_DIST },
+ { LISTITEM_PICTURE_EXP_MODE , SLIDESHOW_EXIF_EXPOSURE_MODE },
+ { LISTITEM_PICTURE_EXP_TIME , SLIDESHOW_EXIF_EXPOSURE_TIME },
+ { LISTITEM_PICTURE_ISO , SLIDESHOW_EXIF_ISO_EQUIV },
+ { LISTITEM_PICTURE_AUTHOR , SLIDESHOW_IPTC_AUTHOR },
+ { LISTITEM_PICTURE_BYLINE , SLIDESHOW_IPTC_BYLINE },
+ { LISTITEM_PICTURE_BYLINE_TITLE , SLIDESHOW_IPTC_BYLINE_TITLE },
+ { LISTITEM_PICTURE_CATEGORY , SLIDESHOW_IPTC_CATEGORY },
+ { LISTITEM_PICTURE_CCD_WIDTH , SLIDESHOW_EXIF_CCD_WIDTH },
+ { LISTITEM_PICTURE_CITY , SLIDESHOW_IPTC_CITY },
+ { LISTITEM_PICTURE_URGENCY , SLIDESHOW_IPTC_URGENCY },
+ { LISTITEM_PICTURE_COPYRIGHT_NOTICE , SLIDESHOW_IPTC_COPYRIGHT_NOTICE },
+ { LISTITEM_PICTURE_COUNTRY , SLIDESHOW_IPTC_COUNTRY },
+ { LISTITEM_PICTURE_COUNTRY_CODE , SLIDESHOW_IPTC_COUNTRY_CODE },
+ { LISTITEM_PICTURE_CREDIT , SLIDESHOW_IPTC_CREDIT },
+ { LISTITEM_PICTURE_IPTCDATE , SLIDESHOW_IPTC_DATE },
+ { LISTITEM_PICTURE_DIGITAL_ZOOM , SLIDESHOW_EXIF_DIGITAL_ZOOM, },
+ { LISTITEM_PICTURE_EXPOSURE , SLIDESHOW_EXIF_EXPOSURE },
+ { LISTITEM_PICTURE_EXPOSURE_BIAS , SLIDESHOW_EXIF_EXPOSURE_BIAS },
+ { LISTITEM_PICTURE_FLASH_USED , SLIDESHOW_EXIF_FLASH_USED },
+ { LISTITEM_PICTURE_HEADLINE , SLIDESHOW_IPTC_HEADLINE },
+ { LISTITEM_PICTURE_COLOUR , SLIDESHOW_COLOUR },
+ { LISTITEM_PICTURE_LIGHT_SOURCE , SLIDESHOW_EXIF_LIGHT_SOURCE },
+ { LISTITEM_PICTURE_METERING_MODE , SLIDESHOW_EXIF_METERING_MODE },
+ { LISTITEM_PICTURE_OBJECT_NAME , SLIDESHOW_IPTC_OBJECT_NAME },
+ { LISTITEM_PICTURE_ORIENTATION , SLIDESHOW_EXIF_ORIENTATION },
+ { LISTITEM_PICTURE_PROCESS , SLIDESHOW_PROCESS },
+ { LISTITEM_PICTURE_REF_SERVICE , SLIDESHOW_IPTC_REF_SERVICE },
+ { LISTITEM_PICTURE_SOURCE , SLIDESHOW_IPTC_SOURCE },
+ { LISTITEM_PICTURE_SPEC_INSTR , SLIDESHOW_IPTC_SPEC_INSTR },
+ { LISTITEM_PICTURE_STATE , SLIDESHOW_IPTC_STATE },
+ { LISTITEM_PICTURE_SUP_CATEGORIES , SLIDESHOW_IPTC_SUP_CATEGORIES },
+ { LISTITEM_PICTURE_TX_REFERENCE , SLIDESHOW_IPTC_TX_REFERENCE },
+ { LISTITEM_PICTURE_WHITE_BALANCE , SLIDESHOW_EXIF_WHITE_BALANCE },
+ { LISTITEM_PICTURE_IMAGETYPE , SLIDESHOW_IPTC_IMAGETYPE },
+ { LISTITEM_PICTURE_SUBLOCATION , SLIDESHOW_IPTC_SUBLOCATION },
+ { LISTITEM_PICTURE_TIMECREATED , SLIDESHOW_IPTC_TIMECREATED },
+ { LISTITEM_PICTURE_GPS_LAT , SLIDESHOW_EXIF_GPS_LATITUDE },
+ { LISTITEM_PICTURE_GPS_LON , SLIDESHOW_EXIF_GPS_LONGITUDE },
+ { LISTITEM_PICTURE_GPS_ALT , SLIDESHOW_EXIF_GPS_ALTITUDE }
+};
+
+CPicturesGUIInfo::CPicturesGUIInfo() = default;
+
+CPicturesGUIInfo::~CPicturesGUIInfo() = default;
+
+void CPicturesGUIInfo::SetCurrentSlide(CFileItem *item)
+{
+ if (m_currentSlide && item && m_currentSlide->GetPath() == item->GetPath())
+ return;
+
+ if (item)
+ {
+ if (item->HasPictureInfoTag()) // Note: item may also be a video
+ {
+ CPictureInfoTag* tag = item->GetPictureInfoTag();
+ if (!tag->Loaded()) // If picture metadata has not been loaded yet, load it now
+ tag->Load(item->GetPath());
+ }
+ m_currentSlide.reset(new CFileItem(*item));
+ }
+ else if (m_currentSlide)
+ {
+ m_currentSlide.reset();
+ }
+}
+
+const CFileItem* CPicturesGUIInfo::GetCurrentSlide() const
+{
+ return m_currentSlide.get();
+}
+
+bool CPicturesGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CPicturesGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ if (item->IsPicture() && info.m_info >= LISTITEM_PICTURE_START && info.m_info <= LISTITEM_PICTURE_END)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ const auto& it = listitem2slideshow_map.find(info.m_info);
+ if (it != listitem2slideshow_map.end())
+ {
+ if (item->HasPictureInfoTag())
+ {
+ value = item->GetPictureInfoTag()->GetInfo(it->second);
+ return true;
+ }
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "CPicturesGUIInfo::GetLabel - cannot map LISTITEM ({}) to SLIDESHOW label!",
+ info.m_info);
+ return false;
+ }
+ }
+ else if (m_currentSlide && info.m_info >= SLIDESHOW_LABELS_START && info.m_info <= SLIDESHOW_LABELS_END)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SLIDESHOW_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ switch (info.m_info)
+ {
+ case SLIDESHOW_FILE_NAME:
+ {
+ value = URIUtils::GetFileName(m_currentSlide->GetPath());
+ return true;
+ }
+ case SLIDESHOW_FILE_PATH:
+ {
+ const std::string path = URIUtils::GetDirectory(m_currentSlide->GetPath());
+ value = CURL(path).GetWithoutUserDetails();
+ return true;
+ }
+ case SLIDESHOW_FILE_SIZE:
+ {
+ if (!m_currentSlide->m_bIsFolder || m_currentSlide->m_dwSize)
+ {
+ value = StringUtils::SizeToString(m_currentSlide->m_dwSize);
+ return true;
+ }
+ break;
+ }
+ case SLIDESHOW_FILE_DATE:
+ {
+ if (m_currentSlide->m_dateTime.IsValid())
+ {
+ value = m_currentSlide->m_dateTime.GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ }
+ case SLIDESHOW_INDEX:
+ {
+ CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (slideshow && slideshow->NumSlides())
+ {
+ value = StringUtils::Format("{}/{}", slideshow->CurrentSlide(), slideshow->NumSlides());
+ return true;
+ }
+ break;
+ }
+ default:
+ {
+ value = m_currentSlide->GetPictureInfoTag()->GetInfo(info.m_info);
+ return true;
+ }
+ }
+ }
+
+ if (item->IsPicture())
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ switch (info.m_info)
+ {
+ case LISTITEM_PICTURE_PATH:
+ {
+ if (!(item->IsZIP() || item->IsRAR() || item->IsCBZ() || item->IsCBR()))
+ {
+ value = item->GetPath();
+ return true;
+ }
+ break;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CPicturesGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CPicturesGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SLIDESHOW_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SLIDESHOW_ISPAUSED:
+ {
+ CGUIWindowSlideShow *slideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ value = (slideShow && slideShow->IsPaused());
+ return true;
+ }
+ case SLIDESHOW_ISRANDOM:
+ {
+ CGUIWindowSlideShow *slideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ value = (slideShow && slideShow->IsShuffled());
+ return true;
+ }
+ case SLIDESHOW_ISACTIVE:
+ {
+ CGUIWindowSlideShow *slideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ value = (slideShow && slideShow->InSlideShow());
+ return true;
+ }
+ case SLIDESHOW_ISVIDEO:
+ {
+ CGUIWindowSlideShow *slideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ value = (slideShow && slideShow->GetCurrentSlide() && slideShow->GetCurrentSlide()->IsVideo());
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/PicturesGUIInfo.h b/xbmc/guilib/guiinfo/PicturesGUIInfo.h
new file mode 100644
index 0000000..68cdb4c
--- /dev/null
+++ b/xbmc/guilib/guiinfo/PicturesGUIInfo.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CPicturesGUIInfo : public CGUIInfoProvider
+{
+public:
+ CPicturesGUIInfo();
+ ~CPicturesGUIInfo() override;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+ void SetCurrentSlide(CFileItem *item);
+ const CFileItem* GetCurrentSlide() const;
+
+private:
+ std::unique_ptr<CFileItem> m_currentSlide;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp
new file mode 100644
index 0000000..dfb9455
--- /dev/null
+++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/PlayerGUIInfo.h"
+
+#include "FileItem.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationVolumeHandling.h"
+#include "cores/AudioEngine/Utils/AEUtil.h"
+#include "cores/DataCacheCore.h"
+#include "cores/EdlEdit.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIDialog.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoHelper.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <charconv>
+#include <cmath>
+
+using namespace KODI::GUILIB::GUIINFO;
+
+CPlayerGUIInfo::CPlayerGUIInfo()
+ : m_appPlayer(CServiceBroker::GetAppComponents().GetComponent<CApplicationPlayer>()),
+ m_appVolume(CServiceBroker::GetAppComponents().GetComponent<CApplicationVolumeHandling>())
+{
+}
+
+CPlayerGUIInfo::~CPlayerGUIInfo() = default;
+
+int CPlayerGUIInfo::GetTotalPlayTime() const
+{
+ return std::lrint(g_application.GetTotalTime());
+}
+
+int CPlayerGUIInfo::GetPlayTime() const
+{
+ return std::lrint(g_application.GetTime());
+}
+
+int CPlayerGUIInfo::GetPlayTimeRemaining() const
+{
+ int iReverse = GetTotalPlayTime() - std::lrint(g_application.GetTime());
+ return iReverse > 0 ? iReverse : 0;
+}
+
+float CPlayerGUIInfo::GetSeekPercent() const
+{
+ int iTotal = GetTotalPlayTime();
+ if (iTotal == 0)
+ return 0.0f;
+
+ float fPercentPlayTime = static_cast<float>(GetPlayTime() * 1000) / iTotal * 0.1f;
+ float fPercentPerSecond = 100.0f / static_cast<float>(iTotal);
+ float fPercent =
+ fPercentPlayTime + fPercentPerSecond * m_appPlayer->GetSeekHandler().GetSeekSize();
+ fPercent = std::max(0.0f, std::min(fPercent, 100.0f));
+ return fPercent;
+}
+
+std::string CPlayerGUIInfo::GetCurrentPlayTime(TIME_FORMAT format) const
+{
+ if (format == TIME_FORMAT_GUESS && GetTotalPlayTime() >= 3600)
+ format = TIME_FORMAT_HH_MM_SS;
+
+ return StringUtils::SecondsToTimeString(std::lrint(GetPlayTime()), format);
+}
+
+std::string CPlayerGUIInfo::GetCurrentPlayTimeRemaining(TIME_FORMAT format) const
+{
+ if (format == TIME_FORMAT_GUESS && GetTotalPlayTime() >= 3600)
+ format = TIME_FORMAT_HH_MM_SS;
+
+ int iTimeRemaining = GetPlayTimeRemaining();
+ if (iTimeRemaining)
+ return StringUtils::SecondsToTimeString(iTimeRemaining, format);
+
+ return std::string();
+}
+
+std::string CPlayerGUIInfo::GetDuration(TIME_FORMAT format) const
+{
+ int iTotal = GetTotalPlayTime();
+ if (iTotal > 0)
+ {
+ if (format == TIME_FORMAT_GUESS && iTotal >= 3600)
+ format = TIME_FORMAT_HH_MM_SS;
+ return StringUtils::SecondsToTimeString(iTotal, format);
+ }
+ return std::string();
+}
+
+std::string CPlayerGUIInfo::GetCurrentSeekTime(TIME_FORMAT format) const
+{
+ if (format == TIME_FORMAT_GUESS && GetTotalPlayTime() >= 3600)
+ format = TIME_FORMAT_HH_MM_SS;
+
+ return StringUtils::SecondsToTimeString(
+ g_application.GetTime() + m_appPlayer->GetSeekHandler().GetSeekSize(), format);
+}
+
+std::string CPlayerGUIInfo::GetSeekTime(TIME_FORMAT format) const
+{
+ if (!m_appPlayer->GetSeekHandler().HasTimeCode())
+ return std::string();
+
+ int iSeekTimeCode = m_appPlayer->GetSeekHandler().GetTimeCodeSeconds();
+ if (format == TIME_FORMAT_GUESS && iSeekTimeCode >= 3600)
+ format = TIME_FORMAT_HH_MM_SS;
+
+ return StringUtils::SecondsToTimeString(iSeekTimeCode, format);
+}
+
+void CPlayerGUIInfo::SetShowInfo(bool showinfo)
+{
+ if (showinfo != m_playerShowInfo)
+ {
+ m_playerShowInfo = showinfo;
+ m_events.Publish(PlayerShowInfoChangedEvent(m_playerShowInfo));
+ }
+}
+
+bool CPlayerGUIInfo::ToggleShowInfo()
+{
+ SetShowInfo(!m_playerShowInfo);
+ return m_playerShowInfo;
+}
+
+bool CPlayerGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ if (item && m_appPlayer->IsPlaying())
+ {
+ CLog::Log(LOGDEBUG, "CPlayerGUIInfo::InitCurrentItem({})", CURL::GetRedacted(item->GetPath()));
+ m_currentItem.reset(new CFileItem(*item));
+ }
+ else
+ {
+ m_currentItem.reset();
+ }
+ return false;
+}
+
+bool CPlayerGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_SEEKOFFSET:
+ {
+ int lastSeekOffset = CServiceBroker::GetDataCacheCore().GetSeekOffSet();
+ std::string seekOffset = StringUtils::SecondsToTimeString(
+ std::abs(lastSeekOffset / 1000), static_cast<TIME_FORMAT>(info.GetData1()));
+ if (lastSeekOffset < 0)
+ value = "-" + seekOffset;
+ else if (lastSeekOffset > 0)
+ value = "+" + seekOffset;
+ return true;
+ }
+ case PLAYER_PROGRESS:
+ value = std::to_string(std::lrintf(g_application.GetPercentage()));
+ return true;
+ case PLAYER_PROGRESS_CACHE:
+ value = std::to_string(std::lrintf(g_application.GetCachePercentage()));
+ return true;
+ case PLAYER_VOLUME:
+ value =
+ StringUtils::Format("{:2.1f} dB", CAEUtil::PercentToGain(m_appVolume->GetVolumeRatio()));
+ return true;
+ case PLAYER_SUBTITLE_DELAY:
+ value = StringUtils::Format("{:2.3f} s", m_appPlayer->GetVideoSettings().m_SubtitleDelay);
+ return true;
+ case PLAYER_AUDIO_DELAY:
+ value = StringUtils::Format("{:2.3f} s", m_appPlayer->GetVideoSettings().m_AudioDelay);
+ return true;
+ case PLAYER_CHAPTER:
+ value = StringUtils::Format("{:02}", m_appPlayer->GetChapter());
+ return true;
+ case PLAYER_CHAPTERCOUNT:
+ value = StringUtils::Format("{:02}", m_appPlayer->GetChapterCount());
+ return true;
+ case PLAYER_CHAPTERNAME:
+ m_appPlayer->GetChapterName(value);
+ return true;
+ case PLAYER_PATH:
+ case PLAYER_FILENAME:
+ case PLAYER_FILEPATH:
+ value = GUIINFO::GetFileInfoLabelValueFromPath(info.m_info, item->GetPath());
+ return true;
+ case PLAYER_TITLE:
+ // use label or drop down to title from path
+ value = item->GetLabel();
+ if (value.empty())
+ value = CUtil::GetTitleFromPath(item->GetPath());
+ return true;
+ case PLAYER_PLAYSPEED:
+ {
+ float speed = m_appPlayer->GetPlaySpeed();
+ if (speed == 1.0f)
+ speed = m_appPlayer->GetPlayTempo();
+ value = StringUtils::Format("{:.2f}", speed);
+ return true;
+ }
+ case PLAYER_TIME:
+ value = GetCurrentPlayTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PLAYER_START_TIME:
+ {
+ const CDateTime time(m_appPlayer->GetStartTime());
+ value = time.GetAsLocalizedTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PLAYER_DURATION:
+ value = GetDuration(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PLAYER_TIME_REMAINING:
+ value = GetCurrentPlayTimeRemaining(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PLAYER_FINISH_TIME:
+ {
+ CDateTime time(CDateTime::GetCurrentDateTime());
+ int playTimeRemaining = GetPlayTimeRemaining();
+ float speed = m_appPlayer->GetPlaySpeed();
+ float tempo = m_appPlayer->GetPlayTempo();
+ if (speed == 1.0f)
+ playTimeRemaining /= tempo;
+ time += CDateTimeSpan(0, 0, 0, playTimeRemaining);
+ value = time.GetAsLocalizedTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ }
+ case PLAYER_TIME_SPEED:
+ {
+ float speed = m_appPlayer->GetPlaySpeed();
+ if (speed != 1.0f)
+ value = StringUtils::Format("{} ({}x)",
+ GetCurrentPlayTime(static_cast<TIME_FORMAT>(info.GetData1())),
+ static_cast<int>(speed));
+ else
+ value = GetCurrentPlayTime(TIME_FORMAT_GUESS);
+ return true;
+ }
+ case PLAYER_SEEKTIME:
+ value = GetCurrentSeekTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case PLAYER_SEEKSTEPSIZE:
+ {
+ int seekSize = m_appPlayer->GetSeekHandler().GetSeekSize();
+ std::string strSeekSize = StringUtils::SecondsToTimeString(abs(seekSize), static_cast<TIME_FORMAT>(info.GetData1()));
+ if (seekSize < 0)
+ value = "-" + strSeekSize;
+ if (seekSize > 0)
+ value = "+" + strSeekSize;
+ return true;
+ }
+ case PLAYER_SEEKNUMERIC:
+ value = GetSeekTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return !value.empty();
+ case PLAYER_CACHELEVEL:
+ {
+ int iLevel = m_appPlayer->GetCacheLevel();
+ if (iLevel >= 0)
+ {
+ value = std::to_string(iLevel);
+ return true;
+ }
+ break;
+ }
+ case PLAYER_ITEM_ART:
+ value = item->GetArt(info.GetData3());
+ return true;
+ case PLAYER_ICON:
+ value = item->GetArt("thumb");
+ if (value.empty())
+ value = item->GetArt("icon");
+ if (fallback)
+ *fallback = item->GetArt("icon");
+ return true;
+ case PLAYER_EDITLIST:
+ case PLAYER_CUTS:
+ case PLAYER_SCENE_MARKERS:
+ case PLAYER_CUTLIST:
+ case PLAYER_CHAPTERS:
+ value = GetContentRanges(info.m_info);
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_PROCESS_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_PROCESS_VIDEODECODER:
+ value = CServiceBroker::GetDataCacheCore().GetVideoDecoderName();
+ return true;
+ case PLAYER_PROCESS_DEINTMETHOD:
+ value = CServiceBroker::GetDataCacheCore().GetVideoDeintMethod();
+ return true;
+ case PLAYER_PROCESS_PIXELFORMAT:
+ value = CServiceBroker::GetDataCacheCore().GetVideoPixelFormat();
+ return true;
+ case PLAYER_PROCESS_VIDEOFPS:
+ value = StringUtils::Format("{:.3f}", CServiceBroker::GetDataCacheCore().GetVideoFps());
+ return true;
+ case PLAYER_PROCESS_VIDEODAR:
+ value = StringUtils::Format("{:.2f}", CServiceBroker::GetDataCacheCore().GetVideoDAR());
+ return true;
+ case PLAYER_PROCESS_VIDEOWIDTH:
+ value = StringUtils::FormatNumber(CServiceBroker::GetDataCacheCore().GetVideoWidth());
+ return true;
+ case PLAYER_PROCESS_VIDEOHEIGHT:
+ value = StringUtils::FormatNumber(CServiceBroker::GetDataCacheCore().GetVideoHeight());
+ return true;
+ case PLAYER_PROCESS_VIDEOSCANTYPE:
+ value = CServiceBroker::GetDataCacheCore().IsVideoInterlaced() ? "i" : "p";
+ return true;
+ case PLAYER_PROCESS_AUDIODECODER:
+ value = CServiceBroker::GetDataCacheCore().GetAudioDecoderName();
+ return true;
+ case PLAYER_PROCESS_AUDIOCHANNELS:
+ value = CServiceBroker::GetDataCacheCore().GetAudioChannels();
+ return true;
+ case PLAYER_PROCESS_AUDIOSAMPLERATE:
+ value = StringUtils::FormatNumber(CServiceBroker::GetDataCacheCore().GetAudioSampleRate());
+ return true;
+ case PLAYER_PROCESS_AUDIOBITSPERSAMPLE:
+ value = StringUtils::FormatNumber(CServiceBroker::GetDataCacheCore().GetAudioBitsPerSample());
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYLIST_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYLIST_LENGTH:
+ case PLAYLIST_POSITION:
+ case PLAYLIST_RANDOM:
+ case PLAYLIST_REPEAT:
+ value = GUIINFO::GetPlaylistLabel(info.m_info, info.GetData1());
+ return true;
+ }
+
+ return false;
+}
+
+bool CPlayerGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_VOLUME:
+ value = static_cast<int>(m_appVolume->GetVolumePercent());
+ return true;
+ case PLAYER_PROGRESS:
+ value = std::lrintf(g_application.GetPercentage());
+ return true;
+ case PLAYER_PROGRESS_CACHE:
+ value = std::lrintf(g_application.GetCachePercentage());
+ return true;
+ case PLAYER_SEEKBAR:
+ value = std::lrintf(GetSeekPercent());
+ return true;
+ case PLAYER_CACHELEVEL:
+ value = m_appPlayer->GetCacheLevel();
+ return true;
+ case PLAYER_CHAPTER:
+ value = m_appPlayer->GetChapter();
+ return true;
+ case PLAYER_CHAPTERCOUNT:
+ value = m_appPlayer->GetChapterCount();
+ return true;
+ case PLAYER_SUBTITLE_DELAY:
+ value = m_appPlayer->GetSubtitleDelay();
+ return true;
+ case PLAYER_AUDIO_DELAY:
+ value = m_appPlayer->GetAudioDelay();
+ return true;
+ }
+
+ return false;
+}
+
+bool CPlayerGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ const CFileItem *item = nullptr;
+ if (gitem->IsFileItem())
+ item = static_cast<const CFileItem*>(gitem);
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_SHOWINFO:
+ value = m_playerShowInfo;
+ return true;
+ case PLAYER_SHOWTIME:
+ value = m_playerShowTime;
+ return true;
+ case PLAYER_MUTED:
+ value = (m_appVolume->IsMuted() ||
+ m_appVolume->GetVolumeRatio() <= CApplicationVolumeHandling::VOLUME_MINIMUM);
+ return true;
+ case PLAYER_HAS_MEDIA:
+ value = m_appPlayer->IsPlaying();
+ return true;
+ case PLAYER_HAS_AUDIO:
+ value = m_appPlayer->IsPlayingAudio();
+ return true;
+ case PLAYER_HAS_VIDEO:
+ value = m_appPlayer->IsPlayingVideo();
+ return true;
+ case PLAYER_HAS_GAME:
+ value = m_appPlayer->IsPlayingGame();
+ return true;
+ case PLAYER_PLAYING:
+ value = m_appPlayer->GetPlaySpeed() == 1.0f;
+ return true;
+ case PLAYER_PAUSED:
+ value = m_appPlayer->IsPausedPlayback();
+ return true;
+ case PLAYER_REWINDING:
+ value = m_appPlayer->GetPlaySpeed() < 0.0f;
+ return true;
+ case PLAYER_FORWARDING:
+ value = m_appPlayer->GetPlaySpeed() > 1.5f;
+ return true;
+ case PLAYER_REWINDING_2x:
+ value = m_appPlayer->GetPlaySpeed() == -2;
+ return true;
+ case PLAYER_REWINDING_4x:
+ value = m_appPlayer->GetPlaySpeed() == -4;
+ return true;
+ case PLAYER_REWINDING_8x:
+ value = m_appPlayer->GetPlaySpeed() == -8;
+ return true;
+ case PLAYER_REWINDING_16x:
+ value = m_appPlayer->GetPlaySpeed() == -16;
+ return true;
+ case PLAYER_REWINDING_32x:
+ value = m_appPlayer->GetPlaySpeed() == -32;
+ return true;
+ case PLAYER_FORWARDING_2x:
+ value = m_appPlayer->GetPlaySpeed() == 2;
+ return true;
+ case PLAYER_FORWARDING_4x:
+ value = m_appPlayer->GetPlaySpeed() == 4;
+ return true;
+ case PLAYER_FORWARDING_8x:
+ value = m_appPlayer->GetPlaySpeed() == 8;
+ return true;
+ case PLAYER_FORWARDING_16x:
+ value = m_appPlayer->GetPlaySpeed() == 16;
+ return true;
+ case PLAYER_FORWARDING_32x:
+ value = m_appPlayer->GetPlaySpeed() == 32;
+ return true;
+ case PLAYER_CAN_PAUSE:
+ value = m_appPlayer->CanPause();
+ return true;
+ case PLAYER_CAN_SEEK:
+ value = m_appPlayer->CanSeek();
+ return true;
+ case PLAYER_SUPPORTS_TEMPO:
+ value = m_appPlayer->SupportsTempo();
+ return true;
+ case PLAYER_IS_TEMPO:
+ value = (m_appPlayer->GetPlayTempo() != 1.0f && m_appPlayer->GetPlaySpeed() == 1.0f);
+ return true;
+ case PLAYER_CACHING:
+ value = m_appPlayer->IsCaching();
+ return true;
+ case PLAYER_SEEKBAR:
+ {
+ CGUIDialog *seekBar = CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_SEEK_BAR);
+ value = seekBar ? seekBar->IsDialogRunning() : false;
+ return true;
+ }
+ case PLAYER_SEEKING:
+ value = m_appPlayer->GetSeekHandler().InProgress();
+ return true;
+ case PLAYER_HASPERFORMEDSEEK:
+ {
+ int requestedLastSecondInterval{0};
+ std::from_chars_result result =
+ std::from_chars(info.GetData3().data(), info.GetData3().data() + info.GetData3().size(),
+ requestedLastSecondInterval);
+ if (result.ec == std::errc::invalid_argument)
+ {
+ value = false;
+ return false;
+ }
+
+ value = CServiceBroker::GetDataCacheCore().HasPerformedSeek(requestedLastSecondInterval);
+ return true;
+ }
+ case PLAYER_PASSTHROUGH:
+ value = m_appPlayer->IsPassthrough();
+ return true;
+ case PLAYER_ISINTERNETSTREAM:
+ if (item)
+ {
+ value = URIUtils::IsInternetStream(item->GetDynPath());
+ return true;
+ }
+ break;
+ case PLAYER_HAS_PROGRAMS:
+ value = (m_appPlayer->GetProgramsCount() > 1) ? true : false;
+ return true;
+ case PLAYER_HAS_RESOLUTIONS:
+ value = CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot() &&
+ CResolutionUtils::HasWhitelist();
+ return true;
+ case PLAYER_HASDURATION:
+ value = g_application.GetTotalTime() > 0;
+ return true;
+ case PLAYER_FRAMEADVANCE:
+ value = CServiceBroker::GetDataCacheCore().IsFrameAdvance();
+ return true;
+ case PLAYER_HAS_SCENE_MARKERS:
+ value = !CServiceBroker::GetDataCacheCore().GetSceneMarkers().empty();
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYLIST_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYLIST_ISRANDOM:
+ {
+ PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer();
+ PLAYLIST::Id playlistid = info.GetData1();
+ if (info.GetData2() > 0 && playlistid != PLAYLIST::TYPE_NONE)
+ value = player.IsShuffled(playlistid);
+ else
+ value = player.IsShuffled(player.GetCurrentPlaylist());
+ return true;
+ }
+ case PLAYLIST_ISREPEAT:
+ {
+ PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer();
+ PLAYLIST::Id playlistid = info.GetData1();
+ if (info.GetData2() > 0 && playlistid != PLAYLIST::TYPE_NONE)
+ value = (player.GetRepeat(playlistid) == PLAYLIST::RepeatState::ALL);
+ else
+ value = player.GetRepeat(player.GetCurrentPlaylist()) == PLAYLIST::RepeatState::ALL;
+ return true;
+ }
+ case PLAYLIST_ISREPEATONE:
+ {
+ PLAYLIST::CPlayListPlayer& player = CServiceBroker::GetPlaylistPlayer();
+ PLAYLIST::Id playlistid = info.GetData1();
+ if (info.GetData2() > 0 && playlistid != PLAYLIST::TYPE_NONE)
+ value = (player.GetRepeat(playlistid) == PLAYLIST::RepeatState::ONE);
+ else
+ value = player.GetRepeat(player.GetCurrentPlaylist()) == PLAYLIST::RepeatState::ONE;
+ return true;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_PROCESS_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case PLAYER_PROCESS_VIDEOHWDECODER:
+ value = CServiceBroker::GetDataCacheCore().IsVideoHwDecoder();
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_ISPLAYING:
+ {
+ if (item)
+ {
+ if (item->HasProperty("playlistposition"))
+ {
+ value = static_cast<int>(item->GetProperty("playlisttype").asInteger()) == CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() &&
+ static_cast<int>(item->GetProperty("playlistposition").asInteger()) == CServiceBroker::GetPlaylistPlayer().GetCurrentSong();
+ return true;
+ }
+ else if (m_currentItem && !m_currentItem->GetPath().empty())
+ {
+ if (!g_application.m_strPlayListFile.empty())
+ {
+ //playlist file that is currently playing or the playlistitem that is currently playing.
+ value = item->IsPath(g_application.m_strPlayListFile) || m_currentItem->IsSamePath(item);
+ }
+ else
+ {
+ value = m_currentItem->IsSamePath(item);
+ }
+ return true;
+ }
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+std::string CPlayerGUIInfo::GetContentRanges(int iInfo) const
+{
+ std::string values;
+
+ CDataCacheCore& data = CServiceBroker::GetDataCacheCore();
+ std::vector<std::pair<float, float>> ranges;
+
+ std::time_t start;
+ int64_t current;
+ int64_t min;
+ int64_t max;
+ data.GetPlayTimes(start, current, min, max);
+
+ std::time_t duration = max - start * 1000;
+ if (duration > 0)
+ {
+ switch (iInfo)
+ {
+ case PLAYER_EDITLIST:
+ case PLAYER_CUTLIST:
+ ranges = GetEditList(data, duration);
+ break;
+ case PLAYER_CUTS:
+ ranges = GetCuts(data, duration);
+ break;
+ case PLAYER_SCENE_MARKERS:
+ ranges = GetSceneMarkers(data, duration);
+ break;
+ case PLAYER_CHAPTERS:
+ ranges = GetChapters(data, duration);
+ break;
+ default:
+ CLog::Log(LOGERROR, "CPlayerGUIInfo::GetContentRanges({}) - unhandled guiinfo", iInfo);
+ break;
+ }
+
+ // create csv string from ranges
+ for (const auto& range : ranges)
+ values += StringUtils::Format("{:.5f},{:.5f},", range.first, range.second);
+
+ if (!values.empty())
+ values.pop_back(); // remove trailing comma
+ }
+
+ return values;
+}
+
+std::vector<std::pair<float, float>> CPlayerGUIInfo::GetEditList(const CDataCacheCore& data,
+ std::time_t duration) const
+{
+ std::vector<std::pair<float, float>> ranges;
+
+ const std::vector<EDL::Edit>& edits = data.GetEditList();
+ for (const auto& edit : edits)
+ {
+ float editStart = edit.start * 100.0f / duration;
+ float editEnd = edit.end * 100.0f / duration;
+ ranges.emplace_back(std::make_pair(editStart, editEnd));
+ }
+ return ranges;
+}
+
+std::vector<std::pair<float, float>> CPlayerGUIInfo::GetCuts(const CDataCacheCore& data,
+ std::time_t duration) const
+{
+ std::vector<std::pair<float, float>> ranges;
+
+ const std::vector<int64_t>& cuts = data.GetCuts();
+ float lastMarker = 0.0f;
+ for (const auto& cut : cuts)
+ {
+ float marker = cut * 100.0f / duration;
+ if (marker != 0)
+ ranges.emplace_back(std::make_pair(lastMarker, marker));
+
+ lastMarker = marker;
+ }
+ return ranges;
+}
+
+std::vector<std::pair<float, float>> CPlayerGUIInfo::GetSceneMarkers(const CDataCacheCore& data,
+ std::time_t duration) const
+{
+ std::vector<std::pair<float, float>> ranges;
+
+ const std::vector<int64_t>& scenes = data.GetSceneMarkers();
+ float lastMarker = 0.0f;
+ for (const auto& scene : scenes)
+ {
+ float marker = scene * 100.0f / duration;
+ if (marker != 0)
+ ranges.emplace_back(std::make_pair(lastMarker, marker));
+
+ lastMarker = marker;
+ }
+ return ranges;
+}
+
+std::vector<std::pair<float, float>> CPlayerGUIInfo::GetChapters(const CDataCacheCore& data,
+ std::time_t duration) const
+{
+ std::vector<std::pair<float, float>> ranges;
+
+ const std::vector<std::pair<std::string, int64_t>>& chapters = data.GetChapters();
+ float lastMarker = 0.0f;
+ for (const auto& chapter : chapters)
+ {
+ float marker = chapter.second * 1000 * 100.0f / duration;
+ if (marker != 0)
+ ranges.emplace_back(std::make_pair(lastMarker, marker));
+
+ lastMarker = marker;
+ }
+ return ranges;
+}
diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.h b/xbmc/guilib/guiinfo/PlayerGUIInfo.h
new file mode 100644
index 0000000..e0cb3c8
--- /dev/null
+++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+#include "utils/EventStream.h"
+#include "utils/TimeFormat.h"
+
+#include <atomic>
+#include <ctime>
+#include <memory>
+#include <utility>
+#include <vector>
+
+class CApplicationPlayer;
+class CApplicationVolumeHandling;
+class CDataCacheCore;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+struct PlayerShowInfoChangedEvent
+{
+ explicit PlayerShowInfoChangedEvent(bool showInfo) : m_showInfo(showInfo) {}
+ virtual ~PlayerShowInfoChangedEvent() = default;
+
+ bool m_showInfo{false};
+};
+
+class CPlayerGUIInfo : public CGUIInfoProvider
+{
+public:
+ CPlayerGUIInfo();
+ ~CPlayerGUIInfo() override;
+
+ CEventStream<PlayerShowInfoChangedEvent>& Events() { return m_events; }
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+ void SetShowTime(bool showtime) { m_playerShowTime = showtime; }
+ void SetShowInfo(bool showinfo);
+ bool GetShowInfo() const { return m_playerShowInfo; }
+ bool ToggleShowInfo();
+
+private:
+ int GetTotalPlayTime() const;
+ int GetPlayTime() const;
+ int GetPlayTimeRemaining() const;
+ float GetSeekPercent() const;
+
+ std::string GetCurrentPlayTime(TIME_FORMAT format) const;
+ std::string GetCurrentPlayTimeRemaining(TIME_FORMAT format) const;
+ std::string GetDuration(TIME_FORMAT format) const;
+ std::string GetCurrentSeekTime(TIME_FORMAT format) const;
+ std::string GetSeekTime(TIME_FORMAT format) const;
+
+ std::string GetContentRanges(int iInfo) const;
+ std::vector<std::pair<float, float>> GetEditList(const CDataCacheCore& data,
+ std::time_t duration) const;
+ std::vector<std::pair<float, float>> GetCuts(const CDataCacheCore& data,
+ std::time_t duration) const;
+ std::vector<std::pair<float, float>> GetSceneMarkers(const CDataCacheCore& data,
+ std::time_t duration) const;
+ std::vector<std::pair<float, float>> GetChapters(const CDataCacheCore& data,
+ std::time_t duration) const;
+
+ std::unique_ptr<CFileItem> m_currentItem;
+ std::atomic_bool m_playerShowTime{false};
+ std::atomic_bool m_playerShowInfo{false};
+ const std::shared_ptr<CApplicationPlayer> m_appPlayer;
+ const std::shared_ptr<CApplicationVolumeHandling> m_appVolume;
+ CEventSource<PlayerShowInfoChangedEvent> m_events;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/SkinGUIInfo.cpp b/xbmc/guilib/guiinfo/SkinGUIInfo.cpp
new file mode 100644
index 0000000..1d51b96
--- /dev/null
+++ b/xbmc/guilib/guiinfo/SkinGUIInfo.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/SkinGUIInfo.h"
+
+#include "ServiceBroker.h"
+#include "addons/Skin.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/SkinSettings.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+bool CSkinGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CSkinGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SKIN_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SKIN_BOOL:
+ {
+ bool bInfo = CSkinSettings::GetInstance().GetBool(info.GetData1());
+ if (bInfo)
+ {
+ value = g_localizeStrings.Get(20122); // True
+ return true;
+ }
+ break;
+ }
+ case SKIN_STRING:
+ {
+ value = CSkinSettings::GetInstance().GetString(info.GetData1());
+ return true;
+ }
+ case SKIN_THEME:
+ {
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
+ return true;
+ }
+ case SKIN_COLOUR_THEME:
+ {
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS);
+ return true;
+ }
+ case SKIN_ASPECT_RATIO:
+ {
+ if (g_SkinInfo)
+ {
+ value = g_SkinInfo->GetCurrentAspect();
+ return true;
+ }
+ break;
+ }
+ case SKIN_FONT:
+ {
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_FONT);
+ return true;
+ }
+ case SKIN_TIMER_ELAPSEDSECS:
+ {
+ value = std::to_string(g_SkinInfo->GetTimerElapsedSeconds(info.GetData3()));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CSkinGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ case SKIN_INTEGER:
+ {
+ value = CSkinSettings::GetInstance().GetInt(info.GetData1());
+ return true;
+ }
+ case SKIN_TIMER_ELAPSEDSECS:
+ {
+ value = g_SkinInfo->GetTimerElapsedSeconds(info.GetData3());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CSkinGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SKIN_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SKIN_BOOL:
+ {
+ value = CSkinSettings::GetInstance().GetBool(info.GetData1());
+ return true;
+ }
+ case SKIN_STRING_IS_EQUAL:
+ {
+ value = StringUtils::EqualsNoCase(CSkinSettings::GetInstance().GetString(info.GetData1()), info.GetData3());
+ return true;
+ }
+ case SKIN_STRING:
+ {
+ value = !CSkinSettings::GetInstance().GetString(info.GetData1()).empty();
+ return true;
+ }
+ case SKIN_HAS_THEME:
+ {
+ std::string theme = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
+ URIUtils::RemoveExtension(theme);
+ value = StringUtils::EqualsNoCase(theme, info.GetData3());
+ return true;
+ }
+ case SKIN_TIMER_IS_RUNNING:
+ {
+ value = g_SkinInfo->TimerIsRunning(info.GetData3());
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/SkinGUIInfo.h b/xbmc/guilib/guiinfo/SkinGUIInfo.h
new file mode 100644
index 0000000..7f6c1c4
--- /dev/null
+++ b/xbmc/guilib/guiinfo/SkinGUIInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CSkinGUIInfo : public CGUIInfoProvider
+{
+public:
+ CSkinGUIInfo() = default;
+ ~CSkinGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp
new file mode 100644
index 0000000..bc01047
--- /dev/null
+++ b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/SystemGUIInfo.h"
+
+#include "FileItem.h"
+#include "GUIPassword.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonType.h"
+#include "application/AppParams.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "network/Network.h"
+#if defined(TARGET_DARWIN_OSX)
+#include "platform/darwin/osx/smc.h"
+#endif
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoHelper.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "powermanagement/PowerManager.h"
+#include "profiles/ProfileManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/MediaSettings.h"
+#include "settings/SettingUtils.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "storage/MediaManager.h"
+#include "storage/discs/IDiscDriveHandler.h"
+#include "utils/AlarmClock.h"
+#include "utils/CPUInfo.h"
+#include "utils/HDRCapabilities.h"
+#include "utils/MemUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/TimeUtils.h"
+#include "windowing/WinSystem.h"
+#include "windows/GUIMediaWindow.h"
+
+using namespace KODI::GUILIB;
+using namespace KODI::GUILIB::GUIINFO;
+
+CSystemGUIInfo::CSystemGUIInfo()
+: m_lastSysHeatInfoTime(-SYSTEM_HEAT_UPDATE_INTERVAL)
+{
+}
+
+std::string CSystemGUIInfo::GetSystemHeatInfo(int info) const
+{
+ if (CTimeUtils::GetFrameTime() - m_lastSysHeatInfoTime >= SYSTEM_HEAT_UPDATE_INTERVAL)
+ {
+ m_lastSysHeatInfoTime = CTimeUtils::GetFrameTime();
+#if defined(TARGET_POSIX)
+ CServiceBroker::GetCPUInfo()->GetTemperature(m_cpuTemp);
+ m_gpuTemp = GetGPUTemperature();
+#endif
+ }
+
+ std::string text;
+ switch(info)
+ {
+ case SYSTEM_CPU_TEMPERATURE:
+ return m_cpuTemp.IsValid() ? g_langInfo.GetTemperatureAsString(m_cpuTemp) : g_localizeStrings.Get(10005); // Not available
+ case SYSTEM_GPU_TEMPERATURE:
+ return m_gpuTemp.IsValid() ? g_langInfo.GetTemperatureAsString(m_gpuTemp) : g_localizeStrings.Get(10005);
+ case SYSTEM_FAN_SPEED:
+ text = StringUtils::Format("{}%", m_fanSpeed * 2);
+ break;
+ case SYSTEM_CPU_USAGE:
+ if (CServiceBroker::GetCPUInfo()->SupportsCPUUsage())
+#if defined(TARGET_DARWIN) || defined(TARGET_WINDOWS)
+ text = StringUtils::Format("{}%", CServiceBroker::GetCPUInfo()->GetUsedPercentage());
+#else
+ text = CServiceBroker::GetCPUInfo()->GetCoresUsageString();
+#endif
+ else
+ text = g_localizeStrings.Get(10005); // Not available
+ break;
+ }
+ return text;
+}
+
+CTemperature CSystemGUIInfo::GetGPUTemperature() const
+{
+ int value = 0;
+ char scale = 0;
+
+#if defined(TARGET_DARWIN_OSX)
+ value = SMCGetTemperature(SMC_KEY_GPU_TEMP);
+ return CTemperature::CreateFromCelsius(value);
+#elif defined(TARGET_WINDOWS_STORE)
+ return CTemperature::CreateFromCelsius(0);
+#else
+ std::string cmd = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_gpuTempCmd;
+ int ret = 0;
+ FILE* p = NULL;
+
+ if (cmd.empty() || !(p = popen(cmd.c_str(), "r")))
+ return CTemperature();
+
+ ret = fscanf(p, "%d %c", &value, &scale);
+ pclose(p);
+
+ if (ret != 2)
+ return CTemperature();
+#endif
+
+ if (scale == 'C' || scale == 'c')
+ return CTemperature::CreateFromCelsius(value);
+ if (scale == 'F' || scale == 'f')
+ return CTemperature::CreateFromFahrenheit(value);
+ return CTemperature();
+}
+
+void CSystemGUIInfo::UpdateFPS()
+{
+ m_frameCounter++;
+ unsigned int curTime = CTimeUtils::GetFrameTime();
+
+ float fTimeSpan = static_cast<float>(curTime - m_lastFPSTime);
+ if (fTimeSpan >= 1000.0f)
+ {
+ fTimeSpan /= 1000.0f;
+ m_fps = m_frameCounter / fTimeSpan;
+ m_lastFPSTime = curTime;
+ m_frameCounter = 0;
+ }
+}
+
+bool CSystemGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CSystemGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_TIME:
+ value = CDateTime::GetCurrentDateTime().GetAsLocalizedTime(static_cast<TIME_FORMAT>(info.GetData1()));
+ return true;
+ case SYSTEM_DATE:
+ if (info.GetData3().empty())
+ value = CDateTime::GetCurrentDateTime().GetAsLocalizedDate(true);
+ else
+ value = CDateTime::GetCurrentDateTime().GetAsLocalizedDate(info.GetData3());
+ return true;
+ case SYSTEM_FREE_SPACE:
+ case SYSTEM_USED_SPACE:
+ case SYSTEM_TOTAL_SPACE:
+ case SYSTEM_FREE_SPACE_PERCENT:
+ case SYSTEM_USED_SPACE_PERCENT:
+ value = g_sysinfo.GetHddSpaceInfo(info.m_info);
+ return true;
+ case SYSTEM_CPU_TEMPERATURE:
+ case SYSTEM_GPU_TEMPERATURE:
+ case SYSTEM_FAN_SPEED:
+ case SYSTEM_CPU_USAGE:
+ value = GetSystemHeatInfo(info.m_info);
+ return true;
+ case SYSTEM_VIDEO_ENCODER_INFO:
+ case NETWORK_MAC_ADDRESS:
+ case SYSTEM_OS_VERSION_INFO:
+ case SYSTEM_CPUFREQUENCY:
+ case SYSTEM_INTERNET_STATE:
+ case SYSTEM_UPTIME:
+ case SYSTEM_TOTALUPTIME:
+ case SYSTEM_BATTERY_LEVEL:
+ value = g_sysinfo.GetInfo(info.m_info);
+ return true;
+ case SYSTEM_PRIVACY_POLICY:
+ value = g_sysinfo.GetPrivacyPolicy();
+ return true;
+ case SYSTEM_SCREEN_RESOLUTION:
+ {
+ const auto winSystem = CServiceBroker::GetWinSystem();
+ if (winSystem)
+ {
+ const RESOLUTION_INFO& resInfo = winSystem->GetGfxContext().GetResInfo();
+
+ if (winSystem->IsFullScreen())
+ value = StringUtils::Format("{}x{} @ {:.2f} Hz - {}", resInfo.iScreenWidth,
+ resInfo.iScreenHeight, resInfo.fRefreshRate,
+ g_localizeStrings.Get(244));
+ else
+ value = StringUtils::Format("{}x{} - {}", resInfo.iScreenWidth, resInfo.iScreenHeight,
+ g_localizeStrings.Get(242));
+ }
+ else
+ {
+ value = "";
+ }
+ return true;
+ }
+ case SYSTEM_BUILD_VERSION_SHORT:
+ value = CSysInfo::GetVersionShort();
+ return true;
+ case SYSTEM_BUILD_VERSION:
+ value = CSysInfo::GetVersion();
+ return true;
+ case SYSTEM_BUILD_DATE:
+ value = CSysInfo::GetBuildDate();
+ return true;
+ case SYSTEM_BUILD_VERSION_CODE:
+ value = CSysInfo::GetVersionCode();
+ return true;
+ case SYSTEM_BUILD_VERSION_GIT:
+ value = CSysInfo::GetVersionGit();
+ return true;
+ case SYSTEM_FREE_MEMORY:
+ case SYSTEM_FREE_MEMORY_PERCENT:
+ case SYSTEM_USED_MEMORY:
+ case SYSTEM_USED_MEMORY_PERCENT:
+ case SYSTEM_TOTAL_MEMORY:
+ {
+ KODI::MEMORY::MemoryStatus stat;
+ KODI::MEMORY::GetMemoryStatus(&stat);
+ int iMemPercentFree = 100 - static_cast<int>(100.0f * (stat.totalPhys - stat.availPhys) / stat.totalPhys + 0.5f);
+ int iMemPercentUsed = 100 - iMemPercentFree;
+
+ if (info.m_info == SYSTEM_FREE_MEMORY)
+ value = StringUtils::Format("{}MB", static_cast<unsigned int>(stat.availPhys / MB));
+ else if (info.m_info == SYSTEM_FREE_MEMORY_PERCENT)
+ value = StringUtils::Format("{}%", iMemPercentFree);
+ else if (info.m_info == SYSTEM_USED_MEMORY)
+ value = StringUtils::Format(
+ "{}MB", static_cast<unsigned int>((stat.totalPhys - stat.availPhys) / MB));
+ else if (info.m_info == SYSTEM_USED_MEMORY_PERCENT)
+ value = StringUtils::Format("{}%", iMemPercentUsed);
+ else if (info.m_info == SYSTEM_TOTAL_MEMORY)
+ value = StringUtils::Format("{}MB", static_cast<unsigned int>(stat.totalPhys / MB));
+ return true;
+ }
+ case SYSTEM_SCREEN_MODE:
+ value = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo().strMode;
+ return true;
+ case SYSTEM_SCREEN_WIDTH:
+ value = StringUtils::Format(
+ "{}", CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo().iScreenWidth);
+ return true;
+ case SYSTEM_SCREEN_HEIGHT:
+ value = StringUtils::Format(
+ "{}", CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo().iScreenHeight);
+ return true;
+ case SYSTEM_FPS:
+ value = StringUtils::Format("{:02.2f}", m_fps);
+ return true;
+#ifdef HAS_DVD_DRIVE
+ case SYSTEM_DVD_LABEL:
+ value = CServiceBroker::GetMediaManager().GetDiskLabel();
+ return true;
+#endif
+ case SYSTEM_ALARM_POS:
+ if (g_alarmClock.GetRemaining("shutdowntimer") == 0.0)
+ value.clear();
+ else
+ {
+ double fTime = g_alarmClock.GetRemaining("shutdowntimer");
+ if (fTime > 60.0)
+ value = StringUtils::Format(g_localizeStrings.Get(13213),
+ g_alarmClock.GetRemaining("shutdowntimer") / 60.0);
+ else
+ value = StringUtils::Format(g_localizeStrings.Get(13214),
+ g_alarmClock.GetRemaining("shutdowntimer"));
+ }
+ return true;
+ case SYSTEM_PROFILENAME:
+ value = CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().getName();
+ return true;
+ case SYSTEM_PROFILECOUNT:
+ value = StringUtils::Format("{0}", CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetNumberOfProfiles());
+ return true;
+ case SYSTEM_PROFILEAUTOLOGIN:
+ {
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+ int iProfileId = profileManager->GetAutoLoginProfileId();
+ if ((iProfileId < 0) || !profileManager->GetProfileName(iProfileId, value))
+ value = g_localizeStrings.Get(37014); // Last used profile
+ return true;
+ }
+ case SYSTEM_PROFILETHUMB:
+ {
+ const std::string& thumb = CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().getThumb();
+ value = thumb.empty() ? "DefaultUser.png" : thumb;
+ return true;
+ }
+ case SYSTEM_LANGUAGE:
+ value = g_langInfo.GetEnglishLanguageName();
+ return true;
+ case SYSTEM_TEMPERATURE_UNITS:
+ value = g_langInfo.GetTemperatureUnitString();
+ return true;
+ case SYSTEM_FRIENDLY_NAME:
+ value = CSysInfo::GetDeviceName();
+ return true;
+ case SYSTEM_STEREOSCOPIC_MODE:
+ {
+ int iStereoMode = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE);
+ value = std::to_string(iStereoMode);
+ return true;
+ }
+ case SYSTEM_GET_CORE_USAGE:
+ value = StringUtils::Format(
+ "{:4.2f}",
+ CServiceBroker::GetCPUInfo()->GetCoreInfo(std::stoi(info.GetData3())).m_usagePercent);
+ return true;
+ case SYSTEM_RENDER_VENDOR:
+ value = CServiceBroker::GetRenderSystem()->GetRenderVendor();
+ return true;
+ case SYSTEM_RENDER_RENDERER:
+ value = CServiceBroker::GetRenderSystem()->GetRenderRenderer();
+ return true;
+ case SYSTEM_RENDER_VERSION:
+ value = CServiceBroker::GetRenderSystem()->GetRenderVersionString();
+ return true;
+ case SYSTEM_ADDON_UPDATE_COUNT:
+ value = CServiceBroker::GetAddonMgr().GetLastAvailableUpdatesCountAsString();
+ return true;
+#if defined(TARGET_LINUX)
+ case SYSTEM_PLATFORM_WINDOWING:
+ value = CServiceBroker::GetWinSystem()->GetName();
+ StringUtils::ToCapitalize(value);
+ return true;
+#endif
+ case SYSTEM_SUPPORTED_HDR_TYPES:
+ {
+ if (CServiceBroker::GetWinSystem()->IsHDRDisplay())
+ {
+ // Assumes HDR10 minimum requirement for HDR
+ std::string types = "HDR10";
+
+ const CHDRCapabilities caps = CServiceBroker::GetWinSystem()->GetDisplayHDRCapabilities();
+
+ if (caps.SupportsHLG())
+ types += ", HLG";
+ if (caps.SupportsHDR10Plus())
+ types += ", HDR10+";
+ if (caps.SupportsDolbyVision())
+ types += ", Dolby Vision";
+
+ value = types;
+ }
+
+ return true;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // NETWORK_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case NETWORK_IP_ADDRESS:
+ {
+ CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
+ if (iface)
+ {
+ value = iface->GetCurrentIPAddress();
+ return true;
+ }
+ break;
+ }
+ case NETWORK_SUBNET_MASK:
+ {
+ CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
+ if (iface)
+ {
+ value = iface->GetCurrentNetmask();
+ return true;
+ }
+ break;
+ }
+ case NETWORK_GATEWAY_ADDRESS:
+ {
+ CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
+ if (iface)
+ {
+ value = iface->GetCurrentDefaultGateway();
+ return true;
+ }
+ break;
+ }
+ case NETWORK_DNS1_ADDRESS:
+ {
+ const std::vector<std::string> nss = CServiceBroker::GetNetwork().GetNameServers();
+ if (nss.size() >= 1)
+ {
+ value = nss[0];
+ return true;
+ }
+ break;
+ }
+ case NETWORK_DNS2_ADDRESS:
+ {
+ const std::vector<std::string> nss = CServiceBroker::GetNetwork().GetNameServers();
+ if (nss.size() >= 2)
+ {
+ value = nss[1];
+ return true;
+ }
+ break;
+ }
+ case NETWORK_DHCP_ADDRESS:
+ {
+ // wtf?
+ std::string dhcpserver;
+ value = dhcpserver;
+ return true;
+ }
+ case NETWORK_LINK_STATE:
+ {
+ std::string linkStatus = g_localizeStrings.Get(151);
+ linkStatus += " ";
+ CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
+ if (iface && iface->IsConnected())
+ linkStatus += g_localizeStrings.Get(15207);
+ else
+ linkStatus += g_localizeStrings.Get(15208);
+ value = linkStatus;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CSystemGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_FREE_MEMORY:
+ case SYSTEM_USED_MEMORY:
+ {
+ KODI::MEMORY::MemoryStatus stat;
+ KODI::MEMORY::GetMemoryStatus(&stat);
+ int memPercentUsed = static_cast<int>(100.0f * (stat.totalPhys - stat.availPhys) / stat.totalPhys + 0.5f);
+ if (info.m_info == SYSTEM_FREE_MEMORY)
+ value = 100 - memPercentUsed;
+ else
+ value = memPercentUsed;
+ return true;
+ }
+ case SYSTEM_FREE_SPACE:
+ case SYSTEM_USED_SPACE:
+ {
+ g_sysinfo.GetHddSpaceInfo(value, info.m_info, true);
+ return true;
+ }
+ case SYSTEM_CPU_USAGE:
+ value = CServiceBroker::GetCPUInfo()->GetUsedPercentage();
+ return true;
+ case SYSTEM_BATTERY_LEVEL:
+ value = CServiceBroker::GetPowerManager().BatteryLevel();
+ return true;
+ }
+
+ return false;
+}
+
+bool CSystemGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // SYSTEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case SYSTEM_ALWAYS_TRUE:
+ value = true;
+ return true;
+ case SYSTEM_ALWAYS_FALSE:
+ value = false;
+ return true;
+ case SYSTEM_ETHERNET_LINK_ACTIVE:
+ // wtf: not implemented - always returns true?!
+ value = true;
+ return true;
+ case SYSTEM_PLATFORM_LINUX:
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_WINDOWS:
+#ifdef TARGET_WINDOWS
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_UWP:
+#ifdef TARGET_WINDOWS_STORE
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_DARWIN:
+#ifdef TARGET_DARWIN
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_DARWIN_OSX:
+#ifdef TARGET_DARWIN_OSX
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_DARWIN_IOS:
+#ifdef TARGET_DARWIN_IOS
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_DARWIN_TVOS:
+#ifdef TARGET_DARWIN_TVOS
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_PLATFORM_ANDROID:
+#if defined(TARGET_ANDROID)
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_MEDIA_DVD:
+ value = CServiceBroker::GetMediaManager().IsDiscInDrive();
+ return true;
+ case SYSTEM_MEDIA_AUDIO_CD:
+ #ifdef HAS_DVD_DRIVE
+ if (CServiceBroker::GetMediaManager().IsDiscInDrive())
+ {
+ MEDIA_DETECT::CCdInfo* pCdInfo = CServiceBroker::GetMediaManager().GetCdInfo();
+ value = pCdInfo && (pCdInfo->IsAudio(1) || pCdInfo->IsCDExtra(1) || pCdInfo->IsMixedMode(1));
+ }
+ else
+ #endif
+ {
+ value = false;
+ }
+ return true;
+#ifdef HAS_DVD_DRIVE
+ case SYSTEM_DVDREADY:
+ value = CServiceBroker::GetMediaManager().GetDriveStatus() != DriveState::NOT_READY;
+ return true;
+ case SYSTEM_TRAYOPEN:
+ value = CServiceBroker::GetMediaManager().GetDriveStatus() == DriveState::OPEN;
+ return true;
+#endif
+ case SYSTEM_CAN_POWERDOWN:
+ value = CServiceBroker::GetPowerManager().CanPowerdown();
+ return true;
+ case SYSTEM_CAN_SUSPEND:
+ value = CServiceBroker::GetPowerManager().CanSuspend();
+ return true;
+ case SYSTEM_CAN_HIBERNATE:
+ value = CServiceBroker::GetPowerManager().CanHibernate();
+ return true;
+ case SYSTEM_CAN_REBOOT:
+ value = CServiceBroker::GetPowerManager().CanReboot();
+ return true;
+ case SYSTEM_SCREENSAVER_ACTIVE:
+ case SYSTEM_IS_SCREENSAVER_INHIBITED:
+ case SYSTEM_DPMS_ACTIVE:
+ case SYSTEM_IDLE_SHUTDOWN_INHIBITED:
+ case SYSTEM_IDLE_TIME:
+ {
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ switch (info.m_info)
+ {
+ case SYSTEM_SCREENSAVER_ACTIVE:
+ value = appPower->IsInScreenSaver();
+ return true;
+ case SYSTEM_IS_SCREENSAVER_INHIBITED:
+ value = appPower->IsScreenSaverInhibited();
+ return true;
+ case SYSTEM_DPMS_ACTIVE:
+ value = appPower->IsDPMSActive();
+ return true;
+ case SYSTEM_IDLE_SHUTDOWN_INHIBITED:
+ value = appPower->IsIdleShutdownInhibited();
+ return true;
+ case SYSTEM_IDLE_TIME:
+ value = appPower->GlobalIdleTime() >= static_cast<int>(info.GetData1());
+ return true;
+ default:
+ return false;
+ }
+ }
+ case SYSTEM_HASLOCKS:
+ value = CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE;
+ return true;
+ case SYSTEM_HAS_PVR:
+ value = true;
+ return true;
+ case SYSTEM_HAS_PVR_ADDON:
+ value = CServiceBroker::GetAddonMgr().HasAddons(ADDON::AddonType::PVRDLL);
+ return true;
+ case SYSTEM_HAS_CMS:
+#if defined(HAS_GL) || defined(HAS_DX)
+ value = true;
+#else
+ value = false;
+#endif
+ return true;
+ case SYSTEM_ISMASTER:
+ value = CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && g_passwordManager.bMasterUser;
+ return true;
+ case SYSTEM_ISFULLSCREEN:
+ value = CServiceBroker::GetWinSystem()->IsFullScreen();
+ return true;
+ case SYSTEM_ISSTANDALONE:
+ value = CServiceBroker::GetAppParams()->IsStandAlone();
+ return true;
+ case SYSTEM_HAS_SHUTDOWN:
+ value = (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME) > 0);
+ return true;
+ case SYSTEM_LOGGEDON:
+ value = !(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_LOGIN_SCREEN);
+ return true;
+ case SYSTEM_SHOW_EXIT_BUTTON:
+ value = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_showExitButton;
+ return true;
+ case SYSTEM_HAS_LOGINSCREEN:
+ value = CServiceBroker::GetSettingsComponent()->GetProfileManager()->UsingLoginScreen();
+ return true;
+ case SYSTEM_INTERNET_STATE:
+ {
+ g_sysinfo.GetInfo(info.m_info);
+ value = g_sysinfo.HasInternet();
+ return true;
+ }
+ case SYSTEM_HAS_CORE_ID:
+ value = CServiceBroker::GetCPUInfo()->HasCoreId(info.GetData1());
+ return true;
+ case SYSTEM_DATE:
+ {
+ if (info.GetData2() == -1) // info doesn't contain valid startDate
+ return false;
+ const CDateTime date = CDateTime::GetCurrentDateTime();
+ int currentDate = date.GetMonth() * 100 + date.GetDay();
+ int startDate = info.GetData1();
+ int stopDate = info.GetData2();
+
+ if (stopDate < startDate)
+ value = currentDate >= startDate || currentDate < stopDate;
+ else
+ value = currentDate >= startDate && currentDate < stopDate;
+ return true;
+ }
+ case SYSTEM_TIME:
+ {
+ int currentTime = CDateTime::GetCurrentDateTime().GetMinuteOfDay();
+ int startTime = info.GetData1();
+ int stopTime = info.GetData2();
+
+ if (stopTime < startTime)
+ value = currentTime >= startTime || currentTime < stopTime;
+ else
+ value = currentTime >= startTime && currentTime < stopTime;
+ return true;
+ }
+ case SYSTEM_ALARM_LESS_OR_EQUAL:
+ {
+ int time = std::lrint(g_alarmClock.GetRemaining(info.GetData3()));
+ int timeCompare = info.GetData2();
+ if (time > 0)
+ value = timeCompare >= time;
+ else
+ value = false;
+ return true;
+ }
+ case SYSTEM_HAS_ALARM:
+ value = g_alarmClock.HasAlarm(info.GetData3());
+ return true;
+ case SYSTEM_SUPPORTS_CPU_USAGE:
+ value = CServiceBroker::GetCPUInfo()->SupportsCPUUsage();
+ return true;
+ case SYSTEM_GET_BOOL:
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(info.GetData3());
+ return true;
+ case SYSTEM_SETTING:
+ {
+ if (StringUtils::EqualsNoCase(info.GetData3(), "hidewatched"))
+ {
+ CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
+ if (window)
+ {
+ value = CMediaSettings::GetInstance().GetWatchedMode(window->CurrentDirectory().GetContent()) == WatchedModeUnwatched;
+ return true;
+ }
+ }
+ else if (StringUtils::EqualsNoCase(info.GetData3(), "hideunwatchedepisodethumbs"))
+ {
+ const std::shared_ptr<CSettingList> setting(std::dynamic_pointer_cast<CSettingList>(
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
+ CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS)));
+ value = setting && !CSettingUtils::FindIntInList(
+ setting, CSettings::VIDEOLIBRARY_THUMB_SHOW_UNWATCHED_EPISODE);
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.h b/xbmc/guilib/guiinfo/SystemGUIInfo.h
new file mode 100644
index 0000000..ed2ea03
--- /dev/null
+++ b/xbmc/guilib/guiinfo/SystemGUIInfo.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+#include "utils/Temperature.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CSystemGUIInfo : public CGUIInfoProvider
+{
+public:
+ CSystemGUIInfo();
+ ~CSystemGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+ float GetFPS() const { return m_fps; }
+ void UpdateFPS();
+
+private:
+ std::string GetSystemHeatInfo(int info) const;
+ CTemperature GetGPUTemperature() const;
+
+ static const int SYSTEM_HEAT_UPDATE_INTERVAL = 60000;
+
+ mutable unsigned int m_lastSysHeatInfoTime;
+ mutable CTemperature m_gpuTemp;
+ mutable CTemperature m_cpuTemp;
+ int m_fanSpeed = 0;
+ float m_fps = 0.0;
+ unsigned int m_frameCounter = 0;
+ unsigned int m_lastFPSTime = 0;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp
new file mode 100644
index 0000000..794434f
--- /dev/null
+++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp
@@ -0,0 +1,818 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/VideoGUIInfo.h"
+
+#include "FileItem.h"
+#include "PlayListPlayer.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/DataCacheCore.h"
+#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/StereoscopicsManager.h"
+#include "guilib/WindowIDs.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoHelper.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "playlists/PlayList.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingUtils.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#include "video/VideoInfoTag.h"
+#include "video/VideoThumbLoader.h"
+
+#include <math.h>
+
+using namespace KODI::GUILIB;
+using namespace KODI::GUILIB::GUIINFO;
+
+CVideoGUIInfo::CVideoGUIInfo()
+ : m_appPlayer(CServiceBroker::GetAppComponents().GetComponent<CApplicationPlayer>())
+{
+}
+
+int CVideoGUIInfo::GetPercentPlayed(const CVideoInfoTag* tag) const
+{
+ CBookmark bookmark = tag->GetResumePoint();
+ if (bookmark.IsPartWay())
+ return std::lrintf(static_cast<float>(bookmark.timeInSeconds) /
+ static_cast<float>(bookmark.totalTimeInSeconds) * 100.0f);
+ else
+ return 0;
+}
+
+bool CVideoGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ if (item && item->IsVideo())
+ {
+ // special case where .strm is used to start an audio stream
+ if (item->IsInternetStream() && m_appPlayer->IsPlayingAudio())
+ return false;
+
+ CLog::Log(LOGDEBUG, "CVideoGUIInfo::InitCurrentItem({})", CURL::GetRedacted(item->GetPath()));
+
+ // Find a thumb for this file.
+ if (!item->HasArt("thumb"))
+ {
+ CVideoThumbLoader loader;
+ loader.LoadItem(item);
+ }
+
+ // find a thumb for this stream
+ if (item->IsInternetStream())
+ {
+ if (!g_application.m_strPlayListFile.empty())
+ {
+ CLog::Log(LOGDEBUG, "Streaming media detected... using {} to find a thumb",
+ g_application.m_strPlayListFile);
+ CFileItem thumbItem(g_application.m_strPlayListFile,false);
+
+ CVideoThumbLoader loader;
+ if (loader.FillThumb(thumbItem))
+ item->SetArt("thumb", thumbItem.GetArt("thumb"));
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CVideoGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ // For videoplayer "offset" and "position" info labels check playlist
+ if (info.GetData1() && ((info.m_info >= VIDEOPLAYER_OFFSET_POSITION_FIRST &&
+ info.m_info <= VIDEOPLAYER_OFFSET_POSITION_LAST) ||
+ (info.m_info >= PLAYER_OFFSET_POSITION_FIRST && info.m_info <= PLAYER_OFFSET_POSITION_LAST)))
+ return GetPlaylistInfo(value, info);
+
+ const CVideoInfoTag* tag = item->GetVideoInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // PLAYER_* / VIDEOPLAYER_* / LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_ART:
+ value = item->GetArt(info.GetData3());
+ return true;
+ case PLAYER_PATH:
+ case PLAYER_FILENAME:
+ case PLAYER_FILEPATH:
+ if (item->HasMusicInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+
+ value = tag->m_strFileNameAndPath;
+ if (value.empty())
+ value = item->GetPath();
+ value = GUIINFO::GetFileInfoLabelValueFromPath(info.m_info, value);
+ return true;
+ case PLAYER_TITLE:
+ value = tag->m_strTitle;
+ return !value.empty();
+ case VIDEOPLAYER_TITLE:
+ value = tag->m_strTitle;
+ return !value.empty();
+ case LISTITEM_TITLE:
+ value = tag->m_strTitle;
+ return true;
+ case VIDEOPLAYER_ORIGINALTITLE:
+ case LISTITEM_ORIGINALTITLE:
+ value = tag->m_strOriginalTitle;
+ return true;
+ case VIDEOPLAYER_GENRE:
+ case LISTITEM_GENRE:
+ value = StringUtils::Join(tag->m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_DIRECTOR:
+ case LISTITEM_DIRECTOR:
+ value = StringUtils::Join(tag->m_director, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_IMDBNUMBER:
+ case LISTITEM_IMDBNUMBER:
+ value = tag->GetUniqueID();
+ return true;
+ case VIDEOPLAYER_DBID:
+ case LISTITEM_DBID:
+ if (tag->m_iDbId > -1)
+ {
+ value = std::to_string(tag->m_iDbId);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_TVSHOWDBID:
+ case LISTITEM_TVSHOWDBID:
+ if (tag->m_iIdShow > -1)
+ {
+ value = std::to_string(tag->m_iIdShow);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_UNIQUEID:
+ case LISTITEM_UNIQUEID:
+ if (!info.GetData3().empty())
+ value = tag->GetUniqueID(info.GetData3());
+ return true;
+ case VIDEOPLAYER_RATING:
+ case LISTITEM_RATING:
+ {
+ float rating = tag->GetRating(info.GetData3()).rating;
+ if (rating > 0.f)
+ {
+ value = StringUtils::FormatNumber(rating);
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_RATING_AND_VOTES:
+ case LISTITEM_RATING_AND_VOTES:
+ {
+ CRating rating = tag->GetRating(info.GetData3());
+ if (rating.rating >= 0.f)
+ {
+ if (rating.rating > 0.f && rating.votes == 0)
+ value = StringUtils::FormatNumber(rating.rating);
+ else if (rating.votes > 0)
+ value = StringUtils::Format(g_localizeStrings.Get(20350),
+ StringUtils::FormatNumber(rating.rating),
+ StringUtils::FormatNumber(rating.votes));
+ else
+ break;
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_USER_RATING:
+ case LISTITEM_USER_RATING:
+ if (tag->m_iUserRating > 0)
+ {
+ value = std::to_string(tag->m_iUserRating);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_VOTES:
+ case LISTITEM_VOTES:
+ value = StringUtils::FormatNumber(tag->GetRating(info.GetData3()).votes);
+ return true;
+ case VIDEOPLAYER_YEAR:
+ case LISTITEM_YEAR:
+ if (tag->HasYear())
+ {
+ value = std::to_string(tag->GetYear());
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_PREMIERED:
+ case LISTITEM_PREMIERED:
+ {
+ CDateTime dateTime;
+ if (tag->m_firstAired.IsValid())
+ dateTime = tag->m_firstAired;
+ else if (tag->HasPremiered())
+ dateTime = tag->GetPremiered();
+
+ if (dateTime.IsValid())
+ {
+ value = dateTime.GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_PLOT:
+ value = tag->m_strPlot;
+ return true;
+ case VIDEOPLAYER_TRAILER:
+ case LISTITEM_TRAILER:
+ value = tag->m_strTrailer;
+ return true;
+ case VIDEOPLAYER_PLOT_OUTLINE:
+ case LISTITEM_PLOT_OUTLINE:
+ value = tag->m_strPlotOutline;
+ return true;
+ case VIDEOPLAYER_EPISODE:
+ case LISTITEM_EPISODE:
+ {
+ int iEpisode = -1;
+ if (tag->m_iEpisode > 0)
+ {
+ iEpisode = tag->m_iEpisode;
+ }
+
+ if (iEpisode >= 0)
+ {
+ value = std::to_string(iEpisode);
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_SEASON:
+ case LISTITEM_SEASON:
+ if (tag->m_iSeason >= 0)
+ {
+ value = std::to_string(tag->m_iSeason);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_TVSHOW:
+ case LISTITEM_TVSHOW:
+ value = tag->m_strShowTitle;
+ return true;
+ case VIDEOPLAYER_STUDIO:
+ case LISTITEM_STUDIO:
+ value = StringUtils::Join(tag->m_studio, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_COUNTRY:
+ case LISTITEM_COUNTRY:
+ value = StringUtils::Join(tag->m_country, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_MPAA:
+ case LISTITEM_MPAA:
+ value = tag->m_strMPAARating;
+ return true;
+ case VIDEOPLAYER_TOP250:
+ case LISTITEM_TOP250:
+ if (tag->m_iTop250 > 0)
+ {
+ value = std::to_string(tag->m_iTop250);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_CAST:
+ case LISTITEM_CAST:
+ value = tag->GetCast();
+ return true;
+ case VIDEOPLAYER_CAST_AND_ROLE:
+ case LISTITEM_CAST_AND_ROLE:
+ value = tag->GetCast(true);
+ return true;
+ case VIDEOPLAYER_ARTIST:
+ case LISTITEM_ARTIST:
+ value = StringUtils::Join(tag->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_ALBUM:
+ case LISTITEM_ALBUM:
+ value = tag->m_strAlbum;
+ return true;
+ case VIDEOPLAYER_WRITER:
+ case LISTITEM_WRITER:
+ value = StringUtils::Join(tag->m_writingCredits, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case VIDEOPLAYER_TAGLINE:
+ case LISTITEM_TAGLINE:
+ value = tag->m_strTagLine;
+ return true;
+ case VIDEOPLAYER_LASTPLAYED:
+ case LISTITEM_LASTPLAYED:
+ {
+ const CDateTime dateTime = tag->m_lastPlayed;
+ if (dateTime.IsValid())
+ {
+ value = dateTime.GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_PLAYCOUNT:
+ case LISTITEM_PLAYCOUNT:
+ if (tag->GetPlayCount() > 0)
+ {
+ value = std::to_string(tag->GetPlayCount());
+ return true;
+ }
+ break;
+
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_DURATION:
+ {
+ int iDuration = tag->GetDuration();
+ if (iDuration > 0)
+ {
+ value = StringUtils::SecondsToTimeString(iDuration, static_cast<TIME_FORMAT>(info.GetData4()));
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_TRACKNUMBER:
+ if (tag->m_iTrack > -1 )
+ {
+ value = std::to_string(tag->m_iTrack);
+ return true;
+ }
+ break;
+ case LISTITEM_PLOT:
+ {
+ std::shared_ptr<CSettingList> setting(std::dynamic_pointer_cast<CSettingList>(
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
+ CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS)));
+ if (tag->m_type != MediaTypeTvShow && tag->m_type != MediaTypeVideoCollection &&
+ tag->GetPlayCount() == 0 && setting &&
+ ((tag->m_type == MediaTypeMovie &&
+ !CSettingUtils::FindIntInList(
+ setting, CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_MOVIES)) ||
+ (tag->m_type == MediaTypeEpisode &&
+ !CSettingUtils::FindIntInList(
+ setting, CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_TVSHOWEPISODES))))
+ {
+ value = g_localizeStrings.Get(20370);
+ }
+ else
+ {
+ value = tag->m_strPlot;
+ }
+ return true;
+ }
+ case LISTITEM_STATUS:
+ value = tag->m_strStatus;
+ return true;
+ case LISTITEM_TAG:
+ value = StringUtils::Join(tag->m_tags, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ return true;
+ case LISTITEM_SET:
+ value = tag->m_set.title;
+ return true;
+ case LISTITEM_SETID:
+ if (tag->m_set.id > 0)
+ {
+ value = std::to_string(tag->m_set.id);
+ return true;
+ }
+ break;
+ case LISTITEM_ENDTIME_RESUME:
+ {
+ const CDateTimeSpan duration(0, 0, 0, tag->GetDuration() - tag->GetResumePoint().timeInSeconds);
+ value = (CDateTime::GetCurrentDateTime() + duration).GetAsLocalizedTime("", false);
+ return true;
+ }
+ case LISTITEM_ENDTIME:
+ {
+ const CDateTimeSpan duration(0, 0, 0, tag->GetDuration());
+ value = (CDateTime::GetCurrentDateTime() + duration).GetAsLocalizedTime("", false);
+ return true;
+ }
+ case LISTITEM_DATE_ADDED:
+ if (tag->m_dateAdded.IsValid())
+ {
+ value = tag->m_dateAdded.GetAsLocalizedDate();
+ return true;
+ }
+ break;
+ case LISTITEM_DBTYPE:
+ value = tag->m_type;
+ return true;
+ case LISTITEM_APPEARANCES:
+ if (tag->m_relevance > -1)
+ {
+ value = std::to_string(tag->m_relevance);
+ return true;
+ }
+ break;
+ case LISTITEM_PERCENT_PLAYED:
+ value = std::to_string(GetPercentPlayed(tag));
+ return true;
+ case LISTITEM_VIDEO_CODEC:
+ value = tag->m_streamDetails.GetVideoCodec();
+ return true;
+ case LISTITEM_VIDEO_RESOLUTION:
+ value = CStreamDetails::VideoDimsToResolutionDescription(tag->m_streamDetails.GetVideoWidth(), tag->m_streamDetails.GetVideoHeight());
+ return true;
+ case LISTITEM_VIDEO_ASPECT:
+ value = CStreamDetails::VideoAspectToAspectDescription(tag->m_streamDetails.GetVideoAspect());
+ return true;
+ case LISTITEM_AUDIO_CODEC:
+ value = tag->m_streamDetails.GetAudioCodec();
+ return true;
+ case LISTITEM_AUDIO_CHANNELS:
+ {
+ int iChannels = tag->m_streamDetails.GetAudioChannels();
+ if (iChannels > 0)
+ {
+ value = std::to_string(iChannels);
+ return true;
+ }
+ break;
+ }
+ case LISTITEM_AUDIO_LANGUAGE:
+ value = tag->m_streamDetails.GetAudioLanguage();
+ return true;
+ case LISTITEM_SUBTITLE_LANGUAGE:
+ value = tag->m_streamDetails.GetSubtitleLanguage();
+ return true;
+ case LISTITEM_FILENAME:
+ case LISTITEM_FILE_EXTENSION:
+ if (item->IsVideoDb())
+ value = URIUtils::GetFileName(tag->m_strFileNameAndPath);
+ else if (item->HasMusicInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ value = URIUtils::GetFileName(item->GetPath());
+
+ if (info.m_info == LISTITEM_FILE_EXTENSION)
+ {
+ std::string strExtension = URIUtils::GetExtension(value);
+ value = StringUtils::TrimLeft(strExtension, ".");
+ }
+ return true;
+ case LISTITEM_FOLDERNAME:
+ case LISTITEM_PATH:
+ if (item->IsVideoDb())
+ {
+ if (item->m_bIsFolder)
+ value = tag->m_strPath;
+ else
+ URIUtils::GetParentPath(tag->m_strFileNameAndPath, value);
+ }
+ else if (item->HasMusicInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ URIUtils::GetParentPath(item->GetPath(), value);
+
+ value = CURL(value).GetWithoutUserDetails();
+
+ if (info.m_info == LISTITEM_FOLDERNAME)
+ {
+ URIUtils::RemoveSlashAtEnd(value);
+ value = URIUtils::GetFileName(value);
+ }
+ return true;
+ case LISTITEM_FILENAME_AND_PATH:
+ if (item->IsVideoDb())
+ value = tag->m_strFileNameAndPath;
+ else if (item->HasMusicInfoTag()) // special handling for music videos, which have both a videotag and a musictag
+ break;
+ else
+ value = item->GetPath();
+
+ value = CURL(value).GetWithoutUserDetails();
+ return true;
+ case LISTITEM_VIDEO_HDR_TYPE:
+ value = tag->m_streamDetails.GetVideoHdrType();
+ return true;
+ }
+ }
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_PLAYLISTLEN:
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO)
+ {
+ value = GUIINFO::GetPlaylistLabel(PLAYLIST_LENGTH);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_PLAYLISTPOS:
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO)
+ {
+ value = GUIINFO::GetPlaylistLabel(PLAYLIST_POSITION);
+ return true;
+ }
+ break;
+ case VIDEOPLAYER_VIDEO_ASPECT:
+ value = CStreamDetails::VideoAspectToAspectDescription(CServiceBroker::GetDataCacheCore().GetVideoDAR());
+ return true;
+ case VIDEOPLAYER_STEREOSCOPIC_MODE:
+ value = CServiceBroker::GetDataCacheCore().GetVideoStereoMode();
+ return true;
+ case VIDEOPLAYER_SUBTITLES_LANG:
+ value = m_subtitleInfo.language;
+ return true;
+ break;
+ case VIDEOPLAYER_COVER:
+ if (m_appPlayer->IsPlayingVideo())
+ {
+ if (fallback)
+ *fallback = "DefaultVideoCover.png";
+
+ value = item->HasArt("thumb") ? item->GetArt("thumb") : "DefaultVideoCover.png";
+ return true;
+ }
+ break;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_STEREOSCOPIC_MODE:
+ value = item->GetProperty("stereomode").asString();
+ if (value.empty() && tag)
+ value = CStereoscopicsManager::NormalizeStereoMode(tag->m_streamDetails.GetStereoMode());
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_VIDEO_CODEC:
+ value = m_videoInfo.codecName;
+ return true;
+ case VIDEOPLAYER_VIDEO_RESOLUTION:
+ value = CStreamDetails::VideoDimsToResolutionDescription(m_videoInfo.width, m_videoInfo.height);
+ return true;
+ case VIDEOPLAYER_HDR_TYPE:
+ value = CStreamDetails::HdrTypeToString(m_videoInfo.hdrType);
+ return true;
+ case VIDEOPLAYER_AUDIO_CODEC:
+ value = m_audioInfo.codecName;
+ return true;
+ case VIDEOPLAYER_AUDIO_CHANNELS:
+ {
+ int iChannels = m_audioInfo.channels;
+ if (iChannels > 0)
+ {
+ value = std::to_string(iChannels);
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_AUDIO_BITRATE:
+ {
+ int iBitrate = m_audioInfo.bitrate;
+ if (iBitrate > 0)
+ {
+ value = std::to_string(std::lrint(static_cast<double>(iBitrate) / 1000.0));
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_VIDEO_BITRATE:
+ {
+ int iBitrate = m_videoInfo.bitrate;
+ if (iBitrate > 0)
+ {
+ value = std::to_string(std::lrint(static_cast<double>(iBitrate) / 1000.0));
+ return true;
+ }
+ break;
+ }
+ case VIDEOPLAYER_AUDIO_LANG:
+ value = m_audioInfo.language;
+ return true;
+ }
+
+ return false;
+}
+
+bool CVideoGUIInfo::GetPlaylistInfo(std::string& value, const CGUIInfo& info) const
+{
+ const PLAYLIST::CPlayList& playlist =
+ CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_VIDEO);
+ if (playlist.size() < 1)
+ return false;
+
+ int index = info.GetData2();
+ if (info.GetData1() == 1)
+ { // relative index (requires current playlist is TYPE_VIDEO)
+ if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != PLAYLIST::TYPE_VIDEO)
+ return false;
+
+ index = CServiceBroker::GetPlaylistPlayer().GetNextSong(index);
+ }
+
+ if (index < 0 || index >= playlist.size())
+ return false;
+
+ const CFileItemPtr playlistItem = playlist[index];
+ // try to set a thumbnail
+ if (!playlistItem->HasArt("thumb"))
+ {
+ CVideoThumbLoader loader;
+ loader.LoadItem(playlistItem.get());
+ // still no thumb? then just the set the default cover
+ if (!playlistItem->HasArt("thumb"))
+ playlistItem->SetArt("thumb", "DefaultVideoCover.png");
+ }
+ if (info.m_info == VIDEOPLAYER_PLAYLISTPOS)
+ {
+ value = std::to_string(index + 1);
+ return true;
+ }
+ else if (info.m_info == VIDEOPLAYER_COVER)
+ {
+ value = playlistItem->GetArt("thumb");
+ return true;
+ }
+ else if (info.m_info == VIDEOPLAYER_ART)
+ {
+ value = playlistItem->GetArt(info.GetData3());
+ return true;
+ }
+
+ return GetLabel(value, playlistItem.get(), 0, CGUIInfo(info.m_info), nullptr);
+}
+
+bool CVideoGUIInfo::GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback)
+{
+ // No fallback for videoplayer "offset" and "position" info labels
+ if (info.GetData1() && ((info.m_info >= VIDEOPLAYER_OFFSET_POSITION_FIRST &&
+ info.m_info <= VIDEOPLAYER_OFFSET_POSITION_LAST) ||
+ (info.m_info >= PLAYER_OFFSET_POSITION_FIRST && info.m_info <= PLAYER_OFFSET_POSITION_LAST)))
+ return false;
+
+ const CVideoInfoTag* tag = item->GetVideoInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_TITLE:
+ value = item->GetLabel();
+ if (value.empty())
+ value = CUtil::GetTitleFromPath(item->GetPath());
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+bool CVideoGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ if (!gitem->IsFileItem())
+ return false;
+
+ const CFileItem *item = static_cast<const CFileItem*>(gitem);
+ const CVideoInfoTag* tag = item->GetVideoInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_PERCENT_PLAYED:
+ value = GetPercentPlayed(tag);
+ return true;
+ }
+ }
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_AUDIOSTREAMCOUNT:
+ value = m_appPlayer->GetAudioStreamCount();
+ return true;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool CVideoGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ if (!gitem->IsFileItem())
+ return false;
+
+ const CFileItem *item = static_cast<const CFileItem*>(gitem);
+ const CVideoInfoTag* tag = item->GetVideoInfoTag();
+ if (tag)
+ {
+ switch (info.m_info)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_HAS_INFO:
+ value = !tag->IsEmpty();
+ return true;
+
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_IS_RESUMABLE:
+ value = tag->GetResumePoint().timeInSeconds > 0;
+ return true;
+ case LISTITEM_IS_COLLECTION:
+ value = tag->m_type == MediaTypeVideoCollection;
+ return true;
+ }
+ }
+
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VIDEOPLAYER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VIDEOPLAYER_CONTENT:
+ {
+ std::string strContent = "files";
+ if (tag)
+ {
+ if (tag->m_type == MediaTypeMovie)
+ strContent = "movies";
+ else if (tag->m_type == MediaTypeEpisode)
+ strContent = "episodes";
+ else if (tag->m_type == MediaTypeMusicVideo)
+ strContent = "musicvideos";
+ }
+ value = StringUtils::EqualsNoCase(info.GetData3(), strContent);
+ return value; // if no match for this provider, other providers shall be asked.
+ }
+ case VIDEOPLAYER_USING_OVERLAYS:
+ value = (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_RENDERMETHOD) == RENDER_OVERLAYS);
+ return true;
+ case VIDEOPLAYER_ISFULLSCREEN:
+ value = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO ||
+ CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME;
+ return true;
+ case VIDEOPLAYER_HASMENU:
+ value = m_appPlayer->GetSupportedMenuType() != MenuType::NONE;
+ return true;
+ case VIDEOPLAYER_HASTELETEXT:
+ value = m_appPlayer->HasTeletextCache();
+ return true;
+ case VIDEOPLAYER_HASSUBTITLES:
+ value = m_appPlayer->GetSubtitleCount() > 0;
+ return true;
+ case VIDEOPLAYER_SUBTITLESENABLED:
+ value = m_appPlayer->GetSubtitleVisible();
+ return true;
+ case VIDEOPLAYER_IS_STEREOSCOPIC:
+ value = !CServiceBroker::GetDataCacheCore().GetVideoStereoMode().empty();
+ return true;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // LISTITEM_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case LISTITEM_IS_STEREOSCOPIC:
+ {
+ std::string stereoMode = item->GetProperty("stereomode").asString();
+ if (stereoMode.empty() && tag)
+ stereoMode = CStereoscopicsManager::NormalizeStereoMode(tag->m_streamDetails.GetStereoMode());
+ if (!stereoMode.empty() && stereoMode != "mono")
+ value = true;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.h b/xbmc/guilib/guiinfo/VideoGUIInfo.h
new file mode 100644
index 0000000..55202b2
--- /dev/null
+++ b/xbmc/guilib/guiinfo/VideoGUIInfo.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+#include <memory>
+
+class CApplicationPlayer;
+class CVideoInfoTag;
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CVideoGUIInfo : public CGUIInfoProvider
+{
+public:
+ CVideoGUIInfo();
+ ~CVideoGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetFallbackLabel(std::string& value,
+ const CFileItem* item,
+ int contextWindow,
+ const CGUIInfo& info,
+ std::string* fallback) override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+
+private:
+ int GetPercentPlayed(const CVideoInfoTag* tag) const;
+ bool GetPlaylistInfo(std::string& value, const CGUIInfo& info) const;
+
+ const std::shared_ptr<CApplicationPlayer> m_appPlayer;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp
new file mode 100644
index 0000000..27a892f
--- /dev/null
+++ b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/VisualisationGUIInfo.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIVisualisationControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+bool CVisualisationGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CVisualisationGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VISUALISATION_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VISUALISATION_PRESET:
+ {
+ CGUIMessage msg(GUI_MSG_GET_VISUALISATION, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ if (msg.GetPointer())
+ {
+ CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
+ if (viz)
+ {
+ value = viz->GetActivePresetName();
+ URIUtils::RemoveExtension(value);
+ return true;
+ }
+ }
+ break;
+ }
+ case VISUALISATION_NAME:
+ {
+ ADDON::AddonPtr addon;
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION);
+ if (CServiceBroker::GetAddonMgr().GetAddon(value, addon, ADDON::OnlyEnabled::CHOICE_YES) &&
+ addon)
+ {
+ value = addon->Name();
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool CVisualisationGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CVisualisationGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VISUALISATION_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case VISUALISATION_LOCKED:
+ {
+ CGUIMessage msg(GUI_MSG_GET_VISUALISATION, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ if (msg.GetPointer())
+ {
+ CGUIVisualisationControl *pVis = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
+ value = pVis->IsLocked();
+ return true;
+ }
+ break;
+ }
+ case VISUALISATION_ENABLED:
+ {
+ value = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION).empty();
+ return true;
+ }
+ case VISUALISATION_HAS_PRESETS:
+ {
+ CGUIMessage msg(GUI_MSG_GET_VISUALISATION, 0, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ if (msg.GetPointer())
+ {
+ CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
+ value = (viz && viz->HasPresets());
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/VisualisationGUIInfo.h b/xbmc/guilib/guiinfo/VisualisationGUIInfo.h
new file mode 100644
index 0000000..d3cb05d
--- /dev/null
+++ b/xbmc/guilib/guiinfo/VisualisationGUIInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CVisualisationGUIInfo : public CGUIInfoProvider
+{
+public:
+ CVisualisationGUIInfo() = default;
+ ~CVisualisationGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/guiinfo/WeatherGUIInfo.cpp b/xbmc/guilib/guiinfo/WeatherGUIInfo.cpp
new file mode 100644
index 0000000..3aa2044
--- /dev/null
+++ b/xbmc/guilib/guiinfo/WeatherGUIInfo.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "guilib/guiinfo/WeatherGUIInfo.h"
+
+#include "FileItem.h"
+#include "LangInfo.h"
+#include "ServiceBroker.h"
+#include "guilib/guiinfo/GUIInfo.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "weather/WeatherManager.h"
+
+using namespace KODI::GUILIB::GUIINFO;
+
+bool CWeatherGUIInfo::InitCurrentItem(CFileItem *item)
+{
+ return false;
+}
+
+bool CWeatherGUIInfo::GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // WEATHER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case WEATHER_CONDITIONS_TEXT:
+ value = CServiceBroker::GetWeatherManager().GetInfo(WEATHER_LABEL_CURRENT_COND);
+ StringUtils::Trim(value);
+ return true;
+ case WEATHER_CONDITIONS_ICON:
+ value = CServiceBroker::GetWeatherManager().GetInfo(WEATHER_IMAGE_CURRENT_ICON);
+ return true;
+ case WEATHER_TEMPERATURE:
+ value = StringUtils::Format(
+ "{}{}", CServiceBroker::GetWeatherManager().GetInfo(WEATHER_LABEL_CURRENT_TEMP),
+ g_langInfo.GetTemperatureUnitString());
+ return true;
+ case WEATHER_LOCATION:
+ value = CServiceBroker::GetWeatherManager().GetInfo(WEATHER_LABEL_LOCATION);
+ return true;
+ case WEATHER_FANART_CODE:
+ value = URIUtils::GetFileName(CServiceBroker::GetWeatherManager().GetInfo(WEATHER_IMAGE_CURRENT_ICON));
+ URIUtils::RemoveExtension(value);
+ return true;
+ case WEATHER_PLUGIN:
+ value = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_WEATHER_ADDON);
+ return true;
+ }
+
+ return false;
+}
+
+bool CWeatherGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ return false;
+}
+
+bool CWeatherGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextWindow, const CGUIInfo &info) const
+{
+ switch (info.m_info)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // WEATHER_*
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ case WEATHER_IS_FETCHED:
+ value = CServiceBroker::GetWeatherManager().IsFetched();
+ return true;;
+ }
+
+ return false;
+}
diff --git a/xbmc/guilib/guiinfo/WeatherGUIInfo.h b/xbmc/guilib/guiinfo/WeatherGUIInfo.h
new file mode 100644
index 0000000..560d025
--- /dev/null
+++ b/xbmc/guilib/guiinfo/WeatherGUIInfo.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/guiinfo/GUIInfoProvider.h"
+
+namespace KODI
+{
+namespace GUILIB
+{
+namespace GUIINFO
+{
+
+class CGUIInfo;
+
+class CWeatherGUIInfo : public CGUIInfoProvider
+{
+public:
+ CWeatherGUIInfo() = default;
+ ~CWeatherGUIInfo() override = default;
+
+ // KODI::GUILIB::GUIINFO::IGUIInfoProvider implementation
+ bool InitCurrentItem(CFileItem *item) override;
+ bool GetLabel(std::string& value, const CFileItem *item, int contextWindow, const CGUIInfo &info, std::string *fallback) const override;
+ bool GetInt(int& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+ bool GetBool(bool& value, const CGUIListItem *item, int contextWindow, const CGUIInfo &info) const override;
+};
+
+} // namespace GUIINFO
+} // namespace GUILIB
+} // namespace KODI
diff --git a/xbmc/guilib/iimage.h b/xbmc/guilib/iimage.h
new file mode 100644
index 0000000..299e74c
--- /dev/null
+++ b/xbmc/guilib/iimage.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class IImage
+{
+public:
+
+ virtual ~IImage() = default;
+
+ /*!
+ \brief Load an image from memory with the format m_strMimeType to determine it's size and orientation
+ \param buffer The memory location where the image data can be found
+ \param bufSize The size of the buffer
+ \param width The ideal width of the texture
+ \param height The ideal height of the texture
+ \return true if the image could be loaded
+ */
+ virtual bool LoadImageFromMemory(unsigned char* buffer, unsigned int bufSize, unsigned int width, unsigned int height)=0;
+ /*!
+ \brief Decodes the previously loaded image data to the output buffer in 32 bit raw bits
+ \param pixels The output buffer
+ \param width The width of the image
+ \param height The height of the image
+ \param pitch The pitch of the output buffer
+ \param format The format of the output buffer (JpegIO only)
+ \return true if the image data could be decoded to the output buffer
+ */
+ virtual bool Decode(unsigned char* const pixels, unsigned int width, unsigned int height, unsigned int pitch, unsigned int format)=0;
+ /*!
+ \brief Encodes an thumbnail from raw bits of given memory location
+ \remarks Caller need to call ReleaseThumbnailBuffer() afterwards to free the output buffer
+ \param bufferin The memory location where the image data can be found
+ \param width The width of the thumbnail
+ \param height The height of the thumbnail
+ \param format The format of the input buffer (JpegIO only)
+ \param pitch The pitch of the input texture stored in bufferin
+ \param destFile The destination path of the thumbnail to determine the image format from the extension
+ \param bufferout The output buffer (will be allocated inside the method)
+ \param bufferoutSize The output buffer size
+ \return true if the thumbnail was successfully created
+ */
+ virtual bool CreateThumbnailFromSurface(unsigned char* bufferin, unsigned int width, unsigned int height, unsigned int format, unsigned int pitch, const std::string& destFile,
+ unsigned char* &bufferout, unsigned int &bufferoutSize)=0;
+ /*!
+ \brief Frees the output buffer allocated by CreateThumbnailFromSurface
+ */
+ virtual void ReleaseThumbnailBuffer() {}
+
+ unsigned int Width() const { return m_width; }
+ unsigned int Height() const { return m_height; }
+ unsigned int originalWidth() const { return m_originalWidth; }
+ unsigned int originalHeight() const { return m_originalHeight; }
+ unsigned int Orientation() const { return m_orientation; }
+ bool hasAlpha() const { return m_hasAlpha; }
+
+protected:
+
+ unsigned int m_width = 0;
+ unsigned int m_height = 0;
+ unsigned int m_originalWidth = 0; ///< original image width before scaling or cropping
+ unsigned int m_originalHeight = 0; ///< original image height before scaling or cropping
+ unsigned int m_orientation = 0;
+ bool m_hasAlpha = false;
+
+};
diff --git a/xbmc/guilib/imagefactory.cpp b/xbmc/guilib/imagefactory.cpp
new file mode 100644
index 0000000..8156782
--- /dev/null
+++ b/xbmc/guilib/imagefactory.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "imagefactory.h"
+
+#include "ServiceBroker.h"
+#include "addons/ExtsMimeSupportList.h"
+#include "addons/ImageDecoder.h"
+#include "addons/addoninfo/AddonType.h"
+#include "guilib/FFmpegImage.h"
+#include "utils/Mime.h"
+
+#include <mutex>
+
+CCriticalSection ImageFactory::m_createSec;
+
+using namespace KODI::ADDONS;
+
+IImage* ImageFactory::CreateLoader(const std::string& strFileName)
+{
+ CURL url(strFileName);
+ return CreateLoader(url);
+}
+
+IImage* ImageFactory::CreateLoader(const CURL& url)
+{
+ if(!url.GetFileType().empty())
+ return CreateLoaderFromMimeType("image/"+url.GetFileType());
+
+ return CreateLoaderFromMimeType(CMime::GetMimeType(url));
+}
+
+IImage* ImageFactory::CreateLoaderFromMimeType(const std::string& strMimeType)
+{
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetMimetypeSupportedAddonInfos(
+ strMimeType, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ // Check asked and given mime type is supported by only for here allowed imagedecoder addons.
+ if (addonInfo.first != ADDON::AddonType::IMAGEDECODER)
+ continue;
+
+ std::unique_lock<CCriticalSection> lock(m_createSec);
+ std::unique_ptr<CImageDecoder> result =
+ std::make_unique<CImageDecoder>(addonInfo.second, strMimeType);
+ if (!result->IsCreated())
+ {
+ continue;
+ }
+ return result.release();
+ }
+
+ return new CFFmpegImage(strMimeType);
+}
diff --git a/xbmc/guilib/imagefactory.h b/xbmc/guilib/imagefactory.h
new file mode 100644
index 0000000..b224ef6
--- /dev/null
+++ b/xbmc/guilib/imagefactory.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "URL.h"
+#include "iimage.h"
+#include "threads/CriticalSection.h"
+
+class ImageFactory
+{
+public:
+ ImageFactory() = default;
+ virtual ~ImageFactory() = default;
+
+ static IImage* CreateLoader(const std::string& strFileName);
+ static IImage* CreateLoader(const CURL& url);
+ static IImage* CreateLoaderFromMimeType(const std::string& strMimeType);
+
+private:
+ static CCriticalSection m_createSec; //!< Critical section for add-on creation.
+};