mirror of https://code.videolan.org/videolan/vlc
225 lines
7.1 KiB
QML
225 lines
7.1 KiB
QML
/*****************************************************************************
|
|
* Copyright (C) 2023 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* ( at your option ) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
import QtQuick 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import QtQuick.Controls 2.12
|
|
|
|
import org.videolan.vlc 0.1
|
|
|
|
Repeater {
|
|
id: repeater
|
|
|
|
property int alignment: Qt.AlignVCenter
|
|
|
|
property real availableWidth: Number.MAX_VALUE
|
|
property real availableHeight: Number.MAX_VALUE
|
|
|
|
signal requestLockUnlockAutoHide(bool lock)
|
|
signal menuOpened(var menu)
|
|
|
|
// NOTE: We apply the 'navigation chain' after adding the item.
|
|
onItemAdded: (index, item) => {
|
|
item.applyNavigation()
|
|
}
|
|
|
|
onItemRemoved: (index, item) => {
|
|
// NOTE: We update the 'navigation chain' after removing the item.
|
|
item.removeNavigation()
|
|
|
|
item.recoverFocus(index)
|
|
}
|
|
|
|
delegate: Loader {
|
|
id: loader
|
|
|
|
// Settings
|
|
|
|
source: PlayerControlbarControls.control(model.id).source
|
|
|
|
focus: (index === 0)
|
|
|
|
Layout.fillWidth: (item && item.Layout.minimumWidth > 0)
|
|
Layout.minimumWidth: Layout.fillWidth ? item.Layout.minimumWidth : (item ? item.implicitWidth : -1)
|
|
Layout.maximumWidth: Layout.fillWidth ? (item ? item.implicitWidth : -1) : -1
|
|
|
|
Layout.minimumHeight: 0
|
|
Layout.maximumHeight: Math.min((item && item.implicitHeight <= 0) ? Number.MAX_VALUE : item.implicitHeight, repeater.availableHeight)
|
|
Layout.fillHeight: true
|
|
|
|
Layout.alignment: repeater.alignment
|
|
|
|
Binding on visible {
|
|
delayed: true // this is important
|
|
value: (loader.x + loader.Layout.minimumWidth <= repeater.availableWidth)
|
|
}
|
|
|
|
// Events
|
|
|
|
onActiveFocusChanged: {
|
|
if (activeFocus && (!!item && !item.focus)) {
|
|
recoverFocus()
|
|
}
|
|
}
|
|
|
|
onLoaded: {
|
|
// control should not request focus if they are not enabled:
|
|
item.focus = Qt.binding(function() { return item.enabled && item.visible })
|
|
|
|
// navigation parent of control is always controlLayout
|
|
// so it can be set here unlike leftItem and rightItem:
|
|
item.Navigation.parentItem = repeater
|
|
|
|
if (typeof item.activeFocusOnTab === "boolean")
|
|
item.activeFocusOnTab = true
|
|
|
|
item.visible = Qt.binding(function() { return loader.visible })
|
|
|
|
if (item.requestLockUnlockAutoHide)
|
|
item.requestLockUnlockAutoHide.connect(repeater.requestLockUnlockAutoHide)
|
|
|
|
if (item.menuOpened)
|
|
item.menuOpened.connect(repeater.menuOpened)
|
|
}
|
|
|
|
// Connections
|
|
|
|
Connections {
|
|
target: item
|
|
|
|
enabled: loader.status === Loader.Ready
|
|
|
|
onEnabledChanged: {
|
|
if (activeFocus && !item.enabled) // Loader has focus but item is not enabled
|
|
recoverFocus()
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (activeFocus && !item.visible)
|
|
recoverFocus()
|
|
}
|
|
}
|
|
|
|
// Functions
|
|
|
|
function applyNavigation() {
|
|
if (item === null) return
|
|
|
|
const itemLeft = repeater.itemAt(index - 1)
|
|
const itemRight = repeater.itemAt(index + 1)
|
|
|
|
if (itemLeft) {
|
|
const componentLeft = itemLeft.item
|
|
|
|
if (componentLeft)
|
|
{
|
|
item.Navigation.leftItem = componentLeft
|
|
|
|
componentLeft.Navigation.rightItem = item
|
|
}
|
|
}
|
|
|
|
if (itemRight) {
|
|
const componentRight = itemRight.item
|
|
|
|
if (componentRight)
|
|
{
|
|
item.Navigation.rightItem = componentRight
|
|
|
|
componentRight.Navigation.leftItem = item
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeNavigation() {
|
|
if (item === null) return
|
|
|
|
const itemLeft = repeater.itemAt(index - 1)
|
|
|
|
// NOTE: The current item was removed from the repeater so we test against the
|
|
// same index.
|
|
const itemRight = repeater.itemAt(index)
|
|
|
|
if (itemLeft) {
|
|
if (itemRight) {
|
|
itemLeft.item.Navigation.rightItem = itemRight.item
|
|
itemRight.item.Navigation.leftItem = itemLeft.item
|
|
}
|
|
else
|
|
itemLeft.item.Navigation.rightItem = null
|
|
}
|
|
else if (itemRight) {
|
|
itemRight.item.Navigation.leftItem = null
|
|
}
|
|
}
|
|
|
|
function recoverFocus(_index) {
|
|
if (item === null) return
|
|
|
|
const controlLayout = repeater.Navigation.parentItem
|
|
|
|
if (!controlLayout || !controlLayout.visible)
|
|
return
|
|
|
|
if (_index === undefined)
|
|
_index = index
|
|
|
|
for (let i = 1; i <= Math.max(_index, repeater.count - (_index + 1)); ++i) {
|
|
if (i <= _index) {
|
|
const leftItem = repeater.itemAt(_index - i)
|
|
|
|
if (_focusIfFocusable(leftItem))
|
|
return
|
|
}
|
|
|
|
if (_index + i <= repeater.count - 1) {
|
|
const rightItem = repeater.itemAt(_index + i)
|
|
|
|
if (_focusIfFocusable(rightItem))
|
|
return
|
|
}
|
|
}
|
|
|
|
// focus to other alignment if focusable control
|
|
// in the same alignment is not found:
|
|
if (!!controlLayout.Navigation.rightItem) {
|
|
controlLayout.Navigation.defaultNavigationRight()
|
|
} else if (!!controlLayout.Navigation.leftItem) {
|
|
controlLayout.Navigation.defaultNavigationLeft()
|
|
} else if (controlLayout.altFocusAction) {
|
|
controlLayout.altFocusAction()
|
|
}
|
|
}
|
|
|
|
// Private
|
|
|
|
function _focusIfFocusable(_loader) {
|
|
if (!!_loader && !!_loader.item && _loader.item.focus) {
|
|
if (item.focusReason !== undefined)
|
|
_loader.item.forceActiveFocus(item.focusReason)
|
|
else {
|
|
console.warn("focusReason is not available in %1!".arg(item))
|
|
_loader.item.forceActiveFocus()
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|